Logo Search packages:      
Sourcecode: yash version File versions  Download package

editing.c

/* Yash: yet another shell */
/* editing.c: main editing module */
/* (C) 2007-2010 magicant */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.  */


#include "../common.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#if HAVE_GETTEXT
# include <libintl.h>
#endif
#if YASH_ENABLE_ALIAS
# include "../alias.h"
#endif
#include "../exec.h"
#include "../expand.h"
#include "../history.h"
#include "../job.h"
#include "../option.h"
#include "../path.h"
#include "../plist.h"
#include "../redir.h"
#include "../strbuf.h"
#include "../util.h"
#include "../xfnmatch.h"
#include "../yash.h"
#include "complete.h"
#include "display.h"
#include "editing.h"
#include "keymap.h"
#include "lineedit.h"
#include "terminfo.h"


/* The type of pairs of a command and an argument. */
00060 struct command {
    le_command_func_T *func;
    wchar_t arg;
};


/* The main buffer where the command line is edited. */
xwcsbuf_T le_main_buffer;
/* The position of the cursor on the command line.
 * 0 <= le_main_index <= le_main_buffer.length */
size_t le_main_index;

/* The history entry that is being edited in the main buffer now.
 * When we're editing no history entry, `main_history_entry' is a pointer to
 * `histlist'. */
static const histentry_T *main_history_entry;
/* The original value of `main_history_entry', converted into a wide string. */
static wchar_t *main_history_value;

/* The direction of currently performed command history search. */
enum le_search_direction le_search_direction;
/* The type of currently performed command history search. */
enum le_search_type le_search_type;
/* Supplementary buffer used in command history search.
 * When search is not being performed, `le_search_buffer.contents' is NULL. */
xwcsbuf_T le_search_buffer;
/* The search result for the current value of `le_search_buffer'.
 * If there is no match, `le_search_result' is `Histlist'. */
const histentry_T *le_search_result;
/* The search string and the direction of the last search. */
static struct {
    enum le_search_direction direction;
    enum le_search_type type;
    wchar_t *value;
} last_search;

/* The last executed command and the currently executing command. */
static struct command last_command, current_command;

/* The type of motion expecting commands. */
enum motion_expect_command {
    MEC_NONE       = 0,       /* just move the cursor */
    MEC_UPPERCASE  = 1 << 0,  /* convert the text to upper case */
    MEC_LOWERCASE  = 1 << 1,  /* convert the text to lower case */
    MEC_SWITCHCASE = 1 << 2,  /* switch case of the text */
    MEC_COPY       = 1 << 3,  /* copy the text to the kill ring */
    MEC_TOSTART    = 1 << 4,  /* move cursor to the beginning of the region. */
    MEC_TOEND      = 1 << 5,  /* move cursor to the end of the region. */
    MEC_DELETE     = 1 << 6,  /* delete the text */
    MEC_INSERT     = 1 << 7,  /* go to insert mode */
    MEC_KILL       = MEC_COPY   | MEC_DELETE,
    MEC_CHANGE     = MEC_DELETE | MEC_INSERT,
    MEC_COPYCHANGE = MEC_KILL   | MEC_INSERT,
    /* MEC_UPPERCASE, MEC_LOWERCASE and MEC_SWITCHCASE are mutually exclusive.
     * If neither MEC_TOSTART nor MEC_TOEND is specified, the cursor is not
     * moved unless MEC_DELETE is specified. If both MEC_TOSTART and MEC_TOEND
     * are specified, the cursor is moved to the end point of the motion (just
     * like MEC_NONE). */
};

/* The keymap state. */
00121 static struct state {
    struct {
      /* When count is not specified, `sign' and `abs' is 0.
       * Otherwise, `sign' is 1 or -1.
       * When the negative sign is specified but digits are not, `abs' is 0.*/
      int sign;
      unsigned abs;
      int multiplier;
#define COUNT_ABS_MAX 999999999
    } count;
    enum motion_expect_command pending_command_motion;
    le_command_func_T *pending_command_char;
} state;

/* The last executed editing command and the then state.
 * Valid iff `.command.func' is non-null. */
static struct {
    struct command command;
    struct state state;
} last_edit_command;

/* The last executed find/till command. */
/* `last_find_command' is valid iff `.func' is non-null. */
static struct command last_find_command;

/* The editing mode before the mode is changed to LE_MODE_CHAR_EXPECT/SEARCH.
 * When the char-expecting/search command finishes, the mode is restored to
 * this mode. */
static le_mode_id_T savemode;

/* If true, characters are overwritten rather than inserted. */
static bool overwrite = false;

/* History of the edit line between editing commands. */
static plist_T undo_history;
/* Index of the current state in the history.
 * If the current state is the newest, the index is `undo_history.length'. */
static size_t undo_index;
/* The history entry that is saved in the undo history. */
static const histentry_T *undo_history_entry;
/* The index that is to be the value of the `index' member of the next undo
 * history entry. */
static size_t undo_save_index;
/* Structure of history entries */
00165 struct undo_history {
    size_t index;        /* index of the cursor */
    wchar_t contents[];  /* contents of the edit line */
};

#define KILL_RING_SIZE 32  /* must be power of 2 */
/* The kill ring */
static wchar_t *kill_ring[KILL_RING_SIZE];
/* The index of the element to which next killed string is assigned. */
static size_t next_kill_index = 0;
/* The index of the last put element. */
static size_t last_put_elem = 0;  /* < KILL_RING_SIZE */
/* The position and length of the last put string. */
static size_t last_put_range_start, last_put_range_length;

/* Set to true if the next completion command should restart completion from
 * scratch. */
static bool reset_completion;
/* The next value of `reset_completion'. */
static bool next_reset_completion;


static void reset_state(void);
static int get_count(int default_value)
    __attribute__((pure));
static void save_current_edit_command(void);
static void save_current_find_command(void);
static void save_undo_history(void);
static void maybe_save_undo_history(void);
static void exec_motion_command(size_t index, bool inclusive);
static void set_motion_expect_command(enum motion_expect_command cmd);
static void exec_motion_expect_command(
      enum motion_expect_command cmd, le_command_func_T motion);
static void exec_motion_expect_command_line(enum motion_expect_command cmd);
static void exec_motion_expect_command_all(void);
static void add_to_kill_ring(const wchar_t *s, size_t n)
    __attribute__((nonnull));
static void set_char_expect_command(le_command_func_T cmd)
    __attribute__((nonnull));
static void set_search_mode(le_mode_id_T mode, enum le_search_direction dir);
static void to_upper_case(wchar_t *s, size_t n)
    __attribute__((nonnull));
static void to_lower_case(wchar_t *s, size_t n)
    __attribute__((nonnull));
static void switch_case(wchar_t *s, size_t n)
    __attribute__((nonnull));

static void redraw_all(bool clear);

static bool alert_if_first(void);
static bool alert_if_last(void);
static void move_cursor_forward_char(int offset);
static void move_cursor_backward_char(int offset);
static void move_cursor_forward_bigword(int count);
static void move_cursor_backward_bigword(int count);
static size_t next_bigword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static size_t next_end_of_bigword_index(
      const wchar_t *s, size_t i, bool progress)
    __attribute__((nonnull));
static size_t previous_bigword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static void move_cursor_forward_semiword(int count);
static void move_cursor_backward_semiword(int count);
static size_t next_semiword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static size_t next_end_of_semiword_index(
      const wchar_t *s, size_t i, bool progress)
    __attribute__((nonnull));
static size_t previous_semiword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static void move_cursor_forward_viword(int count);
static inline bool need_cw_treatment(void)
    __attribute__((pure));
static void move_cursor_backward_viword(int count);
static size_t next_viword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static size_t next_end_of_viword_index(
      const wchar_t *s, size_t i, bool progress)
    __attribute__((nonnull));
static size_t previous_viword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static void move_cursor_forward_emacsword(int count);
static void move_cursor_backward_emacsword(int count);
static size_t next_emacsword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static size_t previous_emacsword_index(const wchar_t *s, size_t i)
    __attribute__((nonnull));
static void find_char(wchar_t c);
static void find_char_rev(wchar_t c);
static void till_char(wchar_t c);
static void till_char_rev(wchar_t c);
static void exec_find(wchar_t c, int count, bool till);
static size_t find_nth_occurence(wchar_t c, int n);

static void put_killed_string(bool after_cursor, bool cursor_on_last_char);
static void insert_killed_string(
      bool after_cursor, bool cursor_on_last_char, size_t index);
static void cancel_undo(int offset);

static void check_reset_completion(void);

static void vi_replace_char(wchar_t c);
static void vi_exec_alias(wchar_t c);
struct xwcsrange { const wchar_t *start, *end; };
static struct xwcsrange get_next_bigword(const wchar_t *s)
    __attribute__((nonnull));
static struct xwcsrange get_prev_bigword(
      const wchar_t *beginning, const wchar_t *s)
    __attribute__((nonnull));

static void replace_horizontal_space(bool deleteafter, const wchar_t *s)
    __attribute__((nonnull));

static void go_to_history_absolute(
      const histentry_T *e, enum le_search_type curpos)
    __attribute__((nonnull));
static void go_to_history_relative(int offset, enum le_search_type curpos);
static void go_to_history(const histentry_T *e, enum le_search_type curpos)
    __attribute__((nonnull));

static bool need_update_last_search_value(void)
    __attribute__((pure));
static void update_search(void);
static void perform_search(const wchar_t *pattern,
      enum le_search_direction dir, enum le_search_type type)
    __attribute__((nonnull));
static void search_again(enum le_search_direction dir);
static void beginning_search(enum le_search_direction dir);
static inline bool beginning_search_check_go_to_history(const wchar_t *prefix)
    __attribute__((nonnull,pure));

#define ALERT_AND_RETURN_IF_PENDING                     \
    do if (state.pending_command_motion != MEC_NONE)    \
      { cmd_alert(L'\0'); return; }                   \
    while (0)


/* Initializes the editing module before starting editing. */
void le_editing_init(void)
{
    wb_init(&le_main_buffer);
    le_main_index = 0;
    main_history_entry = Histlist;
    main_history_value = xwcsdup(L"");

    switch (shopt_lineedit) {
      case shopt_vi:     le_set_mode(LE_MODE_VI_INSERT);  break;
      case shopt_emacs:  le_set_mode(LE_MODE_EMACS);      break;
      default:           assert(false);
    }

    last_command.func = 0;
    last_command.arg = L'\0';

    start_using_history();
    pl_init(&undo_history);
    undo_index = 0;
    undo_save_index = le_main_index;
    undo_history_entry = Histlist;
    save_undo_history();

    reset_completion = true;

    reset_state();
    overwrite = false;
}

/* Finalizes the editing module when editing is finished.
 * Returns the content of the main buffer, which must be freed by the caller. */
wchar_t *le_editing_finalize(void)
{
    assert(le_search_buffer.contents == NULL);

    recfree(pl_toary(&undo_history), free);

    le_complete_cleanup();

    end_using_history();
    free(main_history_value);

    wb_wccat(&le_main_buffer, L'\n');
    return wb_towcs(&le_main_buffer);
}

/* Invokes the specified command. */
void le_invoke_command(le_command_func_T *cmd, wchar_t arg)
{
    current_command.func = cmd;
    current_command.arg = arg;

    next_reset_completion = true;

    cmd(arg);

    last_command = current_command;

    reset_completion |= next_reset_completion;
    if (LE_CURRENT_MODE == LE_MODE_VI_COMMAND)
      if (le_main_index > 0 && le_main_index == le_main_buffer.length)
          le_main_index--;
}

/* Resets `state'. */
void reset_state(void)
{
    state.count.sign = 0;
    state.count.abs = 0;
    state.count.multiplier = 1;
    state.pending_command_motion = MEC_NONE;
    state.pending_command_char = 0;
}

/* Returns the count value.
 * If the count is not set, returns the `default_value'. */
