aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/ui.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
commit339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch)
treea6167fc8fcfc6ae2dc102f57b2473858eac34063 /src/nvim/api/ui.c
parent067dc73729267c0262438a6fdd66e586f8496946 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'src/nvim/api/ui.c')
-rw-r--r--src/nvim/api/ui.c236
1 files changed, 143 insertions, 93 deletions
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index e67607a7e4..836a68546c 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -1,10 +1,8 @@
-// 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 <msgpack/pack.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -12,25 +10,27 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/autocmd.h"
#include "nvim/channel.h"
+#include "nvim/eval.h"
#include "nvim/event/loop.h"
#include "nvim/event/wstream.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/option.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf))
@@ -72,6 +72,11 @@ static void mpack_uint(char **buf, uint32_t val)
}
}
+static void mpack_bool(char **buf, bool val)
+{
+ mpack_w(buf, 0xc2 | (val ? 1 : 0));
+}
+
static void mpack_array(char **buf, uint32_t len)
{
if (len < 0x10) {
@@ -110,23 +115,32 @@ void remote_ui_disconnect(uint64_t channel_id)
}
UIData *data = ui->data;
kv_destroy(data->call_buf);
- pmap_del(uint64_t)(&connected_uis, channel_id);
+ pmap_del(uint64_t)(&connected_uis, channel_id, NULL);
ui_detach_impl(ui, channel_id);
+
+ // Destroy `ui`.
+ XFREE_CLEAR(ui->term_name);
xfree(ui);
}
-/// Wait until ui has connected on stdio channel.
-void remote_ui_wait_for_attach(void)
+/// Wait until ui has connected on stdio channel if only_stdio
+/// is true, otherwise any channel.
+void remote_ui_wait_for_attach(bool only_stdio)
{
- Channel *channel = find_channel(CHAN_STDIO);
- if (!channel) {
- // this function should only be called in --embed mode, stdio channel
- // can be assumed.
- abort();
- }
+ if (only_stdio) {
+ Channel *channel = find_channel(CHAN_STDIO);
+ if (!channel) {
+ // this function should only be called in --embed mode, stdio channel
+ // can be assumed.
+ abort();
+ }
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
- pmap_has(uint64_t)(&connected_uis, CHAN_STDIO));
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
+ map_has(uint64_t, &connected_uis, CHAN_STDIO));
+ } else {
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, -1,
+ ui_active());
+ }
}
/// Activates UI events on the channel.
@@ -148,7 +162,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI already attached to channel: %" PRId64, channel_id);
return;
@@ -162,15 +176,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_nlines = 0;
- ui->pum_pos = false;
- ui->pum_width = 0.0;
- ui->pum_height = 0.0;
ui->pum_row = -1.0;
ui->pum_col = -1.0;
ui->rgb = true;
- ui->override = false;
-
CLEAR_FIELD(ui->ui_ext);
for (size_t i = 0; i < options.size; i++) {
@@ -202,6 +210,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
data->flushed_events = false;
data->ncalls_pos = NULL;
data->ncalls = 0;
+ data->ncells_pending = 0;
data->buf_wptr = data->buf;
data->temp_buf = NULL;
data->wildmenu_active = false;
@@ -210,6 +219,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
ui_attach_impl(ui, channel_id);
+
+ may_trigger_vim_suspend_resume(false);
}
/// @deprecated
@@ -226,12 +237,16 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl
void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
}
+ if (gained) {
+ may_trigger_vim_suspend_resume(false);
+ }
+
do_autocmd_focusgained((bool)gained);
}
@@ -244,7 +259,7 @@ void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
void nvim_ui_detach(uint64_t channel_id, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -252,14 +267,15 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id);
}
-// TODO(bfredl): use me to detach a specifc ui from the server
+// TODO(bfredl): use me to detach a specific ui from the server
void remote_ui_stop(UI *ui)
-{}
+{
+}
void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -280,7 +296,7 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Erro
void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *error)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -290,22 +306,20 @@ void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *e
ui_set_option(ui, false, name, value, error);
}
-static void ui_set_option(UI *ui, bool init, String name, Object value, Error *error)
+static void ui_set_option(UI *ui, bool init, String name, Object value, Error *err)
{
if (strequal(name.data, "override")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
+ VALIDATE_T("override", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->override = value.data.boolean;
return;
}
if (strequal(name.data, "rgb")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
+ VALIDATE_T("rgb", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->rgb = value.data.boolean;
// A little drastic, but only takes effect for legacy uis. For linegrid UI
// only changes metadata for nvim_list_uis(), no refresh needed.
@@ -316,63 +330,53 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
if (strequal(name.data, "term_name")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_name must be a String");
+ VALIDATE_T("term_name", kObjectTypeString, value.type, {
return;
- }
+ });
set_tty_option("term", string_to_cstr(value.data.string));
+ ui->term_name = string_to_cstr(value.data.string);
return;
}
if (strequal(name.data, "term_colors")) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer");
+ VALIDATE_T("term_colors", kObjectTypeInteger, value.type, {
return;
- }
+ });
t_colors = (int)value.data.integer;
- return;
- }
-
- if (strequal(name.data, "term_background")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_background must be a String");
- return;
- }
- set_tty_background(value.data.string.data);
+ ui->term_colors = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_fd")) {
- if (value.type != kObjectTypeInteger || value.data.integer < 0) {
- api_set_error(error, kErrorTypeValidation, "stdin_fd must be a non-negative Integer");
+ VALIDATE_T("stdin_fd", kObjectTypeInteger, value.type, {
return;
- }
-
- if (starting != NO_SCREEN) {
- api_set_error(error, kErrorTypeValidation,
- "stdin_fd can only be used with first attached ui");
+ });
+ VALIDATE_INT((value.data.integer >= 0), "stdin_fd", value.data.integer, {
return;
- }
+ });
+ VALIDATE((starting == NO_SCREEN), "%s", "stdin_fd can only be used with first attached UI", {
+ return;
+ });
stdin_fd = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean");
+ VALIDATE_T("stdin_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdin_isatty = value.data.boolean;
+ ui->stdin_tty = value.data.boolean;
return;
}
if (strequal(name.data, "stdout_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean");
+ VALIDATE_T("stdout_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdout_isatty = value.data.boolean;
+ ui->stdout_tty = value.data.boolean;
return;
}
@@ -382,17 +386,15 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (strequal(name.data, ui_ext_names[i])
|| (i == kUIPopupmenu && is_popupmenu)) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
- name.data);
+ VALIDATE_EXP((value.type == kObjectTypeBoolean), name.data, "Boolean",
+ api_typename(value.type), {
return;
- }
+ });
bool boolval = value.data.boolean;
if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
- // There shouldn't be a reason for an UI to do this ever
+ // There shouldn't be a reason for a UI to do this ever
// so explicitly don't support this.
- api_set_error(error, kErrorTypeValidation,
- "ext_linegrid option cannot be changed");
+ api_set_error(err, kErrorTypeValidation, "ext_linegrid option cannot be changed");
}
ui->ui_ext[i] = boolval;
if (!init) {
@@ -402,8 +404,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
}
- api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
- name.data);
+ api_set_error(err, kErrorTypeValidation, "No such UI option: %s", name.data);
}
/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested
@@ -420,7 +421,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -442,7 +443,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -483,7 +484,7 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
Error *err)
FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -511,6 +512,33 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
ui->pum_pos = true;
}
+/// Tells Nvim when a terminal event has occurred
+///
+/// The following terminal events are supported:
+///
+/// - "termresponse": The terminal sent an OSC or DCS response sequence to
+/// Nvim. The payload is the received response. Sets
+/// |v:termresponse| and fires |TermResponse|.
+///
+/// @param channel_id
+/// @param event Event name
+/// @param payload Event payload
+/// @param[out] err Error details, if any.
+void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *err)
+ FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY
+{
+ if (strequal("termresponse", event.data)) {
+ if (value.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "termresponse must be a string");
+ return;
+ }
+
+ const String termresponse = value.data.string;
+ set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);
+ apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value);
+ }
+}
+
static void flush_event(UIData *data)
{
if (data->cur_event) {
@@ -644,6 +672,8 @@ void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
Array args = data->call_buf;
if (ui->ui_ext[kUILinegrid]) {
ADD_C(args, INTEGER_OBJ(grid));
+ } else {
+ data->client_col = -1; // force cursor update
}
ADD_C(args, INTEGER_OBJ(width));
ADD_C(args, INTEGER_OBJ(height));
@@ -731,8 +761,8 @@ void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cte
ADD_C(args, INTEGER_OBJ(id));
MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE);
MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb, rgb_attrs, true);
- hlattrs2dict(&cterm, rgb_attrs, false);
+ hlattrs2dict(&rgb, NULL, rgb_attrs, true, false);
+ hlattrs2dict(&cterm, NULL, rgb_attrs, false, false);
ADD_C(args, DICTIONARY_OBJ(rgb));
ADD_C(args, DICTIONARY_OBJ(cterm));
@@ -755,7 +785,7 @@ void remote_ui_highlight_set(UI *ui, int id)
}
data->hl_id = id;
MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
- hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb);
+ hlattrs2dict(&dict, NULL, syn_attr2entry(id), ui->rgb, false);
ADD_C(args, DICTIONARY_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -798,7 +828,7 @@ void remote_ui_put(UI *ui, const char *cell)
UIData *data = ui->data;
data->client_col++;
Array args = data->call_buf;
- ADD_C(args, STRING_OBJ(cstr_as_string((char *)cell)));
+ ADD_C(args, CSTR_AS_OBJ((char *)cell));
push_call(ui, "put", args);
}
@@ -812,7 +842,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
data->ncalls++;
char **buf = &data->buf_wptr;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol);
@@ -822,21 +852,24 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
size_t ncells = (size_t)(endcol - startcol);
int last_hl = -1;
uint32_t nelem = 0;
+ bool was_space = false;
for (size_t i = 0; i < ncells; i++) {
repeat++;
- if (i == ncells - 1 || attrs[i] != attrs[i + 1]
- || strcmp(chunk[i], chunk[i + 1]) != 0) {
- if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) {
+ if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
+ if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
// For simplicity leave place for the final "clear" element
// as well, hence the factor of 2 in the check.
mpack_w2(&lenpos, nelem);
+
+ // We only ever set the wrap field on the final "grid_line" event for the line.
+ mpack_bool(buf, false);
remote_ui_flush_buf(ui);
prepare_call(ui, "grid_line");
data->ncalls++;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol + (uint32_t)i - repeat + 1);
@@ -847,31 +880,46 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
nelem++;
mpack_array(buf, csize);
- mpack_str(buf, (const char *)chunk[i]);
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ mpack_str(buf, sc_buf);
if (csize >= 2) {
mpack_uint(buf, (uint32_t)attrs[i]);
if (csize >= 3) {
mpack_uint(buf, repeat);
}
}
+ data->ncells_pending += MIN(repeat, 2);
last_hl = attrs[i];
repeat = 0;
+ was_space = chunk[i] == schar_from_ascii(' ');
}
}
- if (endcol < clearcol) {
+ // If the last chunk was all spaces, add a clearing chunk even if there are
+ // no more cells to clear, so there is no ambiguity about what to clear.
+ if (endcol < clearcol || was_space) {
nelem++;
+ data->ncells_pending += 1;
mpack_array(buf, 3);
mpack_str(buf, " ");
mpack_uint(buf, (uint32_t)clearattr);
mpack_uint(buf, (uint32_t)(clearcol - endcol));
}
mpack_w2(&lenpos, nelem);
+ mpack_bool(buf, flags & kLineFlagWrap);
+
+ if (data->ncells_pending > 500) {
+ // pass off cells to UI to let it start processing them
+ remote_ui_flush_buf(ui);
+ }
} else {
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
remote_ui_highlight_set(ui, attrs[i]);
- remote_ui_put(ui, (const char *)chunk[i]);
- if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ remote_ui_put(ui, sc_buf);
+ if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
data->client_col = -1; // force cursor update
}
}
@@ -917,6 +965,8 @@ void remote_ui_flush_buf(UI *ui)
// we have sent events to the client, but possibly not yet the final "flush"
// event.
data->flushed_events = true;
+
+ data->ncells_pending = 0;
}
/// An intentional flush (vsync) when Nvim is finished redrawing the screen
@@ -945,7 +995,7 @@ static Array translate_contents(UI *ui, Array contents, Arena *arena)
int attr = (int)item.items[0].data.integer;
if (attr) {
Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb);
+ hlattrs2dict(&rgb_attrs, NULL, syn_attr2entry(attr), ui->rgb, false);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));