a modern tui library written in zig
1const std = @import("std");
2const assert = std.debug.assert;
3const vaxis = @import("../main.zig");
4const znvim = @import("znvim");
5
6pub const Event = union(enum) {
7 redraw: *anyopaque,
8 quit: *anyopaque,
9};
10
11pub fn Nvim(comptime T: type) type {
12 if (!@hasField(T, "nvim")) {
13 @compileError("Nvim widget requires an Event to have an 'nvim' event of type Nvim.Event");
14 }
15 return struct {
16 const Self = @This();
17 const Client = znvim.DefaultClient(.file);
18 const EventType = T;
19
20 const log = std.log.scoped(.nvim);
21
22 /// vaxis events handled by Nvim
23 pub const VaxisEvent = union(enum) {
24 key_press: vaxis.Key,
25 };
26
27 alloc: std.mem.Allocator,
28
29 /// true when we have spawned
30 spawned: bool = false,
31 /// true when we have ui attached
32 attached: bool = false,
33
34 client: ?Client = null,
35
36 /// draw mutex. We lock access to the internal model while drawing
37 mutex: std.Thread.Mutex = .{},
38
39 /// the child process
40 process: std.ChildProcess,
41
42 thread: ?std.Thread = null,
43
44 screen: vaxis.AllocatingScreen = undefined,
45 visible_screen: vaxis.AllocatingScreen = undefined,
46
47 hl_map: HighlightMap,
48
49 loop: *vaxis.Loop(T),
50 dirty: bool = false,
51 mode_set: std.ArrayList(Mode),
52
53 /// initialize nvim. Starts the nvim process. UI is not attached until the first
54 /// call to draw
55 pub fn init(alloc: std.mem.Allocator, loop: *vaxis.Loop(T)) !Self {
56 const args = [_][]const u8{ "nvim", "--embed" };
57 var nvim = std.ChildProcess.init(&args, alloc);
58
59 // set to use pipe
60 nvim.stdin_behavior = .Pipe;
61 // set to use pipe
62 nvim.stdout_behavior = .Pipe;
63 // set ignore
64 nvim.stderr_behavior = .Ignore;
65
66 // try spwan
67 try nvim.spawn();
68 return .{
69 .alloc = alloc,
70 .process = nvim,
71 .hl_map = HighlightMap.init(alloc),
72 .loop = loop,
73 .mode_set = std.ArrayList(Mode).init(alloc),
74 };
75 }
76
77 /// spawns the client thread and registers callbacks
78 pub fn spawn(self: *Self) !void {
79 if (self.spawned) return;
80 defer self.spawned = true;
81
82 // get stdin and stdout pipe
83 assert(self.process.stdin != null);
84 assert(self.process.stdout != null);
85 const nvim_stdin = self.process.stdin.?;
86 const nvim_stdout = self.process.stdout.?;
87
88 self.client = try Client.init(
89 nvim_stdin,
90 nvim_stdout,
91 self.alloc,
92 );
93
94 self.thread = try std.Thread.spawn(.{}, Self.nvimLoop, .{self});
95 }
96
97 pub fn deinit(self: *Self) void {
98 if (self.client) |*client| {
99 client.deinit();
100 }
101 _ = self.process.kill() catch |err|
102 log.err("couldn't kill nvim process: {}", .{err});
103 if (self.thread) |thread| {
104 thread.join();
105 }
106 self.screen.deinit(self.alloc);
107 self.visible_screen.deinit(self.alloc);
108 self.hl_map.map.deinit();
109 self.mode_set.deinit();
110 }
111
112 pub fn draw(self: *Self, win: vaxis.Window) !void {
113 self.mutex.lock();
114 defer self.mutex.unlock();
115 self.dirty = false;
116 win.setCursorShape(self.visible_screen.cursor_shape);
117 if (!self.attached) try self.attach(win.width, win.height);
118 if (win.width != self.screen.width or
119 win.height != self.screen.height) try self.resize(win.width, win.height);
120 var row: usize = 0;
121 while (row < self.visible_screen.height) : (row += 1) {
122 var col: usize = 0;
123 while (col < self.visible_screen.width) : (col += 1) {
124 win.writeCell(col, row, self.visible_screen.readCell(col, row).?);
125 }
126 }
127 if (self.visible_screen.cursor_vis)
128 win.showCursor(self.visible_screen.cursor_col, self.visible_screen.cursor_row);
129 }
130
131 pub fn update(self: *Self, event: VaxisEvent) !void {
132 var client = self.client orelse return;
133 switch (event) {
134 .key_press => |key| {
135 const key_str = if (key.text) |text|
136 text
137 else blk: {
138 var buf: [64]u8 = undefined;
139 var alloc = std.heap.FixedBufferAllocator.init(&buf);
140 var w = std.ArrayList(u8).init(alloc.allocator());
141 try w.append('<');
142 if (key.mods.shift)
143 try w.appendSlice("S-");
144 if (key.mods.ctrl)
145 try w.appendSlice("C-");
146 if (key.mods.alt)
147 try w.appendSlice("M-");
148 if (key.mods.super)
149 try w.appendSlice("D-");
150
151 const key_str = switch (key.codepoint) {
152 '<' => "lt",
153 '\\' => "Bslash",
154 '|' => "Bar",
155 vaxis.Key.enter => "CR",
156 vaxis.Key.backspace => "BS",
157 vaxis.Key.tab => "Tab",
158 vaxis.Key.escape => "ESC",
159 vaxis.Key.space => "Space",
160 vaxis.Key.delete => "Del",
161 vaxis.Key.up => "Up",
162 vaxis.Key.down => "Down",
163 vaxis.Key.left => "Left",
164 vaxis.Key.right => "Right",
165
166 else => utf8: {
167 var utf8Buf: [4]u8 = undefined;
168 const n = try std.unicode.utf8Encode(key.codepoint, &utf8Buf);
169 break :utf8 utf8Buf[0..n];
170 },
171 };
172
173 try w.appendSlice(key_str);
174 try w.append('>');
175
176 break :blk w.items;
177 };
178 var payload = try Client.createParams(1, self.alloc);
179 defer Client.freeParams(payload, self.alloc);
180 payload.arr[0] = try znvim.Payload.strToPayload(key_str, self.alloc);
181 try client.notify("nvim_input", payload);
182 },
183 }
184 }
185
186 fn attach(self: *Self, width: usize, height: usize) !void {
187 self.attached = true;
188 try self.client.?.registerNotifyMethod(
189 "redraw",
190 .{
191 .userdata = self,
192 .func = &redrawCallback,
193 },
194 );
195 self.screen = try vaxis.AllocatingScreen.init(self.alloc, width, height);
196 self.visible_screen = try vaxis.AllocatingScreen.init(self.alloc, width, height);
197 const params = try Client.createParams(3, self.alloc);
198 defer Client.freeParams(params, self.alloc);
199
200 var opts = znvim.Payload.mapPayload(self.alloc);
201 try opts.mapPut("ext_linegrid", znvim.Payload.boolToPayload(true));
202
203 params.arr[0] = znvim.Payload.uintToPayload(self.screen.width);
204 params.arr[1] = znvim.Payload.uintToPayload(self.screen.height);
205 params.arr[2] = opts;
206
207 const result = try self.client.?.call("nvim_ui_attach", params);
208 switch (result) {
209 .err => |err| Client.freeParams(err, self.alloc),
210 .result => |r| Client.freeParams(r, self.alloc),
211 }
212 }
213
214 fn resize(self: *Self, width: usize, height: usize) !void {
215 self.screen.deinit(self.alloc);
216 self.visible_screen.deinit(self.alloc);
217 self.screen = try vaxis.AllocatingScreen.init(self.alloc, width, height);
218 self.visible_screen = try vaxis.AllocatingScreen.init(self.alloc, width, height);
219 const params = try Client.createParams(2, self.alloc);
220 defer Client.freeParams(params, self.alloc);
221
222 params.arr[0] = znvim.Payload.uintToPayload(self.screen.width);
223 params.arr[1] = znvim.Payload.uintToPayload(self.screen.height);
224
225 try self.client.?.notify("nvim_ui_try_resize", params);
226 }
227
228 fn redrawCallback(
229 params: znvim.Payload,
230 alloc: std.mem.Allocator,
231 userdata: ?*anyopaque,
232 ) void {
233 _ = alloc; // autofix
234 assert(userdata != null);
235 var self: *Self = @ptrCast(@alignCast(userdata.?));
236 for (params.arr) |event| {
237 assert(event == znvim.Payload.arr);
238 const event_name = event.arr[0].str.value();
239 log.debug("redraw callback event {s}", .{event_name});
240 const event_enum = std.meta.stringToEnum(NvimEvent, event_name) orelse {
241 log.err("unhandled nvim event: {s}", .{event_name});
242 continue;
243 };
244 assert(event.arr[1] == znvim.Payload.arr);
245 self.handleEvent(event_enum, event.arr[1..]);
246 }
247 }
248
249 fn nvimLoop(self: *Self) void {
250 if (self.client) |*client| {
251 while (true) {
252 client.loop() catch |err| {
253 log.err("rpc loop error: {}", .{err});
254 self.loop.postEvent(.{ .nvim = .{ .quit = self } });
255 return;
256 };
257 }
258 }
259 }
260
261 const NvimEvent = enum {
262 chdir,
263 default_colors_set,
264 flush,
265 grid_clear,
266 grid_cursor_goto,
267 grid_line,
268 grid_resize,
269 grid_scroll,
270 hl_attr_define,
271 hl_group_set,
272 mode_change,
273 mode_info_set,
274 option_set,
275 set_icon,
276 set_title,
277 };
278
279 const OptionSet = enum {
280 ambiwidth,
281 arabicshape,
282 emoji,
283 guifont,
284 guifontwide,
285 linespace,
286 mousefocus,
287 mousehide,
288 mousemoveevent,
289 pumblend,
290 showtabline,
291 termguicolors,
292 termsync,
293 ttimeout,
294 ttimeoutlen,
295 verbose,
296
297 ext_cmdline,
298 ext_hlstate,
299 ext_linegrid,
300 ext_messages,
301 ext_multigrid,
302 ext_popupmenu,
303 ext_tabline,
304 ext_termcolors,
305 ext_wildmenu,
306 };
307
308 const Mode = struct {
309 cursor_style_enabled: bool = false,
310 cursor_shape: vaxis.Cell.CursorShape = .default,
311 attr_id: usize = 0,
312 mouse_shape: vaxis.Mouse.Shape = .default,
313 short_name: []const u8 = "",
314 name: []const u8 = "",
315
316 fn deinit(self: Mode, alloc: std.mem.Allocator) void {
317 alloc.free(self.short_name);
318 alloc.free(self.name);
319 }
320
321 const Keys = enum {
322 cursor_shape,
323 blinkon,
324 blinkoff,
325 attr_id,
326 short_name,
327 name,
328 mouse_shape,
329 };
330 };
331
332 const HighlightMap = struct {
333 /// the keys used in rgb_dict field in the nvim rpc event
334 const Keys = enum {
335 foreground,
336 background,
337 special,
338 italic,
339 bold,
340 strikethrough,
341 reverse,
342 underline,
343 undercurl,
344 underdouble,
345 underdotted,
346
347 // not used:
348 altfont,
349 blend,
350 url, // TODO: handle urls
351
352 };
353
354 const Highlight = struct {
355 id: u64,
356 attrs: struct {
357 fg: ?vaxis.Color = null,
358 bg: ?vaxis.Color = null,
359 special: ?vaxis.Color = null,
360 italic: ?bool = null,
361 bold: ?bool = null,
362 strikethrough: ?bool = null,
363 underline: ?bool = null,
364 undercurl: ?bool = null,
365 underdouble: ?bool = null,
366 underdotted: ?bool = null,
367 reverse: ?bool = null,
368 // TODO: urls
369 // url: ?[]const u8 = null,
370 } = .{},
371 };
372
373 alloc: std.mem.Allocator,
374 default: vaxis.Style,
375 map: std.ArrayList(Highlight),
376
377 pub fn init(alloc: std.mem.Allocator) HighlightMap {
378 return .{
379 .alloc = alloc,
380 .default = .{},
381 .map = std.ArrayList(Highlight).init(alloc),
382 };
383 }
384
385 /// returns the requested id, or default if not found
386 pub fn get(self: *HighlightMap, id: u64) vaxis.Style {
387 for (self.map.items) |h| {
388 if (h.id == id) return self.merge(h);
389 } else return self.default;
390 }
391
392 pub fn put(self: *HighlightMap, hl: Highlight) !void {
393 for (self.map.items, 0..) |h, i| {
394 if (h.id == hl.id) {
395 self.map.items[i] = hl;
396 return;
397 }
398 } else try self.map.append(hl);
399 }
400
401 /// merges a Highlight with the default to create a vaxis.Style
402 fn merge(self: HighlightMap, hl: Highlight) vaxis.Style {
403 var result = self.default;
404 const attrs = hl.attrs;
405 if (attrs.fg) |val| result.fg = val;
406 if (attrs.bg) |val| result.bg = val;
407 if (attrs.special) |val| result.ul = val;
408 if (attrs.italic) |val| result.italic = val;
409 if (attrs.bold) |val| result.bold = val;
410 if (attrs.strikethrough) |val| result.strikethrough = val;
411 if (attrs.underline) |val| result.ul_style = if (val) .single else .off;
412 if (attrs.undercurl) |val| result.ul_style = if (val) .single else .off;
413 if (attrs.underdotted) |val| result.ul_style = if (val) .dotted else .off;
414 if (attrs.reverse) |val| result.reverse = val;
415 // TODO: hyperlinks
416 return result;
417 }
418 };
419
420 /// handles an nvim event. params will always be a .arr payload type. Each event
421 /// is of the form: [ "event_name", [ param_tuple ], [param_tuple], ... ]. Each
422 /// event can come with multiple params (IE multiple instances of the same event)
423 fn handleEvent(self: *Self, event: NvimEvent, params: []znvim.Payload) void {
424 switch (event) {
425 .chdir => {
426 // param_tuple: [path]
427 for (params) |param| {
428 assert(param == znvim.Payload.arr);
429 assert(param.arr.len == 1);
430 assert(param.arr[0] == znvim.Payload.str);
431 }
432 },
433 .default_colors_set => {
434 // param_tuple: [rgb_fg, rgb_bg, rgb_sp, cterm_fg, cterm_bg]
435 for (params) |param| {
436 assert(param == znvim.Payload.arr);
437 assert(param.arr.len == 5);
438 self.hl_map.default.fg = vaxis.Color.rgbFromUint(@truncate(param.arr[0].uint));
439 self.hl_map.default.bg = vaxis.Color.rgbFromUint(@truncate(param.arr[1].uint));
440 self.hl_map.default.ul = vaxis.Color.rgbFromUint(@truncate(param.arr[2].uint));
441 }
442 },
443 .flush => {
444 self.mutex.lock();
445 defer self.mutex.unlock();
446 var row: usize = 0;
447 while (row < self.visible_screen.height) : (row += 1) {
448 var col: usize = 0;
449 while (col < self.visible_screen.width) : (col += 1) {
450 self.visible_screen.writeCell(col, row, self.screen.readCell(col, row).?);
451 }
452 }
453 self.visible_screen.cursor_row = self.screen.cursor_row;
454 self.visible_screen.cursor_col = self.screen.cursor_col;
455 self.visible_screen.cursor_vis = self.screen.cursor_vis;
456 self.visible_screen.cursor_shape = self.screen.cursor_shape;
457 if (!self.dirty) {
458 self.dirty = true;
459 self.loop.postEvent(.{ .nvim = .{ .redraw = self } });
460 }
461 },
462 .grid_clear => {
463 var row: usize = 0;
464 var col: usize = 0;
465 while (row < self.screen.height) : (row += 1) {
466 while (col < self.screen.width) : (col += 1) {
467 self.screen.writeCell(col, row, .{ .style = self.hl_map.default });
468 }
469 }
470 },
471 .grid_cursor_goto => {
472 // param_tuple: [grid, row, col]
473 for (params) |param| {
474 assert(param == znvim.Payload.arr);
475 assert(param.arr.len == 3);
476 self.screen.cursor_row = @truncate(param.arr[1].uint);
477 self.screen.cursor_col = @truncate(param.arr[2].uint);
478 self.screen.cursor_vis = true;
479 }
480 },
481 .grid_line => {
482 // param_tuple: [grid, row, col_start, cells, wrap]
483 var style: vaxis.Style = self.hl_map.default;
484 for (params) |param| {
485 assert(param == znvim.Payload.arr);
486 assert(param.arr.len == 5);
487 assert(param.arr[1] == znvim.Payload.uint);
488 assert(param.arr[2] == znvim.Payload.uint);
489 assert(param.arr[3] == znvim.Payload.arr);
490 const row: usize = param.arr[1].uint;
491 var col: usize = param.arr[2].uint;
492 const cells = param.arr[3].arr;
493 for (cells) |cell| {
494 assert(cell == znvim.Payload.arr);
495 switch (cell.arr.len) {
496 1 => {
497 assert(cell.arr[0] == znvim.Payload.str);
498 self.screen.writeCell(col, row, .{
499 .char = .{
500 .grapheme = cell.arr[0].str.value(),
501 },
502 .style = style,
503 });
504 col += 1;
505 },
506 2 => {
507 assert(cell.arr[0] == znvim.Payload.str);
508 assert(cell.arr[1] == znvim.Payload.uint);
509 style = self.hl_map.get(cell.arr[1].uint);
510 self.screen.writeCell(col, row, .{
511 .char = .{
512 .grapheme = cell.arr[0].str.value(),
513 },
514 .style = style,
515 });
516 col += 1;
517 },
518 3 => {
519 assert(cell.arr[0] == znvim.Payload.str);
520 assert(cell.arr[1] == znvim.Payload.uint);
521 assert(cell.arr[2] == znvim.Payload.uint);
522 style = self.hl_map.get(cell.arr[1].uint);
523 var i: usize = 0;
524 while (i < cell.arr[2].uint) : (i += 1) {
525 self.screen.writeCell(col, row, .{
526 .char = .{
527 .grapheme = cell.arr[0].str.value(),
528 },
529 .style = style,
530 });
531 col += 1;
532 }
533 },
534 else => unreachable,
535 }
536 }
537 }
538 },
539 .grid_resize => {
540 // param_tuple: [grid, width, height]
541 // We don't need to handle this since we aren't activating
542 // ui_multigrid. Grid ID 1 will always be our main grid
543 },
544 .grid_scroll => {
545 // param_tuple: [grid, top, bot, left, right, rows, cols]
546 for (params) |param| {
547 assert(param == znvim.Payload.arr);
548 assert(param.arr.len == 7);
549 // we don't care about grid
550 // assert(param.arr[0] == znvim.Payload.uint);
551 assert(param.arr[1] == znvim.Payload.uint);
552 assert(param.arr[2] == znvim.Payload.uint);
553 assert(param.arr[3] == znvim.Payload.uint);
554 assert(param.arr[4] == znvim.Payload.uint);
555 assert(param.arr[5] == znvim.Payload.uint or
556 param.arr[5] == znvim.Payload.int);
557 // we don't care about cols. This is always zero
558 // currently
559 // assert(param.arr[6] == znvim.Payload.uint);
560 const top: usize = @truncate(param.arr[1].uint);
561 const bot: usize = @truncate(param.arr[2].uint);
562 const left: usize = @truncate(param.arr[3].uint);
563 const right: usize = @truncate(param.arr[4].uint);
564 if (param.arr[5] == znvim.Payload.uint) {
565 const rows: usize = @truncate(param.arr[5].uint);
566 var row: usize = top;
567 while (row < bot) : (row += 1) {
568 if (row + rows > bot) break;
569 var col: usize = left;
570 while (col < right) : (col += 1) {
571 const cell = self.screen.readCell(col, row + rows) orelse return;
572 self.screen.writeCell(col, row, cell);
573 }
574 }
575 } else {
576 const rows: usize = @intCast(-param.arr[5].int);
577 var row: usize = bot -| 1;
578 while (row >= top) : (row -|= 1) {
579 if (row + 1 -| rows <= top) break;
580 var col: usize = left;
581 while (col < right) : (col += 1) {
582 const cell = self.screen.readCell(col, row -| rows) orelse unreachable;
583 self.screen.writeCell(col, row, cell);
584 }
585 }
586 }
587 }
588 },
589 .hl_attr_define => {
590 // param_tuple: [id, rgb_attr, cterm_attr, info]
591 for (params) |param| {
592 assert(param == znvim.Payload.arr);
593 assert(param.arr.len == 4);
594 assert(param.arr[0] == znvim.Payload.uint);
595 assert(param.arr[1] == znvim.Payload.map);
596 // we don't care about cterm_attr
597 // assert(param.arr[2] == znvim.Payload.map);
598 assert(param.arr[3] == znvim.Payload.arr);
599 const rgb_dict = param.arr[1].map;
600
601 var hl: HighlightMap.Highlight = .{
602 .id = param.arr[0].uint,
603 };
604 var rgb_iter = rgb_dict.iterator();
605 while (rgb_iter.next()) |kv| {
606 const key = std.meta.stringToEnum(HighlightMap.Keys, kv.key_ptr.*) orelse {
607 log.warn("unhandled highlight key: {s}", .{kv.key_ptr.*});
608 continue;
609 };
610 switch (key) {
611 .foreground => {
612 hl.attrs.fg = vaxis.Color.rgbFromUint(@truncate(kv.value_ptr.*.uint));
613 },
614 .background => {
615 hl.attrs.bg = vaxis.Color.rgbFromUint(@truncate(kv.value_ptr.*.uint));
616 },
617 .special => {
618 hl.attrs.bg = vaxis.Color.rgbFromUint(@truncate(kv.value_ptr.*.uint));
619 },
620 .italic => hl.attrs.italic = kv.value_ptr.*.bool,
621 .bold => hl.attrs.bold = kv.value_ptr.*.bool,
622 .strikethrough => hl.attrs.strikethrough = kv.value_ptr.*.bool,
623 .underline => hl.attrs.underline = kv.value_ptr.*.bool,
624 .undercurl => hl.attrs.undercurl = kv.value_ptr.*.bool,
625 .underdouble => hl.attrs.underdouble = kv.value_ptr.*.bool,
626 .underdotted => hl.attrs.underdotted = kv.value_ptr.*.bool,
627 .reverse => hl.attrs.reverse = kv.value_ptr.*.bool,
628 else => {},
629 }
630 }
631 self.hl_map.put(hl) catch |err| {
632 log.err("couldn't save highlight: {}", .{err});
633 };
634 }
635 },
636 .hl_group_set => {}, // not used right now
637 .mode_change => {
638 // param_tuple: [mode, mode_idx]
639 for (params) |param| {
640 assert(param == znvim.Payload.arr);
641 assert(param.arr.len == 2);
642 assert(param.arr[1] == znvim.Payload.uint);
643 const mode = self.mode_set.items[param.arr[1].uint];
644 log.debug("MODE CHANGE: {}", .{mode.cursor_shape});
645 self.screen.cursor_shape = mode.cursor_shape;
646 }
647 },
648 .mode_info_set => {
649 // param_tuple: [cursor_style_enabled, mode_info]
650 for (params) |param| {
651 assert(param == znvim.Payload.arr);
652 assert(param.arr.len == 2);
653 assert(param.arr[0] == znvim.Payload.bool);
654 assert(param.arr[1] == znvim.Payload.arr);
655 for (param.arr[1].arr) |mode_info| {
656 assert(mode_info == znvim.Payload.map);
657 var iter = mode_info.map.iterator();
658 var mode: Mode = .{};
659 var blink: ?bool = null;
660 var shape: vaxis.Cell.CursorShape = .default;
661 while (iter.next()) |kv| {
662 const key = std.meta.stringToEnum(Mode.Keys, kv.key_ptr.*) orelse continue;
663 switch (key) {
664 .cursor_shape => {
665 if (std.mem.eql(u8, "block", kv.value_ptr.*.str.value()))
666 shape = .block
667 else if (std.mem.eql(u8, "horizontal", kv.value_ptr.*.str.value()))
668 shape = .underline
669 else if (std.mem.eql(u8, "vertical", kv.value_ptr.*.str.value()))
670 shape = .beam;
671 },
672 .short_name => {},
673 .name => {},
674 .mouse_shape => {},
675 .attr_id => {},
676 .blinkon => {
677 if (blink == null and kv.value_ptr.*.uint != 0)
678 blink = true
679 else
680 blink = false;
681 },
682 .blinkoff => {
683 if (blink == null and kv.value_ptr.*.uint != 0)
684 blink = true
685 else
686 blink = false;
687 },
688 }
689 mode.cursor_shape = if (blink == null or !blink.?)
690 shape
691 else
692 @enumFromInt(@intFromEnum(shape) - 1);
693 log.err("key={s}, value={}", .{ kv.key_ptr.*, kv.value_ptr.* });
694 }
695 self.mode_set.append(mode) catch |err| {
696 log.err("couldn't add mode_set: {}", .{err});
697 };
698 }
699 }
700 },
701 .option_set => {
702 // param_tuple: [name, value]
703 for (params) |param| {
704 assert(param == znvim.Payload.arr);
705 assert(param.arr.len == 2);
706 assert(param.arr[0] == znvim.Payload.str);
707 const opt = std.meta.stringToEnum(OptionSet, param.arr[0].str.value()) orelse {
708 log.err("unknonwn 'option_set' key: {s}", .{param.arr[0].str.value()});
709 continue;
710 };
711 switch (opt) {
712 .ambiwidth => {},
713 .arabicshape => {},
714 .emoji => {},
715 .guifont => {},
716 .guifontwide => {},
717 .linespace => {},
718 .mousefocus => {},
719 .mousehide => {},
720 .mousemoveevent => {},
721 .pumblend => {},
722 .showtabline => {},
723 .termguicolors => {},
724 .termsync => {},
725 .ttimeout => {},
726 .ttimeoutlen => {},
727 .verbose => {},
728 .ext_cmdline => {},
729 .ext_hlstate => {},
730 .ext_linegrid => {},
731 .ext_messages => {},
732 .ext_multigrid => {},
733 .ext_popupmenu => {},
734 .ext_tabline => {},
735 .ext_termcolors => {},
736 .ext_wildmenu => {},
737 }
738 }
739 },
740 .set_icon => {
741 // param_tuple: [title]
742 for (params) |param| {
743 assert(param == znvim.Payload.arr);
744 assert(param.arr.len == 1);
745 assert(param.arr[0] == znvim.Payload.str);
746 const icon = param.arr[0].str.value();
747 log.debug("set_icon: {s}", .{icon});
748 }
749 },
750 .set_title => {
751 // param_tuple: [icon]
752 for (params) |param| {
753 assert(param == znvim.Payload.arr);
754 assert(param.arr.len == 1);
755 assert(param.arr[0] == znvim.Payload.str);
756 const title = param.arr[0].str.value();
757 log.debug("set_title: {s}", .{title});
758 }
759 },
760 }
761 }
762 };
763}