int get_count(int default_value)
{
    long long result;

    if (state.count.sign == 0)
      result = (long long) default_value * state.count.multiplier;
    else if (state.count.sign < 0 && state.count.abs == 0)
      result = (long long) -state.count.multiplier;
    else
      result = (long long) state.count.abs * state.count.sign *
          state.count.multiplier;

    if (result < -COUNT_ABS_MAX)
      result = -COUNT_ABS_MAX;
    else if (result > COUNT_ABS_MAX)
      result = COUNT_ABS_MAX;
    return result;
}

/* Saves the currently executing command and the current state in
 * `last_edit_command' if we are not redoing and the mode is not "vi insert". */
void save_current_edit_command(void)
{
    if (current_command.func != cmd_redo
          && LE_CURRENT_MODE != LE_MODE_VI_INSERT) {
      last_edit_command.command = current_command;
      last_edit_command.state = state;
    }
}

/* Saves the currently executing command and the current state in
 * `last_find_command' if we are not redoing/refinding. */
void save_current_find_command(void)
{
    if (current_command.func != cmd_refind_char
          && current_command.func != cmd_refind_char_rev
          && current_command.func != cmd_redo)
      last_find_command = current_command;
}

/* Saves the current contents of the edit line to the undo history.
 * History entries at the current `undo_index' and newer are removed before
 * saving the current. If `undo_history_entry' is different from
 * `main_history_entry', all undo history entries are removed. */
void save_undo_history(void)
{
    for (size_t i = undo_index; i < undo_history.length; i++)
      free(undo_history.contents[i]);
    pl_remove(&undo_history, undo_index, SIZE_MAX);

    struct undo_history *e = xmalloc(sizeof *e +
          (le_main_buffer.length + 1) * sizeof *e->contents);
    e->index = le_main_index;
    wcscpy(e->contents, le_main_buffer.contents);
    pl_add(&undo_history, e);
    assert(undo_index == undo_history.length - 1);
    undo_history_entry = main_history_entry;
}

/* Calls `save_undo_history' if the current contents of the edit line is not
 * saved. */
void maybe_save_undo_history(void)
{
    assert(undo_index <= undo_history.length);
    size_t save_undo_save_index = undo_save_index;
    undo_save_index = le_main_index;
    if (undo_history_entry == main_history_entry) {
      if (undo_index < undo_history.length) {
          struct undo_history *h = undo_history.contents[undo_index];
          if (wcscmp(le_main_buffer.contents, h->contents) == 0) {
            h->index = le_main_index;
            return;
          }
          undo_index++;
      }
    } else {
      if (wcscmp(le_main_buffer.contents, main_history_value) == 0)
          return;

      /* The contents of the buffer has been changed from the value of the
       * history entry, but it's not yet saved in the undo history. We first
       * save the original history value and then save the current buffer
       * contents. */
      struct undo_history *h;
      pl_clear(&undo_history, free);
      h = xmalloc(sizeof *h +
            (wcslen(main_history_value) + 1) * sizeof *h->contents);
      assert(save_undo_save_index <= wcslen(main_history_value));
      h->index = save_undo_save_index;
      wcscpy(h->contents, main_history_value);
      pl_add(&undo_history, h);
      undo_index = 1;
    }
    save_undo_history();
}

/* Applies the currently pending editing command to the range between the
 * current cursor index and the specified index. If no editing command is
 * pending, simply moves the cursor to the specified index. */
/* This function is used for all cursor-moving commands, even when not in the
 * vi mode. */
void exec_motion_command(size_t index, bool inclusive)
{
    assert(le_main_index <= le_main_buffer.length);
    assert(index <= le_main_buffer.length);

    maybe_save_undo_history();

    size_t start_index, end_index;
    if (le_main_index <= index)
      start_index = le_main_index, end_index = index;
    else
      start_index = index, end_index = le_main_index;
    if (inclusive && end_index < le_main_buffer.length)
      end_index++;

    enum motion_expect_command mec = state.pending_command_motion;
    if (mec == MEC_NONE) {
      le_main_index = index;
    } else {
      if (mec & MEC_UPPERCASE) {
          to_upper_case(le_main_buffer.contents + start_index,
                end_index - start_index);
      } else if (mec & MEC_LOWERCASE) {
          to_lower_case(le_main_buffer.contents + start_index,
                end_index - start_index);
      } else if (mec & MEC_SWITCHCASE) {
          switch_case(le_main_buffer.contents + start_index,
                end_index - start_index);
      }
      if (mec & MEC_COPY) {
          add_to_kill_ring(le_main_buffer.contents + start_index,
                end_index - start_index);
      }
      switch (mec & (MEC_TOSTART | MEC_TOEND)) {
          case MEC_TOSTART:             le_main_index = start_index;  break;
          case MEC_TOEND:               le_main_index = end_index;    break;
          case MEC_TOSTART | MEC_TOEND: le_main_index = index;        break;
      }
      if (mec & MEC_DELETE) {
          if (overwrite && index < le_main_index) {
            le_main_index = index;
          } else {
            save_current_edit_command();
            wb_remove(&le_main_buffer,
                  start_index, end_index - start_index);
            le_main_index = start_index;
          }
      }
      if (mec & MEC_INSERT) {
          le_set_mode(LE_MODE_VI_INSERT);
          overwrite = false;
      }
    }
    reset_state();
}

/* Sets the specified motion expecting command as pending.
 * If the command is already pending, the command is executed on the whole
 * line. */
void set_motion_expect_command(enum motion_expect_command cmd)
{
    if (state.pending_command_motion == MEC_NONE) {
      state.count.multiplier = get_count(1);
      state.count.sign = 0;
      state.count.abs = 0;
      state.pending_command_motion = cmd;
    } else {
      if (state.pending_command_motion == cmd)
          exec_motion_expect_command_all();
      else
          cmd_alert(L'\0');
    }
}

/* Executes the specified motion expecting command with the specified motion
 * command. */
void exec_motion_expect_command(
      enum motion_expect_command cmd, le_command_func_T motion)
{
    if (current_command.func != cmd_redo)
      ALERT_AND_RETURN_IF_PENDING;
    state.pending_command_motion = cmd;
    motion(L'\0');
}

/* Executes the specified motion expecting command from the beginning to the end
 * of the line. */
void exec_motion_expect_command_line(enum motion_expect_command cmd)
{
    if (current_command.func != cmd_redo)
      ALERT_AND_RETURN_IF_PENDING;
    state.pending_command_motion = cmd;
    exec_motion_expect_command_all();
}

/* Executes the currently pending motion expecting command from the beginning to
 * the end of the line. */
void exec_motion_expect_command_all(void)
{
    size_t save_index = le_main_index;
    enum motion_expect_command save_pending = state.pending_command_motion;

    le_main_index = 0;
    cmd_end_of_line(L'\0');
    if (!(save_pending & (MEC_DELETE | MEC_TOSTART | MEC_TOEND)))
      le_main_index = save_index;
}

/* Adds the specified string to the kill ring.
 * The maximum number of characters that are added is specified by `n'. */
void add_to_kill_ring(const wchar_t *s, size_t n)
{
    if (n > 0 && s[0] != L'\0') {
      free(kill_ring[next_kill_index]);
      kill_ring[next_kill_index] = xwcsndup(s, n);
      next_kill_index = (next_kill_index + 1) % KILL_RING_SIZE;
    }
}

/* Sets the editing mode to "char expects" and the pending command to `cmd'.
 * The current editing mode is saved in `savemode'. */
void set_char_expect_command(le_command_func_T cmd)
{
    savemode = LE_CURRENT_MODE;
    le_set_mode(LE_MODE_CHAR_EXPECT);
    state.pending_command_char = cmd;
}

/* Starts command history search by setting the editing mode to `mode' with
 * the specified direction `dir'. `mode' must be either LE_MODE_VI_SEARCH or
 * LE_MODE_EMACS_SEARCH.
 * The current editing mode is saved in `savemode'. */
void set_search_mode(le_mode_id_T mode, enum le_search_direction dir)
{
    le_complete_cleanup();

    savemode = LE_CURRENT_MODE;
    le_set_mode(mode);
    le_search_direction = dir;
    switch (mode) {
      case LE_MODE_VI_SEARCH:     le_search_type = SEARCH_VI;     break;
      case LE_MODE_EMACS_SEARCH:  le_search_type = SEARCH_EMACS;  break;
      default:                    assert(false);
    }
    wb_init(&le_search_buffer);
    update_search();
}

/* Converts the first `n' characters of string `s' to upper case.
 * The string must be at least `n' characters long. */
void to_upper_case(wchar_t *s, size_t n)
{
    for (size_t i = 0; i < n; i++)
      s[i] = towupper(s[i]);
}

/* Converts the first `n' characters of string `s' to lower case.
 * The string must be at least `n' characters long. */
void to_lower_case(wchar_t *s, size_t n)
{
    for (size_t i = 0; i < n; i++)
      s[i] = towlower(s[i]);
}

/* Switches case of the first `n' characters of string `s'.
 * The string must be at least `n' characters long. */
void switch_case(wchar_t *s, size_t n)
{
    for (size_t i = 0; i < n; i++) {
      wchar_t c = s[i];
      s[i] = iswlower(c) ? towupper(c) : towlower(c);
    }
}


/********** Basic Commands **********/

/* Does nothing. */
void cmd_noop(wchar_t c __attribute__((unused)))
{
    next_reset_completion = false;
    reset_state();
}

/* Alerts. */
void cmd_alert(wchar_t c __attribute__((unused)))
{
    lebuf_print_alert(true);
    reset_state();
}

/* Inserts the character argument into the buffer.
 * If the count is set, inserts `count' times.
 * If `overwrite' is true, overwrites the character instead of inserting. */
void cmd_self_insert(wchar_t c)
{
    ALERT_AND_RETURN_IF_PENDING;
    if (c == L'\0') {
      cmd_alert(L'\0');
      return;
    }

    int count = get_count(1);
    while (--count >= 0)
      if (overwrite && le_main_index < le_main_buffer.length)
          le_main_buffer.contents[le_main_index++] = c;
      else
          wb_ninsert_force(&le_main_buffer, le_main_index++, &c, 1);
    reset_state();
}

/* Inserts the tab character. */
void cmd_insert_tab(wchar_t c __attribute__((unused)))
{
    cmd_self_insert(L'\t');
}

/* Sets the `le_next_verbatim' flag.
 * The next character will be input to the main buffer even if it's a special
 * character. */
void cmd_expect_verbatim(wchar_t c __attribute__((unused)))
{
    le_next_verbatim = true;
}

/* Adds the specified digit to the accumulating argument. */
/* If `c' is not a digit or a hyphen, does nothing. */
void cmd_digit_argument(wchar_t c)
{
    if (L'0' <= c && c <= L'9') {
      if (state.count.abs > COUNT_ABS_MAX / 10) {
          cmd_alert(L'\0');  // argument too large
          return;
      }
      if (state.count.sign == 0)
          state.count.sign = 1;
      state.count.abs = state.count.abs * 10 + (unsigned) (c - L'0');
    } else if (c == L'-') {
      if (state.count.sign == 0)
          state.count.sign = -1;
      else
          state.count.sign = -state.count.sign;
    }

    next_reset_completion = false;
}

/* If the count is not set, moves the cursor to the beginning of the line.
 * Otherwise, adds the given digit to the count. */
void cmd_bol_or_digit(wchar_t c)
{
    if (state.count.sign == 0)
      cmd_beginning_of_line(c);
    else
      cmd_digit_argument(c);
}

/* Accepts the current line.
 * `le_editstate' is set to LE_EDITSTATE_DONE to induce lineedit to terminate.
 * If history search is currently active, the search result is accepted. If the
 * search was failing, the line is not accepted. */
void cmd_accept_line(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    if (le_search_buffer.contents == NULL) {
      le_editstate = LE_EDITSTATE_DONE;
      reset_state();
    } else {
      if (le_search_result != Histlist)
          le_editstate = LE_EDITSTATE_DONE;
      cmd_srch_accept_search(L'\0');
    }
}

/* Aborts the current line.
 * `le_editstate' is set to LE_EDITSTATE_INTERRUPTED to induce lineedit to
 * terminate. */
void cmd_abort_line(wchar_t c __attribute__((unused)))
{
    cmd_srch_abort_search(L'\0');
    le_editstate = LE_EDITSTATE_INTERRUPTED;
    reset_state();
}

