diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2024-09-12 13:55:40 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2024-09-12 16:44:13 +0200 |
commit | abb5d23eba21a2aafda0346c0c5dd845561b2aa2 (patch) | |
tree | 9e42c1e72e0d61c322c0af8d54a9d740a7e2e45c | |
parent | 73d574b71a10d4661ada20275cafde75aff6c1ba (diff) | |
download | sciteco-abb5d23eba21a2aafda0346c0c5dd845561b2aa2.tar.gz |
function key macros have been reworked into a more generic key macro feature
* ALL keypresses (the UTF-8 sequences resulting from key presses) can now be remapped.
* This is especially useful with Unicode support, as you might want to alias
international characters to their corresponding latin form in the start state,
so you don't have to change keyboard layouts so often.
This is done automatically in Gtk, where we have hardware key press information,
but has to be done with key macros in Curses.
There is a new key mask 4 (bit 3) for that purpose now.
* Also, you might want to define non-ANSI letters to perform special functions in
the start state where it won't be accepted by the parser anyway.
Suppose you have a macro M→, you could define
@^U[^K→]{m→} 1^_U[^K→]
This effectively "extends" the parser and allow you to call macro "→" by a single
key press. See also #5.
* The register prefix has been changed from ^F (for function) to ^K (for key).
This is the only thing you have to change in order to migrate existing
function key macros.
* Key macros are enabled by default. There is no longer any way to disable
function key handling in curses, as I never found any reason or need to disable it.
Theoretically, the default ESCDELAY could turn out to be too small and function
keys don't get through. I doubt that's possible unless on extremely slow serial lines.
Even then, you'd have to increase ESCDELAY and instead of disabling function keys
simply define an escape surrogate.
* The ED flag has been removed and its place is reserved for a future mouse support flag
(which does make sense to disable in curses sometimes).
fnkeys.tes is consequently also enabled by default in sample.teco_ini.
* Key macros are handled as an unit. If one character results in an error,
the entire string is rubbed out.
This fixes the "CLOSE" key on Gtk.
It also makes sure that the original error message is preserved and not overwritten
by some subsequent syntax error.
It was never useful that we kept inserting characters after the first error.
-rw-r--r-- | doc/sciteco.7.template | 232 | ||||
-rw-r--r-- | lib/fnkeys.tes | 59 | ||||
-rw-r--r-- | sample.teco_ini | 4 | ||||
-rw-r--r-- | src/cmdline.c | 223 | ||||
-rw-r--r-- | src/cmdline.h | 27 | ||||
-rw-r--r-- | src/core-commands.c | 7 | ||||
-rw-r--r-- | src/interface-curses/interface.c | 36 | ||||
-rw-r--r-- | src/interface-gtk/interface.c | 48 | ||||
-rw-r--r-- | src/parser.h | 30 | ||||
-rw-r--r-- | src/sciteco.h | 2 |
10 files changed, 380 insertions, 288 deletions
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template index d0574d7..1eebecb 100644 --- a/doc/sciteco.7.template +++ b/doc/sciteco.7.template @@ -96,8 +96,7 @@ according to the current keyboard layout and modifier keys. On the Gtk UI, \*(ST tries to automatically take ANSI letter values in situations where the parser accepts only ANSI characters. -\# On Curses, you might need key macros to achieve the same, -\# but they are not yet implemented. +On Curses, you might need key macros to achieve the same. .IP 2. .SCITECO_TOPIC ctrl Control-combinations (e.g. CTRL+A) are translated to control @@ -129,149 +128,164 @@ there is often an equivalent typed with the caret character (e.g. \(lq^I\(rq). .IP 4. A selection of other keys without printable representation (called -function keys) are translated to user-definable character sequences. -This feature is called function key macros and explained in the -next subsection. +function keys) are looked up as key macros, allowing user-definable character +sequences to be inserted, including immediate editing commands. +If there is no matching key macro, nothing is inserted. +The key macro feature is explained in the next subsection. +.IP 5. +All keys with printable representations are also looked up +as key macros, allowing them to be remapped just like function keys. +Otherwise the corresponding UTF-8 strings are inserted into the command stream. +.IP 6. +The result of key macro lookups or the default printable representations +are processed as immediate editing commands in a context-sensitive manner +(see section +.BR "COMMANDLINE EDITING" ). +By default they are inserted into the command line macro and are +immediately executed. .RE . .LP -All immediate editing commands and regular \*(ST commands however operate on +While \*(ST handles keys with arbitrary Unicode representations, +all immediate editing commands and regular \*(ST commands operate on a language based solely on .B ASCII codes, which is a subset of Unicode. \# This is because we cannot assume the presence of any particular non-ANSI \# symbol on a user's keyboard. +\# Immediate editing commands do not operate directly on function keys +\# because we didn't want to introduce an UI-independent representation +\# of function keys - it would also complicate insertion of immediate +\# editing commands from key macros. Since the \*(ST parser is Unicode-aware, this does not exclude using Unicode glyphs wherever a single character is expected, ie. \fB^^\fIx\fR and \fBU\fIq\fR works with arbitrary Unicode glyphs. All \*(ST macros must be in valid UTF-8. . -.SS Function Key Macros +.SS Key Macros . -.SCITECO_TOPIC "function key" +.SCITECO_TOPIC "key macro" ^K By default function keys except Escape, Backspace and Return are ignored by \*(ST. -By setting bit 6 of the \fBED\fP flag variable, function key handling -is enabled: -.SCITECO_TT -.EX -0,64ED -.SCITECO_TT_END -.EE -This is usually performed in the editor profile. -With certain interfaces (curses) after enabling function keys, -the Escape key might only be handled after a short delay. +With certain interfaces (curses) the Escape key might only be handled +after a short delay. This is because it might be used by the terminal to transmit Escape Sequences. This delay is minimized by \*(ST, so using the escape key should not be a problem even on ncurses/UNIX. +If the default delay is too small, it can be tweaked with the +.B ESCDELAY +environment variable and if necessary a key macro can be +defined as an escape surrogate as described in this section +.RB ( fnkeys.tes +defines the delete key as an escape surrogate for instance). .LP -Enabling function keys also enables Function Key Macros. -These are Q-Register strings inserted into the command stream -(before immediate editing command handling) when certain function -keys (or combinations) are pressed. -The following list of Function Key Macro registers are supported: +To make use of function keys or to remap all other keys, +special Q-Register strings can be defined that are inserted into the command stream +before immediate editing command handling. +The following list of key macro registers are supported: +. .TP 9 -.SCITECO_TOPIC ^FDOWN -.B ^FDOWN +.SCITECO_TOPIC ^KDOWN +.B ^KDOWN .TQ -.SCITECO_TOPIC ^FUP -.B ^FUP +.SCITECO_TOPIC ^KUP +.B ^KUP Inserted when the down/up cursor keys are pressed. .TP -.SCITECO_TOPIC ^FLEFT -.B ^FLEFT +.SCITECO_TOPIC ^KLEFT +.B ^KLEFT .TQ -.SCITECO_TOPIC ^FSLEFT -.B ^FSLEFT +.SCITECO_TOPIC ^KSLEFT +.B ^KSLEFT Inserted when the left or shift-left cursor keys are pressed. .TP -.SCITECO_TOPIC ^FRIGHT -.B ^FRIGHT +.SCITECO_TOPIC ^KRIGHT +.B ^KRIGHT .TQ -.SCITECO_TOPIC ^FSRIGHT -.B ^FSRIGHT +.SCITECO_TOPIC ^KSRIGHT +.B ^KSRIGHT Inserted when the right or shift-right cursor keys are pressed. .TP -.SCITECO_TOPIC ^FHOME -.B ^FHOME +.SCITECO_TOPIC ^KHOME +.B ^KHOME .TQ -.SCITECO_TOPIC ^FSHOME -.B ^FSHOME +.SCITECO_TOPIC ^KSHOME +.B ^KSHOME Inserted when the Home or shift-Home keys are pressed. .TP -.SCITECO_TOPIC ^FF -.BI ^FF x -Inserted when the Fx-key is pressed +.SCITECO_TOPIC ^KF +.BI ^KF x +Inserted when the F\fIx\fP-key is pressed .RI ( x is a number between 0 and 63). .TP -.SCITECO_TOPIC ^FDC -.B ^FDC +.SCITECO_TOPIC ^KDC +.B ^KDC .TQ -.SCITECO_TOPIC ^FSDC -.B ^FSDC +.SCITECO_TOPIC ^KSDC +.B ^KSDC Inserted when the Delete or shift-Delete key is pressed. .TP -.SCITECO_TOPIC ^FIC -.B ^FIC +.SCITECO_TOPIC ^KIC +.B ^KIC .TQ -.SCITECO_TOPIC ^FSIC -.B ^FSIC +.SCITECO_TOPIC ^KSIC +.B ^KSIC Inserted when the Insert or shift-Insert key is pressed. .TP -.SCITECO_TOPIC ^FPPAGE -.B ^FPPAGE +.SCITECO_TOPIC ^KPPAGE +.B ^KPPAGE .TQ -.SCITECO_TOPIC ^FNPAGE -.B ^FNPAGE +.SCITECO_TOPIC ^KNPAGE +.B ^KNPAGE Inserted when the Page-Up or Page-Down key is pressed. .TP -.SCITECO_TOPIC ^FPRINT -.B ^FPRINT +.SCITECO_TOPIC ^KPRINT +.B ^KPRINT .TQ -.SCITECO_TOPIC ^FSPRINT -.B ^FSPRINT +.SCITECO_TOPIC ^KSPRINT +.B ^KSPRINT Inserted when the Print or shift-Print key is pressed. .TP -.SCITECO_TOPIC ^FA1 -.B ^FA1 +.SCITECO_TOPIC ^KA1 +.B ^KA1 .TQ -.SCITECO_TOPIC ^FA3 -.B ^FA3 +.SCITECO_TOPIC ^KA3 +.B ^KA3 .TQ -.SCITECO_TOPIC ^FB2 -.B ^FB2 +.SCITECO_TOPIC ^KB2 +.B ^KB2 .TQ -.SCITECO_TOPIC ^FC1 -.B ^FC1 +.SCITECO_TOPIC ^KC1 +.B ^KC1 .TQ -.SCITECO_TOPIC ^FC3 -.B ^FC3 +.SCITECO_TOPIC ^KC3 +.B ^KC3 Inserted when the numeric key pad's upper left key (7), upper right key (9), central key (5), lower left key (1), or lower right key (3) is pressed and num-lock is disabled. The key-pad's cursor keys are handled like the regular cursor keys. .TP -.SCITECO_TOPIC ^FEND -.B ^FEND +.SCITECO_TOPIC ^KEND +.B ^KEND .TQ -.SCITECO_TOPIC ^FSEND -.B ^FSEND +.SCITECO_TOPIC ^KSEND +.B ^KSEND Inserted when the End or shift-End key is pressed. .TP -.SCITECO_TOPIC ^FHELP -.B ^FHELP +.SCITECO_TOPIC ^KHELP +.B ^KHELP .TQ -.SCITECO_TOPIC ^FSHELP -.B ^FSHELP +.SCITECO_TOPIC ^KSHELP +.B ^KSHELP Inserted when the Help or shift-Help key is pressed. .TQ -.SCITECO_TOPIC ^FCLOSE -.B ^FCLOSE +.SCITECO_TOPIC ^KCLOSE +.B ^KCLOSE Inserted when the Close key has been pressed. More importantly, this key is emulated in some GUIs (notably GTK+) when the user tries to close \*(ST's @@ -280,34 +294,50 @@ This allows customizing \*(ST's behaviour when program termination is requested (e.g. only quit if there are no unsaved buffers). The close key is also special because -it has a default action if function key macros are -disabled or the \(lq^FCLOSE\(rq macro is undefined: +it has a default action if the \(lq^KCLOSE\(rq macro is undefined: It unconditionally quits \*(ST. The default action is \fBnot\fP performed when -\(lq^FCLOSE\(rq has merely been masked out in the +\(lq^KCLOSE\(rq has merely been masked out in the current parser state (see below). +.TP +.BI ^K x +Any other key with printable representation and all control codes +are looked up with a \(lq^K\(rq prefix. +\fIx\fP can usually only be a single Unicode glyph. +\# Although the result of IMEs is looked up in Gtk, which I suppose +\# can be multiple codepoints. +If undefined, \fIx\fP is inserted unmodified. +\# NOTE: Since all function key macros are longer than 2 +\# characters, there shouldn't be any namespace collisions. . .LP -\(lq^F\(rq corresponds to CTRL+F in the above list but +\(lq^K\(rq corresponds to CTRL+K (ASCII code 11) in the above list but might be typed with a caret due to string building characters in long Q-Register names. The names are all derived from key definitions of the curses library \(em not all of them may be supported on any particular user interface. .LP -By default function key macros are effective everywhere \(em -pressing a function key has the same effect as processing -the characters of the corresponding function key macro as +The result of key macro expansion differs from +consecutive key presses in that they are considered an unity. +If insertion of a single character fails (raises an error), +the entire macro expansion is automatically rubbed out. +.LP +By default key macros are effective everywhere \(em +pressing a key has the same effect as processing +the characters of the corresponding key macro as immediate editing commands (or self-inserting characters). -However function key macros that rewrite the current command line +However key macros that rewrite the current command line will only work correctly from specific \*(ST parser states. -\*(ST therefore allows you to mask function key macros in +Another common use of key macros would be to define +aliases of \*(ST commands for non-latin keys on Curses. +\*(ST therefore allows you to mask key macros in specific parser states by evaluating the Q-Register's numeric -part, thus allowing you to control \fIwhere\fP a function key +part, thus allowing you to control \fIwhere\fP a key macro is effective. The numeric part represents a bitmask of states where -function keys are \fIdisabled\fP (so the default value 0 -enables that function key everywhere). +keys macros are \fIdisabled\fP (so the default value 0 +enables that key macro everywhere). \*(ST defines the following state flags: .IP 1 4 Bit 0 represents the \(lqstart\(rq state where \*(ST accepts the @@ -316,23 +346,27 @@ This is the state you will want command line editing macros to be enabled in. .IP 2 Bit 1 represents any string argument. +.IP 4 +Bit 2 represents any case insensitive syntactic character. +This is the state you might want to use for translating +non-latin characters to their latin equivalent. .LP All other bits/flags represent any other parser state. Consequently, setting the register to the inverse of a bitmask of state flags enables the corresponding macro only for the specified states. -For instance, to enable the \(lq^FRIGHT\(rq function key macro +For instance, to enable the \(lq^KRIGHT\(rq key macro only in the \(lqstart\(rq state, you could set: .SCITECO_TT .EX -1^_U[^FRIGHT] +1^_U[^KRIGHT] .SCITECO_TT_END .EE .LP -A set of useful Function Key Macros are provided in the +A set of useful key macros for function keys is provided in the standard library .BR fnkeys.tes . -It demonstrates how Function Key Macros may be used to define +It demonstrates how key macros may be used to define alternate Escape keys (so the delay issue is not experienced), or do insertion and command-line editing using function keys. . @@ -1523,11 +1557,11 @@ the original clipboard contents, though. The numeric parts of the clipboard registers are currently not used by \*(ST. .TP -.BI ^F key -Function key registers as documented in section -\fBKEY TRANSLATION\fP. -Their string-content represents a function key macro -and their numeric part is a function key mask. +.BI ^K key +Key macro registers as documented in section +.BR "KEY TRANSLATION" . +Their string-content represents a key macro +and their numeric part is a key macro mask. None of those registers are automatically initialized on startup. .TP diff --git a/lib/fnkeys.tes b/lib/fnkeys.tes index 081e7d0..fd6b332 100644 --- a/lib/fnkeys.tes +++ b/lib/fnkeys.tes @@ -20,14 +20,14 @@ * Make DELETE an ESCAPE surrogate. * Macro is enabled everywhere. *! -@[DC]{} +@[DC]{} !* * Make SHIFT+DELETE a rubout/re-insert key. * This reverses the ^G modifier for BACKSPACE. * The macro is enabled everywhere. *! -@[SDC]{} +@[SDC]{} !* * Command line editing macros. @@ -41,16 +41,16 @@ Q.pESGETCOLUMN,4EJ Q.p-.M#c } -@[HOME]{(M[HOME]} -1U[HOME] +@[HOME]{(M[HOME]} +1U[HOME] @[END]{ ESLINEFROMPOSITIONESGETLINEENDPOSITIONU.p Q.pESGETCOLUMN,4EJ Q.p:-.M#c } -@[END]{(M[END]} -1U[END] +@[END]{(M[END]} +1U[END] @[NPAGE]{ 0,4EJ @@ -58,72 +58,69 @@ ESPOSITIONFROMLINEU.p Q.p"< Z | Q.p: '-.M#c } -@[NPAGE]{(M[NPAGE]} -1U[NPAGE] +@[NPAGE]{(M[NPAGE]} +1U[NPAGE] @[PPAGE]{ 0,4EJ ESLINEFROMPOSITION-(ESLINESONSCREEN)U.l Q.l"< 0 | Q.lESPOSITIONFROMLINE: '-.M#c } -@[PPAGE]{(M[PPAGE]} -1U[PPAGE] +@[PPAGE]{(M[PPAGE]} +1U[PPAGE] @[LEFT]{ ."=0|.-1'U.p Q.pESGETCOLUMN,4EJ Q.p-.M#c } -@[LEFT]{(M[LEFT]} -1U[LEFT] +@[LEFT]{(M[LEFT]} +1U[LEFT] @[SLEFT]{ 0,0,ESWORDSTARTPOSITIONESWORDSTARTPOSITIONU.p Q.pESGETCOLUMN,4EJ Q.p:-.M#c } -@[SLEFT]{(M[SLEFT]} -1U[SLEFT] +@[SLEFT]{(M[SLEFT]} +1U[SLEFT] @[RIGHT]{ .-Z"=.|.+1'U.p Q.pESGETCOLUMN,4EJ Q.p-.M#c } -@[RIGHT]{(M[RIGHT]} -1U[RIGHT] +@[RIGHT]{(M[RIGHT]} +1U[RIGHT] @[SRIGHT]{ 0,0,ESWORDENDPOSITIONESWORDENDPOSITIONU.p Q.pESGETCOLUMN,4EJ Q.p:-.M#c } -@[SRIGHT]{(M[SRIGHT]} -1U[SRIGHT] +@[SRIGHT]{(M[SRIGHT]} +1U[SRIGHT] @[UP]{ 4EJ(ESLINEFROMPOSITION-1)ESFINDCOLUMN:-.M#c } -@[UP]{(M[UP]} -1U[UP] +@[UP]{(M[UP]} +1U[UP] @[DOWN]{ 4EJ(ESLINEFROMPOSITION+1)ESFINDCOLUMN:-.M#c } -@[DOWN]{(M[DOWN]} -1U[DOWN] +@[DOWN]{(M[DOWN]} +1U[DOWN] -@[CLOSE]{(EX)} -1U[CLOSE] +@[CLOSE]{(EX)} +1U[CLOSE] !* * Zoom with F9/F10 if function keys are enabled. * This is automatically rubbed out. *! -@[F9]{(ESZOOMOUT{-13D}} -1U[F9] -@[F10]{(ESZOOMIN{-12D}} -1U[F10] - -!* enable function key (macro) support *! -0,64ED +@[F9]{(ESZOOMOUT{-13D}} +1U[F9] +@[F10]{(ESZOOMIN{-12D}} +1U[F10] diff --git a/sample.teco_ini b/sample.teco_ini index dd37376..f0f5208 100644 --- a/sample.teco_ini +++ b/sample.teco_ini @@ -53,8 +53,8 @@ EMQ[$SCITECOPATH]/session.tes The size unit is 1pt/100 ! ! [lexer.font]Monospace 1300U[lexer.font] ! -! Uncomment to enable default keyboard macros and function keys ! -! EMQ[$SCITECOPATH]/fnkeys.tes ! +! Enable default function key macros ! +EMQ[$SCITECOPATH]/fnkeys.tes ! Uncomment if XTerm allows clipboard operations ! ! 0,256ED ! diff --git a/src/cmdline.c b/src/cmdline.c index 2236872..25b93c3 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -82,7 +82,7 @@ static teco_string_t teco_last_cmdline = {NULL, 0}; * @param error A GError. * @return FALSE to throw a GError */ -gboolean +static gboolean teco_cmdline_insert(const gchar *data, gsize len, GError **error) { const teco_string_t src = {(gchar *)data, len}; @@ -181,7 +181,7 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) return TRUE; } -gboolean +static gboolean teco_cmdline_rubin(GError **error) { if (!teco_cmdline.str.len) @@ -194,55 +194,52 @@ teco_cmdline_rubin(GError **error) return teco_cmdline_insert(start, next-start, error); } +/** + * Process key press or expansion of key macro. + * + * Should be called only with the results of a single keypress. + * They are considered an unity and in case of errors, we + * rubout the entire sequence (unless there was a $$ return in the + * middle). + * + * @param data Key presses in UTF-8. + * @param len Length of data. + * @param error A GError. + * @return FALSE if error was set. + * If TRUE was returned, there could still have been an error, + * but it has already been handled. + */ gboolean -teco_cmdline_keypress_wc(gunichar key, GError **error) +teco_cmdline_keypress(const gchar *data, gsize len, GError **error) { + const teco_string_t str = {(gchar *)data, len}; teco_machine_t *machine = &teco_cmdline.machine.parent; - g_autoptr(GError) tmp_error = NULL; + + if (!teco_string_validate_utf8(&str)) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, + "Invalid UTF-8 sequence"); + return FALSE; + } /* - * Cleanup messages,etc... + * Cleanup messages, etc... */ teco_interface_msg_clear(); - /* - * Process immediate editing commands, inserting - * characters as necessary into the command line. - */ - if (!machine->current->process_edit_cmd_cb(machine, NULL, key, &tmp_error)) { - if (g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN)) { - /* - * Return from top-level macro, results - * in command line termination. - * The return "arguments" are currently - * ignored. - */ - g_assert(machine->current == &teco_state_start); + gsize start_pc = teco_cmdline.pc; - teco_interface_popup_clear(); + for (guint i = 0; i < len; i = g_utf8_next_char(data+i) - data) { + gunichar chr = g_utf8_get_char(data+i); + g_autoptr(GError) tmp_error = NULL; - if (teco_quit_requested) { - /* cought by user interface */ - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, ""); - return FALSE; - } + /* + * Process immediate editing commands, inserting + * characters as necessary into the command line. + */ + if (machine->current->process_edit_cmd_cb(machine, NULL, chr, &tmp_error)) + continue; - teco_undo_clear(); - /* also empties all Scintilla undo buffers */ - teco_ring_set_scintilla_undo(TRUE); - teco_view_set_scintilla_undo(teco_qreg_view, TRUE); - /* - * FIXME: Reset main machine? - */ - teco_goto_table_clear(&teco_cmdline.machine.goto_table); - teco_expressions_clear(); - g_array_remove_range(teco_loop_stack, 0, teco_loop_stack->len); - - teco_string_clear(&teco_last_cmdline); - teco_last_cmdline = teco_cmdline.str; - memset(&teco_cmdline.str, 0, sizeof(teco_cmdline.str)); - teco_cmdline.effective_len = 0; - } else { + if (!g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN)) { /* * NOTE: Error message already displayed in * teco_cmdline_insert(). @@ -252,29 +249,76 @@ teco_cmdline_keypress_wc(gunichar key, GError **error) * is thrown. They must be executed so * as if the character had never been * inserted. + * Actually we rub out the entire command line + * up until the insertion point. */ - teco_undo_pop(teco_cmdline.pc); - teco_cmdline.effective_len = teco_cmdline.pc; + teco_undo_pop(start_pc); + teco_cmdline.effective_len = start_pc; /* program counter could be messed up */ teco_cmdline.machine.macro_pc = teco_cmdline.effective_len; - } #ifdef HAVE_MALLOC_TRIM + /* + * Undo stacks can grow very large - sometimes large enough to + * make the system swap and become unresponsive. + * This shrinks the program break after lots of memory has + * been freed, reducing the virtual memory size and aiding + * in recovering from swapping issues. + * + * This is particularily important with some memory limiting backends + * after hitting the memory limit* as otherwise the program's resident + * size won't shrink and it would be impossible to recover. + */ + if (g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_MEMLIMIT)) + malloc_trim(0); +#endif + + break; + } + /* - * Undo stacks can grow very large - sometimes large enough to - * make the system swap and become unresponsive. - * This shrinks the program break after lots of memory has - * been freed, reducing the virtual memory size and aiding - * in recovering from swapping issues. - * - * This is particularily important with some memory limiting backends - * after hitting the memory limit* as otherwise the program's resident - * size won't shrink and it would be impossible to recover. + * Return from top-level macro, results + * in command line termination. + * The return "arguments" are currently + * ignored. */ - if (g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN) || - g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_MEMLIMIT)) - malloc_trim(0); + g_assert(machine->current == &teco_state_start); + + teco_interface_popup_clear(); + + if (teco_quit_requested) { + /* caught by user interface */ + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, ""); + return FALSE; + } + + teco_undo_clear(); + /* also empties all Scintilla undo buffers */ + teco_ring_set_scintilla_undo(TRUE); + teco_view_set_scintilla_undo(teco_qreg_view, TRUE); + /* + * FIXME: Reset main machine? + */ + teco_goto_table_clear(&teco_cmdline.machine.goto_table); + teco_expressions_clear(); + g_array_remove_range(teco_loop_stack, 0, teco_loop_stack->len); + + teco_string_clear(&teco_last_cmdline); + teco_last_cmdline = teco_cmdline.str; + memset(&teco_cmdline.str, 0, sizeof(teco_cmdline.str)); + teco_cmdline.effective_len = 0; + +#ifdef HAVE_MALLOC_TRIM + /* see above */ + malloc_trim(0); #endif + + /* + * Continue with the other keys, + * but we obviously can't rub out beyond the return if any + * error occurs later on. + */ + start_pc = teco_cmdline.pc; } /* @@ -284,57 +328,40 @@ teco_cmdline_keypress_wc(gunichar key, GError **error) return TRUE; } -/* - * FIXME: If one character causes an error, we should rub out the - * entire string. - * Usually it will be called only with single keys (strings containing - * single codepoints), but especially teco_cmdline_fnmacro() can emulate - * many key presses at once. - */ -gboolean -teco_cmdline_keypress(const gchar *str, gsize len, GError **error) -{ - for (guint i = 0; i < len; i += g_utf8_next_char(str+i) - (str+i)) { - gunichar chr = g_utf8_get_char_validated(str+i, len-i); - if ((gint32)chr < 0) { - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, - "Invalid UTF-8 sequence"); - return FALSE; - } - if (!teco_cmdline_keypress_wc(chr, error)) - return FALSE; - } - - return TRUE; -} - -gboolean -teco_cmdline_fnmacro(const gchar *name, GError **error) +teco_keymacro_status_t +teco_cmdline_keymacro(const gchar *name, gssize name_len, GError **error) { g_assert(name != NULL); + if (name_len < 0) + name_len = strlen(name); + /* * NOTE: It should be safe to allocate on the stack since * there are only a limited number of possible function key macros. */ - gchar macro_name[1 + strlen(name)]; - macro_name[0] = TECO_CTL_KEY('F'); - memcpy(macro_name+1, name, sizeof(macro_name)-1); - - teco_qreg_t *macro_reg; + gchar macro_name[1 + name_len]; + macro_name[0] = TECO_CTL_KEY('K'); + memcpy(macro_name+1, name, name_len); - if (teco_ed & TECO_ED_FNKEYS && - (macro_reg = teco_qreg_table_find(&teco_qreg_table_globals, macro_name, sizeof(macro_name)))) { + teco_qreg_t *macro_reg = teco_qreg_table_find(&teco_qreg_table_globals, macro_name, sizeof(macro_name)); + if (macro_reg) { teco_int_t macro_mask; if (!macro_reg->vtable->get_integer(macro_reg, ¯o_mask, error)) - return FALSE; + return TECO_KEYMACRO_ERROR; - if (macro_mask & teco_cmdline.machine.parent.current->fnmacro_mask) - return TRUE; + /* + * FIXME: This does not work with Q-Register specs embedded into string arguments. + * There should be a keymacro_mask_cb() instead. + */ + if (!((teco_cmdline.machine.parent.current->keymacro_mask | + teco_cmdline.machine.expectstring.machine.parent.current->keymacro_mask) & ~macro_mask)) + return TECO_KEYMACRO_UNDEFINED; g_auto(teco_string_t) macro_str = {NULL, 0}; return macro_reg->vtable->get_string(macro_reg, ¯o_str.data, ¯o_str.len, NULL, error) && - teco_cmdline_keypress(macro_str.data, macro_str.len, error); + teco_cmdline_keypress(macro_str.data, macro_str.len, error) + ? TECO_KEYMACRO_SUCCESS : TECO_KEYMACRO_ERROR; } /* @@ -342,20 +369,16 @@ teco_cmdline_fnmacro(const gchar *name, GError **error) * except "CLOSE" which quits the application * (this may loose unsaved data but is better than * not doing anything if the user closes the window). - * NOTE: Doing the check here is less efficient than - * doing it in the UI implementations, but defines - * the default actions centrally. - * Also, fnmacros are only handled after key presses. */ - if (!strcmp(name, "CLOSE")) { + if (name_len == 5 && !strncmp(name, "CLOSE", name_len)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, ""); - return FALSE; + return TECO_KEYMACRO_ERROR; } - return TRUE; + return TECO_KEYMACRO_UNDEFINED; } -void +static void teco_cmdline_rubout(void) { const gchar *p; diff --git a/src/cmdline.h b/src/cmdline.h index 4aa862c..f4b84e4 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -60,16 +60,29 @@ typedef struct { extern teco_cmdline_t teco_cmdline; -gboolean teco_cmdline_insert(const gchar *data, gsize len, GError **error); +gboolean teco_cmdline_keypress(const gchar *data, gsize len, GError **error); -gboolean teco_cmdline_rubin(GError **error); +typedef enum { + TECO_KEYMACRO_ERROR = 0, /**< GError occurred */ + TECO_KEYMACRO_SUCCESS, /**< key macro found and inserted */ + TECO_KEYMACRO_UNDEFINED /**< no key macro found */ +} teco_keymacro_status_t; -gboolean teco_cmdline_keypress_wc(gunichar key, GError **error); -gboolean teco_cmdline_keypress(const gchar *str, gsize len, GError **error); +teco_keymacro_status_t teco_cmdline_keymacro(const gchar *name, gssize name_len, GError **error); -gboolean teco_cmdline_fnmacro(const gchar *name, GError **error); - -void teco_cmdline_rubout(void); +static inline gboolean +teco_cmdline_keymacro_c(gchar key, GError **error) +{ + switch (teco_cmdline_keymacro(&key, sizeof(key), error)) { + case TECO_KEYMACRO_ERROR: + return FALSE; + case TECO_KEYMACRO_SUCCESS: + break; + case TECO_KEYMACRO_UNDEFINED: + return teco_cmdline_keypress(&key, sizeof(key), error); + } + return TRUE; +} extern gboolean teco_quit_requested; diff --git a/src/core-commands.c b/src/core-commands.c index ef763d5..c5c60a8 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -1164,7 +1164,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) * * FIXME: Maybe, there should be a special teco_state_t * for beginnings of command-lines? - * It could also be used for a corresponding FNMACRO mask. + * It could also be used for a corresponding KEYMACRO mask. */ if (teco_cmdline.effective_len == 1 && teco_cmdline.str.data[0] == '*') return &teco_state_save_cmdline; @@ -1260,7 +1260,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_start, .end_of_macro_cb = NULL, /* Allowed at the end of a macro! */ .is_start = TRUE, - .fnmacro_mask = TECO_FNMACRO_MASK_START + .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE ); /*$ F< @@ -1971,7 +1971,7 @@ TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_escape, * when it comes to function key macro masking. */ .is_start = TRUE, - .fnmacro_mask = TECO_FNMACRO_MASK_START + .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE ); /*$ EF close @@ -2058,7 +2058,6 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error) * of files. * - 32: Enable/Disable buffer editing hooks * (via execution of macro in global Q-Register \(lqED\(rq) - * - 64: Enable/Disable function key macros * - 128: Enable/Disable enforcement of UNIX98 * \(lq/bin/sh\(rq emulation for operating system command * executions diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 96254a9..1581d98 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -1545,7 +1545,7 @@ teco_interface_blocking_getch(void) * escape sequences. */ #ifdef NCURSES_UNIX - keypad(teco_interface.cmdline_window, teco_ed & TECO_ED_FNKEYS); + keypad(teco_interface.cmdline_window, TRUE); #endif /* no special <CTRL/C> handling */ @@ -1585,6 +1585,8 @@ teco_interface_event_loop_iter(void) static gchar keybuf[4]; static gint keybuf_i = 0; + GError **error = &teco_interface.event_loop_error; + gint key = g_queue_is_empty(teco_interface.input_queue) ? teco_interface_blocking_getch() : GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue)); @@ -1613,24 +1615,24 @@ teco_interface_event_loop_iter(void) * backspace. * In SciTECO backspace is normalized to ^H. */ - if (!teco_cmdline_keypress_wc(TECO_CTL_KEY('H'), - &teco_interface.event_loop_error)) + if (!teco_cmdline_keymacro_c(TECO_CTL_KEY('H'), error)) return; break; case KEY_ENTER: case '\r': case '\n': - if (!teco_cmdline_keypress_wc('\n', &teco_interface.event_loop_error)) + if (!teco_cmdline_keymacro_c('\n', error)) return; break; /* * Function key macros - * FIXME: What about keyname()? + * + * FIXME: Perhaps support everything returned by keyname()? */ #define FN(KEY) \ case KEY_##KEY: \ - if (!teco_cmdline_fnmacro(#KEY, &teco_interface.event_loop_error)) \ + if (!teco_cmdline_keymacro(#KEY, -1, error)) \ return; \ break #define FNS(KEY) FN(KEY); FN(S##KEY) @@ -1640,9 +1642,8 @@ teco_interface_event_loop_iter(void) gchar macro_name[3+1]; g_snprintf(macro_name, sizeof(macro_name), - "F%d", key - KEY_F0); - if (!teco_cmdline_fnmacro(macro_name, - &teco_interface.event_loop_error)) + "F%d", key - KEY_F0); + if (!teco_cmdline_keymacro(macro_name, -1, error)) return; break; } @@ -1662,6 +1663,7 @@ teco_interface_event_loop_iter(void) */ default: if (key > 0xFF) + /* unhandled function key */ return; /* @@ -1669,12 +1671,22 @@ teco_interface_event_loop_iter(void) * a widechar version of Curses. */ keybuf[keybuf_i++] = key; - gunichar cp = g_utf8_get_char_validated(keybuf, keybuf_i); + gsize len = keybuf_i; + gunichar cp = g_utf8_get_char_validated(keybuf, len); if (keybuf_i >= sizeof(keybuf) || cp != (gunichar)-2) keybuf_i = 0; - if ((gint32)cp < 0 || - !teco_cmdline_keypress_wc(cp, &teco_interface.event_loop_error)) + if ((gint32)cp < 0) + /* incomplete or invalid */ return; + switch (teco_cmdline_keymacro(keybuf, len, error)) { + case TECO_KEYMACRO_ERROR: + return; + case TECO_KEYMACRO_SUCCESS: + break; + case TECO_KEYMACRO_UNDEFINED: + if (!teco_cmdline_keypress(keybuf, len, error)) + return; + } } teco_interface_refresh(); diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index 9c1ce6a..f19c76c 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -927,19 +927,19 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) switch (event->keyval) { case GDK_KEY_Escape: - if (!teco_cmdline_keypress_wc('\e', error)) + if (!teco_cmdline_keymacro_c('\e', error)) return FALSE; break; case GDK_KEY_BackSpace: - if (!teco_cmdline_keypress_wc(TECO_CTL_KEY('H'), error)) + if (!teco_cmdline_keymacro_c(TECO_CTL_KEY('H'), error)) return FALSE; break; case GDK_KEY_Tab: - if (!teco_cmdline_keypress_wc('\t', error)) + if (!teco_cmdline_keymacro_c('\t', error)) return FALSE; break; case GDK_KEY_Return: - if (!teco_cmdline_keypress_wc('\n', error)) + if (!teco_cmdline_keymacro_c('\n', error)) return FALSE; break; @@ -948,12 +948,12 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) */ #define FN(KEY, MACRO) \ case GDK_KEY_##KEY: \ - if (!teco_cmdline_fnmacro(#MACRO, error)) \ + if (!teco_cmdline_keymacro(#MACRO, -1, error)) \ return FALSE; \ break #define FNS(KEY, MACRO) \ case GDK_KEY_##KEY: \ - if (!teco_cmdline_fnmacro(event->state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, error)) \ + if (!teco_cmdline_keymacro(event->state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, -1, error)) \ return FALSE; \ break FN(Down, DOWN); FN(Up, UP); @@ -965,8 +965,8 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) gchar macro_name[3+1]; g_snprintf(macro_name, sizeof(macro_name), - "F%d", event->keyval - GDK_KEY_F1 + 1); - if (!teco_cmdline_fnmacro(macro_name, error)) + "F%d", event->keyval - GDK_KEY_F1 + 1); + if (!teco_cmdline_keymacro(macro_name, -1, error)) return FALSE; break; } @@ -994,13 +994,35 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK) { gchar c = teco_interface_get_ansi_key(event); if (c) { - if (!teco_cmdline_keypress_wc(TECO_CTL_KEY(g_ascii_toupper(c)), error)) + if (!teco_cmdline_keymacro_c(TECO_CTL_KEY(g_ascii_toupper(c)), error)) return FALSE; break; } } /* + * First look up a key macro. + * Only if it's undefined, we try to automatically find an ANSI key. + * On the downside, this means we cannot define key macros for dead keys + * or keys that require some sort of input method editing. + * + * FIXME: This might be a good reason to be able to disable the + * automatic ANSIfication, as we could look up the key macro in + * teco_interface_cmdline_commit_cb(). + */ + gunichar cp = gdk_keyval_to_unicode(event->keyval); + if (cp) { + char buf[6]; + gsize len = g_unichar_to_utf8(cp, buf); + teco_keymacro_status_t rc = teco_cmdline_keymacro(buf, len, error); + if (rc == TECO_KEYMACRO_ERROR) + return FALSE; + if (rc == TECO_KEYMACRO_SUCCESS) + break; + g_assert(rc == TECO_KEYMACRO_UNDEFINED); + } + + /* * If the current state is case-insensitive, it is a command name - * which consists only of ANSI letters - we try to * accept non-ANSI letters as well. @@ -1010,10 +1032,11 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) * within Q-Register specs as well. * Unfortunately, Q-Reg specs and string building can be nested * indefinitely. - * This would effectively require a new is_case_sensitive_cb(). + * This would effectively require a new keymacro_mask_cb(). */ - if (teco_cmdline.machine.parent.current->is_case_insensitive || - teco_cmdline.machine.expectstring.machine.parent.current->is_case_insensitive) + if ((teco_cmdline.machine.parent.current->keymacro_mask | + teco_cmdline.machine.expectstring.machine.parent.current->keymacro_mask) & + TECO_KEYMACRO_MASK_CASEINSENSITIVE) teco_interface_get_ansi_key(event); /* @@ -1029,7 +1052,6 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) } teco_interface_refresh(teco_interface_current_view != last_view); - return TRUE; } diff --git a/src/parser.h b/src/parser.h index fe7f559..0303bae 100644 --- a/src/parser.h +++ b/src/parser.h @@ -108,10 +108,11 @@ typedef gboolean (*teco_state_process_edit_cmd_cb_t)(teco_machine_t *ctx, teco_m gunichar key, GError **error); typedef enum { - TECO_FNMACRO_MASK_START = (1 << 0), - TECO_FNMACRO_MASK_STRING = (1 << 1), - TECO_FNMACRO_MASK_DEFAULT = ~((1 << 2)-1) -} teco_fnmacro_mask_t; + TECO_KEYMACRO_MASK_START = (1 << 0), + TECO_KEYMACRO_MASK_STRING = (1 << 1), + TECO_KEYMACRO_MASK_CASEINSENSITIVE = (1 << 2), + TECO_KEYMACRO_MASK_DEFAULT = ~((1 << 3)-1) +} teco_keymacro_mask_t; /** * A teco_machine_t state. @@ -184,28 +185,19 @@ struct teco_state_t { /** * Whether this state is a start state (ie. not within any * escape sequence etc.). - * This is separate of TECO_FNMACRO_MASK_START which is set + * This is separate of TECO_KEYMACRO_MASK_START which is set * only in the main machine's start states. */ bool is_start : 1; /** - * Whether this state accepts case insensitive characters, - * ie. is part of a command name, that can be case folded. - * This is also used to determine which state accepts only - * ANSI characters. - * @fixme But it should be callback to detect all - * string building constructs nested in Q-Reg specs. - */ - bool is_case_insensitive : 1; - /** - * Function key macro mask. + * Key macro mask. * This is not a bitmask since it is compared with values set * from TECO, so the bitorder needs to be defined. * * @fixme If we intend to "forward" masks from other state machines like * teco_machine_stringbuilding_t, this should probably be a callback. */ - teco_fnmacro_mask_t fnmacro_mask : 8; + teco_keymacro_mask_t keymacro_mask : 8; /** * Additional state-dependent callbacks and settings. @@ -245,7 +237,7 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent .end_of_macro_cb = teco_state_end_of_macro, \ .process_edit_cmd_cb = teco_state_process_edit_cmd, \ .is_start = FALSE, \ - .fnmacro_mask = TECO_FNMACRO_MASK_DEFAULT, \ + .keymacro_mask = TECO_KEYMACRO_MASK_DEFAULT, \ ##__VA_ARGS__ \ } @@ -268,7 +260,7 @@ gboolean teco_state_caseinsensitive_process_edit_cmd(teco_machine_t *ctx, teco_m */ #define TECO_DEFINE_STATE_CASEINSENSITIVE(NAME, ...) \ TECO_DEFINE_STATE(NAME, \ - .is_case_insensitive = TRUE, \ + .keymacro_mask = TECO_KEYMACRO_MASK_CASEINSENSITIVE, \ .process_edit_cmd_cb = teco_state_caseinsensitive_process_edit_cmd, \ ##__VA_ARGS__ \ ) @@ -552,7 +544,7 @@ gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco .refresh_cb = (teco_state_refresh_cb_t)teco_state_expectstring_refresh, \ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \ teco_state_expectstring_process_edit_cmd, \ - .fnmacro_mask = TECO_FNMACRO_MASK_STRING, \ + .keymacro_mask = TECO_KEYMACRO_MASK_STRING, \ .expectstring.string_building = TRUE, \ .expectstring.last = TRUE, \ .expectstring.process_cb = NULL, /* do nothing */ \ diff --git a/src/sciteco.h b/src/sciteco.h index 02eed97..894bb90 100644 --- a/src/sciteco.h +++ b/src/sciteco.h @@ -89,7 +89,7 @@ enum { TECO_ED_AUTOCASEFOLD = (1 << 3), TECO_ED_AUTOEOL = (1 << 4), TECO_ED_HOOKS = (1 << 5), - TECO_ED_FNKEYS = (1 << 6), + //TECO_ED_MOUSEKEY = (1 << 6), TECO_ED_SHELLEMU = (1 << 7), TECO_ED_XTERM_CLIPBOARD = (1 << 8) }; |