diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2021-04-16 15:54:35 -0400 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2021-04-16 15:54:35 -0400 |
commit | 51ac447d409bb565178ab9d78d4b5200e89f2cf4 (patch) | |
tree | 0a45c9ba2215d00dd04afe75803ad103bc4236fb | |
parent | 2a3bbb6674fe3b0e4ff996cae750a6e2880315ef (diff) |
comment/uncomment selection
-rw-r--r-- | buffer.c | 108 | ||||
-rw-r--r-- | command.c | 3 | ||||
-rw-r--r-- | command.h | 2 | ||||
-rw-r--r-- | main.c | 1 | ||||
-rw-r--r-- | syntax.c | 24 | ||||
-rw-r--r-- | ted.cfg | 6 |
6 files changed, 141 insertions, 3 deletions
@@ -137,6 +137,12 @@ static void buffer_validate_cursor(TextBuffer *buffer) { buffer_pos_validate(buffer, &buffer->selection_pos); } +// ensure *line points to a line in buffer. +static inline void buffer_validate_line(TextBuffer *buffer, u32 *line) { + if (*line >= buffer->nlines) + *line = buffer->nlines - 1; +} + // update *pos, given that nchars characters were deleted at del_pos. static void buffer_pos_handle_deleted_chars(BufferPos *pos, BufferPos del_pos, u32 nchars) { if (pos->line != del_pos.line) return; @@ -2555,6 +2561,9 @@ void buffer_indent_lines(TextBuffer *buffer, u32 first_line, u32 last_line) { void buffer_dedent_lines(TextBuffer *buffer, u32 first_line, u32 last_line) { assert(first_line <= last_line); + buffer_validate_line(buffer, &first_line); + buffer_validate_line(buffer, &last_line); + buffer_start_edit_chain(buffer); Settings const *settings = buffer_settings(buffer); u8 const tab_width = settings->tab_width; @@ -2583,6 +2592,7 @@ void buffer_dedent_lines(TextBuffer *buffer, u32 first_line, u32 last_line) { buffer_end_edit_chain(buffer); } + void buffer_indent_selection(TextBuffer *buffer) { if (!buffer->selection) return; u32 l1 = buffer->cursor_pos.line; @@ -2608,3 +2618,101 @@ void buffer_dedent_cursor_line(TextBuffer *buffer) { buffer_dedent_lines(buffer, line, line); } + +void buffer_comment_lines(TextBuffer *buffer, u32 first_line, u32 last_line) { + Language lang = buffer_language(buffer); + const char *start = language_comment_start(lang), *end = language_comment_end(lang); + String32 start32 = str32_from_utf8(start), end32 = str32_from_utf8(end); + + buffer_start_edit_chain(buffer); + + for (u32 line_idx = first_line; line_idx <= last_line; ++line_idx) { + // insert comment start + if (start32.len) { + BufferPos sol = buffer_pos_start_of_line(buffer, line_idx); + buffer_insert_text_at_pos(buffer, sol, start32); + } + // insert comment end + if (end32.len) { + BufferPos eol = buffer_pos_end_of_line(buffer, line_idx); + buffer_insert_text_at_pos(buffer, eol, end32); + } + } + + str32_free(&start32); + str32_free(&end32); + + buffer_end_edit_chain(buffer); +} + +static bool buffer_line_starts_with_ascii(TextBuffer *buffer, u32 line_idx, char const *prefix) { + buffer_validate_line(buffer, &line_idx); + Line *line = &buffer->lines[line_idx]; + size_t prefix_len = strlen(prefix); + if (line->len < prefix_len) + return false; + for (size_t i = 0; i < prefix_len; ++i) { + assert(prefix[i] > 0 && prefix[i] <= 127); // check if actually ASCII + if ((char32_t)prefix[i] != line->str[i]) + return false; + } + return true; +} +static bool buffer_line_ends_with_ascii(TextBuffer *buffer, u32 line_idx, char const *suffix) { + buffer_validate_line(buffer, &line_idx); + Line *line = &buffer->lines[line_idx]; + size_t suffix_len = strlen(suffix), line_len = line->len; + if (line_len < suffix_len) + return false; + for (size_t i = 0; i < suffix_len; ++i) { + assert(suffix[i] > 0 && suffix[i] <= 127); // check if actually ASCII + if ((char32_t)suffix[i] != line->str[line_len-suffix_len+i]) + return false; + } + return true; +} + +void buffer_uncomment_lines(TextBuffer *buffer, u32 first_line, u32 last_line) { + Language lang = buffer_language(buffer); + const char *start = language_comment_start(lang), *end = language_comment_end(lang); + u32 start_len = (u32)strlen(start), end_len = (u32)strlen(end); + buffer_start_edit_chain(buffer); + for (u32 line_idx = first_line; line_idx <= last_line; ++line_idx) { + // make sure line is actually commented + if (buffer_line_starts_with_ascii(buffer, line_idx, start) + && buffer_line_ends_with_ascii(buffer, line_idx, end)) { + // we should do the end first, because start and end might be overlapping, + // and it would cause an underflow if we deleted the start first. + BufferPos end_pos = buffer_pos_end_of_line(buffer, line_idx); + end_pos.index -= end_len; + buffer_delete_chars_at_pos(buffer, end_pos, end_len); + + BufferPos start_pos = buffer_pos_start_of_line(buffer, line_idx); + buffer_delete_chars_at_pos(buffer, start_pos, start_len); + } + } + buffer_end_edit_chain(buffer); +} + +void buffer_toggle_comment_lines(TextBuffer *buffer, u32 first_line, u32 last_line) { + Language lang = buffer_language(buffer); + const char *start = language_comment_start(lang), *end = language_comment_end(lang); + // if first line is a comment, uncomment lines, otherwise, comment lines + if (buffer_line_starts_with_ascii(buffer, first_line, start) + && buffer_line_ends_with_ascii(buffer, first_line, end)) + buffer_uncomment_lines(buffer, first_line, last_line); + else + buffer_comment_lines(buffer, first_line, last_line); +} + +void buffer_toggle_comment_selection(TextBuffer *buffer) { + u32 l1, l2; + if (buffer->selection) { + l1 = buffer->cursor_pos.line; + l2 = buffer->selection_pos.line; + sort2_u32(&l1, &l2); // ensure l1 <= l2 + } else { + l1 = l2 = buffer->cursor_pos.line; + } + buffer_toggle_comment_lines(buffer, l1, l2); +} @@ -147,6 +147,9 @@ void command_execute(Ted *ted, Command c, i64 argument) { buffer_newline(buffer); } break; + case CMD_COMMENT_SELECTION: + if (buffer) buffer_toggle_comment_selection(buffer); + break; case CMD_BACKSPACE: if (buffer) buffer_backspace_at_cursor(buffer, argument); @@ -32,6 +32,7 @@ ENUM_U16 { CMD_BACKTAB, CMD_NEWLINE, // insert '\n' + autoindent -- also used to submit line buffers CMD_NEWLINE_BACK, + CMD_COMMENT_SELECTION, // scrolling CMD_PAGE_UP, // move cursor up one page up (where one page is however tall the buffer is) @@ -130,6 +131,7 @@ static CommandName const command_names[] = { {"backtab", CMD_BACKTAB}, {"newline", CMD_NEWLINE}, {"newline-back", CMD_NEWLINE_BACK}, + {"comment-selection", CMD_COMMENT_SELECTION}, {"backspace", CMD_BACKSPACE}, {"delete", CMD_DELETE}, {"backspace-word", CMD_BACKSPACE_WORD}, @@ -1,6 +1,5 @@ // @TODO: // - terminate process not working on windows? -// - comment/uncomment selection #include "base.h" no_warn_start #if _WIN32 @@ -13,6 +13,30 @@ Language language_from_str(char const *str) { return LANG_NONE; } +// start of single line comment for language l +char const *language_comment_start(Language l) { + switch (l) { + case LANG_C: return "/* "; + case LANG_RUST: + case LANG_CPP: return "// "; + case LANG_PYTHON: return "# "; + case LANG_NONE: + case LANG_COUNT: + break; + } + return ""; +} + +// end of single line comment for language l +char const *language_comment_end(Language l) { + switch (l) { + case LANG_C: + return " */"; + default: + return ""; + } +} + // NOTE: returns the color setting, not the color ColorSetting syntax_char_type_to_color(SyntaxCharType t) { switch (t) { @@ -69,6 +69,8 @@ Enter = :newline Shift+Enter = :newline-back Keypad Enter = :newline Shift+Keypad Enter = :newline-back +# toggle selection between commented/uncommented +Ctrl+/ = :comment-selection # deletion Delete = :delete @@ -137,8 +139,8 @@ Ctrl+t = :generate-tags Ctrl+d = :goto-definition Ctrl+g = :goto-line -Ctrl+/ = :split-horizontal -Ctrl+Shift+/ = :split-vertical +Ctrl+\ = :split-horizontal +Ctrl+Shift+\ = :split-vertical # unsplit Ctrl+j = :split-join # switch to the other side of a split |