/* Sets `le_editstate' to LE_EDITSTATE_ERROR.
 * Lineedit will return EOF. */
void cmd_eof(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    cmd_srch_abort_search(L'\0');
    le_editstate = LE_EDITSTATE_ERROR;
    reset_state();
}

/* If the edit line is empty, sets `le_editstate' to LE_EDITSTATE_ERROR (return
 * EOF). Otherwise, alerts. */
void cmd_eof_if_empty(wchar_t c __attribute__((unused)))
{
    if (le_main_buffer.length == 0)
      cmd_eof(L'\0');
    else
      cmd_alert(L'\0');
}

/* If the edit line is empty, sets `le_editstate' to LE_EDITSTATE_ERROR (return
 * EOF). Otherwise, deletes the character under the cursor. */
void cmd_eof_or_delete(wchar_t c __attribute__((unused)))
{
    if (le_main_buffer.length == 0)
      cmd_eof(L'\0');
    else
      cmd_delete_char(L'\0');
}

/* Inserts a hash sign ('#') at the beginning of the line and accepts the line.
 * If any count is set and the line already begins with a hash sign, the hash
 * sign is removed rather than added. The line is accepted anyway. */
void cmd_accept_with_hash(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    if (state.count.sign == 0 || le_main_buffer.contents[0] != L'#')
      wb_insert(&le_main_buffer, 0, L"#");
    else
      wb_remove(&le_main_buffer, 0, 1);
    le_main_index = 0;
    cmd_accept_line(L'\0');
}

/* Changes the editing mode to "vi insert". */
void cmd_setmode_viinsert(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();

    le_set_mode(LE_MODE_VI_INSERT);
    reset_state();
    overwrite = false;
}

/* Changes the editing mode to "vi command". */
void cmd_setmode_vicommand(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();

    if (LE_CURRENT_MODE == LE_MODE_VI_INSERT)
      if (le_main_index > 0)
          le_main_index--;
    le_set_mode(LE_MODE_VI_COMMAND);
    reset_state();
    overwrite = false;
}

/* Changes the editing mode to "emacs". */
void cmd_setmode_emacs(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();

    le_set_mode(LE_MODE_EMACS);
    reset_state();
    overwrite = false;
}

/* Executes the currently pending char-expecting command. */
void cmd_expect_char(wchar_t c)
{
    if (!state.pending_command_char) {
      cmd_alert(L'\0');
      return;
    }

    current_command.func = state.pending_command_char;
    current_command.arg = c;
    state.pending_command_char(c);
}

/* Cancels the currently pending char-expecting command. */
void cmd_abort_expect_char(wchar_t c __attribute__((unused)))
{
    if (!state.pending_command_char) {
      cmd_alert(L'\0');
      return;
    }

    le_set_mode(savemode);
    reset_state();
}

/* Redraws everything. */
void cmd_redraw_all(wchar_t c __attribute__((unused)))
{
    redraw_all(false);
}

/* Clears the screen and redraws everything at the top of the screen. */
void cmd_clear_and_redraw_all(wchar_t c __attribute__((unused)))
{
    redraw_all(true);
}

void redraw_all(bool clear)
{
    next_reset_completion = false;

    le_display_clear(clear);
    le_restore_terminal();
    le_setupterm(false);
    le_set_terminal();
}


/********** Motion Commands **********/

/* Invokes `cmd_alert' and returns true if the cursor is on the first character.
 */
bool alert_if_first(void)
{
    if (le_main_index > 0)
      return false;

    cmd_alert(L'\0');
    return true;
}

/* Invokes `cmd_alert' and returns true if the cursor is on the last character.
 */
bool alert_if_last(void)
{
    if (LE_CURRENT_MODE == LE_MODE_VI_COMMAND) {
      if (state.pending_command_motion != MEC_NONE)
          return false;
      if (le_main_buffer.length > 0
            && le_main_index < le_main_buffer.length - 1)
          return false;
    } else {
      if (le_main_index < le_main_buffer.length)
          return false;
    }

    cmd_alert(L'\0');
    return true;
}

/* Moves forward one character (or `count' characters if the count is set). */
/* exclusive motion command */
void cmd_forward_char(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_forward_char(count);
    else
      move_cursor_backward_char(-count);
}

/* Moves backward one character (or `count' characters if the count is set). */
/* exclusive motion command */
void cmd_backward_char(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_backward_char(count);
    else
      move_cursor_forward_char(-count);
}

/* Moves the cursor forward by `offset'. The `offset' must not be negative. */
void move_cursor_forward_char(int offset)
{
    assert(offset >= 0);
    if (alert_if_last())
      return;

#if COUNT_ABS_MAX > SIZE_MAX
    if (offset > SIZE_MAX)
      offset = SIZE_MAX;
#endif

    size_t new_index;
    if (le_main_buffer.length - le_main_index < (size_t) offset)
      new_index = le_main_buffer.length;
    else
      new_index = le_main_index + offset;
    exec_motion_command(new_index, false);
}

/* Moves the cursor backward by `offset'. The `offset' must not be negative. */
void move_cursor_backward_char(int offset)
{
    assert(offset >= 0);
    if (alert_if_first())
      return;

    size_t new_index;
#if COUNT_ABS_MAX > SIZE_MAX
    if ((int) le_main_index <= offset)
#else
    if (le_main_index <= (size_t) offset)
#endif
      new_index = 0;
    else
      new_index = le_main_index - offset;
    exec_motion_command(new_index, false);
}

/* Moves forward one bigword (or `count' bigwords if the count is set). */
/* exclusive motion command */
void cmd_forward_bigword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_forward_bigword(count);
    else
      move_cursor_backward_bigword(-count);
}

/* Moves the cursor to the end of the current bigword (or the next bigword if
 * already at the end). If the count is set, moves to the end of `count'th
 * bigword. */
/* inclusive motion command */
void cmd_end_of_bigword(wchar_t c __attribute__((unused)))
{
    if (alert_if_last())
      return;

    int count = get_count(1);
    size_t new_index = le_main_index;
    while (--count >= 0 && new_index < le_main_buffer.length)
      new_index = next_end_of_bigword_index(
            le_main_buffer.contents, new_index, true);
    exec_motion_command(new_index, true);
}

/* Moves backward one bigword (or `count' bigwords if the count is set). */
/* exclusive motion command */
void cmd_backward_bigword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_backward_bigword(count);
    else
      move_cursor_forward_bigword(-count);
}

/* Moves the cursor forward `count' bigwords.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_forward_bigword(int count)
{
    if (alert_if_last())
      return;

    size_t new_index = le_main_index;
    if (!need_cw_treatment()) {
      while (count-- > 0 && new_index < le_main_buffer.length)
          new_index = next_bigword_index(le_main_buffer.contents, new_index);
      exec_motion_command(new_index, false);
    } else {
      while (count > 1 && new_index < le_main_buffer.length) {
          new_index = next_bigword_index(le_main_buffer.contents, new_index);
          count--;
      }
      if (count > 0 && new_index < le_main_buffer.length) {
          new_index = next_end_of_bigword_index(
                le_main_buffer.contents, new_index, false);
      }
      exec_motion_command(new_index, true);
    }
}

/* Moves the cursor backward `count' bigwords.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_backward_bigword(int count)
{
    if (alert_if_first())
      return;

    size_t new_index = le_main_index;
    while (count-- > 0 && new_index > 0)
      new_index = previous_bigword_index(le_main_buffer.contents, new_index);
    exec_motion_command(new_index, false);
}

/* Returns the index of the next bigword in string `s', counted from index `i'.
 * The return value is greater than `i' unless `s[i]' is a null character. */
/* A bigword is a sequence of non-blank characters. */
size_t next_bigword_index(const wchar_t *s, size_t i)
{
    while (s[i] != L'\0' && !iswblank(s[i]))
      i++;
    while (s[i] != L'\0' && iswblank(s[i]))
      i++;
    return i;
}

/* Returns the index of the end of the current bigword in string `s', counted
 * from index `i'. If `i' is at the end of the bigword and `progress' is true,
 * the end of the next bigword is returned.
 * The return value is greater than `i' unless `s[i]' is a null character. */
size_t next_end_of_bigword_index(const wchar_t *s, size_t i, bool progress)
{
    const size_t init = i;
start:
    if (s[i] == L'\0')
      return i;
    while (s[i] != L'\0' && iswblank(s[i]))
      i++;
    while (s[i] != L'\0' && !iswblank(s[i]))
      i++;
    i--;
    if (i > init || !progress) {
      return i;
    } else {
      i++;
      goto start;
    }
}

/* Returns the index of the previous bigword in string `s', counted from index
 * `i'. The return value is less than `i' unless `i' is zero. */
size_t previous_bigword_index(const wchar_t *s, size_t i)
{
    const size_t init = i;
start:
    while (i > 0 && iswblank(s[i]))
      i--;
    while (i > 0 && !iswblank(s[i]))
      i--;
    if (i == 0)
      return i;
    i++;
    if (i < init) {
      return i;
    } else {
      i--;
      goto start;
    }
}

/* Moves forward one semiword (or `count' semiwords if the count is set). */
/* exclusive motion command */
void cmd_forward_semiword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_forward_semiword(count);
    else
      move_cursor_backward_semiword(-count);
}

/* Moves the cursor to the end of the current semiword (or the next semiword if
 * already at the end). If the count is set, moves to the end of the `count'th
 * semiword. */
/* inclusive motion command */
void cmd_end_of_semiword(wchar_t c __attribute__((unused)))
{
    if (alert_if_last())
      return;

    int count = get_count(1);
    size_t new_index = le_main_index;
    while (--count >= 0 && new_index < le_main_buffer.length)
      new_index = next_end_of_semiword_index(
            le_main_buffer.contents, new_index, true);
    exec_motion_command(new_index, true);
}

/* Moves backward one semiword (or `count' semiwords if the count is set). */
/* exclusive motion command */
void cmd_backward_semiword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_backward_semiword(count);
    else
      move_cursor_forward_semiword(-count);
}

/* Moves the cursor forward `count' semiwords.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_forward_semiword(int count)
{
    if (alert_if_last())
      return;

    size_t new_index = le_main_index;
    if (!need_cw_treatment()) {
      while (count-- > 0 && new_index < le_main_buffer.length)
          new_index = next_semiword_index(le_main_buffer.contents, new_index);
      exec_motion_command(new_index, false);
    } else {
      while (count > 1 && new_index < le_main_buffer.length) {
          new_index = next_semiword_index(le_main_buffer.contents, new_index);
          count--;
      }
      if (count > 0 && new_index < le_main_buffer.length) {
          new_index = next_end_of_semiword_index(
                le_main_buffer.contents, new_index, false);
      }
      exec_motion_command(new_index, true);
    }
}

/* Moves the cursor backward `count' semiwords.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_backward_semiword(int count)
{
    if (alert_if_first())
      return;

    size_t new_index = le_main_index;
    while (count-- > 0 && new_index > 0)
      new_index = previous_semiword_index(le_main_buffer.contents, new_index);
    exec_motion_command(new_index, false);
}

/* Returns the index of the next semiword in string `s', counted from index `i'.
 * The return value is greater than `i' unless `s[i]' is a null character. */
/* A "semiword" is a sequence of characters that are not <blank> or <punct>. */
size_t next_semiword_index(const wchar_t *s, size_t i)
{
    while (s[i] != L'\0' && !iswblank(s[i]) && !iswpunct(s[i]))
      i++;
    while (s[i] != L'\0' && (iswblank(s[i]) || iswpunct(s[i])))
      i++;
    return i;
}

/* Returns the index of the end of the current semiword in string `s', counted
 * from index `i'. If `i' is at the end of the semiword and `progress' is true,
 * the end of the next semiword is returned.
 * The return value is greater than `i' unless `s[i]' is a null character. */
