ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 865902c9561f792f454c3825f5dddbb88365802d
parent ef274ec6dc1e799a3e33b7dc203cded67160752e
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 27 May 2022 23:19:11 +0200

Improve manpages and make some commands more consistent

Diffstat:
MREADME | 8+++++---
Mbuffer.c | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mbuffer.h | 30++++++++++++++++++++++++++++++
Mkeys_basic.c | 614++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mkeys_command.c | 27+++++++++++++++++----------
Mkeys_config.h | 26+++++++++++++-------------
Mledit.1 | 778++-----------------------------------------------------------------------------
Mleditrc.5 | 723++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mleditrc.example | 29++++++++++++++++-------------
Mview.c | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mview.h | 34+++++++++++++++++++++++++---------
Mwindow.c | 1+
12 files changed, 1237 insertions(+), 1170 deletions(-)

diff --git a/README b/README @@ -23,6 +23,9 @@ To compile with fsanitize=address, run 'make SANITIZE=1' A sample configuration file can be found in leditrc.example. Copy this to ~/.leditrc in order to use it. +Note: If you think important key bindings are missing in the default +configuration, let me know. It definitely isn't set in stone yet. + The documentation can be viewed in ledit.1 and leditrc.5 or at the following locations: gopher://lumidify.org/0/doc/ledit/ledit-current.txt @@ -37,9 +40,8 @@ http://lumidify.org/doc/ledit/leditrc-current.html http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledit/leditrc-current.html https://lumidify.org/doc/ledit/leditrc-current.html -Note that the documentation is far from finished! None of the functions are -documented yet in leditrc.5 and ledit.1 is somewhat outdated. This will -be fixed sometime... +Note that the documentation isn't particularly great. +This might be fixed sometime in the future. Also note that nothing is stable at the moment. In particular, some of the function names mentioned in leditrc.5 will probably be changed still. diff --git a/buffer.c b/buffer.c @@ -820,7 +820,7 @@ buffer_copy_text_to_txtbuf( size_t line_prev_utf8(ledit_line *line, size_t index) { - if (index <= 0) + if (index == 0) return 0; size_t i = index - 1; /* find valid utf8 char - this probably needs to be improved */ @@ -840,8 +840,7 @@ line_next_utf8(ledit_line *line, size_t index) { } /* Warning: this is very inefficient! */ -/* FIXME: at least attempt to be more efficient by starting from the beginning - or end based on approximately where in the line the byte is */ +/* FIXME: Any way to optimize this? */ size_t line_byte_to_char(ledit_line *line, size_t byte) { size_t c = 0; @@ -854,6 +853,70 @@ line_byte_to_char(ledit_line *line, size_t byte) { return c; } +/* FIXME: It might make more sense to add a flag to view_{next,prev}_char_pos + since this is essentially the same, just without the check for is_cursor_position */ +void +buffer_next_char_pos( + ledit_buffer *buffer, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret) { + ledit_line *ll = buffer_get_line(buffer, line); + size_t c = line_byte_to_char(ll, byte); + size_t cur_byte = byte; + for (int i = 0; i < num; i++) { + if (cur_byte >= ll->len) { + if (multiline && line < buffer->lines_num - 1) { + line++; + ll = buffer_get_line(buffer, line); + c = 0; + cur_byte = 0; + i++; + continue; + } else { + break; + } + } + cur_byte = line_next_utf8(ll, cur_byte); + c++; + } + if (line_ret) + *line_ret = line; + if (byte_ret) + *byte_ret = cur_byte <= ll->len ? cur_byte : ll->len; +} + +void +buffer_prev_char_pos( + ledit_buffer *buffer, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret) { + ledit_line *ll = buffer_get_line(buffer, line); + size_t c = line_byte_to_char(ll, byte); + size_t cur_byte = byte; + for (int i = 0; i < num; i++) { + if (cur_byte == 0) { + if (multiline && line > 0) { + line--; + ll = buffer_get_line(buffer, line); + c = line_byte_to_char(ll, ll->len); + cur_byte = ll->len; + i++; + continue; + } else { + break; + } + } + cur_byte = line_prev_utf8(ll, cur_byte); + c--; + } + if (line_ret) + *line_ret = line; + if (byte_ret) + *byte_ret = cur_byte; +} + static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) { ledit_line *l = buffer_get_line(buffer, line); diff --git a/buffer.h b/buffer.h @@ -199,6 +199,36 @@ size_t line_prev_utf8(ledit_line *line, size_t index); size_t line_byte_to_char(ledit_line *line, size_t byte); /* + * Get the line and byte position of the unicode character position 'num' + * positions after the position at line 'line' and byte position 'byte'. + * The new position is returned in 'line_ret' and 'byte_ret'. + * If multiline is non-zero, the new position may be on a different line. + * Otherwise, it is at most the length of the given line. + * A newline counts as one position. + */ +void buffer_next_char_pos( + ledit_buffer *buffer, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret +); + +/* + * Get the line and byte position of the unicode character position 'num' + * positions before the position at line 'line' and byte position 'byte'. + * The new position is returned in 'line_ret' and 'byte_ret'. + * If multiline is non-zero, the new position may be on a different line. + * Otherwise, it is never earlier than position 0 on the given line. + * A newline counts as one position. + */ +void buffer_prev_char_pos( + ledit_buffer *buffer, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret +); + +/* * Insert a mark with key 'mark' (which has byte length 'len') at line 'line' and byte 'byte'. */ void buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte); diff --git a/keys_basic.c b/keys_basic.c @@ -1,3 +1,4 @@ +/* FIXME: should motion callbacks be ignored in visual mode as they currently are? */ /* FIXME: check allowed modes at beginning of functions */ /* FIXME: the stacks here are shared for all views which can cause weird behavior, but I'm not sure what would be more logical */ @@ -45,14 +46,12 @@ * Declarations for all functions that can be used in the configuration. * *************************************************************************/ -static struct action backspace(ledit_view *view, char *text, size_t len); static struct action cursor_left(ledit_view *view, char *text, size_t len); static struct action cursor_right(ledit_view *view, char *text, size_t len); static struct action cursor_up(ledit_view *view, char *text, size_t len); static struct action cursor_down(ledit_view *view, char *text, size_t len); -static struct action return_key(ledit_view *view, char *text, size_t len); -static struct action delete_key(ledit_view *view, char *text, size_t len); -static struct action escape_key(ledit_view *view, char *text, size_t len); +static struct action break_line(ledit_view *view, char *text, size_t len); +static struct action return_to_normal(ledit_view *view, char *text, size_t len); static struct action enter_insert(ledit_view *view, char *text, size_t len); static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len); static struct action key_0(ledit_view *view, char *text, size_t len); @@ -92,7 +91,7 @@ static struct action paste_normal(ledit_view *view, char *text, size_t len); static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len); static struct action change(ledit_view *view, char *text, size_t len); static struct action move_to_eol(ledit_view *view, char *text, size_t len); -static struct action mark_line(ledit_view *view, char *text, size_t len); +static struct action insert_mark(ledit_view *view, char *text, size_t len); static struct action jump_to_mark(ledit_view *view, char *text, size_t len); static struct action next_word(ledit_view *view, char *text, size_t len); static struct action next_word_end(ledit_view *view, char *text, size_t len); @@ -112,6 +111,12 @@ static struct action change_to_eol(ledit_view *view, char *text, size_t len); static struct action delete_to_eol(ledit_view *view, char *text, size_t len); static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len); static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len); +static struct action delete_chars_forwards_multiline(ledit_view *view, char *text, size_t len); +static struct action delete_chars_backwards_multiline(ledit_view *view, char *text, size_t len); +static struct action delete_graphemes_forwards(ledit_view *view, char *text, size_t len); +static struct action delete_graphemes_backwards(ledit_view *view, char *text, size_t len); +static struct action delete_graphemes_forwards_multiline(ledit_view *view, char *text, size_t len); +static struct action delete_graphemes_backwards_multiline(ledit_view *view, char *text, size_t len); static struct action yank(ledit_view *view, char *text, size_t len); static struct action yank_lines(ledit_view *view, char *text, size_t len); static struct action replace(ledit_view *view, char *text, size_t len); @@ -149,80 +154,84 @@ basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes) { /* FIXME: make functions work in more modes (e.g. cursor-to-first-non-whitespace) */ static struct basic_key_cb basic_key_cb_map[] = { - {"append-after-cursor", &append_after_cursor, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"append-after-eol", &append_after_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"append-line-above", &append_line_above, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"append-line-below", &append_line_below, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"backspace", &backspace, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, - {"change", &change, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, + {"append-after-cursor", &append_after_cursor, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, + {"append-after-eol", &append_after_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, + {"append-line-above", &append_line_above, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, + {"append-line-below", &append_line_below, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, + {"change", &change, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, {"change-to-eol", &change_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"clipboard-copy", &clipcopy, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL}, - {"clipboard-paste", &clippaste, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, + {"clipboard-copy", &clipcopy, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"clipboard-paste", &clippaste, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, {"cursor-down", &cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, {"cursor-left", &cursor_left, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, {"cursor-right", &cursor_right, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, - {"cursor-to-beginning", &cursor_to_beginning, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"cursor-to-first-non-whitespace", &cursor_to_first_non_ws, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"cursor-to-beginning", &cursor_to_beginning, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"cursor-to-first-non-whitespace", &cursor_to_first_non_ws, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"cursor-up", &cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, - {"delete", &delete, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, - {"delete-backwards", &delete_chars_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"delete-forwards", &delete_chars_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"delete-key", &delete_key, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, - {"delete-to-eol", &delete_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"enter-commandedit", &enter_commandedit, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"delete", &delete, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT}, + {"delete-chars-backwards", &delete_chars_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-chars-backwards-multiline", &delete_chars_backwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-chars-forwards", &delete_chars_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-chars-forwards-multiline", &delete_chars_forwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-graphemes-backwards", &delete_graphemes_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-graphemes-backwards-multiline", &delete_graphemes_backwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-graphemes-forwards", &delete_graphemes_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-graphemes-forwards-multiline", &delete_graphemes_forwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"delete-to-eol", &delete_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"enter-commandedit", &enter_commandedit, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"enter-insert", &enter_insert, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, - {"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"escape-key", &escape_key, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, - {"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"find-char-forwards", &find_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"find-next-char-backwards", &find_next_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"find-next-char-forwards", &find_next_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"return-to-normal", &return_to_normal, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"find-char-forwards", &find_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"find-next-char-backwards", &find_next_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"find-next-char-forwards", &find_next_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"insert-at-beginning", &insert_at_beginning, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, {"insert-text", &insert_mode_insert_text, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, - {"join-lines", &join_lines, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"jump-to-mark", &jump_to_mark, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"key-0", &key_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"mark-line", &mark_line, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"move-to-eol", &move_to_eol, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"move-to-line", &move_to_line, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"next-bigword", &next_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"next-bigword-end", &next_bigword_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"next-word", &next_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"next-word-end", &next_word_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"paste-normal", &paste_normal, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"paste-normal-backwards", &paste_normal_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"previous-bigword", &prev_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"previous-word", &prev_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-0", &push_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-1", &push_1, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-2", &push_2, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-3", &push_3, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-4", &push_4, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-5", &push_5, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-6", &push_6, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-7", &push_7, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-8", &push_8, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"push-9", &push_9, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"join-lines", &join_lines, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"jump-to-mark", &jump_to_mark, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"key-0", &key_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"insert-mark", &insert_mark, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"move-to-eol", &move_to_eol, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"move-to-line", &move_to_line, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"next-bigword", &next_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"next-bigword-end", &next_bigword_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"next-word", &next_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"next-word-end", &next_word_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"paste-buffer", &paste_normal, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"paste-buffer-backwards", &paste_normal_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"previous-bigword", &prev_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"previous-word", &prev_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-0", &push_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-1", &push_1, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-2", &push_2, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-3", &push_3, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-4", &push_4, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-5", &push_5, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-6", &push_6, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-7", &push_7, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-8", &push_8, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"push-9", &push_9, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"redo", &redo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, {"repeat-command", &repeat_command, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, {"replace", &replace, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, - {"return-key", &return_key, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, - {"screen-down", &screen_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"screen-up", &screen_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"scroll-lines-down", &scroll_lines_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"scroll-lines-up", &scroll_lines_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"scroll-with-cursor-down", &scroll_with_cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"scroll-with-cursor-up", &scroll_with_cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, - {"show-line", &show_line, KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"break-line", &break_line, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"screen-down", &screen_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"screen-up", &screen_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"scroll-lines-down", &scroll_lines_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"scroll-lines-up", &scroll_lines_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"scroll-with-cursor-down", &scroll_with_cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"scroll-with-cursor-up", &scroll_with_cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"show-line", &show_line, KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"switch-selection-end", &switch_selection_end, KEY_FLAG_JUMP_TO_CURSOR, VISUAL}, - {"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, /* FIXME: also in INSERT */ + {"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"undo", &undo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, - {"yank", &yank, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, - {"yank-lines", &yank_lines, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"yank", &yank, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"yank-lines", &yank_lines, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, }; GEN_CB_MAP_HELPERS(basic_key_cb_map, basic_key_cb, text) @@ -698,67 +707,6 @@ delete_selection(ledit_view *view) { * Functions that were declared at the top. * ********************************************/ -/* FIXME: should these delete characters or graphemes? */ -static struct action -delete_chars_forwards(ledit_view *view, char *text, size_t len) { - (void)text; - (void)len; - CHECK_VIEW_LOCKED(view); - int num = get_key_repeat(); - if (num == -1) { - window_show_message(view->window, "Invalid key", -1); - return (struct action){ACTION_NONE, NULL}; - } else if (num == 0) { - num = 1; - } - size_t end_index = view_next_cursor_pos( - view, view->cur_line, view->cur_index, num - ); - delete_range( - view, 0, 0, - view->cur_line, view->cur_index, - view->cur_line, end_index, 1 - ); - paste_buffer_line_based = 0; - view->cur_index = view_get_legal_normal_pos( - view, view->cur_line, view->cur_index - ); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - finalize_repetition_stack(); - return (struct action){ACTION_NONE, NULL}; -} - -static struct action -delete_chars_backwards(ledit_view *view, char *text, size_t len) { - (void)text; - (void)len; - CHECK_VIEW_LOCKED(view); - int num = get_key_repeat(); - if (num == -1) { - window_show_message(view->window, "Invalid key", -1); - return (struct action){ACTION_NONE, NULL}; - } else if (num == 0) { - num = 1; - } - size_t start_index = view_prev_cursor_pos( - view, view->cur_line, view->cur_index, num - ); - delete_range( - view, 0, 0, - view->cur_line, start_index, - view->cur_line, view->cur_index, 1 - ); - paste_buffer_line_based = 0; - /* I guess this is technically unnecessary since only - text before the current position is deleted */ - view->cur_index = view_get_legal_normal_pos( - view, view->cur_line, start_index - ); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - finalize_repetition_stack(); - return (struct action){ACTION_NONE, NULL}; -} - /* used to set cursor - I guess this is sort of a hack */ static void push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group) { @@ -773,7 +721,6 @@ push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_gr static struct action append_line_above(ledit_view *view, char *text, size_t len) { - CHECK_VIEW_LOCKED(view); size_t start, end; /* do this here already so the mode group is the same for the newline insertion */ enter_insert(view, text, len); @@ -789,7 +736,6 @@ append_line_above(ledit_view *view, char *text, size_t len) { static struct action append_line_below(ledit_view *view, char *text, size_t len) { - CHECK_VIEW_LOCKED(view); size_t start, end; enter_insert(view, text, len); view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); @@ -804,19 +750,17 @@ append_line_below(ledit_view *view, char *text, size_t len) { static struct action append_after_cursor(ledit_view *view, char *text, size_t len) { - CHECK_VIEW_LOCKED(view); enter_insert(view, text, len); /* make cursor jump back to original position on undo */ push_undo_empty_insert(view, view->cur_line, view->cur_index, 1); - view->cur_index = view_next_cursor_pos( - view, view->cur_line, view->cur_index, 1 + view_next_cursor_pos( + view, view->cur_line, view->cur_index, 1, 0, NULL, &view->cur_index ); return (struct action){ACTION_NONE, NULL}; } static struct action append_after_eol(ledit_view *view, char *text, size_t len) { - CHECK_VIEW_LOCKED(view); size_t start, end; enter_insert(view, text, len); view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); @@ -852,7 +796,7 @@ move_to_line(ledit_view *view, char *text, size_t len) { } else { if (view->mode == NORMAL) view_wipe_line_cursor_attrs(view, view->cur_line); - else + else if (view->mode == VISUAL) view_set_selection(view, view->sel.line1, view->sel.byte1, line - 1, 0); view->cur_line = line - 1; view->cur_index = 0; @@ -961,7 +905,10 @@ scroll_with_cursor(ledit_view *view, int movement) { ); } } - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } } static struct action @@ -990,6 +937,7 @@ scroll_with_cursor_down(ledit_view *view, char *text, size_t len) { return (struct action){ACTION_NONE, NULL}; } +/* FIXME: Make all these scrolling functions work in visual mode */ /* movement is multiplied with half the window height and the result is added to the display offset the cursor is moved to the bottom if movement is upwards, to the top otherwise FIXME: this is slightly different now @@ -1042,7 +990,10 @@ move_half_screen(ledit_view *view, int movement) { view, x / PANGO_SCALE, y, 0, &view->cur_line, &view->cur_index ); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } } static struct action @@ -1076,7 +1027,6 @@ static struct action delete_to_eol(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); if (!key_stack_empty()) return err_invalid_key(view); size_t start, end; @@ -1089,13 +1039,16 @@ delete_to_eol(ledit_view *view, char *text, size_t len) { delete_range( view, 0, 0, view->cur_line, view->cur_index, - view->cur_line, end, 1 + view->cur_line, end, view->mode != INSERT ); - paste_buffer_line_based = 0; - view->cur_index = view_get_legal_normal_pos( - view, view->cur_line, view->cur_index - ); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode != INSERT) + paste_buffer_line_based = 0; + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index + ); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } return (struct action){ACTION_NONE, NULL}; } @@ -1103,7 +1056,6 @@ static struct action change_to_eol(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); if (!key_stack_empty()) return err_invalid_key(view); view_set_mode(view, INSERT); @@ -1130,7 +1082,6 @@ static struct action change(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); motion_callback cb = NULL; int num = get_key_repeat_and_motion_cb(view, &cb); if (num == -1) @@ -1187,9 +1138,10 @@ change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { delete_range( view, 0, 0, view->cur_line, pos1, - line, pos2, 1 + line, pos2, view->mode != INSERT ); - paste_buffer_line_based = line_based; + if (view->mode != INSERT) + paste_buffer_line_based = line_based; view_wipe_line_cursor_attrs(view, view->cur_line); } @@ -1316,18 +1268,15 @@ static struct action delete(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); motion_callback cb = NULL; int num = get_key_repeat_and_motion_cb(view, &cb); if (num == -1) return err_invalid_key(view); if (delete_selection(view)) { - view_set_mode(view, NORMAL); - view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); clear_key_stack(); } else { /* FIXME: checking equality of the function pointer may be a bit risky */ + /* -> actually, it shouldn't be a problem */ if (cb == &delete_cb) { int lines = num > 0 ? num : 1; size_t new_line; @@ -1361,11 +1310,14 @@ delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { delete_range( view, line_based, 0, view->cur_line, view->cur_index, - line, char_pos, 1 + line, char_pos, view->mode != INSERT ); - paste_buffer_line_based = line_based; - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - finalize_repetition_stack(); + if (view->mode != INSERT) { + paste_buffer_line_based = line_based; + finalize_repetition_stack(); + } + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } /* Note that these paste functions are a bit weird when working with softlines - @@ -1376,7 +1328,6 @@ static struct action paste_normal(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); if (!paste_buffer) { window_show_message(view->window, "Nothing to paste", -1); discard_repetition_stack(); @@ -1426,8 +1377,10 @@ paste_normal(ledit_view *view, char *text, size_t len) { old_line, old_index, view->cur_line, view->cur_index, 1, 1, 1 ); } - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - finalize_repetition_stack(); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode != INSERT) + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -1435,7 +1388,6 @@ static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); if (!paste_buffer) { window_show_message(view->window, "Nothing to paste", -1); discard_repetition_stack(); @@ -1480,8 +1432,10 @@ paste_normal_backwards(ledit_view *view, char *text, size_t len) { 0, 0, view->cur_line, view->cur_index, 0, 1, 1 ); } - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - finalize_repetition_stack(); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode != INSERT) + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -1615,47 +1569,101 @@ push_9(ledit_view *view, char *text, size_t len) { return (struct action){ACTION_NONE, NULL}; } -static struct action -backspace(ledit_view *view, char *text, size_t len) { - (void)text; - (void)len; - CHECK_VIEW_LOCKED(view); - /* FIXME: don't copy to paste buffer on del_sel here; delete entire grapheme */ - if (delete_selection(view)) { - /* NOP */ - } else if (view->cur_index == 0) { - if (view->cur_line != 0) { - ledit_line *l1 = buffer_get_line(view->buffer, view->cur_line - 1); - delete_range(view, 0, 0, view->cur_line - 1, l1->len, view->cur_line, 0, 0); - } - } else { - ledit_line *l = buffer_get_line(view->buffer, view->cur_line); - int i = line_prev_utf8(l, view->cur_index); - delete_range(view, 0, 0, view->cur_line, view->cur_index, view->cur_line, i, 0); - } - return (struct action){ACTION_NONE, NULL}; -} - -static struct action -delete_key(ledit_view *view, char *text, size_t len) { - (void)text; - (void)len; - CHECK_VIEW_LOCKED(view); - ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_line); - if (delete_selection(view)) { - /* NOP */ - } else if (view->cur_index == cur_line->len) { - if (view->cur_line != view->lines_num - 1) { - delete_range(view, 0, 0, view->cur_line, cur_line->len, view->cur_line + 1, 0, 0); - } - } else { - int i = line_next_utf8(cur_line, view->cur_index); - delete_range(view, 0, 0, view->cur_line, view->cur_index, view->cur_line, i, 0); - } - /* FIXME: This was probably a mistake earlier, right? - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);*/ - return (struct action){ACTION_NONE, NULL}; -} +/* FIXME: function to look at pango property to decide when to delete entire grapheme */ +/* FIXME: The cursor may be in an illegal position after one of the delete_chars* + functions, but calling get_legal_normal_pos also would be weird because it + wouldn't necessarily be at the deletion index anymore */ +#define GEN_DELETE_FUNCS(type, next_func, prev_func) \ +static struct action \ +delete_##type##_backwards_base(ledit_view *view, char *text, size_t len, int multiline) { \ + (void)text; \ + (void)len; \ + int num = get_key_repeat(); \ + if (num == -1) { \ + window_show_message(view->window, "Invalid key", -1); \ + return (struct action){ACTION_NONE, NULL}; \ + } else if (num == 0) { \ + num = 1; \ + } \ + size_t start_line, start_index; \ + prev_func( \ + view, view->cur_line, view->cur_index, \ + num, multiline, &start_line, &start_index \ + ); \ + delete_range( \ + view, 0, 0, \ + start_line, start_index, \ + view->cur_line, view->cur_index, view->mode != INSERT \ + ); \ + view->cur_line = start_line; \ + view->cur_index = start_index; \ + if (view->mode == NORMAL) \ + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); \ + if (view->mode != INSERT) { \ + paste_buffer_line_based = 0; \ + finalize_repetition_stack(); \ + } \ + return (struct action){ACTION_NONE, NULL}; \ +} \ + \ +static struct action \ +delete_##type##_backwards(ledit_view *view, char *text, size_t len) { \ + return delete_##type##_backwards_base(view, text, len, 0); \ +} \ + \ +static struct action \ +delete_##type##_backwards_multiline(ledit_view *view, char *text, size_t len) { \ + return delete_##type##_backwards_base(view, text, len, 1); \ +} \ + \ +static struct action \ +delete_##type##_forwards_base(ledit_view *view, char *text, size_t len, int multiline) { \ + (void)text; \ + (void)len; \ + int num = get_key_repeat(); \ + if (num == -1) { \ + window_show_message(view->window, "Invalid key", -1); \ + return (struct action){ACTION_NONE, NULL}; \ + } else if (num == 0) { \ + num = 1; \ + } \ + size_t end_line, end_index; \ + next_func( \ + view, view->cur_line, view->cur_index, \ + num, multiline, &end_line, &end_index \ + ); \ + delete_range( \ + view, 0, 0, \ + view->cur_line, view->cur_index, \ + end_line, end_index, view->mode != INSERT \ + ); \ + if (view->mode == NORMAL) \ + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); \ + if (view->mode != INSERT) { \ + paste_buffer_line_based = 0; \ + finalize_repetition_stack(); \ + } \ + return (struct action){ACTION_NONE, NULL}; \ +} \ + \ +static struct action \ +delete_##type##_forwards(ledit_view *view, char *text, size_t len) { \ + return delete_##type##_forwards_base(view, text, len, 0); \ +} \ + \ +static struct action \ +delete_##type##_forwards_multiline(ledit_view *view, char *text, size_t len) { \ + return delete_##type##_forwards_base(view, text, len, 1); \ +} + +/* Yes, I know, all these helpers are ugly... */ +#define buffer_next_char_pos_helper(view, line, byte, num, multiline, line_ret, byte_ret) \ + buffer_next_char_pos((view)->buffer, line, byte, num, multiline, line_ret, byte_ret) +#define buffer_prev_char_pos_helper(view, line, byte, num, multiline, line_ret, byte_ret) \ + buffer_prev_char_pos((view)->buffer, line, byte, num, multiline, line_ret, byte_ret) + +GEN_DELETE_FUNCS(graphemes, view_next_cursor_pos, view_prev_cursor_pos) +GEN_DELETE_FUNCS(chars, buffer_next_char_pos_helper, buffer_prev_char_pos_helper) static struct action move_to_eol(ledit_view *view, char *text, size_t len) { @@ -1691,15 +1699,15 @@ move_to_eol(ledit_view *view, char *text, size_t len) { view->sel.line1, view->sel.byte1, new_line, end_index ); - } else { + } else if (view->mode == NORMAL) { /* FIXME: this is weird because the cursor is actually on the next soft line, but the alternative has too many weird effects with bidi text */ view->cur_index = view_get_legal_normal_pos( view, view->cur_line, view->cur_index ); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } return (struct action){ACTION_NONE, NULL}; } @@ -1739,9 +1747,11 @@ name(ledit_view *view, char *text, size_t len) { ); \ view->cur_line = new_line; \ view->cur_index = new_index; \ - view_set_line_cursor_attrs( \ - view, view->cur_line, view->cur_index \ - ); \ + if (view->mode == NORMAL) { \ + view_set_line_cursor_attrs( \ + view, view->cur_line, view->cur_index \ + ); \ + } \ } \ discard_repetition_stack(); \ } \ @@ -1818,26 +1828,32 @@ cursor_right(ledit_view *view, char *text, size_t len) { } static struct action -return_key(ledit_view *view, char *text, size_t len) { +break_line(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); int start_group = 1; + /* FIXME: this is unnecessary now because no selection is supported in insert mode */ if (delete_selection(view)) start_group = 0; + if (view->mode == NORMAL) + view_wipe_line_cursor_attrs(view, view->cur_line); insert_text(view, view->cur_line, view->cur_index, "\n", 1, 0, 0, 0, 0, 0, 0, start_group); + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } return (struct action){ACTION_NONE, NULL}; } static void move_cursor_logically(ledit_view *view, int movement_dir, int allow_illegal_index) { if (movement_dir < 0) { - view->cur_index = view_prev_cursor_pos( - view, view->cur_line, view->cur_index, 1 + view_prev_cursor_pos( + view, view->cur_line, view->cur_index, 1, 0, NULL, &view->cur_index ); } else { - view->cur_index = view_next_cursor_pos( - view, view->cur_line, view->cur_index, 1 + view_next_cursor_pos( + view, view->cur_line, view->cur_index, 1, 0, NULL, &view->cur_index ); } if (!allow_illegal_index) { @@ -1848,12 +1864,13 @@ move_cursor_logically(ledit_view *view, int movement_dir, int allow_illegal_inde } static struct action -escape_key(ledit_view *view, char *text, size_t len) { +return_to_normal(ledit_view *view, char *text, size_t len) { (void)text; (void)len; clear_key_stack(); if (view->mode == INSERT) finalize_repetition_stack(); + /* FIXME: I guess this is unnecessary now that insert mode does not support selection */ if (view->mode == INSERT && view->sel_valid) { view_set_mode(view, VISUAL); } else if (view->mode != NORMAL) { @@ -1869,7 +1886,6 @@ static struct action enter_insert(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); if (view->mode == VISUAL) { view_wipe_selection(view); } @@ -1943,7 +1959,6 @@ static struct action join_lines(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); int num = get_key_repeat(); if (num == -1) return err_invalid_key(view); @@ -1951,33 +1966,60 @@ join_lines(ledit_view *view, char *text, size_t len) { num = 1; int start_group = 1; ledit_line *ll1; + ledit_line *ll2; size_t cur_line = view->cur_line; + /* don't return yet so the stuff at the bottom gets called, + in particular finalize_repetition_stack */ + if (cur_line == view->lines_num - 1) + window_show_message(view->window, "No following lines to join", -1); for (int i = 0; i < num; i++) { if (cur_line == view->lines_num - 1) break; - /* getting cur line again should be unnecessary, but - I'll just leave it in case I change the way lines - are stored later */ ll1 = buffer_get_line(view->buffer, cur_line); - /* FIXME: truncate whitespace to one space */ + ll2 = buffer_get_line(view->buffer, cur_line + 1); + /* figure out if the current line ends in whitespace - + this could probably be improved */ + size_t last_char_byte = line_prev_utf8(ll1, ll1->len); + size_t last_ws = view_line_next_non_whitespace(view, cur_line, last_char_byte); + int end_in_ws = (last_ws == ll1->len); /* also works if ll1->len == 0 */ + size_t start_idx = view_line_next_non_whitespace(view, cur_line + 1, 0); + /* save len here because view_delete_range_base calls view_get_legal_normal_pos, + so the returned index may not be right for the following space insertion */ + /* although, on second thought, that only happens when the next line is empty, + which is a special case that is ignored below... */ + size_t len = ll1->len; + size_t len2 = ll2->len; view_delete_range_base( view, DELETE_CHAR, start_group, - cur_line, ll1->len, cur_line + 1, 0, + cur_line, ll1->len, cur_line + 1, start_idx, &view->cur_line, &view->cur_index, NULL ); + /* insert space if there is no other whitespace */ + if (!end_in_ws && len2 > 0) { + ledit_range cur_range = {.line1 = view->cur_line, .byte1 = view->cur_index, .line2 = 0, .byte2 = 0}; + buffer_insert_with_undo_base( + view->buffer, cur_range, 1, 0, + view->mode, cur_line, len, + " ", 1, &view->cur_line, &view->cur_index + ); + } start_group = 0; } buffer_recalc_all_views_from_line(view->buffer, cur_line); - view_set_line_cursor_attrs( - view, view->cur_line, view->cur_index - ); - finalize_repetition_stack(); + /* FIXME: should view_set_line_cursor_attrs just have this check included? */ + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index + ); + } + if (view->mode != INSERT) + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action insert_at_beginning(ledit_view *view, char *text, size_t len) { - CHECK_VIEW_LOCKED(view); if (!key_stack_empty()) return err_invalid_key(view); enter_insert(view, text, len); @@ -2009,8 +2051,8 @@ cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) { new_index = view_line_next_non_whitespace(view, view->cur_line, start); /* next non-whitespace might be on next softline */ if (new_index >= end) { - new_index = view_prev_cursor_pos( - view, view->cur_line, end, 1 + view_prev_cursor_pos( + view, view->cur_line, end, 1, 0, NULL, &new_index ); } } @@ -2018,9 +2060,17 @@ cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) { cb(view, view->cur_line, new_index, KEY_MOTION_CHAR); } else { view->cur_index = new_index; - view_set_line_cursor_attrs( - view, view->cur_line, view->cur_index - ); + if (view->mode == VISUAL) { + view_set_selection( + view, + view->sel.line1, view->sel.byte1, + view->cur_line, view->cur_index + ); + } else if (view->mode == NORMAL) { + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index + ); + } discard_repetition_stack(); } return (struct action){ACTION_NONE, NULL}; @@ -2050,7 +2100,7 @@ cursor_to_beginning(ledit_view *view, char *text, size_t len) { view->sel.line1, view->sel.byte1, view->cur_line, view->cur_index ); - } else { + } else if (view->mode == NORMAL) { view_set_line_cursor_attrs( view, view->cur_line, view->cur_index ); @@ -2099,6 +2149,9 @@ enter_commandedit(ledit_view *view, char *text, size_t len) { return (struct action){ACTION_GRABKEY, &command_key_handler}; } +/* FIXME: support visual mode - maybe change selection to new position + or at least support only searching within the range given by the + selection */ static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len) { (void)text; @@ -2127,7 +2180,7 @@ enter_searchedit_backward(ledit_view *view, char *text, size_t len) { /* FIXME: differentiate between jumping to line and index like nvi */ static struct action -mark_line_cb(ledit_view *view, char *text, size_t len) { +insert_mark_cb(ledit_view *view, char *text, size_t len) { grab_char_cb = NULL; buffer_insert_mark( view->buffer, text, len, view->cur_line, view->cur_index @@ -2143,6 +2196,7 @@ jump_to_mark_cb(ledit_view *view, char *text, size_t len) { if (num > 0) return err_invalid_key(view); size_t line = 0, index = 0; + /* FIXME: better error */ if (buffer_get_mark(view->buffer, text, len, &line, &index)) return err_invalid_key(view); if (view->mode == VISUAL) { @@ -2158,12 +2212,14 @@ jump_to_mark_cb(ledit_view *view, char *text, size_t len) { view_wipe_line_cursor_attrs(view, view->cur_line); view->cur_line = line; view->cur_index = index; - view->cur_index = view_get_legal_normal_pos( - view, view->cur_line, view->cur_index - ); - view_set_line_cursor_attrs( - view, view->cur_line, view->cur_index - ); + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index + ); + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index + ); + } discard_repetition_stack(); } } @@ -2171,11 +2227,11 @@ jump_to_mark_cb(ledit_view *view, char *text, size_t len) { } static struct action -mark_line(ledit_view *view, char *text, size_t len) { +insert_mark(ledit_view *view, char *text, size_t len) { (void)view; (void)text; (void)len; - grab_char_cb = &mark_line_cb; + grab_char_cb = &insert_mark_cb; discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -2229,7 +2285,6 @@ static struct action undo(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); int num = get_key_repeat(); if (num == -1) return err_invalid_key(view); @@ -2237,7 +2292,8 @@ undo(ledit_view *view, char *text, size_t len) { num = 1; view_wipe_selection(view); view_undo(view, num); - finalize_repetition_stack(); + if (view->mode != INSERT) + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -2245,7 +2301,6 @@ static struct action redo(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - CHECK_VIEW_LOCKED(view); int num = get_key_repeat(); if (num == -1) return err_invalid_key(view); @@ -2253,14 +2308,16 @@ redo(ledit_view *view, char *text, size_t len) { num = 1; view_wipe_selection(view); view_redo(view, num); - finalize_repetition_stack(); + if (view->mode != INSERT) + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len) { - CHECK_VIEW_LOCKED(view); - /* FIXME: set cur_index when deleting selection */ + if (!key_stack_empty()) + return err_invalid_key(view); + /* this shouldn't be necessary */ delete_selection(view); insert_text(view, view->cur_line, view->cur_index, text, len, 0, 0, 0, 0, 0, 0, 1); return (struct action){ACTION_NONE, NULL}; @@ -2270,6 +2327,8 @@ static struct action clipcopy(ledit_view *view, char *text, size_t len) { (void)text; (void)len; + if (!key_stack_empty()) + return err_invalid_key(view); /* FIXME: abstract this through view */ clipboard_primary_to_clipboard(view->window); discard_repetition_stack(); @@ -2280,8 +2339,16 @@ static struct action clippaste(ledit_view *view, char *text, size_t len) { (void)text; (void)len; + if (!key_stack_empty()) + return err_invalid_key(view); + /* FIXME: the selection deletion and pasting should be in the same undo group */ + if (view->mode == VISUAL) { + /* Note; this sets the current position */ + delete_selection(view); + } view_paste_clipboard(view); - finalize_repetition_stack(); + if (view->mode != INSERT) + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -2317,14 +2384,17 @@ search_str_forwards(char *haystack, size_t hlen, char *needle, size_t nlen, size } } -/* just to make the macro below works for all cases */ +/* just to make the macro below work for all cases */ /* FIXME: is there a more elegant way to do this? */ -static size_t -dummy_cursor_helper(ledit_view *view, size_t line, size_t index, int num) { - (void)view; - (void)line; - (void)num; - return index; +static void +dummy_cursor_helper( + ledit_view *view, size_t line, size_t byte, + int num, int multiline, size_t *line_ret, size_t *byte_ret) { + (void)view; (void)num; (void)multiline; + if (line_ret) + *line_ret = line; + if (byte_ret) + *byte_ret = byte; } /* FIXME: add checks to functions that current mode is supported */ @@ -2335,7 +2405,7 @@ dummy_cursor_helper(ledit_view *view, size_t line, size_t index, int num) { funcm = func motion, funcn = func normal, funcv = func visual -> these are called to modify the index returned by search_func cur_funcm is called to get the index for a motion callback - cur_funcn is called to position the cursor in normal mode + cur_funcn is called to position the cursor in normal and insert mode cur_funcv is called to position the cursor in visual mode */ #define GEN_MOVE_TO_CHAR(name, search_func, cur_funcm, cur_funcn, cur_funcv) \ static struct action \ @@ -2358,14 +2428,16 @@ name##_cb(ledit_view *view, char *text, size_t len) { } \ if (!ret) { \ if (cb != NULL) { \ - new_index = cur_funcm( \ - view, view->cur_line, new_index, 1 \ + cur_funcm( \ + view, view->cur_line, new_index, \ + 1, 0, NULL, &new_index \ ); \ cb(view, view->cur_line, new_index, KEY_MOTION_CHAR); \ } else { \ if (view->mode == VISUAL) { \ - view->cur_index = cur_funcv( \ - view, view->cur_line, new_index, 1 \ + cur_funcv( \ + view, view->cur_line, new_index, \ + 1, 0, NULL, &view->cur_index \ ); \ view_set_selection( \ view, \ @@ -2373,12 +2445,15 @@ name##_cb(ledit_view *view, char *text, size_t len) { view->cur_line, view->cur_index \ ); \ } else { \ - view->cur_index = cur_funcn( \ - view, view->cur_line, new_index, 1 \ - ); \ - view_set_line_cursor_attrs( \ - view, view->cur_line, view->cur_index \ + cur_funcn( \ + view, view->cur_line, new_index, \ + 1, 0, NULL, &view->cur_index \ ); \ + if (view->mode == NORMAL) { \ + view_set_line_cursor_attrs( \ + view, view->cur_line, view->cur_index \ + ); \ + } \ } \ discard_repetition_stack(); \ } \ @@ -2422,8 +2497,9 @@ replace_cb(ledit_view *view, char *text, size_t len) { size_t start_index = view->cur_index; /* FIXME: replace with (key repeat) * text instead of just text */ /* FIXME: cursor pos or char? */ - size_t end_index = view_next_cursor_pos( - view, view->cur_line, view->cur_index, 1 + size_t end_index; + view_next_cursor_pos( + view, view->cur_line, view->cur_index, 1, 0, NULL, &end_index ); delete_range( view, 0, 0, @@ -2439,6 +2515,7 @@ replace_cb(ledit_view *view, char *text, size_t len) { ); push_undo_empty_insert(view, view->cur_line, view->cur_index, 0); grab_char_cb = NULL; + finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -2447,7 +2524,6 @@ replace(ledit_view *view, char *text, size_t len) { (void)view; (void)text; (void)len; - CHECK_VIEW_LOCKED(view); int num = get_key_repeat(); if (num != 0) return err_invalid_key(view); @@ -2464,6 +2540,7 @@ toggle_hard_line_based(ledit_view *view, char *text, size_t len) { if (num != 0) return err_invalid_key(view); buffer_set_hard_line_based(view->buffer, !view->buffer->hard_line_based); + discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -2566,6 +2643,7 @@ basic_key_handler(ledit_view *view, XEvent *event, int lang_index) { view->window->message_shown = 0; /* FIXME: this is hacky */ basic_key_cb_flags flags; struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found, &flags); + /* FIXME: make message hiding a property of each command so e.g. cursor keys also hide it */ if (found && n > 0 && !view->window->message_shown) window_hide_message(view->window); else if (msg_shown) diff --git a/keys_command.c b/keys_command.c @@ -357,14 +357,16 @@ move_to_next_substitution(ledit_view *view) { if (!next_replace_pos(view, sub_state.line, sub_state.byte, sub_state.max_line, &sub_state.line, &sub_state.byte)) { view->cur_line = sub_state.line; view->cur_index = sub_state.byte; - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); window_show_message(view->window, "No more matches", -1); buffer_unlock_all_views(view->buffer); return 0; } view->cur_line = sub_state.line; view->cur_index = sub_state.byte; - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); window_show_message(view->window, "Replace? (y/Y/n/N)", -1); view_ensure_cursor_shown(view); return 1; @@ -416,9 +418,11 @@ substitute_all_remaining(ledit_view *view) { if (min_line < view->lines_num) buffer_recalc_all_views_from_line(view->buffer, min_line); window_show_message_fmt(view->window, "Replaced %d occurrence(s)", sub_state.num); - /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */ - view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) { + /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */ + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } view_ensure_cursor_shown(view); buffer_unlock_all_views(view->buffer); } @@ -473,9 +477,10 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { /* trying to perform substitution in visual mode would make it unnecessarily complicated */ - if (view->mode == VISUAL) + if (view->mode == VISUAL) { view_wipe_selection(view); - view_set_mode(view, NORMAL); + view_set_mode(view, NORMAL); + } if (confirm) { buffer_lock_all_views_except(view->buffer, view, "Ongoing substitution in other view."); view->cur_command_type = CMD_SUBSTITUTE; @@ -554,7 +559,7 @@ parse_range( *errstr_ret = "Invalid range"; return 1; } - size_t aftermark_idx = next_utf8_len(c + 2, len - cur - 2); + size_t aftermark_idx = cur + 2 + next_utf8_len(c + 2, len - cur - 2); size_t marklen = aftermark_idx - (cur + 1); size_t l, b; if (*(c + 1) == '<' && view->sel_valid) { @@ -869,7 +874,8 @@ void search_next(ledit_view *view) { view_wipe_line_cursor_attrs(view, view->cur_line); search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); view_ensure_cursor_shown(view); if (ret != SEARCH_NORMAL) window_show_message(view->window, search_state_to_str(ret), -1); @@ -879,7 +885,8 @@ void search_prev(ledit_view *view) { view_wipe_line_cursor_attrs(view, view->cur_line); search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index); - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); view_ensure_cursor_shown(view); if (ret != SEARCH_NORMAL) window_show_message(view->window, search_state_to_str(ret), -1); diff --git a/keys_config.h b/keys_config.h @@ -21,21 +21,21 @@ struct { KeySym keysym; ledit_mode modes; } basic_keys_default[] = { - {"backspace", NULL, 0, XK_BackSpace, INSERT}, + {"delete-chars-backwards-multiline", NULL, 0, XK_BackSpace, INSERT}, {"cursor-left", NULL, 0, XK_Left, VISUAL|INSERT|NORMAL}, {"cursor-right", NULL, 0, XK_Right, VISUAL|INSERT|NORMAL}, {"cursor-up", NULL, 0, XK_Up, VISUAL|INSERT|NORMAL}, {"cursor-down", NULL, 0, XK_Down, VISUAL|INSERT|NORMAL}, - {"return-key", NULL, XK_ANY_MOD, XK_Return, INSERT}, - {"delete-key", NULL, 0, XK_Delete, INSERT}, - {"escape-key", NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT}, + {"break-line", NULL, XK_ANY_MOD, XK_Return, INSERT}, + {"delete-chars-forwards-multiline", NULL, 0, XK_Delete, INSERT}, + {"return-to-normal", NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT}, {"enter-insert", "i", 0, 0, NORMAL|VISUAL}, {"cursor-left", "h", 0, 0, NORMAL|VISUAL}, {"cursor-left", "h", ControlMask, 0, NORMAL|VISUAL}, {"cursor-right", "l", 0, 0, NORMAL|VISUAL}, {"cursor-up", "k", 0, 0, NORMAL|VISUAL}, {"cursor-down", "j", 0, 0, NORMAL|VISUAL}, - {"toggle-hard-line-based", "t", ControlMask, 0, NORMAL|VISUAL}, /* FIXME: also in insert */ + {"toggle-hard-line-based", "t", ControlMask, 0, NORMAL|VISUAL|INSERT}, {"cursor-right", NULL, 0, XK_space, NORMAL|VISUAL}, {"cursor-down", "j", ControlMask, 0, NORMAL|VISUAL}, {"cursor-down", "n", ControlMask, 0, NORMAL|VISUAL}, @@ -50,17 +50,17 @@ struct { {"push-7", "7", 0, 0, NORMAL|VISUAL}, {"push-8", "8", 0, 0, NORMAL|VISUAL}, {"push-9", "9", 0, 0, NORMAL|VISUAL}, - {"delete-forwards", "x", 0, 0, NORMAL}, - {"delete-backwards", "X", 0, 0, NORMAL}, + {"delete-graphemes-forwards", "x", 0, 0, NORMAL}, + {"delete-graphemes-backwards", "X", 0, 0, NORMAL}, {"delete", "d", 0, 0, NORMAL|VISUAL}, {"yank", "y", 0, 0, NORMAL|VISUAL}, {"yank-lines", "Y", 0, 0, NORMAL}, {"change", "c", 0, 0, NORMAL|VISUAL}, {"enter-visual", "v", 0, 0, NORMAL}, {"switch-selection-end", "o", 0, 0, VISUAL}, - {"clipboard-copy", "c", ControlMask, 0, VISUAL}, - {"clipboard-paste", "v", ControlMask, 0, INSERT}, - {"show-line", "g", ControlMask, 0, NORMAL|VISUAL}, + {"clipboard-copy", "c", ControlMask, 0, NORMAL|VISUAL|INSERT}, + {"clipboard-paste", "v", ControlMask, 0, VISUAL|INSERT}, + {"show-line", "g", ControlMask, 0, NORMAL|VISUAL|INSERT}, {"enter-commandedit", ":", 0, 0, NORMAL|VISUAL}, {"enter-searchedit-backwards", "?", 0, 0, NORMAL}, {"enter-searchedit-forwards", "/", 0, 0, NORMAL}, @@ -87,13 +87,13 @@ struct { {"move-to-line", "G", 0, 0, NORMAL|VISUAL}, {"join-lines", "J", 0, 0, NORMAL}, {"insert-at-beginning", "I", 0, 0, NORMAL}, - {"paste-normal", "p", 0, 0, NORMAL}, - {"paste-normal-backwards", "P", 0, 0, NORMAL}, + {"paste-buffer", "p", 0, 0, NORMAL}, + {"paste-buffer-backwards", "P", 0, 0, NORMAL}, {"append-after-eol", "A", 0, 0, NORMAL}, {"append-after-cursor", "a", 0, 0, NORMAL}, {"append-line-above", "O", 0, 0, NORMAL}, {"append-line-below", "o", 0, 0, NORMAL}, - {"mark-line", "m", 0, 0, NORMAL|VISUAL}, + {"insert-mark", "m", 0, 0, NORMAL|VISUAL}, {"jump-to-mark", "'", 0, 0, NORMAL|VISUAL}, {"change-to-eol", "C", 0, 0, NORMAL}, {"delete-to-eol", "D", 0, 0, NORMAL}, diff --git a/ledit.1 b/ledit.1 @@ -1,6 +1,6 @@ .\" WARNING: Some parts of this are stolen shamelessly from OpenBSD's .\" vi(1) manpage! -.Dd May 26, 2022 +.Dd May 27, 2022 .Dt LEDIT 1 .Os .Sh NAME @@ -21,11 +21,16 @@ different parts of a file can be shown at the same time. It is assumed that readers of this manual page are already familiar with .Xr vi 1 . -Differences with -.Xr vi 1 -are documented, but it is very likely that many have been missed. -If you find an important difference that is not documented, please -contact me. +.Pp +Most documentation is actually included in +.Xr leditrc 5 +because that's where all the key and command bindings are discussed. +It would be nice to show the default key bindings in a nice format +here, but that would require too much work, so you'll just have to +look at the descriptings in +.Xr leditrc 5 +and the default bindings in +.Pa leditrc.example . .Pp WARNING: All input data is assumed to be utf8! .Sh ANTI-DESCRIPTION @@ -57,761 +62,16 @@ will attempt to read the configuration file .Pa .leditrc in the user's home directory before using the defaults. .El -.Sh BASIC CONCEPTS -Some terminology should probably be explained in order to understand the -rest of this manual. -.Bl -tag -width Ds -.It Ar word -Whatever Pango defines a word to be. -This probably uses Unicode semantics (UAX #29), but I'm not entirely sure. -.It Ar bigword -A set of non-whitespace characters. -.It Ar character -A Unicode character. -Note that, when used as an argument (for instance when setting a mark), -.Ar character -can mean pretty much any string, as long as it is given to the program -in one event. -Yes, this is inconsistent and confusing. -.It Ar grapheme -As defined by Unicode (UAX #29). -A grapheme may be composed of multiple Unicode characters. -The cursor is only allowed to be at valid grapheme boundaries, but -some operations work with characters, while other work with graphemes. -.El -.Pp -Another important concept is the paste buffer. -When text is deleted or explicitly copied (yanked), it is written to the -paste buffer so that it can be pasted later. -The paste buffer is either character or line based, depending on whether -the deletion/copying operation was line or character based. -If a character based paste buffer is pasted, the text is inserted right -at the cursor position. -When it is line based, the text is inserted after the line. .Sh KEY BINDINGS -The key bindings listed here are given as the default English bindings. -These will, of course, not be accurate for other languages or if -the configuration is changed. -.Pp -Some commands change their behavior depending on whether the mode is set -to soft line or hard line. -This is sometimes a bit inconsistent, but at least it might help when -editing longer paragraphs with no manual line breaking. -.Pp -Note that this list is currently not sorted in any logical way. -That will hopefully be fixed in the future. -.Ss NORMAL MODE -.Bl -tag -width Ds -compact -.It Xo -.Op Ar count -.Aq Cm arrow down -.Xc -.It Xo -.Op Ar count -.Aq Cm control-j -.Xc -.It Xo -.Op Ar count -.Aq Cm control-n -.Xc -.It Xo -.Op Ar count -.Cm j -.Xc -Move the cursor down -.Ar count -lines. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Aq Cm arrow up -.Xc -.It Xo -.Op Ar count -.Aq Cm control-p -.Xc -.It Xo -.Op Ar count -.Cm k -.Xc -Move the cursor up -.Ar count -lines. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Aq Cm arrow right -.Xc -.It Xo -.Op Ar count -.Aq Cm space -.Xc -.It Xo -.Op Ar count -.Cm l -.Xc -Move the cursor right -.Ar count -graphemes in the current line. -Note that this is a visual operation, i.e. the cursor will still move right -if the text is right-to-left. -.Pp -.It Xo -.Op Ar count -.Aq Cm arrow left -.Xc -.It Xo -.Op Ar count -.Aq Cm control-h -.Xc -.It Xo -.Op Ar count -.Cm h -.Xc -Move the cursor left -.Ar count -graphemes in the current line. -Note that this is a visual operation, i.e. the cursor will still move left -if the text is right-to-left. -.Pp -.It Aq Cm escape -Clear the key stack (i.e. cancel multi-key command). -.Pp -.It Cm i -Enter insert mode. -.Pp -.It Cm v -Enter visual mode. -.Pp -.It Aq Cm control-t -Toggle mode between hard line and soft line based. -.Pp -.It Cm 0 -Move cursor to beginning of line. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Cm x -.Xc -Delete -.Ar count -graphemes after the cursor on the current line and copy the -deleted text into the paste buffer. -.Pp -.It Xo -.Op Ar count -.Cm X -.Xc -Delete -.Ar count -graphemes before the cursor on the current line and copy the -deleted text into the paste buffer. -.Pp -.It Xo -.Op Ar count -.Cm d -.Ar motion -.Xc -Delete the region of text described by -.Ar count -and -.Ar motion -and copy the deleted text into the paste buffer. -If -.Ar motion -is -.Cm d -again, -.Ar count -lines are deleted, starting with the current line. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Cm D -Delete all text from the current cursor position to the end of -the line and copy the deleted text into the paste buffer. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Cm y -.Ar motion -.Xc -Copy the region of text described by -.Ar count -and -.Ar motion -into the paste buffer. -If -.Ar motion -is -.Cm y -again, -.Ar count -lines are copied, starting with the current line. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Cm Y -.Xc -Copy -.Ar count -lines into the paste buffer. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Cm c -.Ar motion -.Xc -Change the region described by -.Ar count -and -.Ar motion -and copy the changed text into the paste buffer. -.Pp -.It Cm C -Change all text from the current cursor position to the end of -the line and copy the changed text into the paste buffer. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Aq Cm control-g -Show the current filename, whether the buffer has been modified since the -last write, and the current line. -.Pp -.It Cm \&: -Enter the line-editing mode for running a command. -.Pp -.It Cm / -Search forward for a search term. -Note that no regex is currently supported. -.Pp -.It Cm \&? -Search backwards for a search term. -Note that no regex is currently supported. -.Pp -.It Cm n -Move to the next match of the last search term in the direction of the -last search. -.Pp -.It Cm N -Move to the next match of the last search term in the direction opposite -to the direction of the last search. -.Pp -.It Xo -.Op Ar count -.Cm u -.Xc -Undo -.Ar count -operations. -Note that an entire session in insert mode is considered as one operation -when in normal mode. -.Pp -.It Xo -.Op Ar count -.Cm U -.Xc -Redo -.Ar count -operations. -Note that an entire session in insert mode is considered as one operation -when in normal mode. -.Pp -.It Xo -.Op Ar count -.Cm \&. -.Xc -Repeat the last command -.Ar count -times. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-b -.Xc -Move -.Ar count -screens up. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-f -.Xc -Move -.Ar count -screens down. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-y -.Xc -Move -.Ar count -lines up, attemting to leave the cursor in its current line and -character position. -Note that this command works with soft lines, regardless of the -current mode. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-e -.Xc -Move -.Ar count -lines down, attemting to leave the cursor in its current line and -character position. -Note that this command works with soft lines, regardless of the -current mode. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-u -.Xc -Move -.Ar count -lines up. -If -.Ar count -is not given, scroll up the number of lines specified by the last -.Aq Cm control-d -or -.Aq Cm control-u -command. -If this is the first such command, scroll up half a screen. -Note that this command works with soft lines, regardless of the -current mode. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-d -.Xc -Move -.Ar count -lines down. -If -.Ar count -is not given, scroll down the number of lines specified by the last -.Aq Cm control-d -or -.Aq Cm control-u -command. -If this is the first such command, scroll down half a screen. -Note that this command works with soft lines, regardless of the -current mode. -.Pp -.It Cm $ -Move to the last cursor position on the current line. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Cm w -.Xc -Move forward -.Ar count -words. -.Pp -.It Xo -.Op Ar count -.Cm W -.Xc -Move forward -.Ar count -bigwords. -.Pp -.It Xo -.Op Ar count -.Cm e -.Xc -Move forward -.Ar count -end-of-words. -.Pp -.It Xo -.Op Ar count -.Cm E -.Xc -Move forward -.Ar count -end-of-bigwords. -.Pp -.It Xo -.Op Ar count -.Cm b -.Xc -Move backwards -.Ar count -words. -.Pp -.It Xo -.Op Ar count -.Cm B -.Xc -Move backwards -.Ar count -bigwords. -.Pp -.It Xo -.Op Ar count -.Cm G -.Xc -Move to the line number given by -.Ar count . -If -.Ar count -is not given, move to the last line in the buffer. -.Pp -.It Xo -.Op Ar count -.Cm J -.Xc -Join the current line with the next one -.Ar count -times. -Note that this command always works on hard lines, regardless -of the current mode. -Also note that this currently does not compress whitespace between -the lines as other vi-like editors do. -This is due to the author's laziness. -.Pp -.It Cm I -Move cursor to beginning of line and enter insert mode. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Cm p -Paste text from the paste buffer after the current cursor position if the -buffer is character-based and after the current line if it is line-based. -Note that this does take into account the hard line/soft line mode, but -it behaves a bit weirdly when in soft line mode - it inserts the text -after the current soft line but adds newlines on both sides. -This behavior may be changed in the future if it turns out there's a more -logical behavior for soft line mode. -.Pp -.It Cm P -Paste text from the paste buffer before the current cursor position if the -buffer is character-based and before the current line if it is line-based. -The quirk for -.Cm p -applies here as well. -.Pp -.It Cm A -Append text after the current line. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Cm a -Append text after the current cursor position. -.Pp -.It Cm o -Append a new line after the current line and enter insert mode there. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Cm O -Append a new line before the current line and enter insert mode there. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Cm m -.Aq Cm character -.Xc -Mark the current current cursor position as -.Aq Cm character . -.Pp -.It Xo -.Cm ' -.Aq Cm character -.Xc -Jump to a position previously marked as -.Aq Cm character -with -.Cm m . -Note that this will jump to the exact line and character position that -was marked, but when used as a motion, it will be line based. -This is somewhat inconsistent, so it will probably be changed at some point. -.Pp -.It Xo -.Cm r -.Aq Cm character -.Xc -Replace the grapheme at the current cursor position with -.Aq Cm character . -.Pp -.It Cm ^ -Move to the first non-whitespace cursor position on the current line. -This changes behavior depending on the hard/soft line mode. -.Pp -.It Xo -.Op Ar count -.Cm t -.Aq Cm character -.Xc -Search forward, -.Op Ar count -times, through the current line for the cursor position immediately before -.Aq Cm character . -.Pp -.It Xo -.Op Ar count -.Cm T -.Aq Cm character -.Xc -Search backwards, -.Op Ar count -times, through the current line for the cursor position after -.Aq Cm character . -.Pp -.It Xo -.Op Ar count -.Cm f -.Aq Cm character -.Xc -Search forward, -.Op Ar count -times, through the current line for -.Aq Cm character . -.Pp -.It Xo -.Op Ar count -.Cm F -.Aq Cm character -.Xc -Search backwards, -.Op Ar count -times, through the current line for -.Aq Cm character . -.El -.Ss VISUAL MODE -The movement keys generally work the same in visual mode but change the -selection instead of just moving to the new position, so they are not -listed here separately. -.Pp -The -.Cm d , -.Cm y , -and -.Cm c -keys also work similarly, but operate on the range given by the selection. -.Pp -The -.Cm \&: -key automatically pastes the selection range into the line editor so a -command can be run over the range specified by the selection. -.Pp -Additionally, these keys are supported: -.Pp -.Bl -tag -width Ds -compact -.It Cm o -Switch the end of the selection that is modified by the movement keys. -.Pp -.It Aq Cm control-c -Copy the current selection to the clipboard. -.Pp -.It Aq Cm escape -Return to normal mode. -.El -.Ss INSERT MODE -All regular keys simply insert the corresponding text at the current cursor -position. -.Pp -The cursor keys, backspace, delete, and return all work as expected, -although it should be noted that backspace and delete currently always -delete a single unicode character instead of a grapheme. -.Pp -Additionally, the following keys are supported: -.Pp -.Bl -tag -width Ds -compact -.It Aq Cm control-v -Paste text from the clipboard at the current cursor position. -.Pp -.It Aq Cm control-z -Undo one operation. -Note that this, in contrast to the undo in normal mode, does not consider -an entire insert session to be one operation. -.Pp -.It Aq Cm control-y -Redo one operation. -Note that this, in contrast to the redo in normal mode, does not consider -an entire insert session to be one operation. -.Pp -.It Aq Cm escape -Return to normal mode. -.El -.Pp -Note that many keys that are common in other editors are not recognized currently. -That will hopefully be fixed in the future. -.Ss LINE EDITING MODE -These key bindings work in the line editor that is used for searching or -running commands. -.Pp -.Bl -tag -width DS -compact -.It Aq Cm return -Submit the search or command. -.Pp -.It Aq Cm arrow left -Move the cursor one to the left. -.Pp -.It Aq Cm arrow right -Move the cursor one to the right. -.Pp -.It Aq Cm arrow up -.It Aq Cm arrow down -Move through the search or command history. -Note that the search and command histories are separate. -.Pp -.It Aq Cm backspace -Delete one unicode character before the cursor. -.Pp -.It Aq Cm delete -Delete one unicode character after the cursor. -.Pp -.It Aq Cm end -Move the cursor to the end of the line. -.Pp -.It Aq Cm home -Move the cursor to the beginning of the line. -.Pp -.It Aq Cm escape -Cancel the search or command. -.El -.Ss MISCELLANEOUS -.Bl -tag -width DS -.It Keys while performing substitution with confirmation: -.Bl -tag -width Ds -.It Cm y -Confirm the current substitution. -.It Cm n -Reject the current substitution. -.It Cm Y -Confirm the current substitution and all further ones. -.It Cm N -Reject the current substitution and all further ones. -.El -.Pp -Note that these keys are also displayed during the substitution, but only -the default English bindings are shown because implementing anything else -would require work. -.El +See the descriptions given in +.Xr leditrc 5 +and the default bindings in +.Pa leditrc.example . .Sh COMMANDS -Note that the terminology is currently a bit inconsistent. -Sometimes, -.Dq commands -refers to the key commands, sometimes to the commands -written in the line editor, which are documented in this section. -.Pp -Also note that the commands which take filenames currently use the entire rest of -the line as the filename instead of doing any string parsing. -This may be changed in the future. -.Pp -.Bl -tag -width Ds -compact -.It Xo -.Cm :w -.Op Ar filename -.Xc -Write the buffer to -.Op Ar filename , -or, if no filename is given, to the file the buffer was read from. -.Pp -.It Xo -.Cm :w\&! -.Op Ar filename -.Xc -Same as -.Cm :w , -but the file will be attempted to be written to even if there -is something blocking it (e.g. the modified date of the file is newer -than it was when it was opened). -.Pp -.It Cm :q -Quit. -.Pp -.It Cm :q\&! -Quit, even if there are unsaved changes. -.Pp -.It Xo -.Cm :wq -.Op Ar filename -.Xc -.It Xo -.Cm :wq\&! -.Op Ar filename -.Xc -Write and quit afterwards. -The -.Cm \&! -is interpreted as for normal writing. -.Pp -.It Xo -.Sm off -.Op Ar range -.Cm s / Ar pattern Cm / Ar replace Cm / -.Op Ar options -.Sm on -.Xc -Substitute -.Ar pattern -with -.Ar replace -in the given line range. -.Pp -Instead of -.Cm / , -any other unicode character may be used. -The first unicode character after the -.Cm s -is used as the delimiter. -.Pp -If no range is given, substitution is only performed on the current line. -Note that no regex is currently supported. -.Pp -The range consists of two line numbers separated by a comma or the special value -.Cm % , -which refers to the entire file. -The following special values are possible instead of writing a line number directly: -.Bl -tag -width Ds -.It Cm $ -The last line in the file. -.It Xo -.Sm off -.Cm ' Aq Cm mark -.Sm on -.Xc -The line of the previously set mark -.Aq Cm mark . -Note that even though marks can theoretically be any string of characters, -they are only allowed to be one unicode character if they are used in a range. -The special values -.Cm < -and -.Cm > -are possible, which refer to the first and last line, respectively, in the -current selection. -.It Cm \&. -The current line. -.El -.Pp -The -.Ar options -may be a combination of the following: -.Bl -tag -width Ds -.It Cm g -Perform substitution for all occurrences in the given lines instead of just -the first one on each line. -.It Cm c -Confirm each substitution before performing it. -.El -.Pp -.It Cm :v -Open a new view. -Each view is a window that shows the text in the current buffer, -which is synced between the views. -.It Cm :c -Close a view. -.It Cm :c\&! -Close a view, even if it is the last one and there are unsaved changes. -.El +See the descriptions given in +.Xr leditrc 5 +and the default bindings in +.Pa leditrc.example . .Sh MOUSE ACTIONS There currently are not many mouse actions. Clicking and dragging with the left mouse button enters visual mode and diff --git a/leditrc.5 b/leditrc.5 @@ -1,4 +1,4 @@ -.Dd May 26, 2022 +.Dd May 27, 2022 .Dt LEDITRC 5 .Os .Sh NAME @@ -11,6 +11,10 @@ is the configuration file for the text editor .Xr ledit 1 , which can be used to configure the theme and key bindings used. .Pp +The description of the format given here is terrible, so it's +probably more useful to look at the example config provided in +.Pa leditrc.example . +.Pp The parser recognizes four different types of structures: strings, lists, statements, and assignments. .Pp @@ -44,6 +48,44 @@ The assignments/statements must be separated by newlines. .Pp The configuration file consists of several top-level assignments which are described in the following sections. +.Sh BASIC CONCEPTS +Some terminology should probably be explained in order to understand the +rest of this manual. +.Bl -tag -width Ds +.It Ar word +Whatever Pango defines a word to be. +This probably uses Unicode semantics (UAX #29), but I'm not entirely sure. +.It Ar bigword +A sequence of non-whitespace characters. +.It Ar character +A Unicode character. +Note that, when used as an argument (for instance when setting a mark), +.Ar character +can mean pretty much any string, as long as it is given to the program +in one event. +Yes, this is inconsistent and confusing. +.It Ar grapheme +As defined by Unicode (UAX #29). +A grapheme may be composed of multiple Unicode characters. +The cursor is only allowed to be at valid grapheme boundaries, but +some operations work with characters, while other work with graphemes. +.It Ar paste buffer +When text is deleted or explicitly copied (yanked), it is written to the +paste buffer so that it can be pasted later. +The paste buffer is either character or line based, depending on whether +the deletion/copying operation was line or character based. +If a character based paste buffer is pasted, the text is inserted right +at the cursor position. +When it is line based, the text is inserted after the line. +.It Ar softline/hardline +A hardline is an actual line separated from the other lines by a newline +character. A softline is a displayed line, but might only be part of a +hardline if the text is wrapped. +.Nm +can be in hardline or softline mode. +Some commands change their behavior depending on this mode, for instance +to move the cursor down a certain number of softlines instead of hardlines. +.El .Sh THEME The theme may be configured by assigning .Ar theme @@ -168,85 +210,435 @@ Multiple mods or modes can be given by joining them with .Aq func_name may be one of the following functions. The possible modes are listed beside the function names. +If +.Ar num +is listed beside the function name, this means that the function supports +key repetition (the number can be constructed with the +.Ar push +functions). +If +.Ar char +is listed beside the function name, this means that a character must be +typed immediately after calling the function (this is used e.g. to get a +character when setting a mark). +.Pp +Functions that overwrite the paste buffer usually only to that in normal +and visual mode. +It isn't entirely clear what the best behavior here would be. .Bl -tag -width Ds -.It Ar append-after-cursor Op normal -.It Ar append-after-eol Op normal -.It Ar append-line-above Op normal -.It Ar append-line-below Op normal -.It Ar backspace Op insert -.It Ar change Op normal, visual +.It Ar append-after-cursor Op normal, visual, insert +Move the cursor after the current character and enter insert mode. +.It Ar append-after-eol Op normal, visual, insert +Move the cursor to the end of the current line and enter insert mode. +This function modifies its behavior in softline mode. +.It Ar append-line-above Op normal, visual, insert +Insert a line before the current line, move the cursor to it, and enter insert mode. +This function modifies its behavior in softline mode. +Note that even in softline mode, a hardline is inserted, but the +insertion position may be different. +This may not be entirely logical, but I'm not sure what would be more +logical. +.It Ar append-line-below Op normal, visual, insert +Insert a line after the current line, move the cursor to it, and enter insert mode. +This function modifies its behavior in softline mode. +Note that even in softline mode, a hardline is inserted, but the +insertion position may be different. +This may not be entirely logical, but I'm not sure what would be more +logical. +.It Ar change Oo normal, visual, insert Oc Oo num Oc +In normal and insert mode, delete the text from the current +position until the position given by the next motion command +and enter insert mode (or just stay in insert mode). +In visual mode, delete the selected text and enter insert mode. +.Ar num +is used to influence the next motion command. +.Ar change +itself can also be used as the motion command, in which case the +given number of lines is deleted. +When that is the case, the behavior is modified in softline mode. .It Ar change-to-eol Op normal -.It Ar clipboard-copy Op visual -.It Ar clipboard-paste Op insert -.It Ar cursor-down Op normal, visual, insert -.It Ar cursor-left Op normal, visual, insert -.It Ar cursor-right Op normal, visual, insert -.It Ar cursor-to-beginning Op normal, visual -.It Ar cursor-to-first-non-whitespace Op normal -.It Ar cursor-up Op normal, visual, insert -.It Ar delete Op normal, visual -.It Ar delete-backwards Op normal -.It Ar delete-forwards Op normal -.It Ar delete-key Op insert -.It Ar delete-to-eol Op normal -.It Ar enter-commandedit Op normal, visual +Delete the text from the current position until the end of the line +and enter insert mode. +This function modifies its behavior in softline mode. +.It Ar clipboard-copy Op normal, visual, insert +Copy the last text that was selected to the clipboard. +Note: Due to the way this is currently implemented, text can be copied +even if it isn't selected anymore, as long as nothing else has been +selected in the meantime. +I haven't decided yet if this is a feature or a bug. +.It Ar clipboard-paste Op normal, visual, insert +Paste the clipboard contents at the current position. +In visual mode, the current selection is first deleted. +Note: The selection deletion and clipboard insertion are currently +registered as two independent undo operations, so undo/redo will only +undo/redo one of them at a time. +This is a bug, but it is difficult to fix due to the bad design decisions +made during the development of +.Xr ledit 1 . +.It Ar cursor-down Oo normal, visual, insert Oc Oo num Oc +Move the cursor +.Ar num +lines down. +In visual mode, the selection is changed. +This function modifies its behavior in softline mode. +.It Ar cursor-left Oo normal, visual, insert Oc Oo num Oc +Move the cursor +.Ar num +positions to the left, but only on the same line. +In visual mode, the selection is changed. +Note that the movement is visual, i.e. it will actually move logically +forwards in right-to-left text. +.It Ar cursor-right Oo normal, visual, insert Oc Oo num Oc +Move the cursor +.Ar num +positions to the right, but only on the same line. +In visual mode, the selection is changed. +Note that the movement is visual, i.e. it will actually move logically +backwards in right-to-left text. +.It Ar cursor-to-beginning Op normal, visual, insert +Move the cursor to the beginning of the current line. +In visual mode, the selection is changed. +This function modifies its behavior in softline mode. +See also +.Ar key-0 +.It Ar cursor-to-first-non-whitespace Op normal, visual, insert +Move the cursor to the first non-whitespace character on the current line. +In visual mode, the selection is changed. +This function modifies its behavior in softline mode. +.It Ar cursor-up Oo normal, visual, insert Oc Oo num Oc +Move the cursor +.Ar num +lines up. +In visual mode, the selection is changed. +This function modifies its behavior in softline mode. +.It Ar delete Oo normal, visual, insert Oc Oo num Oc +In normal or insert mode, delete the text from the current position +until the position given by the next motion command. +In visual mode, delete the selected text. +.Ar num +is used to influence the next motion command. +.Ar delete +itself can also be used as the motion command, in which case the +given number of lines is deleted. +When that is the case, the behavior is modified in softline mode. +.It Ar delete-chars-backwards Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode characters before the cursor, but at most up to the +beginning of the current line. +.It Ar delete-chars-backwards-multiline Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode characters before the cursor, possibly going onto +a previous line (the newline counts as one character). +.It Ar delete-chars-forwards Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode characters after the cursor, but at most up to the +end of the current line. +.It Ar delete-chars-forwards-multiline Op normal, insert +Delete +.Ar num +unicode characters before the cursor, possibly going onto +another line (the newline counts as one character). +.It Ar delete-graphemes-backwards Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode graphemes before the cursor, but at most up to the +beginning of the current line. +.It Ar delete-graphemes-backwards-multiline Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode graphemes before the cursor, possibly going onto +a previous line (the newline counts as one grapheme). +.It Ar delete-graphemes-forwards Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode graphemes after the cursor, but at most up to the +end of the current line. +.It Ar delete-graphemes-forwards-multiline Oo normal, insert Oc Oo num Oc +Delete +.Ar num +unicode graphemes after the cursor, possibly going onto +another line (the newline counts as one grapheme). +.It Ar delete-to-eol Op normal, insert +Delete everything from the current position to the end of the line. +This function modifies its behavior in softline mode. +.It Ar enter-commandedit Op normal, visual, insert +Open the line editor for typing commands. +In visual mode, the selection range is automatically pasted into the line editor +so commands can be performed on it. .It Ar enter-insert Op normal, visual -.It Ar enter-searchedit-backwards Op normal -.It Ar enter-searchedit-forwards Op normal +Enter insert mode. +.It Ar enter-searchedit-backwards Op normal, insert +Open the line editor for searching backwards. +Note that no regex is currently supported. +.It Ar enter-searchedit-forwards Op normal, insert +Open the line editor for searching forwards. +Note that no regex is currently supported. .It Ar enter-visual Op normal -.It Ar escape-key Op normal, visual, insert -.It Ar find-char-backwards Op normal, visual -.It Ar find-char-forwards Op normal, visual -.It Ar find-next-char-backwards Op normal, visual -.It Ar find-next-char-forwards Op normal, visual +Enter visual mode. +.It Ar return-to-normal Op normal, visual, insert +Return to normal mode. +If already in normal mode, discard all stored previous keys +(e.g. key repetition). +.It Ar find-char-backwards Oo normal, visual, insert Oc Oo num Oc Oo char Oc +Move cursor backward +.Ar num +times to the character given by +.Ar char . +Note that all the +.Ar find-* +functions are weird because the behavior changes slightly depending on the mode +they are called in and whether they are used as a motion command for another +command like +.Ar delete . +This behavior is approximately copied from vi and/or vim. +.It Ar find-char-forwards Oo normal, visual, insert Oc Oo num Oc Oo char Oc +Move cursor forward +.Ar num +times to the character given by +.Ar char . +The caveat mentioned for +.Ar find-char-backwards +also applies. +.It Ar find-next-char-backwards Oo normal, visual, insert Oc Oo num Oc Oo char Oc +Move cursor backward +.Ar num +times to the position after the character given by +.Ar char . +The caveat mentioned for +.Ar find-char-backwards +also applies. +.It Ar find-next-char-forwards Oo normal, visual, insert Oc Oo num Oc Oo char Oc +Move cursor forward +.Ar num +times to the position before the character given by +.Ar char . +The caveat mentioned for +.Ar find-char-backwards +also applies. .It Ar insert-at-beginning Op normal +Move cursor to the beginning of the line and enter insert mode. +This function changes its behavior in softline mode. .It Ar insert-text Op insert -.It Ar join-lines Op normal -.It Ar jump-to-mark Op normal, visual -.It Ar key-0 Op normal, visual -.It Ar mark-line Op normal, visual -.It Ar move-to-eol Op normal, visual -.It Ar move-to-line Op normal, visual -.It Ar next-bigword Op normal, visual -.It Ar next-bigword-end Op normal, visual -.It Ar next-word Op normal, visual -.It Ar next-word-end Op normal, visual -.It Ar paste-normal Op normal -.It Ar paste-normal-backwards normal -.It Ar previous-bigword Op normal, visual -.It Ar previous-word Op normal, visual -.It Ar push-0 Op normal, visual -.It Ar push-1 Op normal, visual -.It Ar push-2 Op normal, visual -.It Ar push-3 Op normal, visual -.It Ar push-4 Op normal, visual -.It Ar push-5 Op normal, visual -.It Ar push-6 Op normal, visual -.It Ar push-7 Op normal, visual -.It Ar push-8 Op normal, visual -.It Ar push-9 Op normal, visual -.It Ar redo Op normal, insert -.It Ar repeat-command Op normal -.It Ar replace Op normal -.It Ar return-key Op insert -.It Ar screen-down Op normal -.It Ar screen-up Op normal -.It Ar scroll-lines-down Op normal -.It Ar scroll-lines-up Op normal -.It Ar scroll-with-cursor-down Op normal -.It Ar scroll-with-cursor-up Op normal -.It Ar search-next Op normal -.It Ar search-previous Op normal -.It Ar show-line Op normal, visual +Insert the typed text at the current cursor position. +.It Ar join-lines Oo normal, insert Oc Oo num Oc +Join the current line with the next +.Ar num +lines. +Whitespace at the beginning of the joined lines is deleted, but it +is ensured that there is always at least a space between two joined +lines. +Note that this function always works on hard lines, regardless +of the current mode. +.It Ar jump-to-mark Oo normal, visual, insert Oc Oo char Oc +Jump to the mark given by +.Ar char . +In visual mode, the selection end is moved to the position of the mark. +.It Ar key-0 Op normal, visual, insert +This is a special function to handle the usual vi behavior of using +the key 0 both for moving to the beginning of the line and for adding +the digit 0 to the end of the current key repetition number. +If there was no previous key or the previous key expects a motion +command, +.Ar cursor-to-beginning +is called. +If the previous key was a number (i.e. one of the +.Ar push +commands), +.Ar push-0 +is called. +.It Ar insert-mark Oo normal, visual, insert Oc Oo char Oc +Insert a mark +.Ar char +with the current position. +.It Ar move-to-eol Op normal, visual, insert +Move to the end of the current line. +In visual mode, the selection end is moved as well. +This function modifies its behavior in softline mode. +.It Ar move-to-line Oo normal, visual, insert Oc Oo num Oc +Move to line number +.Ar num . +If +.Ar num +is not give, move to the last line in the buffer. +In visual mode, the selection end is moved as well. +.It Ar next-bigword Oo normal, visual, insert Oc Oo num Oc +Move forward +.Ar num +bigwords. +In visual mode, the selection is modified as well. +.It Ar next-bigword-end Oo normal, visual, insert Oc Oo num Oc +Move forward +.Ar num +end-of-bigwords. +In visual mode, the selection is modified as well. +.It Ar next-word Oo normal, visual, insert Oc Oo num Oc +Move forward +.Ar num +words. +In visual mode, the selection is modified as well. +.It Ar next-word-end Oo normal, visual, insert Oc Oo num Oc +Move forward +.Ar num +end-of-words. +In visual mode, the selection is modified as well. +.It Ar paste-buffer Op normal, insert +Paste text from the paste buffer after the current cursor position if the buffer +is character-based and after the current line if it is line-based. +Note that this does take into account the hard line/soft line mode, but it behaves +a bit weirdly when in soft line mode - it inserts the text after the current soft +line but adds newlines on both sides. +This behavior may be changed in the future if it turns out there's a more logical +behavior for soft line mode. +.It Ar paste-buffer-backwards Op normal, insert +Paste text from the paste buffer before the current cursor position if the buffer +is character-based and before the current line if it is line-based. +The quirk for +.Ar paste-buffer +applies here as well. +.It Ar previous-bigword Oo normal, visual, insert Oc Oo num Oc +Move backward +.Ar num +bigwords. +In visual mode, the selection is modified as well. +.It Ar previous-word Oo normal, visual, insert Oc Oo num Oc +Move backward +.Ar num +words. +In visual mode, the selection is modified as well. +.It Ar push-0 Op normal, visual, insert +Add the digit 0 to the end of the current key repetition number. +See also +.Ar key-0 +.It Ar push-1 Op normal, visual, insert +Add the digit 1 to the end of the current key repetition number. +.It Ar push-2 Op normal, visual, insert +Add the digit 2 to the end of the current key repetition number. +.It Ar push-3 Op normal, visual, insert +Add the digit 3 to the end of the current key repetition number. +.It Ar push-4 Op normal, visual, insert +Add the digit 4 to the end of the current key repetition number. +.It Ar push-5 Op normal, visual, insert +Add the digit 5 to the end of the current key repetition number. +.It Ar push-6 Op normal, visual, insert +Add the digit 6 to the end of the current key repetition number. +.It Ar push-7 Op normal, visual, insert +Add the digit 7 to the end of the current key repetition number. +.It Ar push-8 Op normal, visual, insert +Add the digit 8 to the end of the current key repetition number. +.It Ar push-9 Op normal, visual, insert +Add the digit 9 to the end of the current key repetition number. +.It Ar redo Oo normal, insert Oc Oo num Oc +Redo +.Ar num +operations. +Note that this changes depending on the mode. +All operations performed in insert mode are considered as one +operation when performing redo in normal mode. +.It Ar repeat-command Oo normal Oc Oo num Oc +Repeat the previous command +.Ar num +times. +.Pp +!!!!!!!!!!!!! NOTE/FIXME: This is broken currently. +In vi, everything done during insert mode is considered to be one +operation, so it counts as one command when using repeat-command. +However, since a lot of commands here now work in insert mode as well, +that doesn't make much sense anymore. +Most of the commands discard the previous key information because that's +what should happen in normal mode, and it is not clear what the logical +action would be in insert mode. +.It Ar replace Oo normal Oc Oo char Oc +Replace the character under the cursor with +.Ar char . +.It Ar break-line Op normal, insert +Break the line at the current position, i.e. insert a newline character. +.It Ar screen-down Oo normal, insert Oc Oo num Oc +Scroll +.Ar num +screens down. +.It Ar screen-up Oo normal, insert Oc Oo num Oc +Scroll +.Ar num +screens up. +.It Ar scroll-lines-down Oo normal, insert Oc Oo num Oc +Move +.Ar num +lines down. +If +.Ar count +is not given, scroll down the number of lines specified by the last +.Ar screen-down +or +.Ar screen-up +command. +If this is the first such command, scroll down half a screen. +Note that this command works with soft lines, regardless of the current mode. +.It Ar scroll-lines-up Oo normal, insert Oc Oo num Oc +Move +.Ar num +lines up. +If +.Ar count +is not given, scroll up the number of lines specified by the last +.Ar screen-down +or +.Ar screen-up +command. +If this is the first such command, scroll up half a screen. +Note that this command works with soft lines, regardless of the current mode. +.It Ar scroll-with-cursor-down Oo normal, insert Oc Oo num Oc +Move +.Ar num +lines down, attemting to leave the cursor in its current line and character position. +Note that this command works with soft lines, regardless of the current mode. +.It Ar scroll-with-cursor-up Oo normal, insert Oc Oo num Oc +Move +.Ar num +lines up, attemting to leave the cursor in its current line and character position. +Note that this command works with soft lines, regardless of the current mode. +.It Ar search-next Op normal, insert +Move to the next search result. +.It Ar search-previous Op normal, insert +Move to the previous search result. +.It Ar show-line Op normal, visual, insert +Show the current file name, whether the buffer has been modified since the last +write, and current line number. .It Ar switch-selection-end Op visual -.It Ar toggle-hard-line-based Op normal, visual -.It Ar undo Op normal, insert -.It Ar yank Op normal, visual -.It Ar yank-lines Op normal +Switch the end of the selection that can be moved. +.It Ar toggle-hard-line-based Op normal, visual, insert +Toggle the line mode between hardline and softline. +.It Ar undo Oo normal, insert Oc Oo num Oc +Undo +.Ar num +operations. +Note that this changes depending on the mode. +All operations performed in insert mode are considered as one +operation when performing undo in normal mode. +.It Ar yank Oo normal, visual, insert Oc Oo num Oc +In normal or insert mode, yank (copy to the paste buffer) the text from +the current position until the position given by the next motion command. +In visual mode, yank the selected text. +.Ar num +is used to influence the next motion command. +.Ar yank +itself can also be used as the motion command, in which case the +given number of lines is yanked. +When that is the case, the behavior is modified in softline mode. +.It Ar yank-lines Oo normal, insert Oc Oo num Oc +Yank (copy to the paste buffer) +.Ar num +lines. +This function modifies its behavior in softline mode. .El .Pp -Note that some of these functions should work in other modes -as well, but I still need to fix that (FIXME!). +Note: There are still a lot of weird parts when using functions in +modes that they weren't originally designed for (e.g. a lot of them +were only made for normal mode but now also work in insert mode). +The behavior is not set in stone yet and will probably still change +quite a bit based on any feedback I receive. .It Ar command-keys .Pp This is the same as @@ -260,29 +652,64 @@ must be a combination of and .Ar edit-search-backwards . .Pp +.Ar substitute +is the mode while performing a substitution with confirmation. +.Ar edit +is the mode while typing a command in the line editor. +.Ar edit-search +is the mode while typing a forwards search in the line editor. +.Ar edit-search-backwards +is the mode while typing a backwards search in the line editor. +.Pp The possible functions are given in the following list, with -the possible modes listed beside each function. +the allowed modes listed beside each function. .Bl -tag -width Ds .It Ar edit-backspace Op edit, edit-search, edit-search-backwards +Delete one unicode character before the cursor. .It Ar edit-cursor-left Op edit, edit-search, edit-search-backwards +Move the cursor one to the left. .It Ar edit-cursor-right Op edit, edit-search, edit-search-backwards +Move the cursor one to the right. .It Ar edit-cursor-to-beginning Op edit, edit-search, edit-search-backwards +Move the cursor to the beginning of the line. .It Ar edit-cursor-to-end Op edit, edit-search, edit-search-backwards +Move the cursor to the end of the line. .It Ar edit-delete Op edit, edit-search, edit-search-backwards +Delete one unicode character after the cursor. .It Ar edit-discard Op edit, edit-search, edit-search-backwards +Exit the line editor and cancel the search or command. .It Ar edit-insert-text Op edit, edit-search, edit-search-backwards +Insert the typed text in the line editor at the current cursor position. .It Ar edit-next-command Op edit +Move forwards through the command history. .It Ar edit-next-search Op edit-search, edit-search-backwards +Move forwards through the search history. .It Ar edit-previous-command Op edit +Move backwards through the command history. .It Ar edit-previous-search Op edit-search, edit-search-backwards +Move backwards through the search history. .It Ar edit-submit Op edit +Submit the command. .It Ar edit-submit-backwards-search Op edit-search-backwards +Submit the search. .It Ar edit-submit-search Op edit-search +Submit the search. .It Ar substitute-no Op substitute +Reject the current substitution. .It Ar substitute-no-all Op substitute +Reject the current substitution and all further ones. .It Ar substitute-yes Op substitute +Confirm the current substitution. .It Ar substitute-yes-all Op substitute +Confirm the current substitution and all further ones. .El +.Pp +Note that the bindings for the substitution commands are also displayed +on screen during the substitution. +However, only the default English bindings are shown because anything +else would require work (and might look very weird if the mapping +includes characters like diacritics that can't be displayed properly +on their own). .It Ar commands .Pp This is a list of statements of the form @@ -291,14 +718,140 @@ This is a list of statements of the form .Aq func_name .Aq text .Pp -The possible functions are given in the following list. +Note that the terminology is currently a bit inconsistent. +Sometimes, +.Dq commands +refers to the key commands, sometimes to the commands +written in the line editor, which are documented in this section. +.Pp +Also note that the commands which take filenames currently use the entire rest of +the line as the filename instead of doing any string parsing. +This may be changed in the future. +.Pp +The possible functions are given in the following list, together with +the calling convention when calling them with the configured bindings. +.Pp +.Bl -tag -width Ds -compact +.It Cm write +.It Xo +.Sm off +.Aq binding-text +.Op \&! +.Sm on +.Op Ar filename +.Xc +Write the buffer to +.Op Ar filename , +or, if no filename is given, to the file the buffer was read from. +If +.Sq \&! +is specified, the file will be attempted to be written to even if there +is something blocking it (e.g. the modified date of the file is newer +than it was when it was opened). +.Pp +.It Cm quit +.It Xo +.Sm off +.Aq binding-text +.Op \&! +.Sm on +.Xc +Quit. +If +.Sq \&! +is specified, quit even when there are unsaved changes. +.Pp +.It Cm write-quit +.It Xo +.Sm off +.Aq binding-text +.Op \&! +.Sm on +.Op Ar filename +.Xc +Write and quit afterwards. +The +.Sq \&! +is interpreted as for normal writing. +.Pp +.It Cm substitute +.It Xo +.Sm off +.Op Ar range +.Aq binding-text +.Cm / Ar pattern Cm / Ar replace Cm / +.Op Ar options +.Sm on +.Xc +Substitute +.Ar pattern +with +.Ar replace +in the given line range. +.Pp +Instead of +.Sq / , +any other unicode character may be used. +The first unicode character after +.Aq binding-text +is used as the delimiter. +.Pp +If no range is given, substitution is only performed on the current line. +Note that no regex is currently supported. +.Pp +The range consists of two line numbers separated by a comma or the special value +.Sq % , +which refers to the entire file. +The following special values are possible instead of writing a line number directly: .Bl -tag -width Ds -.It Ar close-view -.It Ar create-view -.It Ar quit -.It Ar substitute -.It Ar write -.It Ar write-quit +.It Cm $ +The last line in the file. +.It Xo +.Sm off +.Cm ' Aq Cm mark +.Sm on +.Xc +The line of the previously set mark +.Aq Cm mark . +Note that even though marks can theoretically be any string of characters, +they are only allowed to be one unicode character if they are used in a range. +The special values +.Cm < +and +.Cm > +are possible, which refer to the first and last line, respectively, in the +current selection. +.It Cm \&. +The current line. +.El +.Pp +The +.Ar options +may be a combination of the following: +.Bl -tag -width Ds +.It Cm g +Perform substitution for all occurrences in the given lines instead of just +the first one on each line. +.It Cm c +Confirm each substitution before performing it. +.El +.Pp +.It Cm create-view +.It Aq binding-text +Open a new view. +Each view is a window that shows the text in the current buffer, +which is synced between the views. +.It Cm close-view +.It Xo +.Sm off +.Aq binding-text +.Op \&! +.Sm on +.Xc +Close a view. +If +.Sq \&! +is given, close the view even it is the last one and there are unsaved changes. .El .El .Pp @@ -306,6 +859,9 @@ If the .Ar bindings configuration or any part of it is left out, the default is used. +There are some more specific rules, but I'm too lazy to explain them right now. +It is actually possible to overwrite just the default language without +changing the bindings, but why would you want to do that? .Sh LANGUAGE MAPPINGS A language mapping defines a mapping for the text associated with each key or command so the bindings still work with other keyboard layouts. @@ -340,7 +896,16 @@ is the key text given in This is the same as .Ar key-mapping , but for the commands. +Note that only the commands themselves are mapped, but the arguments are left alone. +I don't think it makes much sense to try to map those as well - really the only +reason for mapping commands is so that it is possible to save and quit with a +different keyboard layout. +If someone has a good idea for making other commands properly usable with other +keyboard mappings, I might consider it, though. .El +.Sh EXAMPLES +See the example configuration file +.Pa leditrc.example . .Sh SEE ALSO .Xr ledit 1 .Sh AUTHORS diff --git a/leditrc.example b/leditrc.example @@ -1,3 +1,6 @@ +# Note: This example configuration should be the same as the compiled-in defaults, +# except that this additionally includes the Urdu and Hindi language mappings. + theme = { text-font = Monospace text-size = 12 @@ -19,21 +22,21 @@ theme = { bindings = { language = "English (US)" basic-keys = { - bind backspace keysym backspace modes insert + bind delete-chars-backwards-multiline keysym backspace modes insert bind cursor-left keysym left modes visual|insert|normal bind cursor-right keysym right modes visual|insert|normal bind cursor-up keysym up modes visual|insert|normal bind cursor-down keysym down modes visual|insert|normal - bind return-key keysym return modes insert mods any - bind delete-key keysym delete modes insert mods any - bind escape-key keysym escape modes normal|visual|insert mods any + bind break-line keysym return modes insert mods any + bind delete-chars-forwards-multiline keysym delete modes insert mods any + bind return-to-normal keysym escape modes normal|visual|insert mods any bind enter-insert text "i" modes normal|visual bind cursor-left text "h" modes normal|visual bind cursor-right text "l" modes normal|visual bind cursor-down text "j" modes normal|visual bind cursor-up text "k" modes normal|visual bind cursor-left text "h" modes normal|visual mods control - bind toggle-hard-line-based text "t" modes normal|visual mods control + bind toggle-hard-line-based text "t" modes normal|visual|insert mods control bind cursor-right keysym space modes normal|visual bind cursor-down text "j" modes normal|visual mods control bind cursor-down text "n" modes normal|visual mods control @@ -48,17 +51,17 @@ bindings = { bind push-7 text "7" modes normal|visual bind push-8 text "8" modes normal|visual bind push-9 text "9" modes normal|visual - bind delete-forwards text "x" modes normal - bind delete-backwards text "X" modes normal + bind delete-graphemes-forwards text "x" modes normal + bind delete-graphemes-backwards text "X" modes normal bind delete text "d" modes normal|visual bind yank text "y" modes normal|visual bind yank-lines text "Y" modes normal bind change text "c" modes normal|visual bind enter-visual text "v" modes normal bind switch-selection-end text "o" modes visual - bind clipboard-copy text "c" modes visual mods control - bind clipboard-paste text "v" modes insert mods control - bind show-line text "g" modes normal|visual mods control + bind clipboard-copy text "c" modes normal|insert|visual mods control + bind clipboard-paste text "v" modes visual|insert mods control + bind show-line text "g" modes normal|visual|insert mods control bind enter-commandedit text ":" modes normal|visual bind enter-searchedit-backwards text "?" modes normal bind enter-searchedit-forwards text "/" modes normal @@ -85,13 +88,13 @@ bindings = { bind move-to-line text "G" modes normal|visual bind join-lines text "J" modes normal bind insert-at-beginning text "I" modes normal - bind paste-normal text "p" modes normal - bind paste-normal-backwards text "P" modes normal + bind paste-buffer text "p" modes normal + bind paste-buffer-backwards text "P" modes normal bind append-after-eol text "A" modes normal bind append-after-cursor text "a" modes normal bind append-line-above text "O" modes normal bind append-line-below text "o" modes normal - bind mark-line text "m" modes normal|visual + bind insert-mark text "m" modes normal|visual bind jump-to-mark text "'" modes normal|visual bind change-to-eol text "C" modes normal bind delete-to-eol text "D" modes normal diff --git a/view.c b/view.c @@ -555,8 +555,12 @@ view_line_visible(ledit_view *view, size_t index) { /* FIXME: these functions are only here because they need the PangoLayouts to determine grapheme boundaries. Maybe use a separate library for that? */ -size_t -view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { +void +view_next_cursor_pos( + ledit_view *view, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret) { int nattrs; ledit_line *ll = buffer_get_line(view->buffer, line); size_t c = line_byte_to_char(ll, byte); @@ -564,21 +568,40 @@ view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { PangoLayout *layout = get_pango_layout(view, line); const PangoLogAttr *attrs = pango_layout_get_log_attrs_readonly(layout, &nattrs); - for (size_t i = 0; i < (size_t)num; i++) { + for (int i = 0; i < num; i++) { + if (cur_byte >= ll->len) { + if (multiline && line < view->lines_num - 1) { + line++; + ll = buffer_get_line(view->buffer, line); + layout = get_pango_layout(view, line); + attrs = pango_layout_get_log_attrs_readonly(layout, &nattrs); + c = 0; + cur_byte = 0; + i++; + continue; + } else { + break; + } + } cur_byte = line_next_utf8(ll, cur_byte); for (c++; c < (size_t)nattrs; c++) { if (attrs[c].is_cursor_position) break; cur_byte = line_next_utf8(ll, cur_byte); } - if (cur_byte >= ll->len) - break; } - return cur_byte <= ll->len ? cur_byte : ll->len; + if (line_ret) + *line_ret = line; + if (byte_ret) + *byte_ret = cur_byte <= ll->len ? cur_byte : ll->len; } -size_t -view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { +void +view_prev_cursor_pos( + ledit_view *view, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret) { int nattrs; ledit_line *ll = buffer_get_line(view->buffer, line); size_t c = line_byte_to_char(ll, byte); @@ -587,16 +610,31 @@ view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { const PangoLogAttr *attrs = pango_layout_get_log_attrs_readonly(layout, &nattrs); for (int i = 0; i < num; i++) { + if (cur_byte == 0) { + if (multiline && line > 0) { + line--; + ll = buffer_get_line(view->buffer, line); + layout = get_pango_layout(view, line); + attrs = pango_layout_get_log_attrs_readonly(layout, &nattrs); + c = (size_t)nattrs; + cur_byte = ll->len; + i++; + continue; + } else { + break; + } + } cur_byte = line_prev_utf8(ll, cur_byte); - for (; c > 0; c--) { - if (attrs[c-1].is_cursor_position) + for (; c-- > 0;) { + if (attrs[c].is_cursor_position) break; cur_byte = line_prev_utf8(ll, cur_byte); } - if (cur_byte == 0) - break; } - return cur_byte; + if (line_ret) + *line_ret = line; + if (byte_ret) + *byte_ret = cur_byte; } static int @@ -2010,6 +2048,8 @@ view_redo(ledit_view *view, int num) { static void paste_callback(void *data, char *text, size_t len) { ledit_view *view = (ledit_view *)data; + if (view->mode == NORMAL) + view_wipe_line_cursor_attrs(view, view->cur_line); ledit_range cur_range; cur_range.line1 = view->cur_line; cur_range.byte1 = view->cur_index; @@ -2021,6 +2061,8 @@ paste_callback(void *data, char *text, size_t len) { &view->cur_line, &view->cur_index ); view_ensure_cursor_shown(view); + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } /* FIXME: guard against view being destroyed before paste callback is nulled */ diff --git a/view.h b/view.h @@ -203,18 +203,34 @@ void view_recalc_all_lines(ledit_view *view); */ /* - * Get the byte position of the cursor position 'num' positions after - * the position at line 'line' and byte position 'byte'. - * If it would be past the end of the line, the length of the line is returned. - */ -size_t view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num); + * Get the line and byte position of the cursor position 'num' positions + * after the position at line 'line' and byte position 'byte'. The new + * position is returned in 'line_ret' and 'byte_ret'. + * If multiline is non-zero, the new position may be on a different line. + * Otherwise, it is at most the length of the given line. + * A newline counts as one position. + */ +void view_next_cursor_pos( + ledit_view *view, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret +); /* - * Get the byte position of the cursor position 'num' positions before - * the position at line 'line' and byte position 'byte'. - * If it would be past the beginning of the line, 0 is returned. + * Get the line and byte position of the cursor position 'num' positions + * before the position at line 'line' and byte position 'byte'. The new + * position is returned in 'line_ret' and 'byte_ret'. + * If multiline is non-zero, the new position may be on a different line. + * Otherwise, it is never earlier than position 0 on the given line. + * A newline counts as one position. */ -size_t view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num); +void view_prev_cursor_pos( + ledit_view *view, + size_t line, size_t byte, + int num, int multiline, + size_t *line_ret, size_t *byte_ret +); /* * The next 6 functions all return a line, a byte position, and a diff --git a/window.c b/window.c @@ -364,6 +364,7 @@ window_set_mode(ledit_window *window, ledit_mode mode) { void window_set_mode_extra_text(ledit_window *window, char *text) { + free(window->mode_extra_text); window->mode_extra_text = ledit_strdup(text); window_set_mode(window, window->mode); window->redraw = 1;