summaryrefslogtreecommitdiff
path: root/examples/dte/commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/dte/commands.c')
-rw-r--r--examples/dte/commands.c2594
1 files changed, 0 insertions, 2594 deletions
diff --git a/examples/dte/commands.c b/examples/dte/commands.c
deleted file mode 100644
index 8346309..0000000
--- a/examples/dte/commands.c
+++ /dev/null
@@ -1,2594 +0,0 @@
-#include <errno.h>
-#include <fcntl.h>
-#include <glob.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "commands.h"
-#include "bind.h"
-#include "bookmark.h"
-#include "buffer.h"
-#include "change.h"
-#include "cmdline.h"
-#include "command/alias.h"
-#include "command/args.h"
-#include "command/macro.h"
-#include "compiler.h"
-#include "config.h"
-#include "convert.h"
-#include "copy.h"
-#include "editor.h"
-#include "encoding.h"
-#include "error.h"
-#include "exec.h"
-#include "file-option.h"
-#include "filetype.h"
-#include "frame.h"
-#include "history.h"
-#include "load-save.h"
-#include "lock.h"
-#include "misc.h"
-#include "move.h"
-#include "msg.h"
-#include "regexp.h"
-#include "replace.h"
-#include "screen.h"
-#include "search.h"
-#include "selection.h"
-#include "shift.h"
-#include "show.h"
-#include "spawn.h"
-#include "syntax/color.h"
-#include "syntax/state.h"
-#include "syntax/syntax.h"
-#include "tag.h"
-#include "terminal/cursor.h"
-#include "terminal/mode.h"
-#include "terminal/osc52.h"
-#include "terminal/style.h"
-#include "terminal/terminal.h"
-#include "util/arith.h"
-#include "util/array.h"
-#include "util/ascii.h"
-#include "util/bit.h"
-#include "util/bsearch.h"
-#include "util/debug.h"
-#include "util/log.h"
-#include "util/path.h"
-#include "util/str-util.h"
-#include "util/strtonum.h"
-#include "util/time-util.h"
-#include "util/xmalloc.h"
-#include "util/xsnprintf.h"
-#include "vars.h"
-#include "view.h"
-#include "window.h"
-
-NOINLINE
-static void do_selection_noinline(View *view, SelectionType sel)
-{
- // Should only be called from do_selection()
- BUG_ON(sel == view->selection);
-
- if (sel == SELECT_NONE) {
- unselect(view);
- return;
- }
-
- if (view->selection) {
- if (view->selection != sel) {
- view->selection = sel;
- // TODO: be less brute force about this; only the first/last
- // line of the selection can change in this case
- mark_all_lines_changed(view->buffer);
- }
- return;
- }
-
- view->sel_so = block_iter_get_offset(&view->cursor);
- view->sel_eo = SEL_EO_RECALC;
- view->selection = sel;
-
- // Need to mark current line changed because cursor might
- // move up or down before screen is updated
- view_update_cursor_y(view);
- buffer_mark_lines_changed(view->buffer, view->cy, view->cy);
-}
-
-static void do_selection(View *view, SelectionType sel)
-{
- if (likely(sel == view->selection)) {
- // If `sel` is SELECT_NONE here, it's always equal to select_mode
- BUG_ON(!sel && view->select_mode);
- return;
- }
-
- do_selection_noinline(view, sel);
-}
-
-static char last_flag_or_default(const CommandArgs *a, char def)
-{
- size_t n = a->nr_flags;
- return n ? a->flags[n - 1] : def;
-}
-
-static char last_flag(const CommandArgs *a)
-{
- return last_flag_or_default(a, 0);
-}
-
-static bool has_flag(const CommandArgs *a, unsigned char flag)
-{
- return cmdargs_has_flag(a, flag);
-}
-
-static void handle_select_chars_or_lines_flags(View *view, const CommandArgs *a)
-{
- SelectionType sel;
- if (has_flag(a, 'l')) {
- sel = SELECT_LINES;
- } else if (has_flag(a, 'c')) {
- static_assert(SELECT_CHARS < SELECT_LINES);
- sel = MAX(SELECT_CHARS, view->select_mode);
- } else {
- sel = view->select_mode;
- }
- do_selection(view, sel);
-}
-
-static void handle_select_chars_flag(View *view, const CommandArgs *a)
-{
- BUG_ON(has_flag(a, 'l'));
- handle_select_chars_or_lines_flags(view, a);
-}
-
-static bool cmd_alias(EditorState *e, const CommandArgs *a)
-{
- const char *const name = a->args[0];
- const char *const cmd = a->args[1];
-
- if (unlikely(name[0] == '\0')) {
- return error_msg("Empty alias name not allowed");
- }
- if (unlikely(name[0] == '-')) {
- // Disallowing this simplifies auto-completion for "alias "
- return error_msg("Alias name cannot begin with '-'");
- }
-
- for (size_t i = 0; name[i]; i++) {
- unsigned char c = name[i];
- if (unlikely(!(is_word_byte(c) || c == '-' || c == '?' || c == '!'))) {
- return error_msg("Invalid byte in alias name: %c (0x%02hhX)", c, c);
- }
- }
-
- if (unlikely(find_normal_command(name))) {
- return error_msg("Can't replace existing command %s with an alias", name);
- }
-
- if (likely(cmd)) {
- add_alias(&e->aliases, name, cmd);
- } else {
- remove_alias(&e->aliases, name);
- }
-
- return true;
-}
-
-static bool cmd_bind(EditorState *e, const CommandArgs *a)
-{
- const char *keystr = a->args[0];
- const char *cmd = a->args[1];
- KeyCode key;
- if (unlikely(!parse_key_string(&key, keystr))) {
- return error_msg("invalid key string: %s", keystr);
- }
-
- const bool modes[] = {
- [INPUT_NORMAL] = a->nr_flags == 0 || has_flag(a, 'n'),
- [INPUT_COMMAND] = has_flag(a, 'c'),
- [INPUT_SEARCH] = has_flag(a, 's'),
- };
-
- static_assert(ARRAYLEN(modes) == ARRAYLEN(e->modes));
-
- for (InputMode i = 0; i < ARRAYLEN(modes); i++) {
- if (!modes[i]) {
- continue;
- }
- IntMap *bindings = &e->modes[i].key_bindings;
- if (likely(cmd)) {
- CommandRunner runner = cmdrunner_for_mode(e, i, false);
- add_binding(bindings, key, cached_command_new(&runner, cmd));
- } else {
- remove_binding(bindings, key);
- }
- }
-
- return true;
-}
-
-static bool cmd_bof(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_or_lines_flags(e->view, a);
- move_bof(e->view);
- return true;
-}
-
-static bool cmd_bol(EditorState *e, const CommandArgs *a)
-{
- static const FlagMapping map[] = {
- {'s', BOL_SMART},
- {'t', BOL_SMART | BOL_SMART_TOGGLE},
- };
-
- SmartBolFlags flags = cmdargs_convert_flags(a, map, ARRAYLEN(map));
- handle_select_chars_flag(e->view, a);
- move_bol_smart(e->view, flags);
- return true;
-}
-
-static bool cmd_bolsf(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- View *view = e->view;
- handle_select_chars_or_lines_flags(view, a);
-
- if (!block_iter_bol(&view->cursor)) {
- unsigned int margin = e->options.scroll_margin;
- long top = view->vy + window_get_scroll_margin(e->window, margin);
- if (view->cy > top) {
- move_up(view, view->cy - top);
- } else {
- block_iter_bof(&view->cursor);
- }
- }
-
- view_reset_preferred_x(view);
- return true;
-}
-
-static bool cmd_bookmark(EditorState *e, const CommandArgs *a)
-{
- if (has_flag(a, 'r')) {
- bookmark_pop(e->window, &e->bookmarks);
- return true;
- }
-
- bookmark_push(&e->bookmarks, get_current_file_location(e->view));
- return true;
-}
-
-static bool cmd_case(EditorState *e, const CommandArgs *a)
-{
- change_case(e->view, last_flag_or_default(a, 't'));
- return true;
-}
-
-static void mark_tabbar_changed(Window *window, void* UNUSED_ARG(data))
-{
- window->update_tabbar = true;
-}
-
-static bool cmd_cd(EditorState *e, const CommandArgs *a)
-{
- const char *dir = a->args[0];
- if (unlikely(dir[0] == '\0')) {
- return error_msg("directory argument cannot be empty");
- }
-
- if (streq(dir, "-")) {
- dir = xgetenv("OLDPWD");
- if (!dir) {
- return error_msg("OLDPWD not set");
- }
- }
-
- char buf[8192];
- const char *cwd = getcwd(buf, sizeof(buf));
- if (chdir(dir) != 0) {
- return error_msg_errno("changing directory failed");
- }
-
- if (likely(cwd)) {
- int r = setenv("OLDPWD", cwd, 1);
- if (unlikely(r != 0)) {
- LOG_WARNING("failed to set OLDPWD: %s", strerror(errno));
- }
- }
-
- cwd = getcwd(buf, sizeof(buf));
- if (likely(cwd)) {
- int r = setenv("PWD", cwd, 1);
- if (unlikely(r != 0)) {
- LOG_WARNING("failed to set PWD: %s", strerror(errno));
- }
- }
-
- for (size_t i = 0, n = e->buffers.count; i < n; i++) {
- Buffer *buffer = e->buffers.ptrs[i];
- update_short_filename_cwd(buffer, &e->home_dir, cwd);
- }
-
- frame_for_each_window(e->root_frame, mark_tabbar_changed, NULL);
- return true;
-}
-
-static bool cmd_center_view(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- e->view->force_center = true;
- return true;
-}
-
-static bool cmd_clear(EditorState *e, const CommandArgs *a)
-{
- bool auto_indent = e->buffer->options.auto_indent && !has_flag(a, 'i');
- clear_lines(e->view, auto_indent);
- return true;
-}
-
-static bool cmd_close(EditorState *e, const CommandArgs *a)
-{
- bool force = has_flag(a, 'f');
- if (!force && !view_can_close(e->view)) {
- bool prompt = has_flag(a, 'p');
- if (!prompt) {
- return error_msg (
- "The buffer is modified; "
- "save or run 'close -f' to close without saving"
- );
- }
- static const char str[] = "Close without saving changes? [y/N]";
- if (dialog_prompt(e, str, "ny") != 'y') {
- return false;
- }
- }
-
- bool allow_quit = has_flag(a, 'q');
- if (allow_quit && e->buffers.count == 1 && e->root_frame->frames.count <= 1) {
- e->status = EDITOR_EXIT_OK;
- return true;
- }
-
- bool allow_wclose = has_flag(a, 'w');
- if (allow_wclose && e->window->views.count <= 1) {
- window_close(e->window);
- return true;
- }
-
- window_close_current_view(e->window);
- set_view(e->window->view);
- return true;
-}
-
-static bool cmd_command(EditorState *e, const CommandArgs *a)
-{
- const char *text = a->args[0];
- set_input_mode(e, INPUT_COMMAND);
- if (text) {
- cmdline_set_text(&e->cmdline, text);
- }
- return true;
-}
-
-static bool cmd_compile(EditorState *e, const CommandArgs *a)
-{
- static const FlagMapping map[] = {
- {'1', SPAWN_READ_STDOUT},
- {'p', SPAWN_PROMPT},
- {'s', SPAWN_QUIET},
- };
-
- Compiler *c = find_compiler(&e->compilers, a->args[0]);
- if (unlikely(!c)) {
- return error_msg("No such error parser %s", a->args[0]);
- }
-
- SpawnContext ctx = {
- .editor = e,
- .argv = (const char **)a->args + 1,
- .flags = cmdargs_convert_flags(a, map, ARRAYLEN(map)),
- };
-
- clear_messages(&e->messages);
- bool ok = spawn_compiler(&ctx, c, &e->messages);
- if (e->messages.array.count) {
- activate_current_message_save(e);
- }
- return ok;
-}
-
-static bool cmd_copy(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- const BlockIter save = view->cursor;
- size_t size;
- bool line_copy;
- if (view->selection) {
- size = prepare_selection(view);
- line_copy = (view->selection == SELECT_LINES);
- } else {
- block_iter_bol(&view->cursor);
- BlockIter tmp = view->cursor;
- size = block_iter_eat_line(&tmp);
- line_copy = true;
- }
-
- if (unlikely(size == 0)) {
- return true;
- }
-
- bool internal = has_flag(a, 'i');
- bool clipboard = has_flag(a, 'b');
- bool primary = has_flag(a, 'p');
- if (!(internal || clipboard || primary)) {
- internal = true;
- }
-
- if (internal) {
- copy(&e->clipboard, view, size, line_copy);
- }
-
- Terminal *term = &e->terminal;
- if ((clipboard || primary) && term->features & TFLAG_OSC52_COPY) {
- if (internal) {
- view->cursor = save;
- if (view->selection) {
- size = prepare_selection(view);
- }
- }
- char *buf = block_iter_get_bytes(&view->cursor, size);
- if (!term_osc52_copy(&term->obuf, buf, size, clipboard, primary)) {
- error_msg_errno("OSC 52 copy failed");
- }
- free(buf);
- }
-
- if (!has_flag(a, 'k')) {
- unselect(view);
- }
-
- view->cursor = save;
- // TODO: return false if term_osc52_copy() failed?
- return true;
-}
-
-static bool cmd_cursor(EditorState *e, const CommandArgs *a)
-{
- if (unlikely(a->nr_args == 0)) {
- // Reset all cursor styles
- for (CursorInputMode m = 0; m < ARRAYLEN(e->cursor_styles); m++) {
- e->cursor_styles[m] = get_default_cursor_style(m);
- }
- e->cursor_style_changed = true;
- return true;
- }
-
- CursorInputMode mode = cursor_mode_from_str(a->args[0]);
- if (unlikely(mode >= NR_CURSOR_MODES)) {
- return error_msg("invalid mode argument: %s", a->args[0]);
- }
-
- TermCursorStyle style = get_default_cursor_style(mode);
- if (a->nr_args >= 2) {
- style.type = cursor_type_from_str(a->args[1]);
- if (unlikely(style.type == CURSOR_INVALID)) {
- return error_msg("invalid cursor type: %s", a->args[1]);
- }
- }
-
- if (a->nr_args >= 3) {
- style.color = cursor_color_from_str(a->args[2]);
- if (unlikely(style.color == COLOR_INVALID)) {
- return error_msg("invalid cursor color: %s", a->args[2]);
- }
- }
-
- e->cursor_styles[mode] = style;
- e->cursor_style_changed = true;
- return true;
-}
-
-static bool cmd_cut(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- View *view = e->view;
- const long x = view_get_preferred_x(view);
- if (view->selection) {
- bool is_lines = view->selection == SELECT_LINES;
- cut(&e->clipboard, view, prepare_selection(view), is_lines);
- if (view->selection == SELECT_LINES) {
- move_to_preferred_x(view, x);
- }
- unselect(view);
- } else {
- BlockIter tmp;
- block_iter_bol(&view->cursor);
- tmp = view->cursor;
- cut(&e->clipboard, view, block_iter_eat_line(&tmp), true);
- move_to_preferred_x(view, x);
- }
- return true;
-}
-
-static bool cmd_delete(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- delete_ch(e->view);
- return true;
-}
-
-static bool cmd_delete_eol(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- if (view->selection) {
- // TODO: return false?
- return true;
- }
-
- bool delete_newline_if_at_eol = has_flag(a, 'n');
- BlockIter bi = view->cursor;
- if (delete_newline_if_at_eol) {
- CodePoint ch;
- if (block_iter_get_char(&view->cursor, &ch) == 1 && ch == '\n') {
- delete_ch(view);
- return true;
- }
- }
-
- buffer_delete_bytes(view, block_iter_eol(&bi));
- return true;
-}
-
-static bool cmd_delete_line(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- delete_lines(e->view);
- return true;
-}
-
-static bool cmd_delete_word(EditorState *e, const CommandArgs *a)
-{
- bool skip_non_word = has_flag(a, 's');
- BlockIter bi = e->view->cursor;
- buffer_delete_bytes(e->view, word_fwd(&bi, skip_non_word));
- return true;
-}
-
-static bool cmd_down(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_or_lines_flags(e->view, a);
- move_down(e->view, 1);
- return true;
-}
-
-static bool cmd_eof(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_or_lines_flags(e->view, a);
- move_eof(e->view);
- return true;
-}
-
-static bool cmd_eol(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_flag(e->view, a);
- move_eol(e->view);
- return true;
-}
-
-static bool cmd_eolsf(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- View *view = e->view;
- handle_select_chars_or_lines_flags(view, a);
-
- if (!block_iter_eol(&view->cursor)) {
- Window *window = e->window;
- long margin = window_get_scroll_margin(window, e->options.scroll_margin);
- long bottom = view->vy + window->edit_h - 1 - margin;
- if (view->cy < bottom) {
- move_down(view, bottom - view->cy);
- } else {
- block_iter_eof(&view->cursor);
- }
- }
-
- view_reset_preferred_x(view);
- return true;
-}
-
-static bool cmd_erase(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- erase(e->view);
- return true;
-}
-
-static bool cmd_erase_bol(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- buffer_erase_bytes(e->view, block_iter_bol(&e->view->cursor));
- return true;
-}
-
-static bool cmd_erase_word(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- bool skip_non_word = has_flag(a, 's');
- buffer_erase_bytes(view, word_bwd(&view->cursor, skip_non_word));
- return true;
-}
-
-static bool cmd_errorfmt(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args == 0);
- const char *name = a->args[0];
- if (a->nr_args == 1) {
- remove_compiler(&e->compilers, name);
- return true;
- }
-
- bool ignore = has_flag(a, 'i');
- return add_error_fmt(&e->compilers, name, ignore, a->args[1], a->args + 2);
-}
-
-static bool cmd_exec(EditorState *e, const CommandArgs *a)
-{
- ExecAction actions[3] = {EXEC_TTY, EXEC_TTY, EXEC_TTY};
- SpawnFlags spawn_flags = 0;
- bool lflag = false;
- bool move_after_insert = false;
- bool strip_nl = false;
-
- for (size_t i = 0, n = a->nr_flags, argidx = 0, fd; i < n; i++) {
- switch (a->flags[i]) {
- case 'e': fd = STDERR_FILENO; break;
- case 'i': fd = STDIN_FILENO; break;
- case 'o': fd = STDOUT_FILENO; break;
- case 'p': spawn_flags |= SPAWN_PROMPT; continue;
- case 's': spawn_flags |= SPAWN_QUIET; continue;
- case 't': spawn_flags &= ~SPAWN_QUIET; continue;
- case 'l': lflag = true; continue;
- case 'm': move_after_insert = true; continue;
- case 'n': strip_nl = true; continue;
- default: BUG("unexpected flag"); return false;
- }
- const char *action_name = a->args[argidx++];
- ExecAction action = lookup_exec_action(action_name, fd);
- if (unlikely(action == EXEC_INVALID)) {
- return error_msg("invalid action for -%c: '%s'", a->flags[i], action_name);
- }
- actions[fd] = action;
- }
-
- if (lflag && actions[STDIN_FILENO] == EXEC_BUFFER) {
- // For compat. with old "filter" and "pipe-to" commands
- actions[STDIN_FILENO] = EXEC_LINE;
- }
-
- const char **argv = (const char **)a->args + a->nr_flag_args;
- ssize_t outlen = handle_exec(e, argv, actions, spawn_flags, strip_nl);
- if (outlen <= 0) {
- return outlen == 0;
- }
-
- if (move_after_insert && actions[STDOUT_FILENO] == EXEC_BUFFER) {
- block_iter_skip_bytes(&e->view->cursor, outlen);
- }
- return true;
-}
-
-static bool cmd_ft(EditorState *e, const CommandArgs *a)
-{
- char **args = a->args;
- const char *filetype = args[0];
- if (unlikely(!is_valid_filetype_name(filetype))) {
- return error_msg("Invalid filetype name: '%s'", filetype);
- }
-
- FileDetectionType dt = FT_EXTENSION;
- switch (last_flag(a)) {
- case 'b':
- dt = FT_BASENAME;
- break;
- case 'c':
- dt = FT_CONTENT;
- break;
- case 'f':
- dt = FT_FILENAME;
- break;
- case 'i':
- dt = FT_INTERPRETER;
- break;
- }
-
- size_t nfailed = 0;
- for (size_t i = 1, n = a->nr_args; i < n; i++) {
- if (!add_filetype(&e->filetypes, filetype, args[i], dt)) {
- nfailed++;
- }
- }
-
- return nfailed == 0;
-}
-
-static bool cmd_hi(EditorState *e, const CommandArgs *a)
-{
- if (unlikely(a->nr_args == 0)) {
- exec_builtin_color_reset(e);
- goto update;
- }
-
- char **strs = a->args + 1;
- size_t strs_len = a->nr_args - 1;
- TermColor color;
- ssize_t n = parse_term_color(&color, strs, strs_len);
- if (unlikely(n != strs_len)) {
- if (n < 0) {
- return error_msg("too many colors");
- }
- BUG_ON(n > strs_len);
- return error_msg("invalid color or attribute: '%s'", strs[n]);
- }
-
- TermColorCapabilityType color_type = e->terminal.color_type;
- bool optimize = e->options.optimize_true_color;
- int32_t fg = color_to_nearest(color.fg, color_type, optimize);
- int32_t bg = color_to_nearest(color.bg, color_type, optimize);
- if (
- color_type != TERM_TRUE_COLOR
- && has_flag(a, 'c')
- && (fg != color.fg || bg != color.bg)
- ) {
- return true;
- }
-
- color.fg = fg;
- color.bg = bg;
- set_highlight_color(&e->colors, a->args[0], &color);
-
-update:
- // Don't call update_all_syntax_colors() needlessly; it's called
- // right after config has been loaded
- if (e->status != EDITOR_INITIALIZING) {
- update_all_syntax_colors(&e->syntaxes, &e->colors);
- mark_everything_changed(e);
- }
- return true;
-}
-
-static bool cmd_include(EditorState *e, const CommandArgs *a)
-{
- ConfigFlags flags = has_flag(a, 'q') ? CFG_NOFLAGS : CFG_MUST_EXIST;
- if (has_flag(a, 'b')) {
- flags |= CFG_BUILTIN;
- }
- int err = read_normal_config(e, a->args[0], flags);
- // TODO: Clean up read_normal_config() so this can be simplified to `err == 0`
- return err == 0 || (err == ENOENT && !(flags & CFG_MUST_EXIST));
-}
-
-static bool cmd_insert(EditorState *e, const CommandArgs *a)
-{
- const char *str = a->args[0];
- if (has_flag(a, 'k')) {
- for (size_t i = 0; str[i]; i++) {
- insert_ch(e->view, str[i]);
- }
- return true;
- }
-
- bool move_after = has_flag(a, 'm');
- insert_text(e->view, str, strlen(str), move_after);
- return true;
-}
-
-static bool cmd_join(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- join_lines(e->view);
- return true;
-}
-
-static bool cmd_left(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_flag(e->view, a);
- move_cursor_left(e->view);
- return true;
-}
-
-static bool cmd_line(EditorState *e, const CommandArgs *a)
-{
- const char *str = a->args[0];
- size_t line, column;
- if (unlikely(!str_to_xfilepos(str, &line, &column))) {
- return error_msg("Invalid line number: %s", str);
- }
-
- View *view = e->view;
- long x = view_get_preferred_x(view);
- unselect(view);
-
- if (column >= 1) {
- // Column was specified; move to exact position
- move_to_filepos(view, line, column);
- } else {
- // Column was omitted; move to line while preserving current column
- move_to_line(view, line);
- move_to_preferred_x(view, x);
- }
-
- return true;
-}
-
-static bool cmd_load_syntax(EditorState *e, const CommandArgs *a)
-{
- const char *arg = a->args[0];
- const char *slash = strrchr(arg, '/');
- if (!slash) {
- const char *filetype = arg;
- if (find_syntax(&e->syntaxes, filetype)) {
- return true;
- }
- return !!load_syntax_by_filetype(e, filetype);
- }
-
- const char *filetype = slash + 1;
- if (find_syntax(&e->syntaxes, filetype)) {
- return error_msg("Syntax for filetype %s already loaded", filetype);
- }
-
- int err;
- return !!load_syntax_file(e, arg, CFG_MUST_EXIST, &err);
-}
-
-static bool cmd_macro(EditorState *e, const CommandArgs *a)
-{
- CommandMacroState *m = &e->macro;
- const char *action = a->args[0];
-
- if (streq(action, "play") || streq(action, "run")) {
- for (size_t i = 0, n = m->macro.count; i < n; i++) {
- const char *cmd_str = m->macro.ptrs[i];
- if (!handle_normal_command(e, cmd_str, false)) {
- return false;
- }
- }
- return true;
- }
-
- const char *msg;
- if (streq(action, "toggle")) {
- if (m->recording) {
- goto stop;
- }
- goto record;
- }
-
- if (streq(action, "record")) {
- record:
- msg = macro_record(m) ? "Recording macro" : "Already recording";
- goto message;
- }
-
- if (streq(action, "stop")) {
- stop:
- if (!macro_stop(m)) {
- msg = "Not recording";
- goto message;
- }
- size_t count = m->macro.count;
- const char *plural = (count != 1) ? "s" : "";
- info_msg("Macro recording stopped; %zu command%s saved", count, plural);
- return true;
- }
-
- if (streq(action, "cancel")) {
- msg = macro_cancel(m) ? "Macro recording cancelled" : "Not recording";
- goto message;
- }
-
- return error_msg("Unknown action '%s'", action);
-
-message:
- info_msg("%s", msg);
- // TODO: make this conditional?
- return true;
-}
-
-static bool cmd_match_bracket(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- View *view = e->view;
- CodePoint cursor_char;
- if (!block_iter_get_char(&view->cursor, &cursor_char)) {
- return error_msg("No character under cursor");
- }
-
- CodePoint target = cursor_char;
- BlockIter bi = view->cursor;
- size_t level = 0;
- CodePoint u = 0;
-
- switch (cursor_char) {
- case '<':
- case '[':
- case '{':
- target++;
- // Fallthrough
- case '(':
- target++;
- goto search_fwd;
- case '>':
- case ']':
- case '}':
- target--;
- // Fallthrough
- case ')':
- target--;
- goto search_bwd;
- default:
- return error_msg("Character under cursor not matchable");
- }
-
-search_fwd:
- block_iter_next_char(&bi, &u);
- BUG_ON(u != cursor_char);
- while (block_iter_next_char(&bi, &u)) {
- if (u == target) {
- if (level == 0) {
- block_iter_prev_char(&bi, &u);
- view->cursor = bi;
- return true; // Found
- }
- level--;
- } else if (u == cursor_char) {
- level++;
- }
- }
- goto not_found;
-
-search_bwd:
- while (block_iter_prev_char(&bi, &u)) {
- if (u == target) {
- if (level == 0) {
- view->cursor = bi;
- return true; // Found
- }
- level--;
- } else if (u == cursor_char) {
- level++;
- }
- }
-
-not_found:
- return error_msg("No matching bracket found");
-}
-
-static bool cmd_move_tab(EditorState *e, const CommandArgs *a)
-{
- Window *window = e->window;
- const size_t ntabs = window->views.count;
- const char *str = a->args[0];
- size_t to, from = ptr_array_idx(&window->views, e->view);
- BUG_ON(from >= ntabs);
- if (streq(str, "left")) {
- to = size_decrement_wrapped(from, ntabs);
- } else if (streq(str, "right")) {
- to = size_increment_wrapped(from, ntabs);
- } else {
- if (!str_to_size(str, &to) || to == 0) {
- return error_msg("Invalid tab position %s", str);
- }
- to = MIN(to, ntabs) - 1;
- }
- ptr_array_move(&window->views, from, to);
- window->update_tabbar = true;
- return true;
-}
-
-static bool cmd_msg(EditorState *e, const CommandArgs *a)
-{
- const char *str = a->args[0];
- uint_least64_t np = cmdargs_flagset_value('n') | cmdargs_flagset_value('p');
- if (u64_popcount(a->flag_set & np) + !!str >= 2) {
- return error_msg("flags [-n|-p] and [number] argument are mutually exclusive");
- }
-
- MessageArray *msgs = &e->messages;
- size_t count = msgs->array.count;
- if (count == 0) {
- return true;
- }
-
- size_t p = msgs->pos;
- BUG_ON(p >= count);
- if (has_flag(a, 'n')) {
- p = MIN(p + 1, count - 1);
- } else if (has_flag(a, 'p')) {
- p = p ? p - 1 : 0;
- } else if (str) {
- if (!str_to_size(str, &p) || p == 0) {
- return error_msg("invalid message index: %s", str);
- }
- p = MIN(p - 1, count - 1);
- }
-
- msgs->pos = p;
- return activate_current_message(e);
-}
-
-static bool cmd_new_line(EditorState *e, const CommandArgs *a)
-{
- new_line(e->view, has_flag(a, 'a'));
- return true;
-}
-
-static bool cmd_next(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- size_t i = ptr_array_idx(&e->window->views, e->view);
- size_t n = e->window->views.count;
- BUG_ON(i >= n);
- set_view(e->window->views.ptrs[size_increment_wrapped(i, n)]);
- return true;
-}
-
-static bool xglob(char **args, glob_t *globbuf)
-{
- BUG_ON(!args);
- BUG_ON(!args[0]);
- int err = glob(*args, GLOB_NOCHECK, NULL, globbuf);
- while (err == 0 && *++args) {
- err = glob(*args, GLOB_NOCHECK | GLOB_APPEND, NULL, globbuf);
- }
-
- if (likely(err == 0)) {
- BUG_ON(globbuf->gl_pathc == 0);
- BUG_ON(!globbuf->gl_pathv);
- BUG_ON(!globbuf->gl_pathv[0]);
- return true;
- }
-
- BUG_ON(err == GLOB_NOMATCH);
- globfree(globbuf);
- return error_msg("glob: %s", (err == GLOB_NOSPACE) ? strerror(ENOMEM) : "failed");
-}
-
-static bool cmd_open(EditorState *e, const CommandArgs *a)
-{
- bool temporary = has_flag(a, 't');
- if (unlikely(temporary && a->nr_args > 0)) {
- return error_msg("'open -t' can't be used with filename arguments");
- }
-
- const char *requested_encoding = NULL;
- char **args = a->args;
- if (unlikely(a->nr_flag_args > 0)) {
- // The "-e" flag is the only one that takes an argument, so the
- // above condition implies it was used
- BUG_ON(!has_flag(a, 'e'));
- requested_encoding = args[a->nr_flag_args - 1];
- args += a->nr_flag_args;
- }
-
- Encoding encoding = {.type = ENCODING_AUTODETECT};
- if (requested_encoding) {
- EncodingType enctype = lookup_encoding(requested_encoding);
- if (enctype == UTF8) {
- encoding = encoding_from_type(enctype);
- } else if (conversion_supported_by_iconv(requested_encoding, "UTF-8")) {
- encoding = encoding_from_name(requested_encoding);
- } else {
- if (errno == EINVAL) {
- return error_msg("Unsupported encoding '%s'", requested_encoding);
- }
- return error_msg (
- "iconv conversion from '%s' failed: %s",
- requested_encoding,
- strerror(errno)
- );
- }
- }
-
- if (a->nr_args == 0) {
- View *view = window_open_new_file(e->window);
- view->buffer->temporary = temporary;
- if (requested_encoding) {
- buffer_set_encoding(view->buffer, encoding, e->options.utf8_bom);
- }
- return true;
- }
-
- char **paths = args;
- glob_t globbuf;
- bool use_glob = has_flag(a, 'g');
- if (use_glob) {
- if (!xglob(args, &globbuf)) {
- return false;
- }
- paths = globbuf.gl_pathv;
- }
-
- View *first_opened;
- if (!paths[1]) {
- // Previous view is remembered when opening single file
- first_opened = window_open_file(e->window, paths[0], &encoding);
- } else {
- // It makes no sense to remember previous view when opening multiple files
- first_opened = window_open_files(e->window, paths, &encoding);
- }
-
- if (use_glob) {
- globfree(&globbuf);
- }
-
- return !!first_opened;
-}
-
-static bool cmd_option(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args < 3);
- size_t nstrs = a->nr_args - 1;
- if (unlikely(nstrs & 1)) {
- return error_msg("Missing option value");
- }
-
- char **strs = a->args + 1;
- if (unlikely(!validate_local_options(strs))) {
- return false;
- }
-
- PointerArray *opts = &e->file_options;
- if (has_flag(a, 'r')) {
- const StringView pattern = strview_from_cstring(a->args[0]);
- return add_file_options(opts, FOPTS_FILENAME, pattern, strs, nstrs);
- }
-
- const char *ft_list = a->args[0];
- size_t errors = 0;
- for (size_t pos = 0, len = strlen(ft_list); pos < len; ) {
- const StringView filetype = get_delim(ft_list, &pos, len, ',');
- if (!add_file_options(opts, FOPTS_FILETYPE, filetype, strs, nstrs)) {
- errors++;
- }
- }
-
- return !errors;
-}
-
-static bool cmd_blkdown(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- handle_select_chars_or_lines_flags(view, a);
-
- // If current line is blank, skip past consecutive blank lines
- StringView line;
- fetch_this_line(&view->cursor, &line);
- if (strview_isblank(&line)) {
- while (block_iter_next_line(&view->cursor)) {
- fill_line_ref(&view->cursor, &line);
- if (!strview_isblank(&line)) {
- break;
- }
- }
- }
-
- // Skip past non-blank lines
- while (block_iter_next_line(&view->cursor)) {
- fill_line_ref(&view->cursor, &line);
- if (strview_isblank(&line)) {
- break;
- }
- }
-
- // If we reach the last populated line in the buffer, move down one line
- BlockIter tmp = view->cursor;
- block_iter_eol(&tmp);
- block_iter_skip_bytes(&tmp, 1);
- if (block_iter_is_eof(&tmp)) {
- view->cursor = tmp;
- }
-
- return true;
-}
-
-static bool cmd_blkup(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- handle_select_chars_or_lines_flags(view, a);
-
- // If cursor is on the first line, just move to bol
- if (view->cy == 0) {
- block_iter_bol(&view->cursor);
- return true;
- }
-
- // If current line is blank, skip past consecutive blank lines
- StringView line;
- fetch_this_line(&view->cursor, &line);
- if (strview_isblank(&line)) {
- while (block_iter_prev_line(&view->cursor)) {
- fill_line_ref(&view->cursor, &line);
- if (!strview_isblank(&line)) {
- break;
- }
- }
- }
-
- // Skip past non-blank lines
- while (block_iter_prev_line(&view->cursor)) {
- fill_line_ref(&view->cursor, &line);
- if (strview_isblank(&line)) {
- break;
- }
- }
-
- return true;
-}
-
-static bool cmd_paste(EditorState *e, const CommandArgs *a)
-{
- bool move_after = has_flag(a, 'm');
- bool above_cursor = has_flag(a, 'a');
- bool at_cursor = has_flag(a, 'c');
- PasteLinesType type = PASTE_LINES_BELOW_CURSOR;
-
- if (above_cursor && at_cursor) {
- return error_msg("flags -a and -c are mutually exclusive");
- } else if (above_cursor) {
- type = PASTE_LINES_ABOVE_CURSOR;
- } else if (at_cursor) {
- type = PASTE_LINES_INLINE;
- }
-
- paste(&e->clipboard, e->view, type, move_after);
- return true;
-}
-
-static bool cmd_pgdown(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- handle_select_chars_or_lines_flags(view, a);
-
- Window *window = e->window;
- long margin = window_get_scroll_margin(window, e->options.scroll_margin);
- long bottom = view->vy + window->edit_h - 1 - margin;
- long count;
-
- if (view->cy < bottom) {
- count = bottom - view->cy;
- } else {
- count = window->edit_h - 1 - margin * 2;
- }
-
- move_down(view, count);
- return true;
-}
-
-static bool cmd_pgup(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- handle_select_chars_or_lines_flags(view, a);
-
- Window *window = e->window;
- long margin = window_get_scroll_margin(window, e->options.scroll_margin);
- long top = view->vy + margin;
- long count;
-
- if (view->cy > top) {
- count = view->cy - top;
- } else {
- count = window->edit_h - 1 - margin * 2;
- }
-
- move_up(view, count);
- return true;
-}
-
-static bool cmd_prev(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- size_t i = ptr_array_idx(&e->window->views, e->view);
- size_t n = e->window->views.count;
- BUG_ON(i >= n);
- set_view(e->window->views.ptrs[size_decrement_wrapped(i, n)]);
- return true;
-}
-
-static View *window_find_modified_view(Window *window)
-{
- if (buffer_modified(window->view->buffer)) {
- return window->view;
- }
- for (size_t i = 0, n = window->views.count; i < n; i++) {
- View *view = window->views.ptrs[i];
- if (buffer_modified(view->buffer)) {
- return view;
- }
- }
- return NULL;
-}
-
-static size_t count_modified_buffers(const PointerArray *buffers, View **first)
-{
- View *modified = NULL;
- size_t nr_modified = 0;
- for (size_t i = 0, n = buffers->count; i < n; i++) {
- Buffer *buffer = buffers->ptrs[i];
- if (!buffer_modified(buffer)) {
- continue;
- }
- nr_modified++;
- if (!modified) {
- modified = buffer->views.ptrs[0];
- }
- }
-
- BUG_ON(nr_modified > 0 && !modified);
- *first = modified;
- return nr_modified;
-}
-
-static bool cmd_quit(EditorState *e, const CommandArgs *a)
-{
- int exit_code = EDITOR_EXIT_OK;
- if (a->nr_args) {
- if (!str_to_int(a->args[0], &exit_code)) {
- return error_msg("Not a valid integer argument: '%s'", a->args[0]);
- }
- int max = EDITOR_EXIT_MAX;
- if (exit_code < 0 || exit_code > max) {
- return error_msg("Exit code should be between 0 and %d", max);
- }
- }
-
- View *first_modified = NULL;
- size_t n = count_modified_buffers(&e->buffers, &first_modified);
- if (n == 0) {
- goto exit;
- }
-
- BUG_ON(!first_modified);
- const char *plural = (n > 1) ? "s" : "";
- if (has_flag(a, 'f')) {
- LOG_INFO("force quitting with %zu modified buffer%s", n, plural);
- goto exit;
- }
-
- // Activate a modified view (giving preference to the current view or
- // a view in the current window)
- View *view = window_find_modified_view(e->window);
- set_view(view ? view : first_modified);
-
- if (!has_flag(a, 'p')) {
- return error_msg("Save modified files or run 'quit -f' to quit without saving");
- }
-
- char question[128];
- xsnprintf (
- question, sizeof question,
- "Quit without saving %zu modified buffer%s? [y/N]",
- n, plural
- );
-
- if (dialog_prompt(e, question, "ny") != 'y') {
- return false;
- }
-
- LOG_INFO("quit prompt accepted with %zu modified buffer%s", n, plural);
-
-exit:
- e->status = exit_code;
- return true;
-}
-
-static bool cmd_redo(EditorState *e, const CommandArgs *a)
-{
- char *arg = a->args[0];
- unsigned long change_id = 0;
- if (arg) {
- if (!str_to_ulong(arg, &change_id) || change_id == 0) {
- return error_msg("Invalid change id: %s", arg);
- }
- }
- if (!redo(e->view, change_id)) {
- return false;
- }
-
- unselect(e->view);
- return true;
-}
-
-static bool cmd_refresh(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- mark_everything_changed(e);
- return true;
-}
-
-static bool repeat_insert(EditorState *e, const char *str, unsigned int count, bool move_after)
-{
- size_t str_len = strlen(str);
- size_t bufsize;
- if (unlikely(size_multiply_overflows(count, str_len, &bufsize))) {
- return error_msg("Repeated insert would overflow");
- }
- if (unlikely(bufsize == 0)) {
- return true;
- }
-
- char *buf = malloc(bufsize);
- if (unlikely(!buf)) {
- return error_msg_errno("malloc");
- }
-
- char tmp[4096];
- if (str_len == 1) {
- memset(buf, str[0], bufsize);
- goto insert;
- } else if (bufsize < 2 * sizeof(tmp) || str_len > sizeof(tmp) / 8) {
- for (size_t i = 0; i < count; i++) {
- memcpy(buf + (i * str_len), str, str_len);
- }
- goto insert;
- }
-
- size_t strs_per_tmp = sizeof(tmp) / str_len;
- size_t tmp_len = strs_per_tmp * str_len;
- size_t tmps_per_buf = bufsize / tmp_len;
- size_t remainder = bufsize % tmp_len;
-
- // Create a block of text containing `strs_per_tmp` concatenated strs
- for (size_t i = 0; i < strs_per_tmp; i++) {
- memcpy(tmp + (i * str_len), str, str_len);
- }
-
- // Copy `tmps_per_buf` copies of `tmp` into `buf`
- for (size_t i = 0; i < tmps_per_buf; i++) {
- memcpy(buf + (i * tmp_len), tmp, tmp_len);
- }
-
- // Copy the remainder into `buf` (if any)
- if (remainder) {
- memcpy(buf + (tmps_per_buf * tmp_len), tmp, remainder);
- }
-
- LOG_DEBUG (
- "Optimized %u inserts of %zu bytes into %zu inserts of %zu bytes",
- count, str_len,
- tmps_per_buf, tmp_len
- );
-
-insert:
- insert_text(e->view, buf, bufsize, move_after);
- free(buf);
- return true;
-}
-
-static bool cmd_repeat(EditorState *e, const CommandArgs *a)
-{
- unsigned int count;
- if (unlikely(!str_to_uint(a->args[0], &count))) {
- return error_msg("Not a valid repeat count: %s", a->args[0]);
- }
- if (unlikely(count == 0)) {
- return true;
- }
-
- const Command *cmd = find_normal_command(a->args[1]);
- if (unlikely(!cmd)) {
- return error_msg("No such command: %s", a->args[1]);
- }
-
- CommandArgs a2 = cmdargs_new(a->args + 2);
- current_command = cmd;
- bool ok = parse_args(cmd, &a2);
- current_command = NULL;
- if (unlikely(!ok)) {
- return false;
- }
-
- CommandFunc fn = cmd->cmd;
- if (fn == (CommandFunc)cmd_insert && !has_flag(&a2, 'k')) {
- // Use optimized implementation for repeated "insert"
- return repeat_insert(e, a2.args[0], count, has_flag(&a2, 'm'));
- }
-
- while (count--) {
- fn(e, &a2);
- }
- // TODO: return false if fn() fails?
- return true;
-}
-
-static bool cmd_replace(EditorState *e, const CommandArgs *a)
-{
- static const FlagMapping map[] = {
- {'b', REPLACE_BASIC},
- {'c', REPLACE_CONFIRM},
- {'g', REPLACE_GLOBAL},
- {'i', REPLACE_IGNORE_CASE},
- };
-
- ReplaceFlags flags = cmdargs_convert_flags(a, map, ARRAYLEN(map));
- return reg_replace(e->view, a->args[0], a->args[1], flags);
-}
-
-static bool cmd_right(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_flag(e->view, a);
- move_cursor_right(e->view);
- return true;
-}
-
-static bool stat_changed(const FileInfo *file, const struct stat *st)
-{
- // Don't compare st_mode because we allow chmod 755 etc.
- return !timespecs_equal(get_stat_mtime(st), &file->mtime)
- || st->st_dev != file->dev
- || st->st_ino != file->ino
- || st->st_size != file->size;
-}
-
-static bool save_unmodified_buffer(Buffer *buffer, const char *filename)
-{
- SaveUnmodifiedType type = buffer->options.save_unmodified;
- if (type == SAVE_NONE) {
- LOG_INFO("buffer unchanged; leaving file untouched");
- return true;
- }
-
- BUG_ON(type != SAVE_TOUCH);
- struct timespec times[2];
- if (unlikely(clock_gettime(CLOCK_REALTIME, &times[0]) != 0)) {
- LOG_ERRNO("aborting partial save; clock_gettime() failed");
- return false;
- }
-
- times[1] = times[0];
- if (unlikely(utimensat(AT_FDCWD, filename, times, 0) != 0)) {
- LOG_ERRNO("aborting partial save; utimensat() failed");
- return false;
- }
-
- buffer->file.mtime = times[0];
- LOG_INFO("buffer unchanged; mtime/atime updated");
- return true;
-}
-
-static bool cmd_save(EditorState *e, const CommandArgs *a)
-{
- Buffer *buffer = e->buffer;
- if (unlikely(buffer->stdout_buffer)) {
- const char *f = buffer_filename(buffer);
- info_msg("%s can't be saved; it will be piped to stdout on exit", f);
- return true;
- }
-
- bool dos_nl = has_flag(a, 'd');
- bool unix_nl = has_flag(a, 'u');
- bool crlf = buffer->crlf_newlines;
- if (unlikely(dos_nl && unix_nl)) {
- return error_msg("flags -d and -u can't be used together");
- } else if (dos_nl) {
- crlf = true;
- } else if (unix_nl) {
- crlf = false;
- }
-
- const char *requested_encoding = NULL;
- char **args = a->args;
- if (unlikely(a->nr_flag_args > 0)) {
- BUG_ON(!has_flag(a, 'e'));
- requested_encoding = args[a->nr_flag_args - 1];
- args += a->nr_flag_args;
- }
-
- Encoding encoding = buffer->encoding;
- bool bom = buffer->bom;
- if (requested_encoding) {
- EncodingType et = lookup_encoding(requested_encoding);
- if (et == UTF8) {
- if (encoding.type != UTF8) {
- // Encoding changed
- encoding = encoding_from_type(et);
- bom = e->options.utf8_bom;
- }
- } else if (conversion_supported_by_iconv("UTF-8", requested_encoding)) {
- encoding = encoding_from_name(requested_encoding);
- if (encoding.name != buffer->encoding.name) {
- // Encoding changed
- bom = !!get_bom_for_encoding(encoding.type);
- }
- } else {
- if (errno == EINVAL) {
- return error_msg("Unsupported encoding '%s'", requested_encoding);
- }
- return error_msg (
- "iconv conversion to '%s' failed: %s",
- requested_encoding,
- strerror(errno)
- );
- }
- }
-
- bool b = has_flag(a, 'b');
- bool B = has_flag(a, 'B');
- if (unlikely(b && B)) {
- return error_msg("flags -b and -B can't be used together");
- } else if (b) {
- bom = true;
- } else if (B) {
- bom = false;
- }
-
- char *absolute = buffer->abs_filename;
- bool force = has_flag(a, 'f');
- bool new_locked = false;
- if (a->nr_args > 0) {
- if (args[0][0] == '\0') {
- return error_msg("Empty filename not allowed");
- }
- char *tmp = path_absolute(args[0]);
- if (!tmp) {
- return error_msg_errno("Failed to make absolute path");
- }
- if (absolute && streq(tmp, absolute)) {
- free(tmp);
- } else {
- absolute = tmp;
- }
- } else {
- if (!absolute) {
- if (!has_flag(a, 'p')) {
- return error_msg("No filename");
- }
- set_input_mode(e, INPUT_COMMAND);
- cmdline_set_text(&e->cmdline, "save ");
- return true;
- }
- if (buffer->readonly && !force) {
- return error_msg("Use -f to force saving read-only file");
- }
- }
-
- mode_t old_mode = buffer->file.mode;
- bool hardlinks = false;
- struct stat st;
- bool stat_ok = !stat(absolute, &st);
- if (!stat_ok) {
- if (errno != ENOENT) {
- error_msg("stat failed for %s: %s", absolute, strerror(errno));
- goto error;
- }
- } else {
- if (
- absolute == buffer->abs_filename
- && !force
- && stat_changed(&buffer->file, &st)
- ) {
- error_msg (
- "File has been modified by another process; "
- "use 'save -f' to force overwrite"
- );
- goto error;
- }
- if (S_ISDIR(st.st_mode)) {
- error_msg("Will not overwrite directory %s", absolute);
- goto error;
- }
- hardlinks = (st.st_nlink >= 2);
- }
-
- if (e->options.lock_files) {
- if (absolute == buffer->abs_filename) {
- if (!buffer->locked) {
- if (!lock_file(absolute)) {
- if (!force) {
- error_msg("Can't lock file %s", absolute);
- goto error;
- }
- } else {
- buffer->locked = true;
- }
- }
- } else {
- if (!lock_file(absolute)) {
- if (!force) {
- error_msg("Can't lock file %s", absolute);
- goto error;
- }
- } else {
- new_locked = true;
- }
- }
- }
-
- if (stat_ok) {
- if (absolute != buffer->abs_filename && !force) {
- error_msg("Use -f to overwrite %s", absolute);
- goto error;
- }
- // Allow chmod 755 etc.
- buffer->file.mode = st.st_mode;
- }
-
- if (
- stat_ok
- && buffer->options.save_unmodified != SAVE_FULL
- && !stat_changed(&buffer->file, &st)
- && st.st_uid == buffer->file.uid
- && st.st_gid == buffer->file.gid
- && !buffer_modified(buffer)
- && absolute == buffer->abs_filename
- && encoding.name == buffer->encoding.name
- && crlf == buffer->crlf_newlines
- && bom == buffer->bom
- && save_unmodified_buffer(buffer, absolute)
- ) {
- BUG_ON(new_locked);
- return true;
- }
-
- if (!save_buffer(buffer, absolute, &encoding, crlf, bom, hardlinks)) {
- goto error;
- }
-
- buffer->saved_change = buffer->cur_change;
- buffer->readonly = false;
- buffer->temporary = false;
- buffer->crlf_newlines = crlf;
- buffer->bom = bom;
- if (requested_encoding) {
- buffer->encoding = encoding;
- }
-
- if (absolute != buffer->abs_filename) {
- if (buffer->locked) {
- // Filename changes, release old file lock
- unlock_file(buffer->abs_filename);
- }
- buffer->locked = new_locked;
-
- free(buffer->abs_filename);
- buffer->abs_filename = absolute;
- update_short_filename(buffer, &e->home_dir);
-
- // Filename change is not detected (only buffer_modified() change)
- mark_buffer_tabbars_changed(buffer);
- }
- if (!old_mode && streq(buffer->options.filetype, "none")) {
- // New file and most likely user has not changed the filetype
- if (buffer_detect_filetype(buffer, &e->filetypes)) {
- set_file_options(e, buffer);
- set_editorconfig_options(buffer);
- buffer_update_syntax(e, buffer);
- }
- }
-
- return true;
-
-error:
- if (new_locked) {
- unlock_file(absolute);
- }
- if (absolute != buffer->abs_filename) {
- free(absolute);
- }
- return false;
-}
-
-static bool cmd_scroll_down(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- View *view = e->view;
- view->vy++;
- if (view->cy < view->vy) {
- move_down(view, 1);
- }
- return true;
-}
-
-static bool cmd_scroll_pgdown(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- Window *window = e->window;
- View *view = e->view;
- long max = view->buffer->nl - window->edit_h + 1;
- if (view->vy < max && max > 0) {
- long count = window->edit_h - 1;
- if (view->vy + count > max) {
- count = max - view->vy;
- }
- view->vy += count;
- move_down(view, count);
- } else if (view->cy < view->buffer->nl) {
- move_down(view, view->buffer->nl - view->cy);
- }
- return true;
-}
-
-static bool cmd_scroll_pgup(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- Window *window = e->window;
- View *view = e->view;
- if (view->vy > 0) {
- long count = MIN(window->edit_h - 1, view->vy);
- view->vy -= count;
- move_up(view, count);
- } else if (view->cy > 0) {
- move_up(view, view->cy);
- }
- return true;
-}
-
-static bool cmd_scroll_up(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- Window *window = e->window;
- View *view = e->view;
- if (view->vy) {
- view->vy--;
- }
- if (view->vy + window->edit_h <= view->cy) {
- move_up(view, 1);
- }
- return true;
-}
-
-static uint_least64_t get_flagset_npw(void)
-{
- uint_least64_t npw = 0;
- npw |= cmdargs_flagset_value('n');
- npw |= cmdargs_flagset_value('p');
- npw |= cmdargs_flagset_value('w');
- return npw;
-}
-
-static bool cmd_search(EditorState *e, const CommandArgs *a)
-{
- const char *pattern = a->args[0];
- if (u64_popcount(a->flag_set & get_flagset_npw()) + !!pattern >= 2) {
- return error_msg("flags [-n|-p|-w] and [pattern] argument are mutually exclusive");
- }
-
- View *view = e->view;
- char pattbuf[4096];
- bool use_word_under_cursor = has_flag(a, 'w');
-
- if (use_word_under_cursor) {
- StringView word = view_get_word_under_cursor(view);
- if (word.length == 0) {
- // Error message would not be very useful here
- return false;
- }
- const RegexpWordBoundaryTokens *rwbt = &e->regexp_word_tokens;
- const size_t bmax = sizeof(rwbt->start);
- static_assert_compatible_types(rwbt->start, char[8]);
- if (unlikely(word.length >= sizeof(pattbuf) - (bmax * 2))) {
- return error_msg("word under cursor too long");
- }
- char *ptr = stpncpy(pattbuf, rwbt->start, bmax);
- memcpy(ptr, word.data, word.length);
- memcpy(ptr + word.length, rwbt->end, bmax);
- pattern = pattbuf;
- }
-
- SearchState *search = &e->search;
- SearchCaseSensitivity cs = e->options.case_sensitive_search;
- unselect(view);
-
- if (has_flag(a, 'n')) {
- return search_next(view, search, cs);
- }
- if (has_flag(a, 'p')) {
- return search_prev(view, search, cs);
- }
-
- search->reverse = has_flag(a, 'r');
- if (!pattern) {
- set_input_mode(e, INPUT_SEARCH);
- return true;
- }
-
- bool found;
- search_set_regexp(search, pattern);
- if (use_word_under_cursor) {
- found = search_next_word(view, search, cs);
- } else {
- found = search_next(view, search, cs);
- }
-
- if (!has_flag(a, 'H')) {
- history_add(&e->search_history, pattern);
- }
-
- return found;
-}
-
-static bool cmd_select_block(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- select_block(e->view);
-
- // TODO: return false if select_block() doesn't select anything?
- return true;
-}
-
-static bool cmd_select(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- SelectionType sel = has_flag(a, 'l') ? SELECT_LINES : SELECT_CHARS;
- bool keep = has_flag(a, 'k');
- if (!keep && view->selection && view->selection == sel) {
- sel = SELECT_NONE;
- }
-
- view->select_mode = sel;
- do_selection(view, sel);
- return true;
-}
-
-static bool cmd_set(EditorState *e, const CommandArgs *a)
-{
- bool global = has_flag(a, 'g');
- bool local = has_flag(a, 'l');
- if (!e->buffer) {
- if (unlikely(local)) {
- return error_msg("Flag -l makes no sense in config file");
- }
- global = true;
- }
-
- char **args = a->args;
- size_t count = a->nr_args;
- if (count == 1) {
- return set_bool_option(e, args[0], local, global);
- }
- if (count & 1) {
- return error_msg("One or even number of arguments expected");
- }
-
- size_t errors = 0;
- for (size_t i = 0; i < count; i += 2) {
- if (!set_option(e, args[i], args[i + 1], local, global)) {
- errors++;
- }
- }
-
- return !errors;
-}
-
-static bool cmd_setenv(EditorState* UNUSED_ARG(e), const CommandArgs *a)
-{
- const char *name = a->args[0];
- if (unlikely(streq(name, "DTE_VERSION"))) {
- return error_msg("$DTE_VERSION cannot be changed");
- }
-
- const size_t nr_args = a->nr_args;
- int res;
- if (nr_args == 2) {
- res = setenv(name, a->args[1], true);
- } else {
- BUG_ON(nr_args != 1);
- res = unsetenv(name);
- }
-
- if (likely(res == 0)) {
- return true;
- }
-
- if (errno == EINVAL) {
- return error_msg("Invalid environment variable name '%s'", name);
- }
-
- return error_msg_errno(nr_args == 2 ? "setenv" : "unsetenv");
-}
-
-static bool cmd_shift(EditorState *e, const CommandArgs *a)
-{
- const char *arg = a->args[0];
- int count;
- if (!str_to_int(arg, &count)) {
- return error_msg("Invalid number: %s", arg);
- }
- if (count == 0) {
- return error_msg("Count must be non-zero");
- }
- shift_lines(e->view, count);
- return true;
-}
-
-static bool cmd_show(EditorState *e, const CommandArgs *a)
-{
- bool write_to_cmdline = has_flag(a, 'c');
- if (write_to_cmdline && a->nr_args < 2) {
- return error_msg("\"show -c\" requires 2 arguments");
- }
- return show(e, a->args[0], a->args[1], write_to_cmdline);
-}
-
-static bool cmd_suspend(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- if (e->status == EDITOR_INITIALIZING) {
- LOG_WARNING("suspend request ignored");
- return false;
- }
-
- if (e->session_leader) {
- return error_msg("Session leader can't suspend");
- }
-
- ui_end(e);
- bool suspended = !kill(0, SIGSTOP);
- if (!suspended) {
- error_msg_errno("kill");
- }
-
- term_raw();
- ui_start(e);
- return suspended;
-}
-
-static bool cmd_tag(EditorState *e, const CommandArgs *a)
-{
- if (has_flag(a, 'r')) {
- bookmark_pop(e->window, &e->bookmarks);
- return true;
- }
-
- StringView name;
- if (a->args[0]) {
- name = strview_from_cstring(a->args[0]);
- } else {
- name = view_get_word_under_cursor(e->view);
- if (name.length == 0) {
- return false;
- }
- }
-
- const char *filename = e->buffer->abs_filename;
- size_t ntags = tag_lookup(&e->tagfile, &name, filename, &e->messages);
- activate_current_message_save(e);
- return (ntags > 0);
-}
-
-static bool cmd_title(EditorState *e, const CommandArgs *a)
-{
- Buffer *buffer = e->buffer;
- if (buffer->abs_filename) {
- return error_msg("saved buffers can't be retitled");
- }
- set_display_filename(buffer, xstrdup(a->args[0]));
- mark_buffer_tabbars_changed(buffer);
- return true;
-}
-
-static bool cmd_toggle(EditorState *e, const CommandArgs *a)
-{
- bool global = has_flag(a, 'g');
- bool verbose = has_flag(a, 'v');
- const char *option_name = a->args[0];
- size_t nr_values = a->nr_args - 1;
- if (nr_values == 0) {
- return toggle_option(e, option_name, global, verbose);
- }
-
- char **values = a->args + 1;
- return toggle_option_values(e, option_name, global, verbose, values, nr_values);
-}
-
-static bool cmd_undo(EditorState *e, const CommandArgs *a)
-{
- View *view = e->view;
- bool move_only = has_flag(a, 'm');
- if (move_only) {
- const Change *change = view->buffer->cur_change;
- if (!change->next) {
- // If there's only 1 change, there's nothing meaningful to move to
- return false;
- }
- block_iter_goto_offset(&view->cursor, change->offset);
- view_reset_preferred_x(view);
- return true;
- }
-
- if (!undo(view)) {
- return false;
- }
-
- unselect(view);
- return true;
-}
-
-static bool cmd_unselect(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- unselect(e->view);
- return true;
-}
-
-static bool cmd_up(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_or_lines_flags(e->view, a);
- move_up(e->view, 1);
- return true;
-}
-
-static bool cmd_view(EditorState *e, const CommandArgs *a)
-{
- Window *window = e->window;
- BUG_ON(window->views.count == 0);
- const char *arg = a->args[0];
- size_t idx;
- if (streq(arg, "last")) {
- idx = window->views.count - 1;
- } else {
- if (!str_to_size(arg, &idx) || idx == 0) {
- return error_msg("Invalid view index: %s", arg);
- }
- idx = MIN(idx, window->views.count) - 1;
- }
- set_view(window->views.ptrs[idx]);
- return true;
-}
-
-static bool cmd_wclose(EditorState *e, const CommandArgs *a)
-{
- View *view = window_find_unclosable_view(e->window);
- bool force = has_flag(a, 'f');
- if (!view || force) {
- goto close;
- }
-
- bool prompt = has_flag(a, 'p');
- set_view(view);
- if (!prompt) {
- return error_msg (
- "Save modified files or run 'wclose -f' to close "
- "window without saving"
- );
- }
-
- if (dialog_prompt(e, "Close window without saving? [y/N]", "ny") != 'y') {
- return false;
- }
-
-close:
- window_close(e->window);
- return true;
-}
-
-static bool cmd_wflip(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- Frame *frame = e->window->frame;
- if (!frame->parent) {
- return false;
- }
- frame->parent->vertical ^= 1;
- mark_everything_changed(e);
- return true;
-}
-
-static bool cmd_wnext(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- e->window = next_window(e->window);
- set_view(e->window->view);
- mark_everything_changed(e);
- debug_frame(e->root_frame);
- return true;
-}
-
-static bool cmd_word_bwd(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_flag(e->view, a);
- bool skip_non_word = has_flag(a, 's');
- word_bwd(&e->view->cursor, skip_non_word);
- view_reset_preferred_x(e->view);
- return true;
-}
-
-static bool cmd_word_fwd(EditorState *e, const CommandArgs *a)
-{
- handle_select_chars_flag(e->view, a);
- bool skip_non_word = has_flag(a, 's');
- word_fwd(&e->view->cursor, skip_non_word);
- view_reset_preferred_x(e->view);
- return true;
-}
-
-static bool cmd_wprev(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- e->window = prev_window(e->window);
- set_view(e->window->view);
- mark_everything_changed(e);
- debug_frame(e->root_frame);
- return true;
-}
-
-static bool cmd_wrap_paragraph(EditorState *e, const CommandArgs *a)
-{
- const char *arg = a->args[0];
- unsigned int width = e->buffer->options.text_width;
- if (arg) {
- if (!str_to_uint(arg, &width)) {
- return error_msg("invalid paragraph width: %s", arg);
- }
- unsigned int max = TEXT_WIDTH_MAX;
- if (width < 1 || width > max) {
- return error_msg("width must be between 1 and %u", max);
- }
- }
- format_paragraph(e->view, width);
- return true;
-}
-
-static bool cmd_wresize(EditorState *e, const CommandArgs *a)
-{
- Window *window = e->window;
- if (!window->frame->parent) {
- // Only window
- return false;
- }
-
- ResizeDirection dir = RESIZE_DIRECTION_AUTO;
- switch (last_flag(a)) {
- case 'h':
- dir = RESIZE_DIRECTION_HORIZONTAL;
- break;
- case 'v':
- dir = RESIZE_DIRECTION_VERTICAL;
- break;
- }
-
- const char *arg = a->args[0];
- if (arg) {
- int n;
- if (!str_to_int(arg, &n)) {
- return error_msg("Invalid resize value: %s", arg);
- }
- if (arg[0] == '+' || arg[0] == '-') {
- add_to_frame_size(window->frame, dir, n);
- } else {
- resize_frame(window->frame, dir, n);
- }
- } else {
- equalize_frame_sizes(window->frame->parent);
- }
-
- mark_everything_changed(e);
- debug_frame(e->root_frame);
- // TODO: return false if resize failed?
- return true;
-}
-
-static bool cmd_wsplit(EditorState *e, const CommandArgs *a)
-{
- bool before = has_flag(a, 'b');
- bool use_glob = has_flag(a, 'g') && a->nr_args > 0;
- bool vertical = has_flag(a, 'h');
- bool root = has_flag(a, 'r');
- bool temporary = has_flag(a, 't');
- bool empty = temporary || has_flag(a, 'n');
-
- if (unlikely(empty && a->nr_args > 0)) {
- return error_msg("flags -n and -t can't be used with filename arguments");
- }
-
- char **paths = a->args;
- glob_t globbuf;
- if (use_glob) {
- if (!xglob(a->args, &globbuf)) {
- return false;
- }
- paths = globbuf.gl_pathv;
- }
-
- Frame *frame;
- if (root) {
- frame = split_root_frame(e, vertical, before);
- } else {
- frame = split_frame(e->window, vertical, before);
- }
-
- View *save = e->view;
- e->window = frame->window;
- e->view = NULL;
- e->buffer = NULL;
- mark_everything_changed(e);
-
- View *view;
- if (empty) {
- view = window_open_new_file(e->window);
- view->buffer->temporary = temporary;
- } else if (paths[0]) {
- view = window_open_files(e->window, paths, NULL);
- } else {
- view = window_add_buffer(e->window, save->buffer);
- view->cursor = save->cursor;
- set_view(view);
- }
-
- if (use_glob) {
- globfree(&globbuf);
- }
-
- if (!view) {
- // Open failed, remove new window
- remove_frame(e, e->window->frame);
- e->view = save;
- e->buffer = save->buffer;
- e->window = save->window;
- }
-
- debug_frame(e->root_frame);
- return !!view;
-}
-
-static bool cmd_wswap(EditorState *e, const CommandArgs *a)
-{
- BUG_ON(a->nr_args);
- Frame *frame = e->window->frame;
- Frame *parent = frame->parent;
- if (!parent) {
- return false;
- }
-
- size_t count = parent->frames.count;
- size_t current = ptr_array_idx(&parent->frames, frame);
- BUG_ON(current >= count);
- size_t next = size_increment_wrapped(current, count);
-
- void **ptrs = parent->frames.ptrs;
- Frame *tmp = ptrs[current];
- ptrs[current] = ptrs[next];
- ptrs[next] = tmp;
- mark_everything_changed(e);
- return true;
-}
-
-IGNORE_WARNING("-Wincompatible-pointer-types")
-
-static const Command cmds[] = {
- {"alias", "-", true, 1, 2, cmd_alias},
- {"bind", "-cns", true, 1, 2, cmd_bind},
- {"blkdown", "cl", false, 0, 0, cmd_blkdown},
- {"blkup", "cl", false, 0, 0, cmd_blkup},
- {"bof", "cl", false, 0, 0, cmd_bof},
- {"bol", "cst", false, 0, 0, cmd_bol},
- {"bolsf", "cl", false, 0, 0, cmd_bolsf},
- {"bookmark", "r", false, 0, 0, cmd_bookmark},
- {"case", "lu", false, 0, 0, cmd_case},
- {"cd", "", true, 1, 1, cmd_cd},
- {"center-view", "", false, 0, 0, cmd_center_view},
- {"clear", "i", false, 0, 0, cmd_clear},
- {"close", "fpqw", false, 0, 0, cmd_close},
- {"command", "-", false, 0, 1, cmd_command},
- {"compile", "-1ps", false, 2, -1, cmd_compile},
- {"copy", "bikp", false, 0, 0, cmd_copy},
- {"cursor", "", true, 0, 3, cmd_cursor},
- {"cut", "", false, 0, 0, cmd_cut},
- {"delete", "", false, 0, 0, cmd_delete},
- {"delete-eol", "n", false, 0, 0, cmd_delete_eol},
- {"delete-line", "", false, 0, 0, cmd_delete_line},
- {"delete-word", "s", false, 0, 0, cmd_delete_word},
- {"down", "cl", false, 0, 0, cmd_down},
- {"eof", "cl", false, 0, 0, cmd_eof},
- {"eol", "c", false, 0, 0, cmd_eol},
- {"eolsf", "cl", false, 0, 0, cmd_eolsf},
- {"erase", "", false, 0, 0, cmd_erase},
- {"erase-bol", "", false, 0, 0, cmd_erase_bol},
- {"erase-word", "s", false, 0, 0, cmd_erase_word},
- {"errorfmt", "i", true, 1, 2 + ERRORFMT_CAPTURE_MAX, cmd_errorfmt},
- {"exec", "-e=i=o=lmnpst", false, 1, -1, cmd_exec},
- {"ft", "-bcfi", true, 2, -1, cmd_ft},
- {"hi", "-c", true, 0, -1, cmd_hi},
- {"include", "bq", true, 1, 1, cmd_include},
- {"insert", "km", false, 1, 1, cmd_insert},
- {"join", "", false, 0, 0, cmd_join},
- {"left", "c", false, 0, 0, cmd_left},
- {"line", "", false, 1, 1, cmd_line},
- {"load-syntax", "", true, 1, 1, cmd_load_syntax},
- {"macro", "", false, 1, 1, cmd_macro},
- {"match-bracket", "", false, 0, 0, cmd_match_bracket},
- {"move-tab", "", false, 1, 1, cmd_move_tab},
- {"msg", "np", false, 0, 1, cmd_msg},
- {"new-line", "a", false, 0, 0, cmd_new_line},
- {"next", "", false, 0, 0, cmd_next},
- {"open", "e=gt", false, 0, -1, cmd_open},
- {"option", "-r", true, 3, -1, cmd_option},
- {"paste", "acm", false, 0, 0, cmd_paste},
- {"pgdown", "cl", false, 0, 0, cmd_pgdown},
- {"pgup", "cl", false, 0, 0, cmd_pgup},
- {"prev", "", false, 0, 0, cmd_prev},
- {"quit", "fp", false, 0, 1, cmd_quit},
- {"redo", "", false, 0, 1, cmd_redo},
- {"refresh", "", false, 0, 0, cmd_refresh},
- {"repeat", "-", false, 2, -1, cmd_repeat},
- {"replace", "bcgi", false, 2, 2, cmd_replace},
- {"right", "c", false, 0, 0, cmd_right},
- {"save", "Bbde=fpu", false, 0, 1, cmd_save},
- {"scroll-down", "", false, 0, 0, cmd_scroll_down},
- {"scroll-pgdown", "", false, 0, 0, cmd_scroll_pgdown},
- {"scroll-pgup", "", false, 0, 0, cmd_scroll_pgup},
- {"scroll-up", "", false, 0, 0, cmd_scroll_up},
- {"search", "Hnprw", false, 0, 1, cmd_search},
- {"select", "kl", false, 0, 0, cmd_select},
- {"select-block", "", false, 0, 0, cmd_select_block},
- {"set", "gl", true, 1, -1, cmd_set},
- {"setenv", "", true, 1, 2, cmd_setenv},
- {"shift", "", false, 1, 1, cmd_shift},
- {"show", "c", false, 1, 2, cmd_show},
- {"suspend", "", false, 0, 0, cmd_suspend},
- {"tag", "r", false, 0, 1, cmd_tag},
- {"title", "", false, 1, 1, cmd_title},
- {"toggle", "gv", false, 1, -1, cmd_toggle},
- {"undo", "m", false, 0, 0, cmd_undo},
- {"unselect", "", false, 0, 0, cmd_unselect},
- {"up", "cl", false, 0, 0, cmd_up},
- {"view", "", false, 1, 1, cmd_view},
- {"wclose", "fp", false, 0, 0, cmd_wclose},
- {"wflip", "", false, 0, 0, cmd_wflip},
- {"wnext", "", false, 0, 0, cmd_wnext},
- {"word-bwd", "cs", false, 0, 0, cmd_word_bwd},
- {"word-fwd", "cs", false, 0, 0, cmd_word_fwd},
- {"wprev", "", false, 0, 0, cmd_wprev},
- {"wrap-paragraph", "", false, 0, 1, cmd_wrap_paragraph},
- {"wresize", "hv", false, 0, 1, cmd_wresize},
- {"wsplit", "bghnrt", false, 0, -1, cmd_wsplit},
- {"wswap", "", false, 0, 0, cmd_wswap},
-};
-
-UNIGNORE_WARNINGS
-
-static bool allow_macro_recording(const Command *cmd, char **args)
-{
- CommandFunc fn = cmd->cmd;
- if (fn == (CommandFunc)cmd_macro || fn == (CommandFunc)cmd_command) {
- return false;
- }
-
- if (fn == (CommandFunc)cmd_search) {
- char **args_copy = copy_string_array(args, string_array_length(args));
- CommandArgs a = cmdargs_new(args_copy);
- bool ret = true;
- if (do_parse_args(cmd, &a) == ARGERR_NONE) {
- if (a.nr_args == 0 && !(a.flag_set & get_flagset_npw())) {
- // If command is "search" with no pattern argument and without
- // flags -n, -p or -w, the command would put the editor into
- // search mode, which shouldn't be recorded.
- ret = false;
- }
- }
- free_string_array(args_copy);
- return ret;
- }
-
- if (fn == (CommandFunc)cmd_exec) {
- // TODO: don't record -o with open/tag/eval/msg
- }
-
- return true;
-}
-
-UNITTEST {
- const char *args[4] = {NULL};
- char **argp = (char**)args;
- const Command *cmd = find_normal_command("left");
- BUG_ON(!cmd);
- BUG_ON(!allow_macro_recording(cmd, argp));
-
- cmd = find_normal_command("exec");
- BUG_ON(!cmd);
- BUG_ON(!allow_macro_recording(cmd, argp));
-
- cmd = find_normal_command("command");
- BUG_ON(!cmd);
- BUG_ON(allow_macro_recording(cmd, argp));
-
- cmd = find_normal_command("macro");
- BUG_ON(!cmd);
- BUG_ON(allow_macro_recording(cmd, argp));
-
- cmd = find_normal_command("search");
- BUG_ON(!cmd);
- BUG_ON(allow_macro_recording(cmd, argp));
- args[0] = "xyz";
- BUG_ON(!allow_macro_recording(cmd, argp));
- args[0] = "-n";
- BUG_ON(!allow_macro_recording(cmd, argp));
- args[0] = "-p";
- BUG_ON(!allow_macro_recording(cmd, argp));
- args[0] = "-w";
- BUG_ON(!allow_macro_recording(cmd, argp));
- args[0] = "-Hr";
- BUG_ON(allow_macro_recording(cmd, argp));
- args[1] = "str";
- BUG_ON(!allow_macro_recording(cmd, argp));
-}
-
-static void record_command(const Command *cmd, char **args, void *userdata)
-{
- if (!allow_macro_recording(cmd, args)) {
- return;
- }
- EditorState *e = userdata;
- macro_command_hook(&e->macro, cmd->name, args);
-}
-
-const Command *find_normal_command(const char *name)
-{
- return BSEARCH(name, cmds, command_cmp);
-}
-
-const CommandSet normal_commands = {
- .lookup = find_normal_command,
- .macro_record = record_command,
- .expand_variable = expand_normal_var,
- .expand_env_vars = true,
-};
-
-const char *find_normal_alias(const char *name, void *userdata)
-{
- EditorState *e = userdata;
- return find_alias(&e->aliases, name);
-}
-
-bool handle_normal_command(EditorState *e, const char *cmd, bool allow_recording)
-{
- CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, allow_recording);
- return handle_command(&runner, cmd);
-}
-
-void exec_normal_config(EditorState *e, StringView config)
-{
- CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false);
- exec_config(&runner, config);
-}
-
-int read_normal_config(EditorState *e, const char *filename, ConfigFlags flags)
-{
- CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false);
- return read_config(&runner, filename, flags);
-}
-
-void collect_normal_commands(PointerArray *a, const char *prefix)
-{
- COLLECT_STRING_FIELDS(cmds, name, a, prefix);
-}
-
-UNITTEST {
- CHECK_BSEARCH_ARRAY(cmds, name, strcmp);
-
- for (size_t i = 0, n = ARRAYLEN(cmds); i < n; i++) {
- // Check that flags arrays is null-terminated within bounds
- const char *const flags = cmds[i].flags;
- BUG_ON(flags[ARRAYLEN(cmds[0].flags) - 1] != '\0');
-
- // Count number of real flags (i.e. not including '-' or '=')
- size_t nr_real_flags = 0;
- for (size_t j = (flags[0] == '-' ? 1 : 0); flags[j]; j++) {
- unsigned char flag = flags[j];
- if (ascii_isalnum(flag)) {
- nr_real_flags++;
- } else if (flag != '=') {
- BUG("invalid command flag: 0x%02hhX", flag);
- }
- }
-
- // Check that max. number of real flags fits in CommandArgs::flags
- // array (and also leaves 1 byte for null-terminator)
- CommandArgs a;
- BUG_ON(nr_real_flags >= ARRAYLEN(a.flags));
- }
-}