size_t next_end_of_semiword_index(const wchar_t *s, size_t i, bool progress)
{
    const size_t init = i;
start:
    if (s[i] == L'\0')
      return i;
    while (s[i] != L'\0' && (iswblank(s[i]) || iswpunct(s[i])))
      i++;
    while (s[i] != L'\0' && !iswblank(s[i]) && !iswpunct(s[i]))
      i++;
    i--;
    if (i > init || !progress) {
      return i;
    } else {
      i++;
      goto start;
    }
}

/* Returns the index of the previous semiword in string `s', counted from index
 * `i'. The return value is less than `i' unless `i' is zero. */
size_t previous_semiword_index(const wchar_t *s, size_t i)
{
    const size_t init = i;
start:
    while (i > 0 && (iswblank(s[i]) || iswpunct(s[i])))
      i--;
    while (i > 0 && !iswblank(s[i]) && !iswpunct(s[i]))
      i--;
    if (i == 0)
      return i;
    i++;
    if (i < init) {
      return i;
    } else {
      i--;
      goto start;
    }
}

/* Moves forward one viword (or `count' viwords if the count is set). */
/* exclusive motion command */
void cmd_forward_viword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_forward_viword(count);
    else
      move_cursor_backward_viword(-count);
}

/* Moves the cursor to the end of the current viword (or the next viword if
 * already at the end). If the count is set, moves to the end of the `count'th
 * viword. */
/* inclusive motion command */
void cmd_end_of_viword(wchar_t c __attribute__((unused)))
{
    if (alert_if_last())
      return;

    int count = get_count(1);
    size_t new_index = le_main_index;
    while (--count >= 0 && new_index < le_main_buffer.length)
      new_index = next_end_of_viword_index(
            le_main_buffer.contents, new_index, true);
    exec_motion_command(new_index, true);
}

/* Moves backward one viword (or `count' viwords if the count is set). */
/* exclusive motion command */
void cmd_backward_viword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_backward_viword(count);
    else
      move_cursor_forward_viword(-count);
}

/* Moves the cursor forward `count' viwords.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_forward_viword(int count)
{
    if (alert_if_last())
      return;

    size_t new_index = le_main_index;
    if (!need_cw_treatment()) {
      while (count-- > 0 && new_index < le_main_buffer.length)
          new_index = next_viword_index(le_main_buffer.contents, new_index);
      exec_motion_command(new_index, false);
    } else {
      while (count > 1 && new_index < le_main_buffer.length) {
          new_index = next_viword_index(le_main_buffer.contents, new_index);
          count--;
      }
      if (count > 0 && new_index < le_main_buffer.length) {
          new_index = next_end_of_viword_index(
                le_main_buffer.contents, new_index, false);
      }
      exec_motion_command(new_index, true);
    }
}

/* Checks if we need a special treatment for the "cw" and "cW" commands. */
bool need_cw_treatment(void)
{
    return (state.pending_command_motion & MEC_INSERT)
      && !iswblank(le_main_buffer.contents[le_main_index]);
}

/* Moves the cursor backward `count' viwords.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_backward_viword(int count)
{
    if (alert_if_first())
      return;

    size_t new_index = le_main_index;
    while (count-- > 0 && new_index > 0)
      new_index = previous_viword_index(le_main_buffer.contents, new_index);
    exec_motion_command(new_index, false);
}

/* Returns the index of the next viword in string `s', counted from index `i'.
 * The return value is greater than `i' unless `s[i]' is a null character. */
/* A viword is a sequence either of alphanumeric characters and underscores or
 * of other non-blank characters. */
size_t next_viword_index(const wchar_t *s, size_t i)
{
    if (s[i] == L'_' || iswalnum(s[i])) {
      do
          i++;
      while (s[i] == L'_' || iswalnum(s[i]));
      if (!iswblank(s[i]))
          return i;
    } else if (!iswblank(s[i])) {
      for (;;) {
          if (s[i] == L'\0')
            return i;
          i++;
          if (s[i] == L'_' || iswalnum(s[i]))
            return i;
          if (iswblank(s[i]))
            break;
      }
    }
    /* find the first non-blank character */
    while (iswblank(s[++i]));
    return i;
}

/* Returns the index of the end of the current viword in string `s', counted
 * from index `i'.
 * If `progress' is true:
 *   If `i' is at the end of the viword, the end of the next viword is returned.
 *   The return value is greater than `i' unless `s[i]' is a null character.
 * If `progress' is false:
 *   If `i' is at the end of the viword, `i' is returned. */
size_t next_end_of_viword_index(const wchar_t *s, size_t i, bool progress)
{
    const size_t init = i;
start:
    while (iswblank(s[i]))
      i++;
    if (s[i] == L'\0')
      return i;
    if (s[i] == L'_' || iswalnum(s[i])) {
      do
          i++;
      while (s[i] == L'_' || iswalnum(s[i]));
    } else {
      do
          i++;
      while (s[i] != L'\0' && s[i] != L'_'
            && !iswblank(s[i]) && !iswalnum(s[i]));
    }
    i--;
    if (i > init || !progress) {
      return i;
    } else {
      i++;
      goto start;
    }
}

/* Returns the index of the previous viword in string `s', counted form index
 * `i'. The return value is less than `i' unless `i' is zero. */
size_t previous_viword_index(const wchar_t *s, size_t i)
{
    const size_t init = i;
start:
    while (i > 0 && iswblank(s[i]))
      i--;
    if (s[i] == L'_' || iswalnum(s[i])) {
      do {
          if (i == 0)
            return 0;
          i--;
      } while (s[i] == L'_' || iswalnum(s[i]));
    } else {
      do {
          if (i == 0)
            return 0;
          i--;
      } while (s[i] != L'_' && !iswblank(s[i]) && !iswalnum(s[i]));
    }
    i++;
    if (i < init) {
      return i;
    } else {
      i--;
      goto start;
    }
}

/* Moves to the next emacsword (or the `count'th emacsword if the count is set).
 */
/* exclusive motion command */
void cmd_forward_emacsword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_forward_emacsword(count);
    else
      move_cursor_backward_emacsword(-count);
}

/* Moves backward one emacsword (or `count' emacswords if the count is set). */
/* exclusive motion command */
void cmd_backward_emacsword(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count >= 0)
      move_cursor_backward_emacsword(count);
    else
      move_cursor_forward_emacsword(-count);
}

/* Moves the cursor to the `count'th emacsword.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_forward_emacsword(int count)
{
    size_t new_index = le_main_index;
    while (count-- > 0 && new_index < le_main_buffer.length)
      new_index = next_emacsword_index(le_main_buffer.contents, new_index);
    exec_motion_command(new_index, false);
}

/* Moves the cursor backward `count'th emacsword.
 * If `count' is negative, the cursor is not moved. */
void move_cursor_backward_emacsword(int count)
{
    size_t new_index = le_main_index;
    while (count-- > 0 && new_index > 0)
      new_index = previous_emacsword_index(
            le_main_buffer.contents, new_index);
    exec_motion_command(new_index, false);
}

/* Returns the index of the next emacsword in string `s', counted from index
 * `i'. The return value is greater than `i' unless `s[i]' is a null character.
 */
/* An emacsword is a sequence of non-alphanumeric characters. */
size_t next_emacsword_index(const wchar_t *s, size_t i)
{
    while (s[i] != L'\0' && !iswalnum(s[i]))
      i++;
    while (s[i] != L'\0' && iswalnum(s[i]))
      i++;
    return i;
}

/* Returns the index of the previous emacsword in string `s', counted from
 * index `i'. The return value is less than `i' unless `i' is zero. */
/* An emacsword is a sequence of alphanumeric characters. */
size_t previous_emacsword_index(const wchar_t *s, size_t i)
{
    const size_t init = i;
start:
    while (i > 0 && !iswalnum(s[i]))
      i--;
    while (i > 0 && iswalnum(s[i]))
      i--;
    if (i == 0)
      return i;
    i++;
    if (i < init) {
      return i;
    } else {
      i--;
      goto start;
    }
}

/* Moves the cursor to the beginning of the line. */
/* exclusive motion command */
void cmd_beginning_of_line(wchar_t c __attribute__((unused)))
{
    exec_motion_command(0, false);
}

/* Moves the cursor to the end of the line. */
/* inclusive motion command */
void cmd_end_of_line(wchar_t c __attribute__((unused)))
{
    exec_motion_command(le_main_buffer.length, true);
}

/* Moves the cursor to the `count'th character in the edit line.
 * If the count is not set, moves to the beginning of the line. If the count is
 * negative, moves to the `le_main_buffer.length + count'th character. */
/* exclusive motion command */
void cmd_go_to_column(wchar_t c __attribute__((unused)))
{
    int index = get_count(0);

    if (index >= 0) {
      if (index > 0)
          index--;
#if COUNT_ABS_MAX > SIZE_MAX
      if (index > (int) le_main_buffer.length)
#else
      if ((size_t) index > le_main_buffer.length)
#endif
          index = le_main_buffer.length;
    } else {
#if COUNT_ABS_MAX > SIZE_MAX
      if (-index > (int) le_main_buffer.length)
#else
      if ((size_t) -index > le_main_buffer.length)
#endif
          index = 0;
      else
          index = (int) le_main_buffer.length + index;
    }
    exec_motion_command((size_t) index, false);
}

/* Moves the cursor to the first non-blank character. */
/* exclusive motion command */
void cmd_first_nonblank(wchar_t c __attribute__((unused)))
{
    size_t i = 0;

    while (c = le_main_buffer.contents[i], c != L'\0' && iswblank(c))
      i++;
    exec_motion_command(i, false);
}

/* Sets the editing mode to "char expect" and the pending command to
 * `find_char'. */
void cmd_find_char(wchar_t c __attribute__((unused)))
{
    maybe_save_undo_history();
    set_char_expect_command(find_char);
}

/* Moves the cursor to the `count'th occurrence of `c' after the current
 * position. */
/* inclusive motion command */
void find_char(wchar_t c)
{
    exec_find(c, get_count(1), false);
}

/* Sets the editing mode to "char expect" and the pending command to
 * `find_char_rev'. */
void cmd_find_char_rev(wchar_t c __attribute__((unused)))
{
    maybe_save_undo_history();
    set_char_expect_command(find_char_rev);
}

/* Moves the cursor to the `count'th occurrence of `c' before the current
 * position. */
/* exclusive motion command */
void find_char_rev(wchar_t c)
{
    exec_find(c, -get_count(1), false);
}

/* Sets the editing mode to "char expect" and the pending command to
 * `till_char'. */
void cmd_till_char(wchar_t c __attribute__((unused)))
{
    maybe_save_undo_history();
    set_char_expect_command(till_char);
}

/* Moves the cursor to the character just before `count'th occurrence of `c'
 * after the current position. */
/* inclusive motion command */
void till_char(wchar_t c)
{
    exec_find(c, get_count(1), true);
}

/* Sets the editing mode to "char expect" and the pending command to
 * `till_char_rev'. */
void cmd_till_char_rev(wchar_t c __attribute__((unused)))
{
    maybe_save_undo_history();
    set_char_expect_command(till_char_rev);
}

/* Moves the cursor to the character just after `count'th occurrence of `c'
 * before the current position. */
/* exclusive motion command */
void till_char_rev(wchar_t c)
{
    exec_find(c, -get_count(1), true);
}

/* Executes the find/till command. */
void exec_find(wchar_t c, int count, bool till)
{
    le_set_mode(savemode);
    save_current_find_command();

    size_t new_index = find_nth_occurence(c, count);
    if (new_index == SIZE_MAX)
      goto error;
    if (till) {
      if (new_index >= le_main_index) {
          if (new_index == 0)
            goto error;
          new_index--;
      } else {
          if (new_index == le_main_buffer.length)
            goto error;
          new_index++;
      }
    }
    exec_motion_command(new_index, new_index >= le_main_index);
    return;

error:
    cmd_alert(L'\0');
    return;
}

/* Finds the position of the `n'th occurrence of `c' in the edit line from the
 * current position. Returns `SIZE_MAX' on failure (no such occurrence). */
