diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2023-11-09 23:19:53 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2023-11-09 23:19:53 +0100 |
| commit | 1566b6faa8534118c3566188181367cd0868468f (patch) | |
| tree | 1de8d4b369efb5e592685a31088f798a6b63ffa1 /examples/dte/misc.c | |
| parent | 349991bf6efe473ab9a5cbdae0a8114d72b997e3 (diff) | |
| download | crep-1566b6faa8534118c3566188181367cd0868468f.tar.gz | |
Added partial matching and introduced threads
Diffstat (limited to 'examples/dte/misc.c')
| -rw-r--r-- | examples/dte/misc.c | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/examples/dte/misc.c b/examples/dte/misc.c new file mode 100644 index 0000000..4ed640b --- /dev/null +++ b/examples/dte/misc.c @@ -0,0 +1,764 @@ +#include <stdlib.h> +#include <string.h> +#include "misc.h" +#include "buffer.h" +#include "change.h" +#include "indent.h" +#include "move.h" +#include "options.h" +#include "regexp.h" +#include "selection.h" +#include "util/debug.h" +#include "util/macros.h" +#include "util/string.h" +#include "util/string-view.h" +#include "util/utf8.h" + +typedef struct { + String buf; + char *indent; + size_t indent_len; + size_t indent_width; + size_t cur_width; + size_t text_width; +} ParagraphFormatter; + +static bool line_has_opening_brace(StringView line) +{ + static regex_t re; + static bool compiled; + if (!compiled) { + // TODO: Reimplement without using regex + static const char pat[] = "\\{[ \t]*(//.*|/\\*.*\\*/[ \t]*)?$"; + regexp_compile_or_fatal_error(&re, pat, REG_NEWLINE | REG_NOSUB); + compiled = true; + } + + regmatch_t m; + return regexp_exec(&re, line.data, line.length, 0, &m, 0); +} + +static bool line_has_closing_brace(StringView line) +{ + strview_trim_left(&line); + return line.length > 0 && line.data[0] == '}'; +} + +/* + * Stupid { ... } block selector. + * + * Because braces can be inside strings or comments and writing real + * parser for many programming languages does not make sense the rules + * for selecting a block are made very simple. Line that matches \{\s*$ + * starts a block and line that matches ^\s*\} ends it. + */ +void select_block(View *view) +{ + BlockIter sbi, ebi, bi = view->cursor; + StringView line; + int level = 0; + + // If current line does not match \{\s*$ but matches ^\s*\} then + // cursor is likely at end of the block you want to select + fetch_this_line(&bi, &line); + if (!line_has_opening_brace(line) && line_has_closing_brace(line)) { + block_iter_prev_line(&bi); + } + + while (1) { + fetch_this_line(&bi, &line); + if (line_has_opening_brace(line)) { + if (level++ == 0) { + sbi = bi; + block_iter_next_line(&bi); + break; + } + } + if (line_has_closing_brace(line)) { + level--; + } + + if (!block_iter_prev_line(&bi)) { + return; + } + } + + while (1) { + fetch_this_line(&bi, &line); + if (line_has_closing_brace(line)) { + if (--level == 0) { + ebi = bi; + break; + } + } + if (line_has_opening_brace(line)) { + level++; + } + + if (!block_iter_next_line(&bi)) { + return; + } + } + + view->cursor = sbi; + view->sel_so = block_iter_get_offset(&ebi); + view->sel_eo = SEL_EO_RECALC; + view->selection = SELECT_LINES; + + mark_all_lines_changed(view->buffer); +} + +static int get_indent_of_matching_brace(const View *view) +{ + const LocalOptions *options = &view->buffer->options; + BlockIter bi = view->cursor; + StringView line; + int level = 0; + + while (block_iter_prev_line(&bi)) { + fetch_this_line(&bi, &line); + if (line_has_opening_brace(line)) { + if (level++ == 0) { + return get_indent_width(options, &line); + } + } + if (line_has_closing_brace(line)) { + level--; + } + } + + return -1; +} + +void unselect(View *view) +{ + view->select_mode = SELECT_NONE; + if (view->selection) { + view->selection = SELECT_NONE; + mark_all_lines_changed(view->buffer); + } +} + +void insert_text(View *view, const char *text, size_t size, bool move_after) +{ + size_t del_count = 0; + if (view->selection) { + del_count = prepare_selection(view); + unselect(view); + } + buffer_replace_bytes(view, del_count, text, size); + if (move_after) { + block_iter_skip_bytes(&view->cursor, size); + } +} + +void delete_ch(View *view) +{ + size_t size = 0; + if (view->selection) { + size = prepare_selection(view); + unselect(view); + } else { + const LocalOptions *options = &view->buffer->options; + begin_change(CHANGE_MERGE_DELETE); + if (options->emulate_tab) { + size = get_indent_level_bytes_right(options, &view->cursor); + } + if (size == 0) { + BlockIter bi = view->cursor; + size = block_iter_next_column(&bi); + } + } + buffer_delete_bytes(view, size); +} + +void erase(View *view) +{ + size_t size = 0; + if (view->selection) { + size = prepare_selection(view); + unselect(view); + } else { + const LocalOptions *options = &view->buffer->options; + begin_change(CHANGE_MERGE_ERASE); + if (options->emulate_tab) { + size = get_indent_level_bytes_left(options, &view->cursor); + block_iter_back_bytes(&view->cursor, size); + } + if (size == 0) { + CodePoint u; + size = block_iter_prev_char(&view->cursor, &u); + } + } + buffer_erase_bytes(view, size); +} + +// Go to beginning of whitespace (tabs and spaces) under cursor and +// return number of whitespace bytes after cursor after moving cursor +static size_t goto_beginning_of_whitespace(View *view) +{ + BlockIter bi = view->cursor; + size_t count = 0; + CodePoint u; + + // Count spaces and tabs at or after cursor + while (block_iter_next_char(&bi, &u)) { + if (u != '\t' && u != ' ') { + break; + } + count++; + } + + // Count spaces and tabs before cursor + while (block_iter_prev_char(&view->cursor, &u)) { + if (u != '\t' && u != ' ') { + block_iter_next_char(&view->cursor, &u); + break; + } + count++; + } + return count; +} + +static bool ws_only(const StringView *line) +{ + for (size_t i = 0, n = line->length; i < n; i++) { + char ch = line->data[i]; + if (ch != ' ' && ch != '\t') { + return false; + } + } + return true; +} + +// Non-empty line can be used to determine size of indentation for the next line +static bool find_non_empty_line_bwd(BlockIter *bi) +{ + block_iter_bol(bi); + do { + StringView line; + fill_line_ref(bi, &line); + if (!ws_only(&line)) { + return true; + } + } while (block_iter_prev_line(bi)); + return false; +} + +static void insert_nl(View *view) +{ + size_t del_count = 0; + size_t ins_count = 1; + char *ins = NULL; + + // Prepare deleted text (selection or whitespace around cursor) + if (view->selection) { + del_count = prepare_selection(view); + unselect(view); + } else { + // Trim whitespace around cursor + del_count = goto_beginning_of_whitespace(view); + } + + // Prepare inserted indentation + const LocalOptions *options = &view->buffer->options; + if (options->auto_indent) { + // Current line will be split at cursor position + BlockIter bi = view->cursor; + size_t len = block_iter_bol(&bi); + StringView line; + fill_line_ref(&bi, &line); + line.length = len; + if (ws_only(&line)) { + // This line is (or will become) white space only; find previous, + // non whitespace only line + if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { + fill_line_ref(&bi, &line); + ins = get_indent_for_next_line(options, &line); + } + } else { + ins = get_indent_for_next_line(options, &line); + } + } + + begin_change(CHANGE_MERGE_NONE); + if (ins) { + // Add newline before indent + ins_count = strlen(ins); + memmove(ins + 1, ins, ins_count); + ins[0] = '\n'; + ins_count++; + + buffer_replace_bytes(view, del_count, ins, ins_count); + free(ins); + } else { + buffer_replace_bytes(view, del_count, "\n", ins_count); + } + end_change(); + + // Move after inserted text + block_iter_skip_bytes(&view->cursor, ins_count); +} + +void insert_ch(View *view, CodePoint ch) +{ + if (ch == '\n') { + insert_nl(view); + return; + } + + const Buffer *buffer = view->buffer; + const LocalOptions *options = &buffer->options; + char buf[8]; + char *ins = buf; + char *alloc = NULL; + size_t del_count = 0; + size_t ins_count = 0; + + if (view->selection) { + // Prepare deleted text (selection) + del_count = prepare_selection(view); + unselect(view); + } else if (options->overwrite) { + // Delete character under cursor unless we're at end of line + BlockIter bi = view->cursor; + del_count = block_iter_is_eol(&bi) ? 0 : block_iter_next_column(&bi); + } else if (ch == '}' && options->auto_indent && options->brace_indent) { + BlockIter bi = view->cursor; + StringView curlr; + block_iter_bol(&bi); + fill_line_ref(&bi, &curlr); + if (ws_only(&curlr)) { + int width = get_indent_of_matching_brace(view); + if (width >= 0) { + // Replace current (ws only) line with some indent + '}' + block_iter_bol(&view->cursor); + del_count = curlr.length; + if (width) { + alloc = make_indent(options, width); + ins = alloc; + ins_count = strlen(ins); + // '}' will be replace the terminating NUL + } + } + } + } + + // Prepare inserted text + if (ch == '\t' && options->expand_tab) { + ins_count = options->indent_width; + static_assert(sizeof(buf) >= INDENT_WIDTH_MAX); + memset(ins, ' ', ins_count); + } else { + u_set_char_raw(ins, &ins_count, ch); + } + + // Record change + begin_change(del_count ? CHANGE_MERGE_NONE : CHANGE_MERGE_INSERT); + buffer_replace_bytes(view, del_count, ins, ins_count); + end_change(); + free(alloc); + + // Move after inserted text + block_iter_skip_bytes(&view->cursor, ins_count); +} + +static void join_selection(View *view) +{ + size_t count = prepare_selection(view); + size_t len = 0, join = 0; + BlockIter bi; + CodePoint ch = 0; + + unselect(view); + bi = view->cursor; + + begin_change_chain(); + while (count > 0) { + if (!len) { + view->cursor = bi; + } + + count -= block_iter_next_char(&bi, &ch); + if (ch == '\t' || ch == ' ') { + len++; + } else if (ch == '\n') { + len++; + join++; + } else { + if (join) { + buffer_replace_bytes(view, len, " ", 1); + // Skip the space we inserted and the char we read last + block_iter_next_char(&view->cursor, &ch); + block_iter_next_char(&view->cursor, &ch); + bi = view->cursor; + } + len = 0; + join = 0; + } + } + + // Don't replace last \n that is at end of the selection + if (join && ch == '\n') { + join--; + len--; + } + + if (join) { + if (ch == '\n') { + // Don't add space to end of line + buffer_delete_bytes(view, len); + } else { + buffer_replace_bytes(view, len, " ", 1); + } + } + end_change_chain(view); +} + +void join_lines(View *view) +{ + BlockIter bi = view->cursor; + + if (view->selection) { + join_selection(view); + return; + } + + if (!block_iter_next_line(&bi)) { + return; + } + if (block_iter_is_eof(&bi)) { + return; + } + + BlockIter next = bi; + CodePoint u; + size_t count = 1; + block_iter_prev_char(&bi, &u); + while (block_iter_prev_char(&bi, &u)) { + if (u != '\t' && u != ' ') { + block_iter_next_char(&bi, &u); + break; + } + count++; + } + while (block_iter_next_char(&next, &u)) { + if (u != '\t' && u != ' ') { + break; + } + count++; + } + + view->cursor = bi; + if (u == '\n') { + buffer_delete_bytes(view, count); + } else { + buffer_replace_bytes(view, count, " ", 1); + } +} + +void clear_lines(View *view, bool auto_indent) +{ + char *indent = NULL; + if (auto_indent) { + BlockIter bi = view->cursor; + if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { + StringView line; + fill_line_ref(&bi, &line); + indent = get_indent_for_next_line(&view->buffer->options, &line); + } + } + + size_t del_count = 0; + if (view->selection) { + view->selection = SELECT_LINES; + del_count = prepare_selection(view); + unselect(view); + // Don't delete last newline + if (del_count) { + del_count--; + } + } else { + block_iter_eol(&view->cursor); + del_count = block_iter_bol(&view->cursor); + } + + if (!indent && !del_count) { + return; + } + + size_t ins_count = indent ? strlen(indent) : 0; + buffer_replace_bytes(view, del_count, indent, ins_count); + free(indent); + block_iter_skip_bytes(&view->cursor, ins_count); +} + +void delete_lines(View *view) +{ + long x = view_get_preferred_x(view); + size_t del_count; + if (view->selection) { + view->selection = SELECT_LINES; + del_count = prepare_selection(view); + unselect(view); + } else { + block_iter_bol(&view->cursor); + BlockIter tmp = view->cursor; + del_count = block_iter_eat_line(&tmp); + } + buffer_delete_bytes(view, del_count); + move_to_preferred_x(view, x); +} + +void new_line(View *view, bool above) +{ + if (above && block_iter_prev_line(&view->cursor) == 0) { + // Already on first line; insert newline at bof + block_iter_bol(&view->cursor); + buffer_insert_bytes(view, "\n", 1); + return; + } + + const LocalOptions *options = &view->buffer->options; + char *ins = NULL; + block_iter_eol(&view->cursor); + + if (options->auto_indent) { + BlockIter bi = view->cursor; + if (find_non_empty_line_bwd(&bi)) { + StringView line; + fill_line_ref(&bi, &line); + ins = get_indent_for_next_line(options, &line); + } + } + + size_t ins_count; + if (ins) { + ins_count = strlen(ins); + memmove(ins + 1, ins, ins_count); + ins[0] = '\n'; + ins_count++; + buffer_insert_bytes(view, ins, ins_count); + free(ins); + } else { + ins_count = 1; + buffer_insert_bytes(view, "\n", 1); + } + + block_iter_skip_bytes(&view->cursor, ins_count); +} + +static void add_word(ParagraphFormatter *pf, const char *word, size_t len) +{ + size_t i = 0; + size_t word_width = 0; + while (i < len) { + word_width += u_char_width(u_get_char(word, len, &i)); + } + + if (pf->cur_width && pf->cur_width + 1 + word_width > pf->text_width) { + string_append_byte(&pf->buf, '\n'); + pf->cur_width = 0; + } + + if (pf->cur_width == 0) { + if (pf->indent_len) { + string_append_buf(&pf->buf, pf->indent, pf->indent_len); + } + pf->cur_width = pf->indent_width; + } else { + string_append_byte(&pf->buf, ' '); + pf->cur_width++; + } + + string_append_buf(&pf->buf, word, len); + pf->cur_width += word_width; +} + +static bool is_paragraph_separator(const StringView *line) +{ + StringView trimmed = *line; + strview_trim(&trimmed); + + return + trimmed.length == 0 + // TODO: make this configurable + || strview_equal_cstring(&trimmed, "/*") + || strview_equal_cstring(&trimmed, "*/") + ; +} + +static bool in_paragraph(const LocalOptions *options, const StringView *line, size_t indent_width) +{ + if (get_indent_width(options, line) != indent_width) { + return false; + } + return !is_paragraph_separator(line); +} + +static size_t paragraph_size(View *view) +{ + const LocalOptions *options = &view->buffer->options; + BlockIter bi = view->cursor; + StringView line; + block_iter_bol(&bi); + fill_line_ref(&bi, &line); + if (is_paragraph_separator(&line)) { + // Not in paragraph + return 0; + } + size_t indent_width = get_indent_width(options, &line); + + // Go to beginning of paragraph + while (block_iter_prev_line(&bi)) { + fill_line_ref(&bi, &line); + if (!in_paragraph(options, &line, indent_width)) { + block_iter_eat_line(&bi); + break; + } + } + view->cursor = bi; + + // Get size of paragraph + size_t size = 0; + do { + size_t bytes = block_iter_eat_line(&bi); + if (!bytes) { + break; + } + size += bytes; + fill_line_ref(&bi, &line); + } while (in_paragraph(options, &line, indent_width)); + return size; +} + +void format_paragraph(View *view, size_t text_width) +{ + size_t len; + if (view->selection) { + view->selection = SELECT_LINES; + len = prepare_selection(view); + } else { + len = paragraph_size(view); + } + if (!len) { + return; + } + + const LocalOptions *options = &view->buffer->options; + char *sel = block_iter_get_bytes(&view->cursor, len); + StringView sv = string_view(sel, len); + size_t indent_width = get_indent_width(options, &sv); + char *indent = make_indent(options, indent_width); + + ParagraphFormatter pf = { + .buf = STRING_INIT, + .indent = indent, + .indent_len = indent ? strlen(indent) : 0, + .indent_width = indent_width, + .cur_width = 0, + .text_width = text_width + }; + + for (size_t i = 0; true; ) { + while (i < len) { + size_t tmp = i; + if (!u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { + break; + } + i = tmp; + } + if (i == len) { + break; + } + + size_t start = i; + while (i < len) { + size_t tmp = i; + if (u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { + break; + } + i = tmp; + } + + add_word(&pf, sel + start, i - start); + } + + if (pf.buf.len) { + string_append_byte(&pf.buf, '\n'); + } + buffer_replace_bytes(view, len, pf.buf.buffer, pf.buf.len); + if (pf.buf.len) { + block_iter_skip_bytes(&view->cursor, pf.buf.len - 1); + } + string_free(&pf.buf); + free(pf.indent); + free(sel); + + unselect(view); +} + +void change_case(View *view, char mode) +{ + bool was_selecting = false; + bool move = true; + size_t text_len; + if (view->selection) { + SelectionInfo info; + init_selection(view, &info); + view->cursor = info.si; + text_len = info.eo - info.so; + unselect(view); + was_selecting = true; + move = !info.swapped; + } else { + CodePoint u; + if (!block_iter_get_char(&view->cursor, &u)) { + return; + } + text_len = u_char_size(u); + } + + String dst = string_new(text_len); + char *src = block_iter_get_bytes(&view->cursor, text_len); + size_t i = 0; + switch (mode) { + case 'l': + while (i < text_len) { + CodePoint u = u_to_lower(u_get_char(src, text_len, &i)); + string_append_codepoint(&dst, u); + } + break; + case 'u': + while (i < text_len) { + CodePoint u = u_to_upper(u_get_char(src, text_len, &i)); + string_append_codepoint(&dst, u); + } + break; + case 't': + while (i < text_len) { + CodePoint u = u_get_char(src, text_len, &i); + u = u_is_upper(u) ? u_to_lower(u) : u_to_upper(u); + string_append_codepoint(&dst, u); + } + break; + default: + BUG("unhandled case mode"); + } + + buffer_replace_bytes(view, text_len, dst.buffer, dst.len); + free(src); + + if (move && dst.len > 0) { + if (was_selecting) { + // Move cursor back to where it was + size_t idx = dst.len; + u_prev_char(dst.buffer, &idx); + block_iter_skip_bytes(&view->cursor, idx); + } else { + block_iter_skip_bytes(&view->cursor, dst.len); + } + } + + string_free(&dst); +} |
