diff options
Diffstat (limited to 'examples/dte/commands.c')
| -rw-r--r-- | examples/dte/commands.c | 2594 |
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, ×[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)); - } -} |