size_t find_nth_occurence(wchar_t c, int n)
{
    size_t i = le_main_index;

    if (n == 0) {
      return i;
    } else if (c == L'\0') {
      return SIZE_MAX;  /* no such occurrence */
    } else if (n >= 0) {
      while (n > 0 && i < le_main_buffer.length) {
          i++;
          if (le_main_buffer.contents[i] == c)
            n--;
      }
    } else {
      while (n < 0 && i > 0) {
          i--;
          if (le_main_buffer.contents[i] == c)
            n++;
      }
    }
    if (n != 0)
      return SIZE_MAX;  /* no such occurrence */
    else
      return i;
}

/* Redoes the last find/till command. */
void cmd_refind_char(wchar_t c __attribute__((unused)))
{
    if (!last_find_command.func) {
      cmd_alert(L'\0');
      return;
    }

    last_find_command.func(last_find_command.arg);
}

/* Redoes the last find/till command in the reverse direction. */
void cmd_refind_char_rev(wchar_t c __attribute__((unused)))
{
    if (!last_find_command.func) {
      cmd_alert(L'\0');
      return;
    }

    if (state.count.sign == 0)
      state.count.sign = -1, state.count.abs = 1;
    else if (state.count.sign >= 0)
      state.count.sign = -1;
    else
      state.count.sign = 1;
    last_find_command.func(last_find_command.arg);
}


/********** Editing Commands **********/

/* Removes the character under the cursor.
 * If the count is set, `count' characters are killed. */
void cmd_delete_char(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_forward_char);
}

/* Removes the bigword after the cursor.
 * If the count is set, `count' bigwords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_delete_bigword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_forward_bigword);
}

/* Removes the semiword after the cursor.
 * If the count is set, `count' semiwords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_delete_semiword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_forward_semiword);
}

/* Removes the viword after the cursor.
 * If the count is set, `count' viwords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_delete_viword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_forward_viword);
}

/* Removes the emacsword after the cursor.
 * If the count is set, `count' emacswords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_delete_emacsword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_forward_emacsword);
}

/* Removes the character behind the cursor.
 * If the count is set, `count' characters are killed. */
void cmd_backward_delete_char(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_backward_char);
}

/* Removes the bigword behind the cursor.
 * If the count is set, `count' bigwords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_delete_bigword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_backward_bigword);
}

/* Removes the semiword behind the cursor.
 * If the count is set, `count' semiwords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_delete_semiword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_backward_semiword);
}

/* Removes the viword behind the cursor.
 * If the count is set, `count' viwords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_delete_viword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_backward_viword);
}

/* Removes the emacsword behind the cursor.
 * If the count is set, `count' emacswords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_delete_emacsword(wchar_t c __attribute__((unused)))
{
    enum motion_expect_command cmd;

    cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL;
    exec_motion_expect_command(cmd, cmd_backward_emacsword);
}

/* Removes all characters in the edit line. */
void cmd_delete_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command_line(MEC_DELETE);
}

/* Removes all characters after the cursor. */
void cmd_forward_delete_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_DELETE, cmd_end_of_line);
}

/* Removes all characters behind the cursor. */
void cmd_backward_delete_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_DELETE, cmd_beginning_of_line);
}

/* Kills the character under the cursor.
 * If the count is set, `count' characters are killed. */
void cmd_kill_char(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_forward_char);
}

/* Kills the bigword after the cursor.
 * If the count is set, `count' bigwords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_kill_bigword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_forward_bigword);
}

/* Kills the semiword after the cursor.
 * If the count is set, `count' semiwords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_kill_semiword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_forward_semiword);
}

/* Kills the viword after the cursor.
 * If the count is set, `count' viwords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_kill_viword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_forward_viword);
}

/* Kills the emacsword after the cursor.
 * If the count is set, `count' emacswords are killed.
 * If the cursor is at the end of the line, the terminal is alerted. */
void cmd_kill_emacsword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_forward_emacsword);
}

/* Kills the character behind the cursor.
 * If the count is set, `count' characters are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_kill_char(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_backward_char);
}

/* Kills the bigword behind the cursor.
 * If the count is set, `count' bigwords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_kill_bigword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_backward_bigword);
}

/* Kills the semiword behind the cursor.
 * If the count is set, `count' semiwords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_kill_semiword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_backward_semiword);
}

/* Kills the viword behind the cursor.
 * If the count is set, `count' viwords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_kill_viword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_backward_viword);
}

/* Kills the emacsword behind the cursor.
 * If the count is set, `count' emacswords are killed.
 * If the cursor is at the beginning of the line, the terminal is alerted. */
void cmd_backward_kill_emacsword(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_backward_emacsword);
}

/* Kills all characters in the edit line. */
void cmd_kill_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command_line(MEC_KILL);
}

/* Kills all characters after the cursor. */
void cmd_forward_kill_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_end_of_line);
}

/* Kills all characters before the cursor. */
void cmd_backward_kill_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_beginning_of_line);
}

/* Inserts the last-killed string before the cursor.
 * If the count is set, inserts `count' times.
 * The cursor is left on the last character inserted. */
void cmd_put_before(wchar_t c __attribute__((unused)))
{
    put_killed_string(false, true);
}

/* Inserts the last-killed string after the cursor.
 * If the count is set, inserts `count' times.
 * The cursor is left on the last character inserted. */
void cmd_put(wchar_t c __attribute__((unused)))
{
    put_killed_string(true, true);
}

/* Inserts the last-killed string before the cursor.
 * If the count is set, inserts `count' times.
 * The cursor is left after the inserted string. */
void cmd_put_left(wchar_t c __attribute__((unused)))
{
    put_killed_string(false, false);
}

/* Inserts the last-killed text at the current cursor position (`count' times).
 * If `after_cursor' is true, the text is inserted after the current cursor
 * position. Otherwise, before the current position.
 * If `cursor_on_last_char' is true, the cursor is left on the last character
 * inserted. Otherwise, the cursor is left after the inserted text. */
void put_killed_string(bool after_cursor, bool cursor_on_last_char)
{
    ALERT_AND_RETURN_IF_PENDING;
    save_current_edit_command();
    maybe_save_undo_history();

    size_t index = (next_kill_index - 1) % KILL_RING_SIZE;
    if (kill_ring[index] == NULL) {
      cmd_alert(L'\0');
      return;
    }

    insert_killed_string(after_cursor, cursor_on_last_char, index);
}

/* Inserts the killed text at the current cursor position (`count' times).
 * If `after_cursor' is true, the text is inserted after the current cursor
 * position. Otherwise, before the current position.
 * If `cursor_on_last_char' is true, the cursor is left on the last character
 * inserted. Otherwise, the cursor is left after the inserted text.
 * `index' specifies the text in the kill ring to be inserted. If the text
 * does not exist at the specified index in the kill ring, this function does
 * nothing. */
void insert_killed_string(
      bool after_cursor, bool cursor_on_last_char, size_t index)
{
    const wchar_t *s = kill_ring[index];
    if (s == NULL)
      return;

    last_put_elem = index;
    if (after_cursor && le_main_index < le_main_buffer.length)
      le_main_index++;

    size_t offset = le_main_buffer.length - le_main_index;
    for (int count = get_count(1); --count >= 0; )
      wb_insert(&le_main_buffer, le_main_index, s);
    assert(le_main_buffer.length >= offset + 1);

    last_put_range_start = le_main_index;
    le_main_index = le_main_buffer.length - offset;
    last_put_range_length = le_main_index - last_put_range_start;
    if (cursor_on_last_char)
      le_main_index--;

    reset_state();
}

/* Replaces the string just inserted by `cmd_put_left' with the previously
 * killed string. */
void cmd_put_pop(wchar_t c __attribute__((unused)))
{
    static bool last_success = false;

    ALERT_AND_RETURN_IF_PENDING;
    if ((last_command.func != cmd_put_left
          && last_command.func != cmd_put
          && last_command.func != cmd_put_before
          && (last_command.func != cmd_put_pop || !last_success))
          || kill_ring[last_put_elem] == NULL) {
      last_success = false;
      cmd_alert(L'\0');
      return;
    }
    last_success = true;
    save_current_edit_command();
    maybe_save_undo_history();

    size_t index = last_put_elem;
    do
      index = (index - 1) % KILL_RING_SIZE;
    while (kill_ring[index] == NULL);

    /* Remove the just inserted text. */
    assert(last_put_range_start <= le_main_buffer.length);
    wb_remove(&le_main_buffer, last_put_range_start, last_put_range_length);
    le_main_index = last_put_range_start;

    insert_killed_string(false, false, index);
}

/* Undoes the last editing command. */
void cmd_undo(wchar_t c __attribute__((unused)))
{
    cancel_undo(-get_count(1));
}

/* Undoes all changes to the edit line. */
void cmd_undo_all(wchar_t c __attribute__((unused)))
{
    cancel_undo(-COUNT_ABS_MAX);
}

/* Cancels the last undo. */
void cmd_cancel_undo(wchar_t c __attribute__((unused)))
{
    cancel_undo(get_count(1));
}

/* Cancels all previous undo. */
void cmd_cancel_undo_all(wchar_t c __attribute__((unused)))
{
    cancel_undo(COUNT_ABS_MAX);
}

/* Performs "undo"/"cancel undo".
 * `undo_index' is increased by `offset' and the contents of the history entry
 * of the new index is set to the edit line.
 * `offset' must be between `-COUNT_ABS_MAX' and `COUNT_ABS_MAX'. */
void cancel_undo(int offset)
{
    maybe_save_undo_history();

    if (offset < 0) {
      if (undo_index == 0)
          goto error;
#if COUNT_ABS_MAX > SIZE_MAX
      if (-offset > (int) undo_index)
#else
      if ((size_t) -offset > undo_index)
#endif
          undo_index = 0;
      else
          undo_index += offset;
    } else {
      if (undo_index + 1 >= undo_history.length)
          goto error;
#if COUNT_ABS_MAX > SIZE_MAX
      if (offset >= (int) (undo_history.length - undo_index))
#else
      if ((size_t) offset >= undo_history.length - undo_index)
#endif
          undo_index = undo_history.length - 1;
      else
          undo_index += offset;
    }

    const struct undo_history *entry = undo_history.contents[undo_index];
    wb_replace(&le_main_buffer, 0, SIZE_MAX, entry->contents, SIZE_MAX);
    assert(entry->index <= le_main_buffer.length);
    le_main_index = entry->index;

    reset_state();
    return;

error:
    cmd_alert(L'\0');
    return;
}

/* Redoes the last editing command. */
/* XXX: currently vi's "i" command cannot be redone. */
void cmd_redo(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    if (!last_edit_command.command.func) {
      cmd_alert(L'\0');
      return;
    }

    if (state.count.sign != 0)
      last_edit_command.state.count = state.count;
    state = last_edit_command.state;
    last_edit_command.command.func(last_edit_command.command.arg);
}


/********** Completion Commands **********/

/* Performs command line completion. */
void cmd_complete(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete(lecr_normal);

    reset_state();
}

/* Selects the next completion candidate.
 * If the count is set, selects the `count'th next candidate. */
void cmd_complete_next_candidate(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete_select_candidate(get_count(1));

    reset_state();
}

/* Selects the previous completion candidate.
 * If the count is set, selects the `count'th previous candidate. */
void cmd_complete_prev_candidate(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete_select_candidate(-get_count(1));

    reset_state();
}

/* Selects the first candidate in the next column.
 * If the count is set, selects that of the `count'th next column. */
void cmd_complete_next_column(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete_select_column(get_count(1));

    reset_state();
}

/* Selects the first candidate in the previous column.
 * If the count is set, selects that of the `count'th previous column. */
void cmd_complete_prev_column(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete_select_column(-get_count(1));

    reset_state();
}

/* Selects the first candidate in the next page.
 * If the count is set, selects that of the `count'th next page. */
void cmd_complete_next_page(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete_select_page(get_count(1));

    reset_state();
}

/* Selects the first candidate in the previous page.
 * If the count is set, selects that of the `count'th previous page. */
void cmd_complete_prev_page(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete_select_page(-get_count(1));

    reset_state();
}

/* Performs command line completion and
 *   * if the count is not set, list all the candidates without changing the
 *     main buffer.
 *   * if the count is set, complete the `count'th candidate. */
