aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/ui_compositor.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/ui_compositor.c')
-rw-r--r--src/nvim/ui_compositor.c385
1 files changed, 385 insertions, 0 deletions
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
new file mode 100644
index 0000000000..2ca0d1a6eb
--- /dev/null
+++ b/src/nvim/ui_compositor.c
@@ -0,0 +1,385 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// Compositor: merge floating grids with the main grid for display in
+// TUI and non-multigrid UIs.
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "nvim/lib/kvec.h"
+#include "nvim/log.h"
+#include "nvim/main.h"
+#include "nvim/ascii.h"
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/memory.h"
+#include "nvim/ui_compositor.h"
+#include "nvim/ugrid.h"
+#include "nvim/screen.h"
+#include "nvim/syntax.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/os/os.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_compositor.c.generated.h"
+#endif
+
+static UI *compositor = NULL;
+static int composed_uis = 0;
+kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE;
+
+static size_t bufsize = 0;
+static schar_T *linebuf;
+static sattr_T *attrbuf;
+
+#ifndef NDEBUG
+static int chk_width = 0, chk_height = 0;
+#endif
+
+static ScreenGrid *curgrid;
+
+static bool valid_screen = true;
+static bool msg_scroll_mode = false;
+static int msg_first_invalid = 0;
+
+void ui_comp_init(void)
+{
+ if (compositor != NULL) {
+ return;
+ }
+ compositor = xcalloc(1, sizeof(UI));
+
+ compositor->rgb = true;
+ compositor->grid_resize = ui_comp_grid_resize;
+ compositor->grid_clear = ui_comp_grid_clear;
+ compositor->grid_scroll = ui_comp_grid_scroll;
+ compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
+ compositor->raw_line = ui_comp_raw_line;
+ compositor->win_scroll_over_start = ui_comp_win_scroll_over_start;
+ compositor->win_scroll_over_reset = ui_comp_win_scroll_over_reset;
+
+ // Be unopinionated: will be attached together with a "real" ui anyway
+ compositor->width = INT_MAX;
+ compositor->height = INT_MAX;
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ compositor->ui_ext[i] = true;
+ }
+
+ // TODO(bfredl): this will be more complicated if we implement
+ // hlstate per UI (i e reduce hl ids for non-hlstate UIs)
+ compositor->ui_ext[kUIHlState] = false;
+
+ kv_push(layers, &default_grid);
+ curgrid = &default_grid;
+
+ ui_attach_impl(compositor);
+}
+
+
+void ui_comp_attach(UI *ui)
+{
+ composed_uis++;
+ ui->composed = true;
+}
+
+void ui_comp_detach(UI *ui)
+{
+ composed_uis--;
+ if (composed_uis == 0) {
+ xfree(linebuf);
+ xfree(attrbuf);
+ linebuf = NULL;
+ attrbuf = NULL;
+ bufsize = 0;
+ }
+ ui->composed = false;
+}
+
+bool ui_comp_should_draw(void)
+{
+ return composed_uis != 0 && valid_screen;
+}
+
+/// TODO(bfredl): later on the compositor should just use win_float_pos events,
+/// though that will require slight event order adjustment: emit the win_pos
+/// events in the beginning of update_screen(0), rather than in ui_flush()
+bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width)
+{
+ if (grid->comp_index != 0) {
+ bool moved = (row != grid->comp_row) || (col != grid->comp_col);
+ if (ui_comp_should_draw()) {
+ // Redraw the area covered by the old position, and is not covered
+ // by the new position. Disable the grid so that compose_area() will not
+ // use it.
+ grid->comp_disabled = true;
+ compose_area(grid->comp_row, row,
+ grid->comp_col, grid->comp_col + grid->Columns);
+ if (grid->comp_col < col) {
+ compose_area(MAX(row, grid->comp_row),
+ MIN(row+height, grid->comp_row+grid->Rows),
+ grid->comp_col, col);
+ }
+ if (col+width < grid->comp_col+grid->Columns) {
+ compose_area(MAX(row, grid->comp_row),
+ MIN(row+height, grid->comp_row+grid->Rows),
+ col+width, grid->comp_col+grid->Columns);
+ }
+ compose_area(row+height, grid->comp_row+grid->Rows,
+ grid->comp_col, grid->comp_col + grid->Columns);
+ grid->comp_disabled = false;
+ }
+ grid->comp_row = row;
+ grid->comp_col = col;
+ return moved;
+ }
+#ifndef NDEBUG
+ for (size_t i = 0; i < kv_size(layers); i++) {
+ if (kv_A(layers, i) == grid) {
+ assert(false);
+ }
+ }
+#endif
+ // not found: new grid
+ kv_push(layers, grid);
+ grid->comp_row = row;
+ grid->comp_col = col;
+ grid->comp_index = kv_size(layers)-1;
+ return true;
+}
+
+void ui_comp_remove_grid(ScreenGrid *grid)
+{
+ assert(grid != &default_grid);
+ if (grid->comp_index == 0) {
+ // grid wasn't present
+ return;
+ }
+
+ if (curgrid == grid) {
+ curgrid = &default_grid;
+ }
+
+ for (size_t i = grid->comp_index; i < kv_size(layers)-1; i++) {
+ kv_A(layers, i) = kv_A(layers, i+1);
+ kv_A(layers, i)->comp_index = i;
+ }
+ (void)kv_pop(layers);
+ grid->comp_index = 0;
+
+ if (ui_comp_should_draw()) {
+ // inefficent: only draw up to grid->comp_index
+ compose_area(grid->comp_row, grid->comp_row+grid->Rows,
+ grid->comp_col, grid->comp_col+grid->Columns);
+ }
+}
+
+bool ui_comp_set_grid(handle_T handle)
+{
+ if (curgrid->handle == handle) {
+ return true;
+ }
+ ScreenGrid *grid = NULL;
+ for (size_t i = 0; i < kv_size(layers); i++) {
+ if (kv_A(layers, i)->handle == handle) {
+ grid = kv_A(layers, i);
+ break;
+ }
+ }
+ if (grid != NULL) {
+ curgrid = grid;
+ return true;
+ }
+ return false;
+}
+
+static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
+ Integer r, Integer c)
+{
+ if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) {
+ return;
+ }
+ int cursor_row = curgrid->comp_row+(int)r;
+ int cursor_col = curgrid->comp_col+(int)c;
+
+ if (cursor_col >= default_grid.Columns || cursor_row >= default_grid.Rows) {
+ // TODO(bfredl): this happens with 'writedelay', refactor?
+ // abort();
+ return;
+ }
+ ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col);
+}
+
+
+/// Baseline implementation. This is always correct, but we can sometimes
+/// do something more efficient (where efficiency means smaller deltas to
+/// the downstream UI.)
+static void compose_line(Integer row, Integer startcol, Integer endcol,
+ bool wrap)
+{
+ int col = (int)startcol;
+ ScreenGrid *grid = NULL;
+
+ while (col < endcol) {
+ int until = 0;
+ for (size_t i = 0; i < kv_size(layers); i++) {
+ ScreenGrid *g = kv_A(layers, i);
+ if (g->comp_row > row || row >= g->comp_row + g->Rows
+ || g->comp_disabled) {
+ continue;
+ }
+ if (g->comp_col <= col && col < g->comp_col+g->Columns) {
+ grid = g;
+ until = g->comp_col+g->Columns;
+ } else if (g->comp_col > col) {
+ until = MIN(until, g->comp_col);
+ }
+ }
+ until = MIN(until, (int)endcol);
+
+ assert(grid != NULL);
+ assert(until > col);
+ assert(until <= default_grid.Columns);
+ size_t n = (size_t)(until-col);
+ size_t off = grid->line_offset[row-grid->comp_row]
+ + (size_t)(col-grid->comp_col);
+ memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf));
+ memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf));
+ col = until;
+ }
+ assert(endcol <= chk_width);
+ assert(row < chk_height);
+ // TODO(bfredl): too conservative, need check
+ // grid->line_wraps if grid->Width == Width
+ wrap = wrap && grid && grid->handle == 1;
+ ui_composed_call_raw_line(1, row, startcol, endcol, endcol, 0, wrap,
+ (const schar_T *)linebuf, (const sattr_T *)attrbuf);
+}
+
+static void compose_area(Integer startrow, Integer endrow,
+ Integer startcol, Integer endcol)
+{
+ endrow = MIN(endrow, default_grid.Rows);
+ endcol = MIN(endcol, default_grid.Columns);
+ for (int r = (int)startrow; r < endrow; r++) {
+ compose_line(r, startcol, endcol, false);
+ }
+}
+
+
+static void draw_line(ScreenGrid *grid, Integer row, Integer startcol,
+ Integer endcol, Integer clearcol, Integer clearattr,
+ bool wrap, const schar_T *chunk, const sattr_T *attrs)
+{
+ row += grid->comp_row;
+ startcol += grid->comp_col;
+ endcol += grid->comp_col;
+ clearcol += grid->comp_col;
+ wrap = wrap && grid->handle == 1;
+ assert(clearcol <= default_grid.Columns);
+ if (kv_size(layers) > grid->comp_index+1) {
+ compose_line(row, startcol, clearcol, wrap);
+ } else {
+ ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
+ wrap, chunk, attrs);
+ }
+}
+
+static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
+ Integer startcol, Integer endcol,
+ Integer clearcol, Integer clearattr, bool wrap,
+ const schar_T *chunk, const sattr_T *attrs)
+{
+ if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
+ return;
+ }
+ draw_line(curgrid, row, startcol, endcol, clearcol, clearattr, wrap, chunk,
+ attrs);
+}
+
+/// The screen is invalid and will soon be cleared
+///
+/// Don't redraw floats until screen is cleared
+void ui_comp_invalidate_screen(void)
+{
+ valid_screen = false;
+}
+
+static void ui_comp_grid_clear(UI *ui, Integer grid)
+{
+ // By design, only first grid uses clearing.
+ assert(grid == 1);
+ ui_composed_call_grid_clear(1);
+ valid_screen = true;
+}
+
+// TODO(bfredl): These events are somewhat of a hack. multiline messages
+// should later on be a separate grid, then this would just be ordinary
+// ui_comp_put_grid and ui_comp_remove_grid calls.
+static void ui_comp_win_scroll_over_start(UI *ui)
+{
+ msg_scroll_mode = true;
+ msg_first_invalid = ui->height;
+}
+
+static void ui_comp_win_scroll_over_reset(UI *ui)
+{
+ msg_scroll_mode = false;
+ for (size_t i = 1; i < kv_size(layers); i++) {
+ ScreenGrid *grid = kv_A(layers, i);
+ if (grid->comp_row+grid->Rows > msg_first_invalid) {
+ compose_area(msg_first_invalid, grid->comp_row+grid->Rows,
+ grid->comp_col, grid->comp_col+grid->Columns);
+ }
+ }
+}
+
+static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
+ Integer bot, Integer left, Integer right,
+ Integer rows, Integer cols)
+{
+ if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
+ return;
+ }
+ top += curgrid->comp_row;
+ bot += curgrid->comp_row;
+ left += curgrid->comp_col;
+ right += curgrid->comp_col;
+ if (!msg_scroll_mode && kv_size(layers) > curgrid->comp_index+1) {
+ // TODO(bfredl):
+ // 1. check if rectangles actually overlap
+ // 2. calulate subareas that can scroll.
+ if (rows > 0) {
+ bot -= rows;
+ } else {
+ top += (-rows);
+ }
+ compose_area(top, bot, left, right);
+ } else {
+ msg_first_invalid = MIN(msg_first_invalid, (int)top);
+ ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols);
+ }
+}
+
+static void ui_comp_grid_resize(UI *ui, Integer grid,
+ Integer width, Integer height)
+{
+ if (grid == 1) {
+ ui_composed_call_grid_resize(1, width, height);
+#ifndef NDEBUG
+ chk_width = (int)width;
+ chk_height = (int)height;
+#endif
+ size_t new_bufsize = (size_t)width;
+ if (bufsize != new_bufsize) {
+ xfree(linebuf);
+ xfree(attrbuf);
+ linebuf = xmalloc(new_bufsize * sizeof(*linebuf));
+ attrbuf = xmalloc(new_bufsize * sizeof(*attrbuf));
+ bufsize = new_bufsize;
+ }
+ }
+}
+