Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <stdlib.h> | ||
2 | #include <string.h> | ||
3 | #include "show.h" | ||
4 | #include "bind.h" | ||
5 | #include "buffer.h" | ||
6 | #include "change.h" | ||
7 | #include "cmdline.h" | ||
8 | #include "command/alias.h" | ||
9 | #include "command/error.h" | ||
10 | #include "command/macro.h" | ||
11 | #include "command/serialize.h" | ||
12 | #include "commands.h" | ||
13 | #include "compiler.h" | ||
14 | #include "completion.h" | ||
15 | #include "config.h" | ||
16 | #include "edit.h" | ||
17 | #include "editor.h" | ||
18 | #include "file-option.h" | ||
19 | #include "filetype.h" | ||
20 | #include "frame.h" | ||
21 | #include "mode.h" | ||
22 | #include "msg.h" | ||
23 | #include "options.h" | ||
24 | #include "syntax/color.h" | ||
25 | #include "tag.h" | ||
26 | #include "terminal/cursor.h" | ||
27 | #include "terminal/key.h" | ||
28 | #include "terminal/style.h" | ||
29 | #include "util/array.h" | ||
30 | #include "util/bsearch.h" | ||
31 | #include "util/debug.h" | ||
32 | #include "util/environ.h" | ||
33 | #include "util/intern.h" | ||
34 | #include "util/log.h" | ||
35 | #include "util/unicode.h" | ||
36 | #include "util/xmalloc.h" | ||
37 | #include "util/xsnprintf.h" | ||
38 | #include "util/xstring.h" | ||
39 | #include "view.h" | ||
40 | #include "window.h" | ||
41 | |||
42 | typedef enum { | ||
43 | DTERC = 1 << 0, // Use "dte" filetype (and syntax highlighter) | ||
44 | LASTLINE = 1 << 1, // Move cursor to last line (e.g. most recent history entry) | ||
45 | MSGLINE = 1 << 2, // Move cursor to line corresponding to `e->messages[0].pos` | ||
46 | } ShowHandlerFlags; | ||
47 | |||
48 | typedef bool (*ShowHandlerFunc)(EditorState *e, const char *name, bool cmdline); | ||
49 | typedef void (*CompleteArgFunc)(EditorState *e, PointerArray *a, const char *prefix); | ||
50 | |||
51 | typedef struct { | ||
52 | const char name[10]; | ||
53 | uint8_t flags; // ShowHandlerFlags | ||
54 | DumpFunc dump; | ||
55 | ShowHandlerFunc show; | ||
56 | CompleteArgFunc complete_arg; | ||
57 | } ShowHandler; | ||
58 | |||
59 | // NOLINTNEXTLINE(readability-function-size) | ||
60 | 1 | static void open_temporary_buffer ( | |
61 | EditorState *e, | ||
62 | const char *text, | ||
63 | size_t text_len, | ||
64 | const char *cmd, | ||
65 | const char *cmd_arg, | ||
66 | const char *filetype, | ||
67 | ShowHandlerFlags flags | ||
68 | ) { | ||
69 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1 times.
|
1 | const char *sp = cmd_arg[0] ? " " : ""; |
70 | 1 | View *view = window_open_new_file(e->window); | |
71 | 1 | Buffer *buffer = view->buffer; | |
72 | 1 | buffer_set_display_filename(buffer, xasprintf("(%s%s%s)", cmd, sp, cmd_arg)); | |
73 | 1 | buffer->temporary = true; | |
74 | |||
75 |
2/4✓ Branch 0 (7→8) taken 1 times.
✗ Branch 1 (7→9) not taken.
✓ Branch 2 (8→9) taken 1 times.
✗ Branch 3 (8→12) not taken.
|
1 | filetype = filetype ? filetype : ((flags & DTERC) ? "dte" : NULL); |
76 | if (filetype) { | ||
77 | 1 | buffer->options.filetype = str_intern(filetype); | |
78 | 1 | set_file_options(e, buffer); | |
79 | 1 | buffer_update_syntax(e, buffer); | |
80 | } | ||
81 | |||
82 |
1/2✓ Branch 0 (12→13) taken 1 times.
✗ Branch 1 (12→25) not taken.
|
1 | if (text_len == 0) { |
83 | return; | ||
84 | } | ||
85 | |||
86 | 1 | BUG_ON(!text); | |
87 | |||
88 | // We don't use buffer_insert_bytes() here, because the call to | ||
89 | // record_insert() would make the initial text contents undoable | ||
90 |
1/2✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→18) taken 1 times.
|
1 | if (unlikely(text[text_len - 1] != '\n')) { |
91 | ✗ | LOG_ERROR("no final newline in text"); | |
92 | ✗ | do_insert(view, "\n", 1); | |
93 | } | ||
94 | 1 | do_insert(view, text, text_len); | |
95 | |||
96 |
1/2✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→22) taken 1 times.
|
1 | if (flags & LASTLINE) { |
97 | ✗ | block_iter_eof(&view->cursor); | |
98 | ✗ | block_iter_prev_line(&view->cursor); | |
99 |
1/2✗ Branch 0 (22→23) not taken.
✓ Branch 1 (22→25) taken 1 times.
|
1 | } else if (flags & MSGLINE) { |
100 | ✗ | const MessageList *msgs = &e->messages[0]; | |
101 | ✗ | if (msgs->array.count > 0) { | |
102 | ✗ | block_iter_goto_line(&view->cursor, msgs->pos); | |
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | ✗ | static bool show_normal_alias(EditorState *e, const char *alias_name, bool cflag) | |
108 | { | ||
109 | ✗ | const char *cmd_str = find_alias(&e->aliases, alias_name); | |
110 | ✗ | if (!cmd_str) { | |
111 | ✗ | if (find_normal_command(alias_name)) { | |
112 | ✗ | return info_msg(&e->err, "%s is a built-in command, not an alias", alias_name); | |
113 | } | ||
114 | ✗ | return info_msg(&e->err, "%s is not a known alias", alias_name); | |
115 | } | ||
116 | |||
117 | ✗ | if (!cflag) { | |
118 | ✗ | return info_msg(&e->err, "%s is aliased to: %s", alias_name, cmd_str); | |
119 | } | ||
120 | |||
121 | ✗ | push_input_mode(e, e->command_mode); | |
122 | ✗ | cmdline_set_text(&e->cmdline, cmd_str); | |
123 | ✗ | return true; | |
124 | } | ||
125 | |||
126 | 1 | static bool show_binding(EditorState *e, const char *keystr, bool cflag) | |
127 | { | ||
128 | 1 | KeyCode key = keycode_from_str(keystr); | |
129 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (key == KEY_NONE) { |
130 | 1 | return error_msg(&e->err, "invalid key string: %s", keystr); | |
131 | } | ||
132 | |||
133 | // Use canonical key string in printed messages | ||
134 | ✗ | char buf[KEYCODE_STR_BUFSIZE]; | |
135 | ✗ | size_t len = keycode_to_str(key, buf); | |
136 | ✗ | BUG_ON(len == 0); | |
137 | ✗ | keystr = buf; | |
138 | |||
139 | ✗ | if (u_is_unicode(key)) { | |
140 | ✗ | return error_msg(&e->err, "%s is not a bindable key", keystr); | |
141 | } | ||
142 | |||
143 | ✗ | const CachedCommand *b = lookup_binding(&e->normal_mode->key_bindings, key); | |
144 | ✗ | if (!b) { | |
145 | ✗ | return info_msg(&e->err, "%s is not bound to a command", keystr); | |
146 | } | ||
147 | |||
148 | ✗ | if (!cflag) { | |
149 | ✗ | return info_msg(&e->err, "%s is bound to: %s", keystr, b->cmd_str); | |
150 | } | ||
151 | |||
152 | ✗ | push_input_mode(e, e->command_mode); | |
153 | ✗ | cmdline_set_text(&e->cmdline, b->cmd_str); | |
154 | ✗ | return true; | |
155 | } | ||
156 | |||
157 | ✗ | static bool show_color(EditorState *e, const char *name, bool cflag) | |
158 | { | ||
159 | ✗ | const TermStyle *hl = find_style(&e->styles, name); | |
160 | ✗ | if (!hl) { | |
161 | ✗ | return info_msg(&e->err, "no color entry with name '%s'", name); | |
162 | } | ||
163 | |||
164 | ✗ | if (!cflag) { | |
165 | ✗ | char buf[TERM_STYLE_BUFSIZE]; | |
166 | ✗ | const char *style_str = term_style_to_string(buf, hl); | |
167 | ✗ | return info_msg(&e->err, "color '%s' is set to: %s", name, style_str); | |
168 | } | ||
169 | |||
170 | ✗ | CommandLine *c = &e->cmdline; | |
171 | ✗ | push_input_mode(e, e->command_mode); | |
172 | ✗ | cmdline_clear(c); | |
173 | ✗ | string_append_hl_style(&c->buf, name, hl); | |
174 | ✗ | c->pos = c->buf.len; | |
175 | ✗ | return true; | |
176 | } | ||
177 | |||
178 | 1 | static bool show_cursor(EditorState *e, const char *mode_str, bool cflag) | |
179 | { | ||
180 | 1 | CursorInputMode mode = cursor_mode_from_str(mode_str); | |
181 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (mode >= NR_CURSOR_MODES) { |
182 | 1 | return error_msg(&e->err, "no cursor entry for '%s'", mode_str); | |
183 | } | ||
184 | |||
185 | ✗ | TermCursorStyle style = e->cursor_styles[mode]; | |
186 | ✗ | char colorbuf[COLOR_STR_BUFSIZE]; | |
187 | ✗ | const char *type = cursor_type_to_str(style.type); | |
188 | ✗ | const char *color = cursor_color_to_str(colorbuf, style.color); | |
189 | |||
190 | ✗ | if (!cflag) { | |
191 | ✗ | return info_msg(&e->err, "cursor '%s' is set to: %s %s", mode_str, type, color); | |
192 | } | ||
193 | |||
194 | ✗ | char buf[64]; | |
195 | ✗ | xsnprintf(buf, sizeof buf, "cursor %s %s %s", mode_str, type, color); | |
196 | ✗ | push_input_mode(e, e->command_mode); | |
197 | ✗ | cmdline_set_text(&e->cmdline, buf); | |
198 | ✗ | return true; | |
199 | } | ||
200 | |||
201 | ✗ | static bool show_env(EditorState *e, const char *name, bool cflag) | |
202 | { | ||
203 | ✗ | const char *value = getenv(name); | |
204 | ✗ | if (!value) { | |
205 | ✗ | return info_msg(&e->err, "no environment variable with name '%s'", name); | |
206 | } | ||
207 | |||
208 | ✗ | if (!cflag) { | |
209 | ✗ | return info_msg(&e->err, "$%s is set to: %s", name, value); | |
210 | } | ||
211 | |||
212 | ✗ | push_input_mode(e, e->command_mode); | |
213 | ✗ | cmdline_set_text(&e->cmdline, value); | |
214 | ✗ | return true; | |
215 | } | ||
216 | |||
217 | 1 | static String dump_env(EditorState* UNUSED_ARG(e)) | |
218 | { | ||
219 | 1 | String buf = string_new(4096); | |
220 |
2/2✓ Branch 0 (7→4) taken 168 times.
✓ Branch 1 (7→8) taken 1 times.
|
169 | for (size_t i = 0; environ[i]; i++) { |
221 | 168 | string_append_cstring(&buf, environ[i]); | |
222 | 168 | string_append_byte(&buf, '\n'); | |
223 | } | ||
224 | 1 | return buf; | |
225 | } | ||
226 | |||
227 | 1 | static String dump_setenv(EditorState* UNUSED_ARG(e)) | |
228 | { | ||
229 | 1 | String buf = string_new(4096); | |
230 |
2/2✓ Branch 0 (16→4) taken 168 times.
✓ Branch 1 (16→17) taken 1 times.
|
169 | for (size_t i = 0; environ[i]; i++) { |
231 | 168 | const char *str = environ[i]; | |
232 | 168 | const char *delim = strchr(str, '='); | |
233 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 168 times.
|
168 | if (unlikely(!delim || delim == str)) { |
234 | ✗ | continue; | |
235 | } | ||
236 | 168 | string_append_literal(&buf, "setenv "); | |
237 |
3/4✓ Branch 0 (7→8) taken 168 times.
✗ Branch 1 (7→9) not taken.
✓ Branch 2 (8→9) taken 3 times.
✓ Branch 3 (8→10) taken 165 times.
|
168 | if (unlikely(str[0] == '-' || delim[1] == '-')) { |
238 | 3 | string_append_literal(&buf, "-- "); | |
239 | } | ||
240 | 168 | const StringView name = string_view(str, delim - str); | |
241 | 168 | string_append_escaped_arg_sv(&buf, name, true); | |
242 | 168 | string_append_byte(&buf, ' '); | |
243 | 168 | string_append_escaped_arg(&buf, delim + 1, true); | |
244 | 168 | string_append_byte(&buf, '\n'); | |
245 | } | ||
246 | 1 | return buf; | |
247 | } | ||
248 | |||
249 | ✗ | static const char *flag_for_builtin_mode(const EditorState *e, const ModeHandler *m) | |
250 | { | ||
251 | ✗ | if (m == e->normal_mode) { | |
252 | return ""; | ||
253 | ✗ | } else if (m == e->command_mode) { | |
254 | return "-c "; | ||
255 | ✗ | } else if (m == e->search_mode) { | |
256 | ✗ | return "-s "; | |
257 | } | ||
258 | return NULL; | ||
259 | } | ||
260 | |||
261 | ✗ | static bool show_mode(EditorState *e, const char *name, bool cflag) | |
262 | { | ||
263 | ✗ | const ModeHandler *mode = get_mode_handler(&e->modes, name); | |
264 | ✗ | if (!mode) { | |
265 | ✗ | return info_msg(&e->err, "no mode with name '%s'", name); | |
266 | } | ||
267 | |||
268 | ✗ | String str = string_new(4096); | |
269 | ✗ | const char *flag = flag_for_builtin_mode(e, mode); | |
270 | ✗ | if (!flag) { | |
271 | ✗ | string_append_def_mode(&str, mode); | |
272 | ✗ | string_append_byte(&str, '\n'); | |
273 | } | ||
274 | |||
275 | ✗ | dump_bindings(&mode->key_bindings, flag ? flag : name, &str); | |
276 | |||
277 | ✗ | if (cflag) { | |
278 | ✗ | buffer_insert_bytes(e->view, str.buffer, str.len); | |
279 | } else { | ||
280 | ✗ | open_temporary_buffer(e, str.buffer, str.len, "def-mode", name, NULL, DTERC); | |
281 | } | ||
282 | |||
283 | ✗ | string_free(&str); | |
284 | ✗ | return true; | |
285 | } | ||
286 | |||
287 | 1 | static bool show_builtin(EditorState *e, const char *name, bool cflag) | |
288 | { | ||
289 | 1 | const BuiltinConfig *cfg = get_builtin_config(name); | |
290 |
1/2✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
|
1 | if (!cfg) { |
291 | 1 | return error_msg(&e->err, "no built-in config with name '%s'", name); | |
292 | } | ||
293 | |||
294 | ✗ | const StringView sv = cfg->text; | |
295 | ✗ | if (cflag) { | |
296 | ✗ | buffer_insert_bytes(e->view, sv.data, sv.length); | |
297 | } else { | ||
298 | ✗ | bool script = str_has_prefix(name, "script/"); | |
299 | ✗ | const char *ft = script ? filetype_str_from_extension(name) : "dte"; | |
300 | ✗ | open_temporary_buffer(e, sv.data, sv.length, "builtin", name, ft, 0); | |
301 | } | ||
302 | |||
303 | return true; | ||
304 | } | ||
305 | |||
306 | ✗ | static bool show_compiler(EditorState *e, const char *name, bool cflag) | |
307 | { | ||
308 | ✗ | const Compiler *compiler = find_compiler(&e->compilers, name); | |
309 | ✗ | if (!compiler) { | |
310 | ✗ | return info_msg(&e->err, "no errorfmt entry found for '%s'", name); | |
311 | } | ||
312 | |||
313 | ✗ | String str = string_new(512); | |
314 | ✗ | dump_compiler(compiler, name, &str); | |
315 | ✗ | if (cflag) { | |
316 | ✗ | buffer_insert_bytes(e->view, str.buffer, str.len); | |
317 | } else { | ||
318 | ✗ | open_temporary_buffer(e, str.buffer, str.len, "errorfmt", name, NULL, DTERC); | |
319 | } | ||
320 | |||
321 | ✗ | string_free(&str); | |
322 | ✗ | return true; | |
323 | } | ||
324 | |||
325 | ✗ | static bool show_msg(EditorState *e, const char *name, bool cflag) | |
326 | { | ||
327 | ✗ | size_t idx = name[0] - 'A'; | |
328 | ✗ | if (idx >= ARRAYLEN(e->messages) || name[1] != '\0') { | |
329 | ✗ | return info_msg(&e->err, "no message list '%s'", name); | |
330 | } | ||
331 | |||
332 | ✗ | const MessageList *msgs = &e->messages[idx]; | |
333 | ✗ | String str = dump_messages(msgs); | |
334 | |||
335 | ✗ | if (cflag) { | |
336 | ✗ | buffer_insert_bytes(e->view, str.buffer, str.len); | |
337 | } else { | ||
338 | ✗ | open_temporary_buffer(e, str.buffer, str.len, "msg", name, NULL, 0); | |
339 | ✗ | if (msgs->array.count > 0) { | |
340 | ✗ | block_iter_goto_line(&e->view->cursor, msgs->pos); | |
341 | } | ||
342 | } | ||
343 | |||
344 | ✗ | string_free(&str); | |
345 | ✗ | return true; | |
346 | } | ||
347 | |||
348 | 1 | static bool show_option(EditorState *e, const char *name, bool cflag) | |
349 | { | ||
350 | 1 | const char *value = get_option_value_string(e, name); | |
351 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (!value) { |
352 | 1 | return error_msg(&e->err, "invalid option name: %s", name); | |
353 | } | ||
354 | |||
355 | ✗ | if (!cflag) { | |
356 | ✗ | return info_msg(&e->err, "%s is set to: %s", name, value); | |
357 | } | ||
358 | |||
359 | ✗ | push_input_mode(e, e->command_mode); | |
360 | ✗ | cmdline_set_text(&e->cmdline, value); | |
361 | ✗ | return true; | |
362 | } | ||
363 | |||
364 | 1 | static void collect_all_options(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) | |
365 | { | ||
366 | 1 | collect_options(a, prefix, false, false); | |
367 | 1 | } | |
368 | |||
369 | ✗ | static void do_collect_cursor_modes(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) | |
370 | { | ||
371 | ✗ | collect_cursor_modes(a, prefix); | |
372 | ✗ | } | |
373 | |||
374 | 1 | static void do_collect_builtin_configs(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) | |
375 | { | ||
376 | 1 | collect_builtin_configs(a, prefix); | |
377 | 1 | } | |
378 | |||
379 | ✗ | static void do_collect_builtin_includes(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) | |
380 | { | ||
381 | ✗ | collect_builtin_includes(a, prefix); | |
382 | ✗ | } | |
383 | |||
384 | ✗ | static void do_collect_modes(EditorState *e, PointerArray *a, const char *prefix) | |
385 | { | ||
386 | ✗ | collect_modes(&e->modes, a, prefix); | |
387 | ✗ | } | |
388 | |||
389 | ✗ | static void do_collect_env(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) | |
390 | { | ||
391 | ✗ | collect_env(environ, a, strview(prefix), ""); | |
392 | ✗ | } | |
393 | |||
394 | ✗ | static void collect_show_msg_args(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) | |
395 | { | ||
396 | ✗ | static const char args[][2] = {"A", "B", "C"}; | |
397 | ✗ | COLLECT_STRINGS(args, a, prefix); | |
398 | ✗ | } | |
399 | |||
400 | 1 | static bool show_wsplit(EditorState *e, const char *name, bool cflag) | |
401 | { | ||
402 |
1/2✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
|
1 | if (!streq(name, "this")) { |
403 | 1 | return error_msg(&e->err, "invalid window: %s", name); | |
404 | } | ||
405 | |||
406 | ✗ | const Window *w = e->window; | |
407 | ✗ | char buf[(4 * DECIMAL_STR_MAX(w->x)) + 4]; | |
408 | ✗ | xsnprintf(buf, sizeof buf, "%d,%d %dx%d", w->x, w->y, w->w, w->h); | |
409 | |||
410 | ✗ | if (!cflag) { | |
411 | ✗ | return info_msg(&e->err, "current window dimensions: %s", buf); | |
412 | } | ||
413 | |||
414 | ✗ | push_input_mode(e, e->command_mode); | |
415 | ✗ | cmdline_set_text(&e->cmdline, buf); | |
416 | ✗ | return true; | |
417 | } | ||
418 | |||
419 | typedef struct { | ||
420 | const char *name; | ||
421 | const char *value; | ||
422 | } CommandAlias; | ||
423 | |||
424 | 77 | static int alias_cmp(const void *ap, const void *bp) | |
425 | { | ||
426 | 77 | const CommandAlias *a = ap; | |
427 | 77 | const CommandAlias *b = bp; | |
428 | 77 | return strcmp(a->name, b->name); | |
429 | } | ||
430 | |||
431 | 1 | static String dump_normal_aliases(EditorState *e) | |
432 | { | ||
433 | 1 | const size_t count = e->aliases.count; | |
434 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1 times.
|
1 | if (unlikely(count == 0)) { |
435 | ✗ | return string_new(0); | |
436 | } | ||
437 | |||
438 | // Clone the contents of the HashMap as an array of name/value pairs | ||
439 | 1 | CommandAlias *array = xmallocarray(count, sizeof(*array)); | |
440 | 1 | size_t n = 0; | |
441 |
2/2✓ Branch 0 (8→6) taken 24 times.
✓ Branch 1 (8→9) taken 1 times.
|
25 | for (HashMapIter it = hashmap_iter(&e->aliases); hashmap_next(&it); ) { |
442 | 24 | array[n++] = (CommandAlias) { | |
443 | 24 | .name = it.entry->key, | |
444 | 24 | .value = it.entry->value, | |
445 | }; | ||
446 | } | ||
447 | |||
448 | // Sort the array | ||
449 | 1 | BUG_ON(n != count); | |
450 | 1 | qsort(array, count, sizeof(array[0]), alias_cmp); | |
451 | |||
452 | // Serialize the aliases in sorted order | ||
453 | 1 | String buf = string_new(4096); | |
454 |
2/2✓ Branch 0 (22→14) taken 24 times.
✓ Branch 1 (22→23) taken 1 times.
|
25 | for (size_t i = 0; i < count; i++) { |
455 | 24 | const char *name = array[i].name; | |
456 | 24 | string_append_literal(&buf, "alias "); | |
457 |
1/2✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 24 times.
|
24 | if (unlikely(name[0] == '-')) { |
458 | ✗ | string_append_literal(&buf, "-- "); | |
459 | } | ||
460 | 24 | string_append_escaped_arg(&buf, name, true); | |
461 | 24 | string_append_byte(&buf, ' '); | |
462 | 24 | string_append_escaped_arg(&buf, array[i].value, true); | |
463 | 24 | string_append_byte(&buf, '\n'); | |
464 | } | ||
465 | |||
466 | 1 | free(array); | |
467 | 1 | return buf; | |
468 | } | ||
469 | |||
470 | typedef struct { | ||
471 | const char *name; | ||
472 | const ModeHandler *handler; | ||
473 | } ModeHandlerEntry; | ||
474 | |||
475 | ✗ | static int mhe_cmp(const void *ap, const void *bp) | |
476 | { | ||
477 | ✗ | const ModeHandlerEntry *a = ap; | |
478 | ✗ | const ModeHandlerEntry *b = bp; | |
479 | ✗ | return strcmp(a->name, b->name); | |
480 | } | ||
481 | |||
482 | 1 | static String dump_all_bindings(EditorState *e) | |
483 | { | ||
484 | 1 | String buf = string_new(4096); | |
485 |
1/2✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→6) not taken.
|
1 | if (dump_bindings(&e->normal_mode->key_bindings, "", &buf)) { |
486 | 1 | string_append_byte(&buf, '\n'); | |
487 | } | ||
488 | |||
489 | 1 | size_t count = e->modes.count; | |
490 | 1 | BUG_ON(count < 3); | |
491 | 1 | count -= 3; | |
492 | |||
493 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→27) taken 1 times.
|
1 | if (count) { |
494 | // Clone custom modes in HashMap as an array | ||
495 | ✗ | ModeHandlerEntry *array = xmallocarray(count, sizeof(*array)); | |
496 | ✗ | size_t n = 0; | |
497 | ✗ | for (HashMapIter it = hashmap_iter(&e->modes); hashmap_next(&it); ) { | |
498 | ✗ | const char *name = it.entry->key; | |
499 | ✗ | const ModeHandler *handler = it.entry->value; | |
500 | ✗ | bool is_builtin_mode = !!flag_for_builtin_mode(e, handler); | |
501 | ✗ | if (is_builtin_mode) { | |
502 | ✗ | continue; | |
503 | } | ||
504 | ✗ | array[n++] = (ModeHandlerEntry) { | |
505 | .name = name, | ||
506 | .handler = handler, | ||
507 | }; | ||
508 | } | ||
509 | |||
510 | // Sort the array | ||
511 | ✗ | BUG_ON(n != count); | |
512 | ✗ | qsort(array, count, sizeof(array[0]), mhe_cmp); | |
513 | |||
514 | // Serialize bindings for each mode, sorted by mode name | ||
515 | ✗ | for (size_t i = 0; i < count; i++) { | |
516 | ✗ | const char *name = array[i].name; | |
517 | ✗ | const ModeHandler *handler = array[i].handler; | |
518 | ✗ | const IntMap *bindings = &handler->key_bindings; | |
519 | ✗ | if (dump_bindings(bindings, name, &buf)) { | |
520 | ✗ | string_append_byte(&buf, '\n'); | |
521 | } | ||
522 | } | ||
523 | |||
524 | ✗ | free(array); | |
525 | } | ||
526 | |||
527 | 1 | dump_bindings(&e->command_mode->key_bindings, "-c ", &buf); | |
528 | 1 | string_append_byte(&buf, '\n'); | |
529 | 1 | dump_bindings(&e->search_mode->key_bindings, "-s ", &buf); | |
530 | 1 | return buf; | |
531 | } | ||
532 | |||
533 | 1 | static String dump_modes(EditorState *e) | |
534 | { | ||
535 | // TODO: Serialize in alphabetical order, instead of table order? | ||
536 | 1 | String buf = string_new(256); | |
537 |
2/2✓ Branch 0 (7→4) taken 3 times.
✓ Branch 1 (7→8) taken 1 times.
|
4 | for (HashMapIter it = hashmap_iter(&e->modes); hashmap_next(&it); ) { |
538 | 3 | const ModeHandler *mode = it.entry->value; | |
539 | 3 | string_append_def_mode(&buf, mode); | |
540 | 3 | string_append_byte(&buf, '\n'); | |
541 | } | ||
542 | 1 | return buf; | |
543 | } | ||
544 | |||
545 | 1 | static String dump_frames(EditorState *e) | |
546 | { | ||
547 | 1 | String str = string_new(4096); | |
548 | 1 | dump_frame(e->root_frame, 0, &str); | |
549 | 1 | return str; | |
550 | } | ||
551 | |||
552 | 1 | static String dump_compilers(EditorState *e) | |
553 | { | ||
554 | 1 | String buf = string_new(4096); | |
555 |
2/2✓ Branch 0 (7→4) taken 3 times.
✓ Branch 1 (7→8) taken 1 times.
|
4 | for (HashMapIter it = hashmap_iter(&e->compilers); hashmap_next(&it); ) { |
556 | 3 | const char *name = it.entry->key; | |
557 | 3 | const Compiler *c = it.entry->value; | |
558 | 3 | dump_compiler(c, name, &buf); | |
559 | 3 | string_append_byte(&buf, '\n'); | |
560 | } | ||
561 | 1 | return buf; | |
562 | } | ||
563 | |||
564 | 1 | static String dump_cursors(EditorState *e) | |
565 | { | ||
566 | 1 | String buf = string_new(128); | |
567 | 1 | char colorbuf[COLOR_STR_BUFSIZE]; | |
568 |
2/2✓ Branch 0 (15→4) taken 4 times.
✓ Branch 1 (15→16) taken 1 times.
|
5 | for (CursorInputMode m = 0; m < ARRAYLEN(e->cursor_styles); m++) { |
569 | 4 | const TermCursorStyle *style = &e->cursor_styles[m]; | |
570 | 4 | string_append_literal(&buf, "cursor "); | |
571 | 4 | string_append_cstring(&buf, cursor_mode_to_str(m)); | |
572 | 4 | string_append_byte(&buf, ' '); | |
573 | 4 | string_append_cstring(&buf, cursor_type_to_str(style->type)); | |
574 | 4 | string_append_byte(&buf, ' '); | |
575 | 4 | string_append_cstring(&buf, cursor_color_to_str(colorbuf, style->color)); | |
576 | 4 | string_append_byte(&buf, '\n'); | |
577 | } | ||
578 | 1 | return buf; | |
579 | } | ||
580 | |||
581 | // Dump option values and FileOption entries | ||
582 | 1 | static String dump_options_and_fileopts(EditorState *e) | |
583 | { | ||
584 | 1 | String str = dump_options(&e->options, &e->buffer->options); | |
585 | 1 | string_append_literal(&str, "\n\n"); | |
586 | 1 | dump_file_options(&e->file_options, &str); | |
587 | 1 | return str; | |
588 | } | ||
589 | |||
590 | 1 | static String dump_paste(EditorState *e) | |
591 | { | ||
592 | 1 | const Clipboard *clip = &e->clipboard; | |
593 | 1 | return string_new_from_buf(clip->buf, clip->len); | |
594 | } | ||
595 | |||
596 | 1 | static String do_dump_options(EditorState *e) {return dump_options(&e->options, &e->buffer->options);} | |
597 | 2 | static String do_dump_builtin_configs(EditorState* UNUSED_ARG(e)) {return dump_builtin_configs();} | |
598 | 2 | static String do_dump_hl_styles(EditorState *e) {return dump_hl_styles(&e->styles);} | |
599 | 1 | static String do_dump_filetypes(EditorState *e) {return dump_filetypes(&e->filetypes);} | |
600 | 1 | static String do_dump_messages_a(EditorState *e) {return dump_messages(&e->messages[0]);} | |
601 | 2 | static String do_dump_macro(EditorState *e) {return dump_macro(&e->macro);} | |
602 | 1 | static String do_dump_buffer(EditorState *e) {return dump_buffer(e->view);} | |
603 | ✗ | static String do_dump_tags(EditorState *e) {return dump_tags(&e->tagfile, &e->err);} | |
604 | 1 | static String dump_command_history(EditorState *e) {return history_dump(&e->command_history);} | |
605 | 1 | static String dump_search_history(EditorState *e) {return history_dump(&e->search_history);} | |
606 | 1 | static String dump_file_history(EditorState *e) {return file_history_dump(&e->file_history);} | |
607 | static String dump_show_subcmds(EditorState *e); // Forward declaration | ||
608 | |||
609 | static const ShowHandler show_handlers[] = { | ||
610 | {"alias", DTERC, dump_normal_aliases, show_normal_alias, collect_normal_aliases}, | ||
611 | {"bind", DTERC, dump_all_bindings, show_binding, collect_bound_normal_keys}, | ||
612 | {"buffer", 0, do_dump_buffer, NULL, NULL}, | ||
613 | {"builtin", 0, do_dump_builtin_configs, show_builtin, do_collect_builtin_configs}, | ||
614 | {"color", DTERC, do_dump_hl_styles, show_color, collect_hl_styles}, | ||
615 | {"command", DTERC | LASTLINE, dump_command_history, NULL, NULL}, | ||
616 | {"cursor", DTERC, dump_cursors, show_cursor, do_collect_cursor_modes}, | ||
617 | {"def-mode", DTERC, dump_modes, show_mode, do_collect_modes}, | ||
618 | {"env", 0, dump_env, show_env, do_collect_env}, | ||
619 | {"errorfmt", DTERC, dump_compilers, show_compiler, collect_compilers}, | ||
620 | {"ft", DTERC, do_dump_filetypes, NULL, NULL}, | ||
621 | {"hi", DTERC, do_dump_hl_styles, show_color, collect_hl_styles}, | ||
622 | {"include", 0, do_dump_builtin_configs, show_builtin, do_collect_builtin_includes}, | ||
623 | {"macro", DTERC, do_dump_macro, NULL, NULL}, | ||
624 | {"msg", MSGLINE, do_dump_messages_a, show_msg, collect_show_msg_args}, | ||
625 | {"open", LASTLINE, dump_file_history, NULL, NULL}, | ||
626 | {"option", DTERC, dump_options_and_fileopts, show_option, collect_all_options}, | ||
627 | {"paste", 0, dump_paste, NULL, NULL}, | ||
628 | {"search", LASTLINE, dump_search_history, NULL, NULL}, | ||
629 | {"set", DTERC, do_dump_options, show_option, collect_all_options}, | ||
630 | {"setenv", DTERC, dump_setenv, show_env, do_collect_env}, | ||
631 | {"show", DTERC, dump_show_subcmds, NULL, NULL}, | ||
632 | {"tag", 0, do_dump_tags, NULL, NULL}, | ||
633 | {"wsplit", 0, dump_frames, show_wsplit, NULL}, | ||
634 | }; | ||
635 | |||
636 | 24 | static const char *arg_hint_for_subcmd(const ShowHandler *handler) | |
637 | { | ||
638 | 24 | const ShowHandlerFunc func = handler->show; | |
639 |
4/4✓ Branch 0 (2→3) taken 23 times.
✓ Branch 1 (2→5) taken 1 times.
✓ Branch 2 (3→4) taken 9 times.
✓ Branch 3 (3→5) taken 14 times.
|
24 | return (func == show_binding) ? " [key]" : (func ? " [name]" : ""); |
640 | } | ||
641 | |||
642 | 1 | static String dump_show_subcmds(EditorState* UNUSED_ARG(e)) | |
643 | { | ||
644 | 1 | String str = string_new(256); | |
645 | 1 | string_append_literal(&str, "# Available `show` sub-commands:\n"); | |
646 |
2/2✓ Branch 0 (10→5) taken 24 times.
✓ Branch 1 (10→11) taken 1 times.
|
25 | for (size_t i = 0; i < ARRAYLEN(show_handlers); i++) { |
647 | 24 | const ShowHandler *handler = &show_handlers[i]; | |
648 | 24 | string_append_cstring(&str, "show "); | |
649 | 24 | string_append_cstring(&str, handler->name); | |
650 | 24 | string_append_cstring(&str, arg_hint_for_subcmd(handler)); | |
651 | 24 | string_append_byte(&str, '\n'); | |
652 | } | ||
653 | 1 | return str; | |
654 | } | ||
655 | |||
656 | 251 | static const ShowHandler *lookup_show_handler(const char *name) | |
657 | { | ||
658 | 251 | const ShowHandler *handler = BSEARCH(name, show_handlers, vstrcmp); | |
659 | 251 | return handler; | |
660 | } | ||
661 | |||
662 | 71 | DumpFunc get_dump_function(const char *name) | |
663 | { | ||
664 | 71 | const ShowHandler *handler = lookup_show_handler(name); | |
665 |
2/2✓ Branch 0 (3→4) taken 47 times.
✓ Branch 1 (3→5) taken 24 times.
|
71 | return handler ? handler->dump : NULL; |
666 | } | ||
667 | |||
668 | 24 | UNITTEST { | |
669 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
670 | 24 | CHECK_BSEARCH_ARRAY(show_handlers, name); | |
671 | 24 | BUG_ON(!lookup_show_handler("alias")); | |
672 | 24 | BUG_ON(!lookup_show_handler("set")); | |
673 | 24 | BUG_ON(!lookup_show_handler("show")); | |
674 | 24 | BUG_ON(!lookup_show_handler("wsplit")); | |
675 | 24 | BUG_ON(lookup_show_handler("alia")); | |
676 | 24 | BUG_ON(lookup_show_handler("sete")); | |
677 | 24 | BUG_ON(lookup_show_handler("")); | |
678 | 24 | BUG_ON(!get_dump_function("msg")); | |
679 | 24 | BUG_ON(get_dump_function("_")); | |
680 | // NOLINTEND(bugprone-assert-side-effect) | ||
681 | 24 | } | |
682 | |||
683 | 8 | bool show(EditorState *e, const char *type, const char *key, bool cflag) | |
684 | { | ||
685 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 8 times.
|
8 | const ShowHandler *handler = lookup_show_handler(type ? type : "show"); |
686 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 7 times.
|
8 | if (!handler) { |
687 | 1 | return error_msg(&e->err, "invalid argument: '%s'", type); | |
688 | } | ||
689 | |||
690 |
1/2✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 7 times.
|
7 | if (!type) { |
691 | ✗ | type = ""; | |
692 | } | ||
693 | |||
694 |
2/2✓ Branch 0 (9→10) taken 6 times.
✓ Branch 1 (9→13) taken 1 times.
|
7 | if (key) { |
695 |
2/2✓ Branch 0 (10→11) taken 1 times.
✓ Branch 1 (10→12) taken 5 times.
|
6 | if (!handler->show) { |
696 | 1 | return error_msg(&e->err, "'show %s' doesn't take extra arguments", type); | |
697 | } | ||
698 | 5 | return handler->show(e, key, cflag); | |
699 | } | ||
700 | |||
701 | 1 | String str = handler->dump(e); | |
702 | 1 | open_temporary_buffer(e, str.buffer, str.len, "show", type, NULL, handler->flags); | |
703 | 1 | string_free(&str); | |
704 | 1 | return true; | |
705 | } | ||
706 | |||
707 | 2 | void collect_show_subcommands(PointerArray *a, const char *prefix) | |
708 | { | ||
709 | 2 | COLLECT_STRING_FIELDS(show_handlers, name, a, prefix); | |
710 | 2 | } | |
711 | |||
712 | 4 | void collect_show_subcommand_args(EditorState *e, PointerArray *a, const char *name, const char *arg_prefix) | |
713 | { | ||
714 | 4 | const ShowHandler *handler = lookup_show_handler(name); | |
715 |
2/4✓ Branch 0 (3→4) taken 4 times.
✗ Branch 1 (3→6) not taken.
✓ Branch 2 (4→5) taken 4 times.
✗ Branch 3 (4→6) not taken.
|
4 | if (handler && handler->complete_arg) { |
716 | 4 | handler->complete_arg(e, a, arg_prefix); | |
717 | } | ||
718 | 4 | } | |
719 |