void cmd_complete_list(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();
    le_complete_cleanup();
    /* leave `next_reset_completion' to be true because the results of this
     * command cannot be used by succeeding completion commands. */
    // next_reset_completion = false;

    le_complete_fix_candidate(get_count(0));

    reset_state();
}

/* Performs command line completion and replaces the current word with all of
 * the generated candidates. */
void cmd_complete_all(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete(lecr_substitute_all_candidates);

    reset_state();
}

/* Performs command line completion and replaces the current word with the
 * longest common prefix of the candidates. */
void cmd_complete_max(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    le_complete(lecr_longest_common_prefix);

    reset_state();
}

/* Clears the current candidates. */
void cmd_clear_candidates(wchar_t c __attribute__((unused)))
{
    le_complete_cleanup();
}

void check_reset_completion(void)
{
    if (reset_completion) {
      maybe_save_undo_history();
      le_complete_cleanup();
      reset_completion = false;
    }
    next_reset_completion = false;
}


/********** Vi-Mode Specific Commands **********/

/* Sets the editing mode to "vi expect" and the pending command to
 * `vi_replace_char'. */
void cmd_vi_replace_char(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    set_char_expect_command(vi_replace_char);
}

/* Replaces the character under the cursor with `c'.
 * If the count is set, the `count' characters are replaced. */
void vi_replace_char(wchar_t c)
{
    save_current_edit_command();
    le_set_mode(savemode);

    if (c != L'\0') {
      int count = get_count(1);

      if (count > 0 && le_main_index < le_main_buffer.length) {
          do {
            le_main_buffer.contents[le_main_index] = c;
            count--, le_main_index++;
          } while (count > 0 && le_main_index < le_main_buffer.length);
          le_main_index--;
      }
      reset_state();
    } else {
      cmd_alert(L'\0');
    }
}

/* Moves the cursor to the beginning of the line and sets the editing mode to
 * "vi insert". */
void cmd_vi_insert_beginning(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    le_main_index = 0;
    cmd_setmode_viinsert(L'\0');
}

/* Moves the cursor forward one character and sets the editing mode to "vi
 * insert". */
void cmd_vi_append(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    if (le_main_index < le_main_buffer.length)
      le_main_index++;
    cmd_setmode_viinsert(L'\0');
}

/* Moves the cursor to the end of the line and sets the editing mode to "vi
 * insert".*/
void cmd_vi_append_to_eol(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    le_main_index = le_main_buffer.length;
    cmd_setmode_viinsert(L'\0');
}

/* Sets the editing mode to "vi insert" with the `overwrite' flag true. */
void cmd_vi_replace(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    cmd_setmode_viinsert(L'\0');
    overwrite = true;
}

/* Sets the pending command to MEC_SWITCHCASE.
 * The count multiplier is set to the current count.
 * If the pending command is already set to MEC_SWITCHCASE, the whole line is
 * switch-cased. */
void cmd_vi_switch_case(wchar_t c __attribute__((unused)))
{
    set_motion_expect_command(MEC_SWITCHCASE | MEC_TOSTART);
}

/* Switches the case of the character under the cursor and advances the cursor.
 * If the count is set, `count' characters are changed. */
void cmd_vi_switch_case_char(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_SWITCHCASE | MEC_TOEND, cmd_forward_char);
}

/* Sets the pending command to MEC_COPY.
 * The count multiplier is set to the current count.
 * If the pending command is already set to MEC_COPY, the whole line is copied
 * to the kill ring. */
void cmd_vi_yank(wchar_t c __attribute__((unused)))
{
    set_motion_expect_command(MEC_COPY);
}

/* Copies the content of the edit line from the current position to the end. */
void cmd_vi_yank_to_eol(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_COPY, cmd_end_of_line);
}

/* Sets the pending command to MEC_KILL.
 * The count multiplier is set to the current count. 
 * If the pending command is already set to MEC_KILL, the whole line is moved
 * to the kill ring. */
void cmd_vi_delete(wchar_t c __attribute__((unused)))
{
    set_motion_expect_command(MEC_KILL);
}

/* Deletes the content of the edit line from the current position to the end and
 * put it in the kill ring. */
/* cmd_vi_delete_to_eol is the same as cmd_forward_kill_line.
void cmd_vi_delete_to_eol(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_KILL, cmd_end_of_line);
}
*/

/* Sets the pending command to MEC_CHANGE.
 * The count multiplier is set to the current count. 
 * If the pending command is already set to MEC_CHANGE, the whole line is
 * deleted and the editing mode is set to "vi insert". */
void cmd_vi_change(wchar_t c __attribute__((unused)))
{
    set_motion_expect_command(MEC_CHANGE);
}

/* Deletes the content of the edit line from the current position to the end and
 * sets the editing mode to "vi insert". */
void cmd_vi_change_to_eol(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_CHANGE, cmd_end_of_line);
}

/* Deletes all the content of the edit line and sets the editing mode to "vi
 * insert". */
void cmd_vi_change_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command_line(MEC_CHANGE);
}

/* Sets the pending command to `MEC_COPYCHANGE'.
 * The count multiplier is set to the current count. 
 * If the pending command is already set to `MEC_COPYCHANGE', the whole line is
 * moved to the kill ring and the editing mode is set to "vi insert". */
void cmd_vi_yank_and_change(wchar_t c __attribute__((unused)))
{
    set_motion_expect_command(MEC_COPYCHANGE);
}

/* Deletes the content of the edit line from the current position to the end,
 * put it in the kill ring, and sets the editing mode to "vi insert". */
void cmd_vi_yank_and_change_to_eol(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_COPYCHANGE, cmd_end_of_line);
}

/* Deletes all the content of the edit line, put it in the kill ring, and sets
 * the editing mode to "vi insert". */
void cmd_vi_yank_and_change_line(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command_line(MEC_COPYCHANGE);
}

/* Kills the character under the cursor and sets the editing mode to
 * "vi insert". If the count is set, `count' characters are killed. */
void cmd_vi_substitute(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(MEC_COPYCHANGE, cmd_forward_char);
}

/* Appends a space followed by the last bigword from the newest history entry.
 * If the count is specified, the `count'th word is appended.
 * The mode is changed to vi-insert. */
void cmd_vi_append_last_bigword(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    save_current_edit_command();
    maybe_save_undo_history();

    wchar_t *lastcmd = NULL;
    int count = get_count(-1);
    if (count == 0 || histlist.count == 0)
      goto fail;

    struct xwcsrange range;
    lastcmd = malloc_mbstowcs(histlist.Newest->value);
    if (lastcmd == NULL)
      goto fail;
    if (count >= 0) {
      /* find the count'th word */
      range.start = range.end = lastcmd;
      do {
          struct xwcsrange r = get_next_bigword(range.end);
          if (r.start == r.end)
            break;
          range = r;
      } while (--count > 0 && *range.end != L'\0');
    } else {
      /* find the count'th last word */
      range.start = range.end = lastcmd + wcslen(lastcmd);
      do {
          struct xwcsrange r = get_prev_bigword(lastcmd, range.start);
          if (r.start == r.end)
            break;
          range = r;
      } while (++count < 0 && lastcmd < range.start);
    }
    assert(range.start <= range.end);
    if (range.start == range.end)
      goto fail;

    if (le_main_index < le_main_buffer.length)
      le_main_index++;
    size_t len = range.end - range.start;
    wb_ninsert_force(&le_main_buffer, le_main_index, L" ", 1);
    le_main_index += 1;
    wb_ninsert_force(&le_main_buffer, le_main_index, range.start, len);
    le_main_index += len;
    free(lastcmd);
    cmd_setmode_viinsert(L'\0');
    return;

fail:
    free(lastcmd);
    cmd_alert(L'\0');
    return;
}

struct xwcsrange get_next_bigword(const wchar_t *s)
{
    struct xwcsrange result;
    while (iswblank(*s))
      s++;
    result.start = s;
    while (*s != L'\0' && !iswblank(*s))
      s++;
    result.end = s;
    return result;
    /* result.start == result.end if no bigword found */
}

struct xwcsrange get_prev_bigword(const wchar_t *beginning, const wchar_t *s)
{
    struct xwcsrange result;
    assert(beginning <= s);
    do {
      if (beginning == s) {
          result.start = result.end = s;
          return result;
      }
    } while (iswblank(*--s));
    result.end = s + 1;
    do {
      if (beginning == s) {
          result.start = s;
          return result;
      }
    } while (!iswblank(*--s));
    result.start = s + 1;
    return result;
    /* result.start == result.end if no bigword found */
}

/* Sets the editing mode to "vi expect" and the pending command to
 * `vi_exec_alias'. */
void cmd_vi_exec_alias(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    set_char_expect_command(vi_exec_alias);
}

/* Appends the value of the alias `_c' to the pre-buffer so that the alias value
 * is interpreted as commands, where `c' in the alias name is the argument of
 * this command. */
void vi_exec_alias(wchar_t c)
{
    le_set_mode(savemode);
    state.pending_command_char = 0;

#if YASH_ENABLE_ALIAS
    wchar_t aliasname[3] = { L'_', c, L'\0', };
    const wchar_t *aliasvalue = get_alias_value(aliasname);
    if (aliasvalue) {
      char *mbaliasvalue = malloc_wcstombs(aliasvalue);
      if (mbaliasvalue) {
          append_to_prebuffer(mbaliasvalue);
          return;
      }
    }
#else
    (void) c;
#endif
    cmd_alert(L'\0');
}

/* Invokes an external command to edit the current line and accepts the result.
 * If the count is set, goes to the `count'th history entry and edit it.
 * If the editor returns a non-zero status, the line is not accepted. */
/* cf. history.c:fc_edit_and_exec_entries */
void cmd_vi_edit_and_accept(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;

    char *tempfile;
    int fd;
    FILE *f;
    pid_t cpid;
    int savelaststatus;

    if (state.count.sign != 0) {
      int num = get_count(0);
      if (num < 0)
          goto error0;
      const histentry_T *e = get_history_entry((unsigned) num);
      if (e == NULL)
          goto error0;
      go_to_history(e, SEARCH_VI);
    }
    le_complete_cleanup();
    le_suspend_readline();

    fd = create_temporary_file(&tempfile, S_IRUSR | S_IWUSR);
    if (fd < 0) {
      xerror(errno, Ngt("cannot create temporary file to edit history"));
      goto error1;
    }
    f = fdopen(fd, "w");
    if (f == NULL) {
      xerror(errno, Ngt("cannot open temporary file to edit history"));
      xclose(fd);
      goto error2;
    }

    savelaststatus = laststatus;
    cpid = fork_and_reset(0, true, 0);
    if (cpid < 0) { // fork failed
      xerror(0, Ngt("cannot invoke editor to edit history"));
      fclose(f);
      if (unlink(tempfile) < 0)
          xerror(errno, Ngt("cannot remove temporary file `%s'"), tempfile);
error2:
      free(tempfile);
error1:
      le_resume_readline();
error0:
      cmd_alert(L'\0');
    } else if (cpid > 0) {  // parent process
      fclose(f);

      wchar_t **namep = wait_for_child(cpid,
            doing_job_control_now ? cpid : 0,
            doing_job_control_now);
      if (namep)
          *namep = malloc_wprintf(L"vi %s", tempfile);
      if (laststatus != Exit_SUCCESS)
          goto end;

      f = fopen(tempfile, "r");
      if (f == NULL) {
          cmd_alert(L'\0');
      } else {
          wint_t c;

          wb_clear(&le_main_buffer);
          while ((c = fgetwc(f)) != WEOF)
            wb_wccat(&le_main_buffer, (wchar_t) c);
          fclose(f);

          /* remove trailing newline */
          while (le_main_buffer.length > 0 &&
                le_main_buffer.contents[le_main_buffer.length - 1] == L'\n')
            wb_remove(&le_main_buffer, le_main_buffer.length - 1, 1);

          le_main_index = le_main_buffer.length;

          le_editstate = LE_EDITSTATE_DONE;
end:
          reset_state();
      }

      laststatus = savelaststatus;
      unlink(tempfile);
      free(tempfile);
      if (shopt_notify || shopt_notifyle)
          print_job_status_all();
      le_resume_readline();
    } else {  // child process
      fwprintf(f, L"%ls\n", le_main_buffer.contents);
      fclose(f);

      wchar_t *command = malloc_wprintf(L"vi %s", tempfile);
      free(tempfile);
      exec_wcs(command, gt("lineedit"), true);
#ifndef NDEBUG
      free(command);
#endif
      assert(false);
    }
}

