summaryrefslogtreecommitdiff
path: root/src/dged/buffer_view.c
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2023-07-12 16:20:50 +0200
committerAlbert Cervin <albert@acervin.com>2023-10-19 22:41:33 +0200
commit54c9b4b533210b77be998f458ff96bdc54272f64 (patch)
treeeb434343bb1083172af50b7372d1e2745af00f8f /src/dged/buffer_view.c
parent3a8ae83aa13636679c151027cace905fa87ebd8e (diff)
downloaddged-54c9b4b533210b77be998f458ff96bdc54272f64.tar.gz
dged-54c9b4b533210b77be998f458ff96bdc54272f64.tar.xz
dged-54c9b4b533210b77be998f458ff96bdc54272f64.zip
big buffer/buffer_view rework
A buffer is only the text and the corresponding operation. A buffer view holds information about scroll, dot and mark positions. One way to think about it is that a buffer is stateless whereas a buffer view is stateful.
Diffstat (limited to 'src/dged/buffer_view.c')
-rw-r--r--src/dged/buffer_view.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c
new file mode 100644
index 0000000..2c69161
--- /dev/null
+++ b/src/dged/buffer_view.c
@@ -0,0 +1,418 @@
+#include <string.h>
+
+#include "buffer.h"
+#include "buffer_view.h"
+#include "display.h"
+
+struct modeline {
+ uint8_t *buffer;
+ uint32_t sz;
+};
+
+static bool maybe_delete_region(struct buffer_view *view) {
+ struct region reg = region_new(view->dot, view->mark);
+ if (view->mark_set && region_has_size(reg)) {
+ buffer_delete(view->buffer, reg);
+ buffer_view_clear_mark(view);
+ view->dot = reg.begin;
+ return true;
+ }
+
+ return false;
+}
+
+struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline,
+ bool line_numbers) {
+ struct buffer_view v = (struct buffer_view){
+ .dot = (struct location){.line = 0, .col = 0},
+ .mark = (struct location){.line = 0, .col = 0},
+ .mark_set = false,
+ .scroll = (struct location){.line = 0, .col = 0},
+ .buffer = buffer,
+ .modeline = NULL,
+ .line_numbers = line_numbers,
+ .fringe_width = 0,
+ };
+
+ if (modeline) {
+ v.modeline = calloc(1, sizeof(struct modeline));
+ v.modeline->buffer = malloc(1024);
+ v.modeline->sz = 1024;
+ v.modeline->buffer[0] = '\0';
+ }
+
+ return v;
+}
+
+struct buffer_view buffer_view_clone(const struct buffer_view *view) {
+ struct buffer_view c = {
+ .dot = view->dot,
+ .mark = view->mark,
+ .mark_set = view->mark_set,
+ .scroll = view->scroll,
+ .buffer = view->buffer,
+ .modeline = NULL,
+ .line_numbers = view->line_numbers,
+ };
+
+ if (view->modeline) {
+ c.modeline = calloc(1, sizeof(struct modeline));
+ c.modeline->buffer = malloc(view->modeline->sz);
+ memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz);
+ }
+
+ return c;
+}
+
+void buffer_view_destroy(struct buffer_view *view) {
+ if (view->modeline != NULL) {
+ free(view->modeline->buffer);
+ free(view->modeline);
+ }
+}
+
+void buffer_view_add(struct buffer_view *view, uint8_t *txt, uint32_t nbytes) {
+ maybe_delete_region(view);
+ view->dot = buffer_add(view->buffer, view->dot, txt, nbytes);
+}
+
+void buffer_view_goto_beginning(struct buffer_view *view) {
+ view->dot = (struct location){.line = 1, .col = 0};
+}
+
+void buffer_view_goto_end(struct buffer_view *view) {
+ view->dot = buffer_end(view->buffer);
+}
+
+void buffer_view_goto(struct buffer_view *view, struct location to) {
+ view->dot = buffer_clamp(view->buffer, (int64_t)to.line, (int64_t)to.col);
+}
+
+void buffer_view_forward_char(struct buffer_view *view) {
+ view->dot = buffer_next_char(view->buffer, view->dot);
+}
+
+void buffer_view_backward_char(struct buffer_view *view) {
+ view->dot = buffer_previous_char(view->buffer, view->dot);
+}
+
+void buffer_view_forward_word(struct buffer_view *view) {
+ view->dot = buffer_next_word(view->buffer, view->dot);
+}
+
+void buffer_view_backward_word(struct buffer_view *view) {
+ view->dot = buffer_previous_word(view->buffer, view->dot);
+}
+
+void buffer_view_forward_line(struct buffer_view *view) {
+ view->dot = buffer_next_line(view->buffer, view->dot);
+}
+
+void buffer_view_backward_line(struct buffer_view *view) {
+ view->dot = buffer_previous_line(view->buffer, view->dot);
+}
+
+void buffer_view_forward_nlines(struct buffer_view *view, uint32_t nlines) {
+ view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line + nlines,
+ (int64_t)view->dot.col);
+}
+
+void buffer_view_backward_nlines(struct buffer_view *view, uint32_t nlines) {
+ view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line - nlines,
+ (int64_t)view->dot.col);
+}
+
+void buffer_view_goto_end_of_line(struct buffer_view *view) {
+ view->dot.col = buffer_num_chars(view->buffer, view->dot.line);
+}
+
+void buffer_view_goto_beginning_of_line(struct buffer_view *view) {
+ view->dot.col = 0;
+}
+
+void buffer_view_newline(struct buffer_view *view) {
+ view->dot = buffer_newline(view->buffer, view->dot);
+}
+
+void buffer_view_indent(struct buffer_view *view) {
+ view->dot = buffer_indent(view->buffer, view->dot);
+}
+
+void buffer_view_copy(struct buffer_view *view) {
+ if (!view->mark_set) {
+ return;
+ }
+
+ view->dot = buffer_copy(view->buffer, region_new(view->dot, view->mark));
+ buffer_view_clear_mark(view);
+}
+
+void buffer_view_cut(struct buffer_view *view) {
+ if (!view->mark_set) {
+ return;
+ }
+
+ view->dot = buffer_cut(view->buffer, region_new(view->dot, view->mark));
+ buffer_view_clear_mark(view);
+}
+
+void buffer_view_paste(struct buffer_view *view) {
+ maybe_delete_region(view);
+ view->dot = buffer_paste(view->buffer, view->dot);
+}
+
+void buffer_view_paste_older(struct buffer_view *view) {
+ view->dot = buffer_paste_older(view->buffer, view->dot);
+}
+
+void buffer_view_forward_delete_char(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(view->dot, buffer_next_char(view->buffer, view->dot)));
+}
+
+void buffer_view_backward_delete_char(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(buffer_previous_char(view->buffer, view->dot), view->dot));
+}
+
+void buffer_view_forward_delete_word(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(view->dot, buffer_next_word(view->buffer, view->dot)));
+}
+
+void buffer_view_backward_delete_word(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(buffer_previous_word(view->buffer, view->dot), view->dot));
+}
+
+void buffer_view_kill_line(struct buffer_view *view) {
+ uint32_t nchars =
+ buffer_num_chars(view->buffer, view->dot.line) - view->dot.col;
+ if (nchars == 0) {
+ nchars = 1;
+ }
+
+ struct region reg = region_new(view->dot, (struct location){
+ .line = view->dot.line,
+ .col = view->dot.col + nchars,
+ });
+
+ buffer_cut(view->buffer, reg);
+}
+
+void buffer_view_set_mark(struct buffer_view *view) {
+ buffer_view_set_mark_at(view, view->dot);
+}
+
+void buffer_view_clear_mark(struct buffer_view *view) {
+ view->mark_set = false;
+}
+
+void buffer_view_set_mark_at(struct buffer_view *view, struct location mark) {
+ view->mark = mark;
+ view->mark_set = true;
+}
+
+struct location buffer_view_dot_to_relative(struct buffer_view *view) {
+ return (struct location){
+ .line = view->dot.line - view->scroll.line,
+ .col = view->dot.col - view->scroll.col + view->fringe_width,
+ };
+}
+
+void buffer_view_undo(struct buffer_view *view) {
+ view->dot = buffer_undo(view->buffer, view->dot);
+}
+
+static uint32_t longest_linenum(struct buffer_view *view) {
+ uint32_t total_lines = buffer_num_lines(view->buffer);
+ uint32_t longest_nchars = 10;
+ if (total_lines < 10) {
+ longest_nchars = 1;
+ } else if (total_lines < 100) {
+ longest_nchars = 2;
+ } else if (total_lines < 1000) {
+ longest_nchars = 3;
+ } else if (total_lines < 10000) {
+ longest_nchars = 4;
+ } else if (total_lines < 100000) {
+ longest_nchars = 5;
+ } else if (total_lines < 1000000) {
+ longest_nchars = 6;
+ } else if (total_lines < 10000000) {
+ longest_nchars = 7;
+ } else if (total_lines < 100000000) {
+ longest_nchars = 8;
+ } else if (total_lines < 1000000000) {
+ longest_nchars = 9;
+ }
+
+ return longest_nchars;
+}
+
+static uint32_t render_line_numbers(struct buffer_view *view,
+ struct command_list *commands,
+ uint32_t height) {
+ uint32_t longest_nchars = longest_linenum(view);
+ static char buf[16];
+
+ uint32_t nlines_buf = buffer_num_lines(view->buffer);
+ uint32_t line = view->scroll.line;
+ uint32_t relline = 0;
+
+ for (; relline < height && line < nlines_buf; ++line, ++relline) {
+ command_list_set_index_color_bg(commands, 8);
+ command_list_set_index_color_fg(commands, line == view->dot.line ? 15 : 7);
+ uint32_t chars = snprintf(buf, 16, "%*d", longest_nchars + 1, line + 1);
+ command_list_draw_text_copy(commands, 0, relline, (uint8_t *)buf, chars);
+ command_list_reset_color(commands);
+ command_list_draw_repeated(commands, longest_nchars + 1, relline, ' ', 1);
+ }
+
+ for (; relline < height; ++relline) {
+ command_list_set_index_color_bg(commands, 8);
+ command_list_set_index_color_fg(commands, 7);
+ command_list_draw_repeated(commands, 0, relline, ' ', longest_nchars + 1);
+ command_list_reset_color(commands);
+ command_list_draw_repeated(commands, longest_nchars + 1, relline, ' ', 1);
+ }
+
+ return longest_nchars + 2;
+}
+
+static void render_modeline(struct modeline *modeline, struct buffer_view *view,
+ struct command_list *commands, uint32_t window_id,
+ uint32_t width, uint32_t height,
+ uint64_t frame_time) {
+ char buf[width * 4];
+
+ static uint64_t samples[10] = {0};
+ static uint32_t samplei = 0;
+ static uint64_t avg = 0;
+
+ // calc a moving average with a window of the last 10 frames
+ ++samplei;
+ samplei %= 10;
+ avg += 0.1 * (frame_time - samples[samplei]);
+ samples[samplei] = frame_time;
+
+ time_t now = time(NULL);
+ struct tm *lt = localtime(&now);
+ char left[128], right[128];
+
+ snprintf(left, 128, " %c%c %d:%-16s (%d, %d) (%s)",
+ view->buffer->modified ? '*' : '-',
+ view->buffer->readonly ? '%' : '-', window_id, view->buffer->name,
+ view->dot.line + 1, view->dot.col, view->buffer->lang.name);
+ snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour,
+ lt->tm_min);
+
+ snprintf(buf, width * 4, "%s%*s%s", left,
+ (int)(width - (strlen(left) + strlen(right))), "", right);
+
+ if (strcmp(buf, (char *)modeline->buffer) != 0) {
+ modeline->buffer = realloc(modeline->buffer, width * 4);
+ modeline->sz = width * 4;
+ strcpy((char *)modeline->buffer, buf);
+ }
+
+ command_list_set_index_color_bg(commands, 8);
+ command_list_draw_text(commands, 0, height - 1, modeline->buffer,
+ strlen((char *)modeline->buffer));
+ command_list_reset_color(commands);
+}
+
+void buffer_view_update(struct buffer_view *view,
+ struct buffer_view_update_params *params) {
+
+ uint32_t height = params->height;
+ uint32_t width = params->width;
+
+ // render modeline
+ uint32_t modeline_height = 0;
+ if (view->modeline != NULL) {
+ modeline_height = 1;
+ render_modeline(view->modeline, view, params->commands, params->window_id,
+ params->width, params->height, params->frame_time);
+ }
+
+ height -= modeline_height;
+
+ // render line numbers
+ uint32_t linum_width = 0;
+ if (view->line_numbers) {
+ linum_width = render_line_numbers(view, params->commands, height);
+ }
+
+ width -= linum_width;
+ view->fringe_width = linum_width;
+
+ /* Make sure the dot is always inside buffer limits.
+ * It can be outside for example if the text is changed elsewhere. */
+ view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line,
+ (int64_t)view->dot.col);
+
+ // update scroll position if needed
+ if (view->dot.line >= view->scroll.line + height || view->dot.line < view->scroll.line) {
+ // put dot in the middle, height-wise
+ view->scroll.line = buffer_clamp(view->buffer, (int64_t)view->dot.line - params->height / 2,
+ 0).line;
+ }
+
+ if (view->dot.col >= view->scroll.col + width || view->dot.col < view->scroll.col) {
+ view->scroll.col = buffer_clamp(view->buffer, view->dot.line, view->dot.col).col;
+ }
+
+ // color region
+ if (view->mark_set) {
+ struct region reg = region_new(view->dot, view->mark);
+ if (region_has_size(reg)) {
+ buffer_add_text_property(view->buffer, reg.begin, reg.end,
+ (struct text_property){
+ .type = TextProperty_Colors,
+ .colors =
+ (struct text_property_colors){
+ .set_bg = true,
+ .bg = 5,
+ .set_fg = false,
+ },
+ });
+ }
+ }
+
+ // update buffer
+ struct command_list *buf_cmds = command_list_create(
+ width * height, params->frame_alloc, params->window_x + linum_width,
+ params->window_y, view->buffer->name);
+ struct buffer_update_params bufparams = {
+ .commands = buf_cmds,
+ .origin = view->scroll,
+ .width = width,
+ .height = height,
+ };
+ buffer_update(view->buffer, &bufparams);
+
+ // draw buffer commands nested inside this command list
+ command_list_draw_command_list(params->commands, buf_cmds);
+ buffer_clear_text_properties(view->buffer);
+}