summaryrefslogtreecommitdiff
path: root/examples/dte/screen-view.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2023-11-09 23:19:53 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2023-11-09 23:19:53 +0100
commit1566b6faa8534118c3566188181367cd0868468f (patch)
tree1de8d4b369efb5e592685a31088f798a6b63ffa1 /examples/dte/screen-view.c
parent349991bf6efe473ab9a5cbdae0a8114d72b997e3 (diff)
downloadcrep-1566b6faa8534118c3566188181367cd0868468f.tar.gz
Added partial matching and introduced threads
Diffstat (limited to 'examples/dte/screen-view.c')
-rw-r--r--examples/dte/screen-view.c427
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);
+ }
+}