aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/ui.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/ui.c')
-rw-r--r--src/nvim/ui.c521
1 files changed, 229 insertions, 292 deletions
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index d968cbc390..96232ab223 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -1,3 +1,6 @@
+// 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
+
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -5,17 +8,17 @@
#include <limits.h>
#include "nvim/vim.h"
+#include "nvim/log.h"
#include "nvim/ui.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ex_getln.h"
#include "nvim/fold.h"
#include "nvim/main.h"
-#include "nvim/mbyte.h"
#include "nvim/ascii.h"
#include "nvim/misc1.h"
-#include "nvim/misc2.h"
#include "nvim/mbyte.h"
#include "nvim/garray.h"
#include "nvim/memory.h"
@@ -27,14 +30,17 @@
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/os/signal.h"
+#include "nvim/popupmnu.h"
#include "nvim/screen.h"
-#include "nvim/syntax.h"
+#include "nvim/highlight.h"
#include "nvim/window.h"
+#include "nvim/cursor_shape.h"
#ifdef FEAT_TUI
# include "nvim/tui/tui.h"
#else
# include "nvim/msgpack_rpc/server.h"
#endif
+#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.c.generated.h"
@@ -43,26 +49,45 @@
#define MAX_UI_COUNT 16
static UI *uis[MAX_UI_COUNT];
+static bool ui_ext[kUIExtCount] = { 0 };
static size_t ui_count = 0;
-static int row = 0, col = 0;
-static struct {
- int top, bot, left, right;
-} sr;
-static int current_attr_code = 0;
+static int ui_mode_idx = SHAPE_IDX_N;
+static int cursor_row = 0, cursor_col = 0;
static bool pending_cursor_update = false;
static int busy = 0;
-static int height, width;
+static bool pending_mode_info_update = false;
+static bool pending_mode_update = false;
+static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE;
-// This set of macros allow us to use UI_CALL to invoke any function on
-// registered UI instances. The functions can have 0-5 arguments(configurable
-// by SELECT_NTH)
+#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
+# define UI_LOG(funname, ...)
+#else
+static size_t uilog_seen = 0;
+static char uilog_last_event[1024] = { 0 };
+# define UI_LOG(funname, ...) \
+ do { \
+ if (strequal(uilog_last_event, STR(funname))) { \
+ uilog_seen++; \
+ } else { \
+ if (uilog_seen > 0) { \
+ logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, \
+ "%s (+%zu times...)", uilog_last_event, uilog_seen); \
+ } \
+ logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, STR(funname)); \
+ uilog_seen = 0; \
+ xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \
+ } \
+ } while (0)
+#endif
+
+// UI_CALL invokes a function on all registered UI instances. The functions can
+// have 0-10 arguments (configurable by SELECT_NTH).
//
-// See http://stackoverflow.com/a/11172679 for a better explanation of how it
-// works.
+// See http://stackoverflow.com/a/11172679 for how it works.
#ifdef _MSC_VER
# define UI_CALL(funname, ...) \
do { \
- flush_cursor_update(); \
+ UI_LOG(funname, 0); \
for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \
UI_CALL_MORE(funname, __VA_ARGS__); \
@@ -71,37 +96,42 @@ static int height, width;
#else
# define UI_CALL(...) \
do { \
- flush_cursor_update(); \
+ UI_LOG(__VA_ARGS__, 0); \
for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \
UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \
} \
} while (0)
#endif
-#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore)
-#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6
+#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, MORE, \
+ MORE, MORE, MORE, MORE, ZERO, ignore)
+#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...) a11
#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
+// Resolves to UI_CALL_MORE or UI_CALL_ZERO.
#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
#define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__)
#define UI_CALL_ZERO(method) if (ui->method) ui->method(ui)
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_events_call.generated.h"
+#endif
+
void ui_builtin_start(void)
{
#ifdef FEAT_TUI
tui_start();
#else
- fprintf(stderr, "Neovim was built without a Terminal UI," \
- "press Ctrl+C to exit\n");
-
+ fprintf(stderr, "Nvim headless-mode started.\n");
size_t len;
char **addrs = server_address_list(&len);
if (addrs != NULL) {
- fprintf(stderr, "currently listening on the following address(es)\n");
+ fprintf(stderr, "Listening on:\n");
for (size_t i = 0; i < len; i++) {
fprintf(stderr, "\t%s\n", addrs[i]);
}
xfree(addrs);
}
+ fprintf(stderr, "Press CTRL+C to exit.\n");
#endif
}
@@ -112,6 +142,9 @@ void ui_builtin_stop(void)
bool ui_rgb_attached(void)
{
+ if (!headless_mode && p_tgc) {
+ return true;
+ }
for (size_t i = 0; i < ui_count; i++) {
if (uis[i]->rgb) {
return true;
@@ -125,29 +158,15 @@ bool ui_active(void)
return ui_count != 0;
}
-void ui_suspend(void)
-{
- UI_CALL(suspend);
- UI_CALL(flush);
-}
-
-void ui_set_title(char *title)
+void ui_event(char *name, Array args)
{
- UI_CALL(set_title, title);
- UI_CALL(flush);
+ bool args_consumed = false;
+ UI_CALL(event, name, args, &args_consumed);
+ if (!args_consumed) {
+ api_free_array(args);
+ }
}
-void ui_set_icon(char *icon)
-{
- UI_CALL(set_icon, icon);
- UI_CALL(flush);
-}
-
-// May update the shape of the cursor.
-void ui_cursor_shape(void)
-{
- ui_mode_change();
-}
void ui_refresh(void)
{
@@ -155,56 +174,82 @@ void ui_refresh(void)
return;
}
+ if (updating_screen) {
+ ui_schedule_refresh();
+ return;
+ }
+
int width = INT_MAX, height = INT_MAX;
+ bool ext_widgets[kUIExtCount];
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ ext_widgets[i] = true;
+ }
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
- width = ui->width < width ? ui->width : width;
- height = ui->height < height ? ui->height : height;
+ width = MIN(ui->width, width);
+ height = MIN(ui->height, height);
+ for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
+ ext_widgets[j] &= ui->ui_ext[j];
+ }
}
- row = col = 0;
+ cursor_row = cursor_col = 0;
+ pending_cursor_update = true;
+
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ ui_ext[i] = ext_widgets[i];
+ if (i < kUIGlobalCount) {
+ ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]),
+ BOOLEAN_OBJ(ext_widgets[i]));
+ }
+ }
+
+ ui_default_colors_set();
+
+ int save_p_lz = p_lz;
+ p_lz = false; // convince redrawing() to return true ...
screen_resize(width, height);
+ p_lz = save_p_lz;
+
+ ui_mode_info_set();
+ pending_mode_update = true;
+ ui_cursor_shape();
}
-void ui_resize(int new_width, int new_height)
+static void ui_refresh_event(void **argv)
{
- width = new_width;
- height = new_height;
-
- UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1));
- UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1));
- UI_CALL(update_sp, (ui->rgb ? normal_sp : -1));
-
- sr.top = 0;
- sr.bot = height - 1;
- sr.left = 0;
- sr.right = width - 1;
- UI_CALL(resize, width, height);
+ ui_refresh();
}
-void ui_busy_start(void)
+void ui_schedule_refresh(void)
{
- if (!(busy++)) {
- UI_CALL(busy_start);
- }
+ loop_schedule(&main_loop, event_create(ui_refresh_event, 0));
}
-void ui_busy_stop(void)
+void ui_resize(int width, int height)
{
- if (!(--busy)) {
- UI_CALL(busy_stop);
- }
+ ui_call_grid_resize(1, width, height);
}
-void ui_mouse_on(void)
+void ui_default_colors_set(void)
+{
+ ui_call_default_colors_set(normal_fg, normal_bg, normal_sp,
+ cterm_normal_fg_color, cterm_normal_bg_color);
+}
+
+void ui_busy_start(void)
{
- UI_CALL(mouse_on);
+ if (!(busy++)) {
+ ui_call_busy_start();
+ }
}
-void ui_mouse_off(void)
+void ui_busy_stop(void)
{
- UI_CALL(mouse_off);
+ if (!(--busy)) {
+ ui_call_busy_stop();
+ }
}
void ui_attach_impl(UI *ui)
@@ -214,6 +259,19 @@ void ui_attach_impl(UI *ui)
}
uis[ui_count++] = ui;
+ ui_refresh_options();
+
+ for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
+ ui_set_ext_option(ui, i, ui->ui_ext[i]);
+ }
+
+ bool sent = false;
+ if (ui->ui_ext[kUIHlState]) {
+ sent = highlight_use_hlstate();
+ }
+ if (!sent) {
+ ui_send_all_hls(ui);
+ }
ui_refresh();
}
@@ -239,283 +297,162 @@ void ui_detach_impl(UI *ui)
shift_index++;
}
- if (--ui_count) {
- ui_refresh();
+ if (--ui_count
+ // During teardown/exit the loop was already destroyed, cannot schedule.
+ // https://github.com/neovim/neovim/pull/5119#issuecomment-258667046
+ && !exiting) {
+ ui_schedule_refresh();
}
}
-void ui_clear(void)
-{
- UI_CALL(clear);
-}
-
-// Set scrolling region for window 'wp'.
-// The region starts 'off' lines from the start of the window.
-// Also set the vertical scroll region for a vertically split window. Always
-// the full width of the window, excluding the vertical separator.
-void ui_set_scroll_region(win_T *wp, int off)
-{
- sr.top = wp->w_winrow + off;
- sr.bot = wp->w_winrow + wp->w_height - 1;
-
- if (wp->w_width != Columns) {
- sr.left = wp->w_wincol;
- sr.right = wp->w_wincol + wp->w_width - 1;
- }
-
- UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
-}
-
-// Reset scrolling region to the whole screen.
-void ui_reset_scroll_region(void)
-{
- sr.top = 0;
- sr.bot = (int)Rows - 1;
- sr.left = 0;
- sr.right = (int)Columns - 1;
- UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
-}
-
-void ui_append_lines(int count)
-{
- UI_CALL(scroll, -count);
-}
-
-void ui_delete_lines(int count)
+void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
{
- UI_CALL(scroll, count);
-}
-
-void ui_eol_clear(void)
-{
- UI_CALL(eol_clear);
-}
-
-void ui_start_highlight(int attr_code)
-{
- current_attr_code = attr_code;
-
- if (!ui_count) {
+ if (ext < kUIGlobalCount) {
+ ui_refresh();
return;
}
-
- set_highlight_args(current_attr_code);
-}
-
-void ui_stop_highlight(void)
-{
- current_attr_code = HL_NORMAL;
-
- if (!ui_count) {
- return;
+ if (ui->option_set) {
+ ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
+ BOOLEAN_OBJ(active));
}
-
- set_highlight_args(current_attr_code);
}
-void ui_visual_bell(void)
+void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol,
+ int clearattr, bool wrap)
{
- UI_CALL(visual_bell);
-}
+ size_t off = grid->line_offset[row] + (size_t)startcol;
-void ui_puts(uint8_t *str)
-{
- uint8_t *ptr = str;
- uint8_t c;
-
- while ((c = *ptr)) {
- if (c < 0x20) {
- parse_control_character(c);
- ptr++;
- } else {
- send_output(&ptr);
- }
+ UI_CALL(raw_line, grid->handle, row, startcol, endcol, clearcol, clearattr,
+ wrap, (const schar_T *)grid->chars + off,
+ (const sattr_T *)grid->attrs + off);
+
+ if (p_wd) { // 'writedelay': flush & delay each time.
+ int old_row = cursor_row, old_col = cursor_col;
+ handle_T old_grid = cursor_grid_handle;
+ // If 'writedelay' is active, set the cursor to indicate what was drawn.
+ ui_grid_cursor_goto(grid->handle, row, MIN(clearcol, (int)Columns-1));
+ ui_flush();
+ uint64_t wd = (uint64_t)labs(p_wd);
+ os_microdelay(wd * 1000u, true);
+ ui_grid_cursor_goto(old_grid, old_row, old_col);
}
}
-void ui_putc(uint8_t c)
+void ui_cursor_goto(int new_row, int new_col)
{
- uint8_t buf[2] = {c, 0};
- ui_puts(buf);
+ ui_grid_cursor_goto(DEFAULT_GRID_HANDLE, new_row, new_col);
}
-void ui_cursor_goto(int new_row, int new_col)
+void ui_grid_cursor_goto(handle_T grid_handle, int new_row, int new_col)
{
- if (new_row == row && new_col == col) {
+ if (new_row == cursor_row
+ && new_col == cursor_col
+ && grid_handle == cursor_grid_handle) {
return;
}
- row = new_row;
- col = new_col;
+
+ cursor_row = new_row;
+ cursor_col = new_col;
+ cursor_grid_handle = grid_handle;
pending_cursor_update = true;
}
-void ui_update_menu(void)
+void ui_mode_info_set(void)
{
- UI_CALL(update_menu);
+ pending_mode_info_update = true;
}
int ui_current_row(void)
{
- return row;
+ return cursor_row;
}
int ui_current_col(void)
{
- return col;
+ return cursor_col;
}
void ui_flush(void)
{
- UI_CALL(flush);
-}
-
-static void send_output(uint8_t **ptr)
-{
- uint8_t *p = *ptr;
-
- while (*p >= 0x20) {
- size_t clen = (size_t)mb_ptr2len(p);
- UI_CALL(put, p, (size_t)clen);
- col++;
- if (mb_ptr2cells(p) > 1) {
- // double cell character, blank the next cell
- UI_CALL(put, NULL, 0);
- col++;
- }
- if (col >= width) {
- ui_linefeed();
- }
- p += clen;
- }
-
- *ptr = p;
-}
-
-static void parse_control_character(uint8_t c)
-{
- if (c == '\n') {
- ui_linefeed();
- } else if (c == '\r') {
- ui_carriage_return();
- } else if (c == '\b') {
- ui_cursor_left();
- } else if (c == Ctrl_L) {
- ui_cursor_right();
- } else if (c == Ctrl_G) {
- UI_CALL(bell);
- }
-}
-
-static void set_highlight_args(int attr_code)
-{
- HlAttrs rgb_attrs = { false, false, false, false, false, -1, -1, -1 };
- HlAttrs cterm_attrs = rgb_attrs;
-
- if (attr_code == HL_NORMAL) {
- goto end;
- }
-
- int rgb_mask = 0;
- int cterm_mask = 0;
- attrentry_T *aep = syn_cterm_attr2entry(attr_code);
-
- if (!aep) {
- goto end;
- }
-
- rgb_mask = aep->rgb_ae_attr;
- cterm_mask = aep->cterm_ae_attr;
-
- rgb_attrs.bold = rgb_mask & HL_BOLD;
- rgb_attrs.underline = rgb_mask & HL_UNDERLINE;
- rgb_attrs.undercurl = rgb_mask & HL_UNDERCURL;
- rgb_attrs.italic = rgb_mask & HL_ITALIC;
- rgb_attrs.reverse = rgb_mask & (HL_INVERSE | HL_STANDOUT);
- cterm_attrs.bold = cterm_mask & HL_BOLD;
- cterm_attrs.underline = cterm_mask & HL_UNDERLINE;
- cterm_attrs.undercurl = cterm_mask & HL_UNDERCURL;
- cterm_attrs.italic = cterm_mask & HL_ITALIC;
- cterm_attrs.reverse = cterm_mask & (HL_INVERSE | HL_STANDOUT);
-
- if (aep->rgb_fg_color != normal_fg) {
- rgb_attrs.foreground = aep->rgb_fg_color;
- }
-
- if (aep->rgb_bg_color != normal_bg) {
- rgb_attrs.background = aep->rgb_bg_color;
+ cmdline_ui_flush();
+ win_ui_flush();
+ if (pending_cursor_update) {
+ ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col);
+ pending_cursor_update = false;
}
-
- if (aep->rgb_sp_color != normal_sp) {
- rgb_attrs.special = aep->rgb_sp_color;
+ if (pending_mode_info_update) {
+ Array style = mode_style_array();
+ bool enabled = (*p_guicursor != NUL);
+ ui_call_mode_info_set(enabled, style);
+ api_free_array(style);
+ pending_mode_info_update = false;
}
-
- if (cterm_normal_fg_color != aep->cterm_fg_color) {
- cterm_attrs.foreground = aep->cterm_fg_color - 1;
+ if (pending_mode_update) {
+ char *full_name = shape_table[ui_mode_idx].full_name;
+ ui_call_mode_change(cstr_as_string(full_name), ui_mode_idx);
+ pending_mode_update = false;
}
-
- if (cterm_normal_bg_color != aep->cterm_bg_color) {
- cterm_attrs.background = aep->cterm_bg_color - 1;
- }
-
-end:
- UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs));
+ ui_call_flush();
}
-static void ui_linefeed(void)
+
+/// Check if current mode has changed.
+/// May update the shape of the cursor.
+void ui_cursor_shape(void)
{
- int new_col = 0;
- int new_row = row;
- if (new_row < sr.bot) {
- new_row++;
- } else {
- UI_CALL(scroll, 1);
+ if (!full_screen) {
+ return;
}
- ui_cursor_goto(new_row, new_col);
-}
+ int new_mode_idx = cursor_get_mode_idx();
-static void ui_carriage_return(void)
-{
- int new_col = 0;
- ui_cursor_goto(row, new_col);
+ if (new_mode_idx != ui_mode_idx) {
+ ui_mode_idx = new_mode_idx;
+ pending_mode_update = true;
+ }
+ conceal_check_cursor_line();
}
-static void ui_cursor_left(void)
+/// Returns true if `widget` is externalized.
+bool ui_is_external(UIExtension widget)
{
- int new_col = col - 1;
- assert(new_col >= 0);
- ui_cursor_goto(row, new_col);
+ return ui_ext[widget];
}
-static void ui_cursor_right(void)
+Array ui_array(void)
{
- int new_col = col + 1;
- assert(new_col < width);
- ui_cursor_goto(row, new_col);
+ Array all_uis = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < ui_count; i++) {
+ UI *ui = uis[i];
+ Dictionary info = ARRAY_DICT_INIT;
+ PUT(info, "width", INTEGER_OBJ(ui->width));
+ PUT(info, "height", INTEGER_OBJ(ui->height));
+ PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
+ for (UIExtension j = 0; j < kUIExtCount; j++) {
+ PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
+ }
+ if (ui->inspect) {
+ ui->inspect(ui, &info);
+ }
+ ADD(all_uis, DICTIONARY_OBJ(info));
+ }
+ return all_uis;
}
-static void flush_cursor_update(void)
+void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
{
- if (pending_cursor_update) {
- pending_cursor_update = false;
- UI_CALL(cursor_goto, row, col);
+ if (grid_handle == DEFAULT_GRID_HANDLE) {
+ screen_resize(width, height);
+ return;
}
-}
-// Notify that the current mode has changed. Can be used to change cursor
-// shape, for example.
-static void ui_mode_change(void)
-{
- int mode;
- if (!full_screen) {
+ win_T *wp = get_win_by_grid_handle(grid_handle);
+ if (wp == NULL) {
+ api_set_error(error, kErrorTypeValidation,
+ "No window with the given handle");
return;
}
- /* Get a simple UI mode out of State. */
- if ((State & REPLACE) == REPLACE)
- mode = REPLACE;
- else if (State & INSERT)
- mode = INSERT;
- else
- mode = NORMAL;
- UI_CALL(mode_change, mode);
- conceal_check_cursur_line();
+
+ wp->w_grid.requested_rows = (int)height;
+ wp->w_grid.requested_cols = (int)width;
+ redraw_win_later(wp, SOME_VALID);
}