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/screen-view.c | |
| parent | 349991bf6efe473ab9a5cbdae0a8114d72b997e3 (diff) | |
| download | crep-1566b6faa8534118c3566188181367cd0868468f.tar.gz | |
Added partial matching and introduced threads
Diffstat (limited to 'examples/dte/screen-view.c')
| -rw-r--r-- | examples/dte/screen-view.c | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/examples/dte/screen-view.c b/examples/dte/screen-view.c new file mode 100644 index 0000000..0f8d72c --- /dev/null +++ b/examples/dte/screen-view.c @@ -0,0 +1,427 @@ +#include "screen.h" +#include "indent.h" +#include "selection.h" +#include "syntax/highlight.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/str-util.h" +#include "util/utf8.h" + +typedef struct { + const View *view; + size_t line_nr; + size_t offset; + ssize_t sel_so; + ssize_t sel_eo; + + const unsigned char *line; + size_t size; + size_t pos; + size_t indent_size; + size_t trailing_ws_offset; + const TermColor **colors; +} LineInfo; + +// Like mask_color() but can change bg color only if it has not been changed yet +static void mask_color2(const ColorScheme *colors, TermColor *color, const TermColor *over) +{ + int32_t default_bg = colors->builtin[BC_DEFAULT].bg; + if (over->bg != COLOR_KEEP && (color->bg == default_bg || color->bg < 0)) { + color->bg = over->bg; + } + + if (over->fg != COLOR_KEEP) { + color->fg = over->fg; + } + + if (!(over->attr & ATTR_KEEP)) { + color->attr = over->attr; + } +} + +static void mask_selection_and_current_line ( + const ColorScheme *colors, + const LineInfo *info, + TermColor *color +) { + if (info->offset >= info->sel_so && info->offset < info->sel_eo) { + mask_color(color, &colors->builtin[BC_SELECTION]); + } else if (info->line_nr == info->view->cy) { + mask_color2(colors, color, &colors->builtin[BC_CURRENTLINE]); + } +} + +static bool is_non_text(CodePoint u, bool display_special) +{ + if (u == '\t') { + return display_special; + } + return u < 0x20 || u == 0x7F || u_is_unprintable(u); +} + +static WhitespaceErrorFlags get_ws_error(const LocalOptions *opts) +{ + WhitespaceErrorFlags taberrs = WSE_TAB_INDENT | WSE_TAB_AFTER_INDENT; + WhitespaceErrorFlags extra = opts->expand_tab ? taberrs : WSE_SPACE_INDENT; + return opts->ws_error | ((opts->ws_error & WSE_AUTO_INDENT) ? extra : 0); +} + +static bool whitespace_error(const LineInfo *info, CodePoint u, size_t i) +{ + const View *view = info->view; + WhitespaceErrorFlags flags = get_ws_error(&view->buffer->options); + WhitespaceErrorFlags trailing = flags & (WSE_TRAILING | WSE_ALL_TRAILING); + if (i >= info->trailing_ws_offset && trailing) { + // Trailing whitespace + if ( + // Cursor is not on this line + info->line_nr != view->cy + // or is positioned before any trailing whitespace + || view->cx < info->trailing_ws_offset + // or user explicitly wants trailing space under cursor highlighted + || flags & WSE_ALL_TRAILING + ) { + return true; + } + } + + bool in_indent = (i < info->indent_size); + if (u == '\t') { + WhitespaceErrorFlags mask = in_indent ? WSE_TAB_INDENT : WSE_TAB_AFTER_INDENT; + return (flags & mask) != 0; + } + if (!in_indent) { + // All checks below here only apply to indentation + return false; + } + + const char *line = info->line; + size_t pos = i; + size_t count = 0; + while (pos > 0 && line[pos - 1] == ' ') { + pos--; + } + while (pos < info->size && line[pos] == ' ') { + pos++; + count++; + } + + WhitespaceErrorFlags mask; + if (count >= view->buffer->options.tab_width) { + // Spaces used instead of tab + mask = WSE_SPACE_INDENT; + } else if (pos < info->size && line[pos] == '\t') { + // Space before tab + mask = WSE_SPACE_INDENT; + } else { + // Less than tab width spaces at end of indentation + mask = WSE_SPACE_ALIGN; + } + return (flags & mask) != 0; +} + +static CodePoint screen_next_char(EditorState *e, LineInfo *info) +{ + size_t count, pos = info->pos; + CodePoint u = info->line[pos]; + TermColor color; + bool ws_error = false; + + if (likely(u < 0x80)) { + info->pos++; + count = 1; + if (u == '\t' || u == ' ') { + ws_error = whitespace_error(info, u, pos); + } + } else { + u = u_get_nonascii(info->line, info->size, &info->pos); + count = info->pos - pos; + + if ( + u_is_special_whitespace(u) // Highly annoying no-break space etc. + && (info->view->buffer->options.ws_error & WSE_SPECIAL) + ) { + ws_error = true; + } + } + + if (info->colors && info->colors[pos]) { + color = *info->colors[pos]; + } else { + color = e->colors.builtin[BC_DEFAULT]; + } + if (is_non_text(u, e->options.display_special)) { + mask_color(&color, &e->colors.builtin[BC_NONTEXT]); + } + if (ws_error) { + mask_color(&color, &e->colors.builtin[BC_WSERROR]); + } + mask_selection_and_current_line(&e->colors, info, &color); + set_color(&e->terminal, &e->colors, &color); + + info->offset += count; + return u; +} + +static void screen_skip_char(TermOutputBuffer *obuf, LineInfo *info) +{ + CodePoint u = info->line[info->pos++]; + info->offset++; + if (likely(u < 0x80)) { + if (likely(!ascii_iscntrl(u))) { + obuf->x++; + } else if (u == '\t' && obuf->tab_mode != TAB_CONTROL) { + obuf->x = next_indent_width(obuf->x, obuf->tab_width); + } else { + // Control + obuf->x += 2; + } + } else { + size_t pos = info->pos; + info->pos--; + u = u_get_nonascii(info->line, info->size, &info->pos); + obuf->x += u_char_width(u); + info->offset += info->pos - pos; + } +} + +static bool is_notice(const char *word, size_t len) +{ + switch (len) { + case 3: return mem_equal(word, "XXX", 3); + case 4: return mem_equal(word, "TODO", 4); + case 5: return mem_equal(word, "FIXME", 5); + } + return false; +} + +// Highlight certain words inside comments +static void hl_words(Terminal *term, const ColorScheme *colors, const LineInfo *info) +{ + const TermColor *cc = find_color(colors, "comment"); + const TermColor *nc = find_color(colors, "notice"); + + if (!info->colors || !cc || !nc) { + return; + } + + size_t i = info->pos; + if (i >= info->size) { + return; + } + + // Go to beginning of partially visible word inside comment + while (i > 0 && info->colors[i] == cc && is_word_byte(info->line[i])) { + i--; + } + + // This should be more than enough. I'm too lazy to iterate characters + // instead of bytes and calculate text width. + const size_t max = info->pos + term->width * 4 + 8; + + size_t si; + while (i < info->size) { + if (info->colors[i] != cc || !is_word_byte(info->line[i])) { + if (i > max) { + break; + } + i++; + } else { + // Beginning of a word inside comment + si = i++; + while ( + i < info->size && info->colors[i] == cc + && is_word_byte(info->line[i]) + ) { + i++; + } + if (is_notice(info->line + si, i - si)) { + for (size_t j = si; j < i; j++) { + info->colors[j] = nc; + } + } + } + } +} + +static void line_info_init ( + LineInfo *info, + const View *view, + const BlockIter *bi, + size_t line_nr +) { + *info = (LineInfo) { + .view = view, + .line_nr = line_nr, + .offset = block_iter_get_offset(bi), + }; + + if (!view->selection) { + info->sel_so = -1; + info->sel_eo = -1; + } else if (view->sel_eo != SEL_EO_RECALC) { + // Already calculated + info->sel_so = view->sel_so; + info->sel_eo = view->sel_eo; + BUG_ON(info->sel_so > info->sel_eo); + } else { + SelectionInfo sel; + init_selection(view, &sel); + info->sel_so = sel.so; + info->sel_eo = sel.eo; + } +} + +static void line_info_set_line ( + LineInfo *info, + const StringView *line, + const TermColor **colors +) { + BUG_ON(line->length == 0); + BUG_ON(line->data[line->length - 1] != '\n'); + + info->line = line->data; + info->size = line->length - 1; + info->pos = 0; + info->colors = colors; + + { + size_t i, n; + for (i = 0, n = info->size; i < n; i++) { + char ch = info->line[i]; + if (ch != '\t' && ch != ' ') { + break; + } + } + info->indent_size = i; + } + + static_assert_compatible_types(info->trailing_ws_offset, size_t); + info->trailing_ws_offset = SIZE_MAX; + for (ssize_t i = info->size - 1; i >= 0; i--) { + char ch = info->line[i]; + if (ch != '\t' && ch != ' ') { + break; + } + info->trailing_ws_offset = i; + } +} + +static void print_line(EditorState *e, LineInfo *info) +{ + // Screen might be scrolled horizontally. Skip most invisible + // characters using screen_skip_char(), which is much faster than + // buf_skip(screen_next_char(info)). + // + // There can be a wide character (tab, control code etc.) that is + // partially visible and can't be skipped using screen_skip_char(). + Terminal *term = &e->terminal; + TermOutputBuffer *obuf = &term->obuf; + while (obuf->x + 8 < obuf->scroll_x && info->pos < info->size) { + screen_skip_char(obuf, info); + } + + const ColorScheme *colors = &e->colors; + hl_words(term, colors, info); + + while (info->pos < info->size) { + BUG_ON(obuf->x > obuf->scroll_x + obuf->width); + CodePoint u = screen_next_char(e, info); + if (!term_put_char(obuf, u)) { + // +1 for newline + info->offset += info->size - info->pos + 1; + return; + } + } + + TermColor color; + if (e->options.display_special && obuf->x >= obuf->scroll_x) { + // Syntax highlighter highlights \n but use default color anyway + color = colors->builtin[BC_DEFAULT]; + mask_color(&color, &colors->builtin[BC_NONTEXT]); + mask_selection_and_current_line(colors, info, &color); + set_color(term, colors, &color); + term_put_char(obuf, '$'); + } + + color = colors->builtin[BC_DEFAULT]; + mask_selection_and_current_line(colors, info, &color); + set_color(term, colors, &color); + info->offset++; + term_clear_eol(term); +} + +void update_range(EditorState *e, const View *view, long y1, long y2) +{ + const int edit_x = view->window->edit_x; + const int edit_y = view->window->edit_y; + const int edit_w = view->window->edit_w; + const int edit_h = view->window->edit_h; + + Terminal *term = &e->terminal; + TermOutputBuffer *obuf = &term->obuf; + term_output_reset(term, edit_x, edit_w, view->vx); + obuf->tab_width = view->buffer->options.tab_width; + obuf->tab_mode = e->options.display_special ? TAB_SPECIAL : TAB_NORMAL; + + BlockIter bi = view->cursor; + for (long i = 0, n = view->cy - y1; i < n; i++) { + block_iter_prev_line(&bi); + } + for (long i = 0, n = y1 - view->cy; i < n; i++) { + block_iter_eat_line(&bi); + } + block_iter_bol(&bi); + + LineInfo info; + line_info_init(&info, view, &bi, y1); + + y1 -= view->vy; + y2 -= view->vy; + + bool got_line = !block_iter_is_eof(&bi); + hl_fill_start_states(view->buffer, &e->colors, info.line_nr); + long i; + for (i = y1; got_line && i < y2; i++) { + obuf->x = 0; + term_move_cursor(obuf, edit_x, edit_y + i); + + StringView line; + fill_line_nl_ref(&bi, &line); + bool next_changed; + const TermColor **colors = hl_line(view->buffer, &e->colors, &line, info.line_nr, &next_changed); + line_info_set_line(&info, &line, colors); + print_line(e, &info); + + got_line = !!block_iter_next_line(&bi); + info.line_nr++; + + if (next_changed && i + 1 == y2 && y2 < edit_h) { + // More lines need to be updated not because their contents have + // changed but because their highlight state has + y2++; + } + } + + if (i < y2 && info.line_nr == view->cy) { + // Dummy empty line is shown only if cursor is on it + TermColor color = e->colors.builtin[BC_DEFAULT]; + + obuf->x = 0; + mask_color2(&e->colors, &color, &e->colors.builtin[BC_CURRENTLINE]); + set_color(term, &e->colors, &color); + + term_move_cursor(obuf, edit_x, edit_y + i++); + term_clear_eol(term); + } + + if (i < y2) { + set_builtin_color(term, &e->colors, BC_NOLINE); + } + for (; i < y2; i++) { + obuf->x = 0; + term_move_cursor(obuf, edit_x, edit_y + i); + term_put_char(obuf, '~'); + term_clear_eol(term); + } +} |