/* Performs command line completion and
 *   * if the count is not set, list all the candidates without changing the
 *     main buffer.
 *   * if the count is set, complete the `count'th candidate and set the mode
 *     to vi-insert. */
void cmd_vi_complete_list(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();
    le_complete_cleanup();
    /* leave `next_reset_completion' to be true because the results of this
     * command cannot be used by succeeding completion commands. */
    // next_reset_completion = false;

    size_t oldindex = le_main_index;
    if (le_main_index < le_main_buffer.length)
      le_main_index++;

    if (le_complete_fix_candidate(get_count(0))) {
      cmd_setmode_viinsert(L'\0');
    } else {
      le_main_index = oldindex;
      reset_state();
    }
}

/* Performs command line completion and replaces the current word with all of
 * the generated candidates.
 * The mode is changed to vi-insert. */
void cmd_vi_complete_all(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    if (le_main_index < le_main_buffer.length)
      le_main_index++;
    le_complete(lecr_substitute_all_candidates);

    cmd_setmode_viinsert(L'\0');
}

/* Performs command line completion and replaces the current word with the
 * longest common prefix of the candidates.
 * The mode is changed to vi-insert. */
void cmd_vi_complete_max(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    check_reset_completion();

    if (le_main_index < le_main_buffer.length)
      le_main_index++;
    le_complete(lecr_longest_common_prefix);

    cmd_setmode_viinsert(L'\0');
}

/* Starts vi-like command history search in the forward direction. */
void cmd_vi_search_forward(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    set_search_mode(LE_MODE_VI_SEARCH, FORWARD);
}

/* Starts vi-like command history search in the backward direction. */
void cmd_vi_search_backward(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    set_search_mode(LE_MODE_VI_SEARCH, BACKWARD);
}


/********** Emacs-Mode Specific Commands **********/

/* Moves the character before the cursor to the right by `count' characters. */
void cmd_emacs_transpose_chars(wchar_t c __attribute__((unused)))
{
    //ALERT_AND_RETURN_IF_PENDING;
    if (state.pending_command_motion != MEC_NONE || le_main_index == 0)
      goto error;
    maybe_save_undo_history();
    if (state.count.sign == 0
          && le_main_index == le_main_buffer.length && le_main_index >= 2)
      le_main_index--;

    int count = get_count(1);
    size_t index;

    if (count >= 0) {
#if COUNT_ABS_MAX > SIZE_MAX
      if (count <= (int) (le_main_buffer.length - le_main_index))
#else
      if ((size_t) count <= le_main_buffer.length - le_main_index)
#endif
          index = le_main_index + (size_t) count;
      else {
          le_main_index = le_main_buffer.length;
          goto error;
      }
    } else {
#if COUNT_ABS_MAX > SIZE_MAX
      if (-count < (int) le_main_index)
#else
      if ((size_t) -count < le_main_index)
#endif
          index = le_main_index + (size_t) count;
      else {
          le_main_index = 0;
          goto error;
      }
    }

    size_t old_index = le_main_index;

    assert(le_main_index > 0 && count > 0);
    assert(0 < index && index <= le_main_buffer.length);
    c = le_main_buffer.contents[old_index - 1];
    wb_remove(&le_main_buffer, old_index - 1, 1);
    wb_ninsert(&le_main_buffer, index - 1, &c, 1);
    le_main_index = index;
    if (index > old_index)
      index = old_index;
    reset_state();
    return;

error:
    cmd_alert(L'\0');
}

/* Interchanges the word before the cursor and the `count' words after the
 * cursor. */
void cmd_emacs_transpose_words(wchar_t c __attribute__((unused)))
{
    if (state.pending_command_motion != MEC_NONE || le_main_index == 0)
      goto error;
    maybe_save_undo_history();

    int count = get_count(1);
    size_t w1start, w1end, w2start, w2end, new_index;
    xwcsbuf_T buf;

    if (count == 0)
      goto end;

    w1start = previous_emacsword_index(le_main_buffer.contents, le_main_index);
    w1end = next_emacsword_index(le_main_buffer.contents, w1start);
    if (count >= 0) {
      w2end = next_emacsword_index(le_main_buffer.contents, w1end);
      w2start = previous_emacsword_index(le_main_buffer.contents, w2end);
      while (--count > 0) {
          if (w2end == le_main_buffer.length)
            goto error;
          w2end = next_emacsword_index(le_main_buffer.contents, w2end);
      }
      new_index = w2end;
    } else {
      w2start = w1start, w2end = w1end;
      w1start = previous_emacsword_index(le_main_buffer.contents, w2start);
      w1end = next_emacsword_index(le_main_buffer.contents, w1start);
      while (++count < 0) {
          if (w1start == 0)
            goto error;
          w1start = previous_emacsword_index(
                le_main_buffer.contents, w1start);
      }
      new_index = w1start + (w2end - w2start);
    }
    if (w1end >= w2start)
      goto error;
    wb_init(&buf);
    wb_ncat_force(&buf, le_main_buffer.contents + w2start, w2end - w2start);
    wb_ncat_force(&buf, le_main_buffer.contents + w1end, w2start - w1end);
    wb_ncat_force(&buf, le_main_buffer.contents + w1start, w1end - w1start);
    assert(buf.length == w2end - w1start);
    wb_replace_force(&le_main_buffer, w1start, buf.length,
          buf.contents, buf.length);
    wb_destroy(&buf);
    le_main_index = new_index;
end:
    reset_state();
    return;

error:
    cmd_alert(L'\0');
}

/* Converts the word after the cursor to lower case.
 * If the count is set, `count' words are converted.
 * The cursor is left after the last converted word. */
void cmd_emacs_downcase_word(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(
          MEC_LOWERCASE | MEC_TOEND, cmd_forward_emacsword);
}

/* Converts `count' words after the cursor to upper case.
 * If the count is set, `count' words are converted.
 * The cursor is left after the last converted word. */
void cmd_emacs_upcase_word(wchar_t c __attribute__((unused)))
{
    exec_motion_expect_command(
          MEC_UPPERCASE | MEC_TOEND, cmd_forward_emacsword);
}

/* Capitalizes the word after the cursor.
 * If the count is set, `count' words are capitalized.
 * The cursor is left after the last capitalized word. */
void cmd_emacs_capitalize_word(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();

    int count = get_count(1);

    if (count > 0) {
      wchar_t *s = le_main_buffer.contents + le_main_index;
      do {
          while (*s != L'\0' && !iswalnum(*s))
            s++;
          *s = towupper(*s);
          s++;
          while (*s != L'\0' && iswalnum(*s))
            s++;
      } while (*s != L'\0' && --count > 0);
      le_main_index = s - le_main_buffer.contents;
    } else if (count < 0) {
      size_t index = le_main_index;
      do {
          index = previous_emacsword_index(le_main_buffer.contents, index);
          le_main_buffer.contents[index] =
            towupper(le_main_buffer.contents[index]);
      } while (index > 0 && ++count < 0);
    }
    reset_state();
}

/* Deletes blank characters around the cursor.
 * If the count is set, only blanks before the cursor are deleted. */
void cmd_emacs_delete_horizontal_space(wchar_t c __attribute__((unused)))
{
    replace_horizontal_space(state.count.sign == 0, L"");
}

/* Replaces blank characters around the cursor with a space.
 * The cursor is left after the space.
 * A space is inserted even if there are no blanks around the cursor.
 * If the count is specified, blanks are replaced with `count' spaces. */
void cmd_emacs_just_one_space(wchar_t c __attribute__((unused)))
{
    int count = get_count(1);
    if (count < 0)
      count = 0;
    else if (count > 1000)
      count = 1000;

    wchar_t s[count + 1];
    wmemset(s, L' ', count);
    s[count] = L'\0';

    replace_horizontal_space(true, s);
}

/* Replaces blank characters around the cursor with the specified string.
 * If `deleteafter' is true, blanks after the cursor are replaced as well as
 * blanks before the cursor. If `deleteafter' is false, only blanks before the
 * cursor are replaced.
 * The cursor is left after the replacement. */
void replace_horizontal_space(bool deleteafter, const wchar_t *s)
{
    ALERT_AND_RETURN_IF_PENDING;
    maybe_save_undo_history();

    size_t start_index = le_main_index;
    while (start_index > 0
          && iswblank(le_main_buffer.contents[start_index - 1]))
      start_index--;

    size_t end_index = le_main_index;
    if (deleteafter)
      while (end_index < le_main_buffer.length
            && iswblank(le_main_buffer.contents[end_index]))
          end_index++;

    size_t slen = wcslen(s);

    wb_replace_force(&le_main_buffer, start_index, end_index - start_index,
          s, slen);
    le_main_index = start_index + slen;
    reset_state();
}

/* Starts emacs-like command history search in the forward direction. */
void cmd_emacs_search_forward(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    set_search_mode(LE_MODE_EMACS_SEARCH, FORWARD);
}

/* Starts emacs-like command history search in the backward direction. */
void cmd_emacs_search_backward(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    set_search_mode(LE_MODE_EMACS_SEARCH, BACKWARD);
}


/********** History-Related Commands **********/

/* Goes to the oldest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor position is not changed. */
void cmd_oldest_history(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(histlist.Oldest, SEARCH_PREFIX);
}

/* Goes to the newest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor position is not changed. */
void cmd_newest_history(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(histlist.Newest, SEARCH_PREFIX);
}

/* Goes to the newest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor position is not changed. */
void cmd_return_history(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(Histlist, SEARCH_PREFIX);
}

/* Goes to the oldest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor is put at the beginning of line. */
void cmd_oldest_history_bol(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(histlist.Oldest, SEARCH_VI);
}

/* Goes to the newest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor is put at the beginning of line. */
void cmd_newest_history_bol(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(histlist.Newest, SEARCH_VI);
}

/* Goes to the newest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor is put at the beginning of line. */
void cmd_return_history_bol(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(Histlist, SEARCH_VI);
}

/* Goes to the oldest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor is put at the end of line. */
void cmd_oldest_history_eol(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(histlist.Oldest, SEARCH_EMACS);
}

/* Goes to the newest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor is put at the end of line. */
void cmd_newest_history_eol(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(histlist.Newest, SEARCH_EMACS);
}

/* Goes to the newest history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * The cursor is put at the end of line. */
void cmd_return_history_eol(wchar_t c __attribute__((unused)))
{
    go_to_history_absolute(Histlist, SEARCH_EMACS);
}

/* Goes to the specified history entry.
 * If the count is specified, goes to the history entry whose number is count.
 * If the specified entry is not found, the terminal is alerted.
 * See `go_to_history' for the meaning of `curpos'. */
void go_to_history_absolute(const histentry_T *e, enum le_search_type curpos)
{
    ALERT_AND_RETURN_IF_PENDING;

    if (state.count.sign == 0) {
      if (histlist.count == 0)
          goto alert;
    } else {
      int num = get_count(0);
      if (num <= 0)
          goto alert;
      e = get_history_entry((unsigned) num);
      if (e == NULL)
          goto alert;
    }
    go_to_history(e, curpos);
    reset_state();
    return;

alert:
    cmd_alert(L'\0');
    return;
}

/* Goes to the `count'th next history entry.
 * The cursor position is not changed. */
void cmd_next_history(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    go_to_history_relative(get_count(1), SEARCH_PREFIX);
}

/* Goes to the `count'th previous history entry.
 * The cursor position is not changed. */
void cmd_prev_history(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    go_to_history_relative(-get_count(1), SEARCH_PREFIX);
}

/* Goes to the `count'th next history entry.
 * The cursor is put at the beginning of line. */
void cmd_next_history_bol(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    go_to_history_relative(get_count(1), SEARCH_VI);
}

/* Goes to the `count'th previous history entry.
 * The cursor is put at the beginning of line. */
void cmd_prev_history_bol(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    go_to_history_relative(-get_count(1), SEARCH_VI);
}

/* Goes to the `count'th next history entry.
 * The cursor is put at the end of line. */
void cmd_next_history_eol(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    go_to_history_relative(get_count(1), SEARCH_EMACS);
}

/* Goes to the `count'th previous history entry.
 * The cursor is put at the end of line. */
void cmd_prev_history_eol(wchar_t c __attribute__((unused)))
{
    ALERT_AND_RETURN_IF_PENDING;
    go_to_history_relative(-get_count(1), SEARCH_EMACS);
}

/* Goes to the `offset'th next history entry.
 * See `go_to_history' for the meaning of `curpos'. */
void go_to_history_relative(int offset, enum le_search_type curpos)
{
    const histentry_T *e = main_history_entry;
    if (offset > 0) {
      do {
          if (e == Histlist)
            goto alert;
          e = e->Next;
      } while (--offset > 0);
    } else if (offset < 0) {
      do {
          e = e->Prev;
          if (e == Histlist)
            goto alert;
      } while (++offset < 0);
    }
    go_to_history(e, curpos);
    reset_state();
    return;

alert:
    cmd_alert(L'\0');
    return;
}

/* Sets the value of the specified history entry to the main buffer.
 * The value of `curpos' specifies where the cursor is left:
 *  SEARCH_PREFIX: the current position (unless it exceeds the buffer length)
 *  SEARCH_VI:     the beginning of the buffer
 *  SEARCH_EMACS:  the end of the buffer */
void go_to_history(const histentry_T *e, enum le_search_type curpos)
{
    maybe_save_undo_history();

    free(main_history_value);
    wb_clear(&le_main_buffer);
    if (e == undo_history_entry && undo_index < undo_history.length) {
      struct undo_history *h = undo_history.contents[undo_index];
      wb_cat(&le_main_buffer, h->contents);
      assert(h->index <= le_main_buffer.length);
      le_main_index = h->index;
    } else {
      if (e != Histlist)
          wb_mbscat(&le_main_buffer, e->value);
      switch (curpos) {
          case SEARCH_PREFIX:
            if (le_main_index > le_main_buffer.length)
                le_main_index = le_main_buffer.length;
            break;
          case SEARCH_VI:
            le_main_index = 0;
            break;
          case SEARCH_EMACS:
            le_main_index = le_main_buffer.length;
            break;
      }
    }
    main_history_entry = e;
    main_history_value = xwcsdup(le_main_buffer.contents);
    undo_save_index = le_main_index;
}

/***** History Search Commands *****/

/* Appends the argument character to the search buffer. */
void cmd_srch_self_insert(wchar_t c)
{
    if (le_search_buffer.contents == NULL || c == L'\0') {
      cmd_alert(L'\0');
      return;
    }

    wb_wccat(&le_search_buffer, c);
    update_search();
}

/* Removes the last character from the search buffer.
 * If there are no characters in the buffer, calls `cmd_srch_abort_search' (for
 * vi-like search) or `cmd_alert' (for emacs-like search). */
void cmd_srch_backward_delete_char(wchar_t c __attribute__((unused)))
{
    if (le_search_buffer.contents == NULL) {
      cmd_alert(L'\0');
      return;
    }

    if (le_search_buffer.length == 0) {
      switch (le_search_type) {
          case SEARCH_VI:
            cmd_srch_abort_search(L'\0');
            return;
          case SEARCH_EMACS:
            cmd_alert(L'\0');
            return;
          default:
            assert(false);
      }
    }

    wb_remove(&le_search_buffer, le_search_buffer.length - 1, 1);
    update_search();
}

/* Removes all characters from the search buffer. */
void cmd_srch_backward_delete_line(wchar_t c __attribute__((unused)))
{
    if (le_search_buffer.contents == NULL) {
      cmd_alert(L'\0');
      return;
    }

    wb_clear(&le_search_buffer);
    update_search();
}

/* Settles the current search result and continues the search with the current
 * pattern in the forward direction. This is like `cmd_search_again_forward'
 * but this command can be used during the search. */
void cmd_srch_continue_forward(wchar_t c __attribute__((unused)))
{
    if (le_search_buffer.contents == NULL) {
      cmd_alert(L'\0');
      return;
    }

    le_search_direction = FORWARD;
    if (le_search_result != Histlist)
      go_to_history(le_search_result, le_search_type);
    update_search();
}

/* Settles the current search result and continues the search with the current
 * pattern in the backward direction. This is like `cmd_search_again_backward'
 * but this command can be used during the search. */
void cmd_srch_continue_backward(wchar_t c __attribute__((unused)))
{
    if (le_search_buffer.contents == NULL) {
      cmd_alert(L'\0');
      return;
    }

    le_search_direction = BACKWARD;
    if (le_search_result != Histlist)
      go_to_history(le_search_result, le_search_type);
    update_search();
}

/* Finishes the history search with the current result candidate.
 * If no search is being performed, does nothing. */
void cmd_srch_accept_search(wchar_t c __attribute__((unused)))
{
    if (le_search_buffer.contents == NULL)
      return;

    last_search.direction = le_search_direction;
    last_search.type = le_search_type;
    if (need_update_last_search_value()) {
      free(last_search.value);
      last_search.value = wb_towcs(&le_search_buffer);
    } else {
      wb_destroy(&le_search_buffer);
    }
    le_search_buffer.contents = NULL;
    le_set_mode(savemode);
    if (le_search_result == Histlist) {
      cmd_alert(L'\0');
    } else {
      go_to_history(le_search_result, le_search_type);
    }
    reset_state();
}

/* Checks if we should update `last_search.value' to the current value of
 * `le_search_buffer'. */
bool need_update_last_search_value(void)
{
    switch (le_search_type) {
      case SEARCH_PREFIX:
          break;
      case SEARCH_VI:
          if (le_search_buffer.contents[0] == L'\0')
            return false;
          if (le_search_buffer.contents[0] == L'^'
                && le_search_buffer.contents[1] == L'\0')
            return false;
          return true;
      case SEARCH_EMACS:
          return le_search_buffer.contents[0] != L'\0';
    }
    assert(false);
}

/* Aborts the history search.
 * If no search is being performed, does nothing. */
void cmd_srch_abort_search(wchar_t c __attribute__((unused)))
{
    if (le_search_buffer.contents == NULL)
      return;

    wb_destroy(&le_search_buffer);
    le_search_buffer.contents = NULL;
    le_set_mode(savemode);
    reset_state();
}

/* Re-calculates the search result candidate. */
void update_search(void)
{
    const wchar_t *pattern = le_search_buffer.contents;
    if (pattern[0] == L'\0') {
      switch (le_search_type) {
          case SEARCH_PREFIX:
            break;
          case SEARCH_VI:
            pattern = last_search.value;
            if (pattern == NULL) {
                le_search_result = Histlist;
                goto done;
            }
            break;
          case SEARCH_EMACS:
            le_search_result = Histlist;
            goto done;
      }
    }

    perform_search(pattern, le_search_direction, le_search_type);
done:
    reset_state();
}

/* Performs history search with the given parameters and updates the result
 * candidate. */
void perform_search(const wchar_t *pattern,
      enum le_search_direction dir, enum le_search_type type)
{
    const histentry_T *e = main_history_entry;
    xfnmatch_T *xfnm;

    if (dir == FORWARD && e == Histlist)
      goto done;

    switch (type) {
      case SEARCH_PREFIX: {
          wchar_t *p = escape(pattern, NULL);
          xfnm = xfnm_compile(p, XFNM_HEADONLY);
          free(p);
          break;
      }
      case SEARCH_VI: {
          xfnmflags_T flags = 0;
          if (pattern[0] == L'^') {
            flags |= XFNM_HEADONLY;
            pattern++;
            if (pattern[0] == L'\0') {
                e = Histlist;
                goto done;
            }
          }
          xfnm = xfnm_compile(pattern, flags);
          break;
      }
      case SEARCH_EMACS: {
          wchar_t *p = escape(pattern, NULL);
          xfnm = xfnm_compile(p, 0);
          free(p);
          break;
      }
      default:
          assert(false);
    }
    if (xfnm == NULL) {
      e = Histlist;
      goto done;
    }

    for (;;) {
      switch (dir) {
          case FORWARD:   e = e->Next;  break;
          case BACKWARD:  e = e->Prev;  break;
      }
      if (e == Histlist)
          break;
      if (xfnm_match(xfnm, e->value) == 0)
          break;
    }
    xfnm_free(xfnm);
done:
    le_search_result = e;
}

/* Redoes the last search. */
void cmd_search_again(wchar_t c __attribute__((unused)))
{
    search_again(last_search.direction);
}

/* Redoes the last search in the reverse direction. */
void cmd_search_again_rev(wchar_t c __attribute__((unused)))
{
    switch (last_search.direction) {
      case FORWARD:   search_again(BACKWARD);  break;
      case BACKWARD:  search_again(FORWARD);   break;
    }
}

/* Redoes the last search in the forward direction. */
void cmd_search_again_forward(wchar_t c __attribute__((unused)))
{
    search_again(FORWARD);
}

/* Redoes the last search in the backward direction. */
void cmd_search_again_backward(wchar_t c __attribute__((unused)))
{
    search_again(BACKWARD);
}

/* Performs command search for the last search pattern in the specified
 * direction. If the count is set, re-search `count' times. If the count is
 * negative, search in the opposite direction. */
void search_again(enum le_search_direction dir)
{
    ALERT_AND_RETURN_IF_PENDING;

    if (last_search.value == NULL) {
      cmd_alert(L'\0');
      return;
    }

    int count = get_count(1);
    if (count < 0) {
      count = -count;
      switch (dir) {
          case FORWARD:  dir = BACKWARD; break;
          case BACKWARD: dir = FORWARD;  break;
      }
    }

    while (--count >= 0) {
      perform_search(last_search.value, dir, last_search.type);
      if (le_search_result == Histlist) {
          cmd_alert(L'\0');
          break;
      } else {
          go_to_history(le_search_result, last_search.type);
      }
    }
    reset_state();
}

/* Searches the history in the forward direction for an entry that has a common
 * prefix with the current buffer contents. The "prefix" is the first
 * `le_main_index' characters of the buffer. */
void cmd_beginning_search_forward(wchar_t c __attribute__((unused)))
{
    beginning_search(FORWARD);
}

/* Searches the history in the backward direction for an entry that has a common
 * prefix with the current buffer contents. The "prefix" is the first
 * `le_main_index' characters of the buffer. */
void cmd_beginning_search_backward(wchar_t c __attribute__((unused)))
{
    beginning_search(BACKWARD);
}

/* Searches the history in the specified direction for an entry that has a
 * common prefix with the current buffer contents. The "prefix" is the first
 * `le_main_index' characters of the buffer. */
void beginning_search(enum le_search_direction dir)
{
    ALERT_AND_RETURN_IF_PENDING;

    int count = get_count(1);
    if (count < 0) {
      count = -count;
      switch (dir) {
          case FORWARD:  dir = BACKWARD; break;
          case BACKWARD: dir = FORWARD;  break;
      }
    }

    wchar_t *prefix = xwcsndup(le_main_buffer.contents, le_main_index);
    while (--count >= 0) {
      perform_search(prefix, dir, SEARCH_PREFIX);
      if (le_search_result == Histlist) {
          if (dir == FORWARD && beginning_search_check_go_to_history(prefix))
            go_to_history(le_search_result, SEARCH_PREFIX);
          else
            cmd_alert(L'\0');
          break;
      } else {
          go_to_history(le_search_result, SEARCH_PREFIX);
      }
    }
    free(prefix);
    reset_state();
}

bool beginning_search_check_go_to_history(const wchar_t *prefix)
{
    if (le_search_result == undo_history_entry
          && undo_index < undo_history.length) {
      const struct undo_history *h = undo_history.contents[undo_index];
      return matchwcsprefix(h->contents, prefix);
    } else {
      return false;
    }
}


/* vim: set ts=8 sts=4 sw=4 noet tw=80: */

Generated by  Doxygen 1.6.0   Back to index