diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cmdline.c | 260 | ||||
| -rw-r--r-- | src/cmdline.h | 52 | ||||
| -rw-r--r-- | src/core-commands.c | 40 | ||||
| -rw-r--r-- | src/core-commands.h | 1 | ||||
| -rw-r--r-- | src/doc.c | 3 | ||||
| -rw-r--r-- | src/file-utils.c | 13 | ||||
| -rw-r--r-- | src/file-utils.h | 4 | ||||
| -rw-r--r-- | src/interface-curses/curses-info-popup.c | 32 | ||||
| -rw-r--r-- | src/interface-curses/interface.c | 344 | ||||
| -rw-r--r-- | src/interface-gtk/interface.c | 162 | ||||
| -rw-r--r-- | src/interface.h | 3 | ||||
| -rw-r--r-- | src/lexer.c | 40 | ||||
| -rw-r--r-- | src/lexer.h | 14 | ||||
| -rw-r--r-- | src/main.c | 1 | ||||
| -rw-r--r-- | src/sciteco.h | 3 | ||||
| -rw-r--r-- | src/stdio-commands.c | 2 | ||||
| -rw-r--r-- | src/symbols.c | 21 | ||||
| -rw-r--r-- | src/view.c | 28 |
18 files changed, 515 insertions, 508 deletions
diff --git a/src/cmdline.c b/src/cmdline.c index 089bd7a..e1a4628 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -61,11 +61,49 @@ int malloc_trim(size_t pad); #define TECO_DEFAULT_BREAK_CHARS " \t\v\r\n\f<>,;@" -teco_cmdline_t teco_cmdline = {}; +/** Style used for the asterisk at the beginning of the command line */ +#define STYLE_ASTERISK 64 -/** Last terminated command line */ +teco_cmdline_t teco_cmdline = { + .height = 1 +}; + +/** + * Last terminated command line. + * This is not a teco_doc_scintilla_t since we have to return it as a string + * at the end of the day. + */ static teco_string_t teco_last_cmdline = {NULL, 0}; +void +teco_cmdline_init(void) +{ + teco_cmdline.view = teco_view_new(); + teco_view_setup(teco_cmdline.view); + + teco_cmdline_ssm(SCI_SETUNDOCOLLECTION, FALSE, 0); + teco_cmdline_ssm(SCI_SETVSCROLLBAR, FALSE, 0); + teco_cmdline_ssm(SCI_STYLESETBOLD, STYLE_ASTERISK, TRUE); + teco_cmdline_ssm(SCI_SETMARGINTYPEN, 1, SC_MARGIN_TEXT); + teco_cmdline_ssm(SCI_MARGINSETSTYLE, 0, STYLE_ASTERISK); + teco_cmdline_ssm(SCI_SETMARGINWIDTHN, 1, + teco_cmdline_ssm(SCI_TEXTWIDTH, STYLE_ASTERISK, (sptr_t)"*")); + /* NOTE: might not work on all UIs */ + teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_STRIKE); + teco_cmdline_ssm(SCI_INDICSETFORE, INDICATOR_RUBBEDOUT, + teco_cmdline_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + + /* single line mode - EOL characters won't break the line */ + teco_cmdline_ssm(SCI_SETLINEENDTYPESALLOWED, SC_LINE_END_TYPE_NONE, 0); + /* render tabs as "TAB" without indentation */ + teco_cmdline_ssm(SCI_SETTABDRAWMODE, SCTD_CONTROLCHAR, 0); + + /* + * FIXME: Something resets the margin text, so we have to set it last. + */ + teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*"); +} + /** * Insert string into command line and execute * it immediately. @@ -83,40 +121,49 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) g_auto(teco_string_t) old_cmdline = {NULL, 0}; gsize repl_pc = 0; - teco_cmdline.machine.macro_pc = teco_cmdline.pc = teco_cmdline.effective_len; + gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_cmdline.machine.macro_pc = teco_cmdline.pc = effective_len; + + const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); - if (len <= teco_cmdline.str.len - teco_cmdline.effective_len && - !teco_string_cmp(&src, teco_cmdline.str.data + teco_cmdline.effective_len, len)) { - teco_cmdline.effective_len += len; + if (len <= macro_len - effective_len && + !teco_string_cmp(&src, macro + effective_len, len)) { + /* extend effective command line from rubbed out part */ + teco_cmdline_ssm(SCI_GOTOPOS, effective_len+len, 0); } else { - if (teco_cmdline.effective_len < teco_cmdline.str.len) + /* discard rubbed out part of the command line */ + if (effective_len < macro_len) /* * Automatically disable immediate editing modifier. * FIXME: Should we show a message as when pressing ^G? */ teco_cmdline.modifier_enabled = FALSE; - teco_cmdline.str.len = teco_cmdline.effective_len; - teco_string_append(&teco_cmdline.str, data, len); - teco_cmdline.effective_len = teco_cmdline.str.len; + teco_cmdline_ssm(SCI_DELETERANGE, effective_len, macro_len - effective_len); + teco_cmdline_ssm(SCI_ADDTEXT, len, (sptr_t)data); + + /* the pointer shouldn't have changed... */ + macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); } + effective_len += len; /* * Parse/execute characters, one at a time so * undo tokens get emitted for the corresponding characters. */ - while (teco_cmdline.pc < teco_cmdline.effective_len) { + while (teco_cmdline.pc < effective_len) { g_autoptr(GError) tmp_error = NULL; - if (!teco_machine_main_step(&teco_cmdline.machine, teco_cmdline.str.data, - teco_cmdline.pc+1, &tmp_error)) { + if (!teco_machine_main_step(&teco_cmdline.machine, macro, teco_cmdline.pc+1, &tmp_error)) { if (g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_CMDLINE)) { /* * Result of command line replacement (}): - * Exchange command lines, avoiding deep copying + * Exchange command lines */ teco_qreg_t *cmdline_reg = teco_qreg_table_find(&teco_qreg_table_globals, "\e", 1); - teco_string_t new_cmdline; + g_auto(teco_string_t) new_cmdline = {NULL, 0}; if (!cmdline_reg->vtable->get_string(cmdline_reg, &new_cmdline.data, &new_cmdline.len, NULL, error)) @@ -127,16 +174,26 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) * new command line. This avoids unnecessary rubouts * and insertions when the command line is updated. */ - teco_cmdline.pc = teco_string_diff(&teco_cmdline.str, new_cmdline.data, new_cmdline.len); + teco_cmdline.pc = teco_string_diff(&new_cmdline, macro, effective_len); teco_undo_pop(teco_cmdline.pc); + /* + * We don't replace the command line's document, since that would + * reset the line end type and other configurable settings. + * Also, we don't clear the document to avoid unnecessary restylings + * if syntax highlighting is enabled on the command line. + */ g_assert(old_cmdline.len == 0); - old_cmdline = teco_cmdline.str; - teco_cmdline.str = new_cmdline; - teco_cmdline.effective_len = new_cmdline.len; + teco_string_init(&old_cmdline, macro, effective_len); + teco_cmdline_ssm(SCI_DELETERANGE, teco_cmdline.pc, + old_cmdline.len-teco_cmdline.pc); + teco_cmdline_ssm(SCI_ADDTEXT, new_cmdline.len-teco_cmdline.pc, + (sptr_t)new_cmdline.data+teco_cmdline.pc); + + macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + macro_len = effective_len = new_cmdline.len; teco_cmdline.machine.macro_pc = repl_pc = teco_cmdline.pc; - continue; } @@ -148,17 +205,26 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) /* * Error during command-line replacement. * Replay previous command-line. - * This avoids deep copying. + * The commands leading up to the failed replacement + * will be left rubbed out. */ teco_undo_pop(repl_pc); - teco_string_clear(&teco_cmdline.str); - teco_cmdline.str = old_cmdline; + /* + * May cause restyling of the command lines, + * but that's probably okay - it's just a fallback. + */ + teco_cmdline_ssm(SCI_CLEARALL, 0, 0); + teco_cmdline_ssm(SCI_ADDTEXT, old_cmdline.len, (sptr_t)old_cmdline.data); + teco_string_clear(&old_cmdline); memset(&old_cmdline, 0, sizeof(old_cmdline)); teco_cmdline.machine.macro_pc = teco_cmdline.pc = repl_pc; - /* rubout cmdline replacement command */ - teco_cmdline.effective_len--; + /* rub out cmdline replacement command */ + teco_cmdline_ssm(SCI_GOTOPOS, --effective_len, 0); + + macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); continue; } } @@ -177,14 +243,18 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) static gboolean teco_cmdline_rubin(GError **error) { - if (!teco_cmdline.str.len) - return TRUE; - - const gchar *start, *end, *next; - start = teco_cmdline.str.data+teco_cmdline.effective_len; - end = teco_cmdline.str.data+teco_cmdline.str.len; - next = g_utf8_find_next_char(start, end) ? : end; - return teco_cmdline_insert(start, next-start, error); + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); + gsize pos = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); + gchar buf[4+1]; + struct Sci_TextRangeFull range = { + .chrg = {pos, MIN(macro_len, pos+sizeof(buf)-1)}, + .lpstrText = buf + }; + gsize len = teco_cmdline_ssm(SCI_GETTEXTRANGEFULL, 0, (sptr_t)&range); + + const gchar *end = buf+len; + const gchar *next = g_utf8_find_next_char(buf, end) ? : end; + return teco_cmdline_insert(buf, next-buf, error); } /** @@ -219,7 +289,7 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error) */ teco_interface_msg_clear(); - gsize start_pc = teco_cmdline.effective_len; + gsize start_pc = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); for (guint i = 0; i < len; i = g_utf8_next_char(data+i) - data) { gunichar chr = g_utf8_get_char(data+i); @@ -246,9 +316,9 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error) * up until the insertion point. */ teco_undo_pop(start_pc); - teco_cmdline.effective_len = start_pc; + teco_cmdline_ssm(SCI_GOTOPOS, start_pc, 0); /* program counter could be messed up */ - teco_cmdline.machine.macro_pc = teco_cmdline.effective_len; + teco_cmdline.machine.macro_pc = start_pc; #ifdef HAVE_MALLOC_TRIM /* @@ -297,12 +367,16 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error) g_array_remove_range(teco_loop_stack, 0, teco_loop_stack->len); teco_string_clear(&teco_last_cmdline); - teco_last_cmdline = teco_cmdline.str; + teco_last_cmdline.len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_last_cmdline.data = g_malloc(teco_last_cmdline.len + 1); + teco_cmdline_ssm(SCI_GETTEXT, teco_last_cmdline.len, + (sptr_t)teco_last_cmdline.data); /* * FIXME: Preserve the command line after the $$. + * This would be useful for command line editing macros. + * Perhaps just call teco_cmdline_insert(). */ - memset(&teco_cmdline.str, 0, sizeof(teco_cmdline.str)); - teco_cmdline.effective_len = 0; + teco_cmdline_ssm(SCI_CLEARALL, 0, 0); #ifdef HAVE_MALLOC_TRIM /* see above */ @@ -317,10 +391,8 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error) start_pc = 0; } - /* - * Echo command line - */ - teco_interface_cmdline_update(&teco_cmdline); + teco_cmdline_update(); + return TRUE; } @@ -377,20 +449,48 @@ teco_cmdline_keymacro(const gchar *name, gssize name_len, GError **error) static void teco_cmdline_rubout(void) { - const gchar *p; - p = g_utf8_find_prev_char(teco_cmdline.str.data, - teco_cmdline.str.data+teco_cmdline.effective_len); - if (p) { - teco_cmdline.effective_len = p - teco_cmdline.str.data; - teco_undo_pop(teco_cmdline.effective_len); + gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); + gssize p = teco_view_glyphs2bytes_relative(teco_cmdline.view, effective_len, -1); + if (p >= 0) { + teco_cmdline_ssm(SCI_GOTOPOS, p, 0); + teco_undo_pop(p); } } -static void TECO_DEBUG_CLEANUP +/** + * Update the command line, i.e. prepare it for displaying. + * + * This updates the indicators and scrolls the caret, which isn't done every time + * we touch the command line itself. + */ +void +teco_cmdline_update(void) +{ + /* + * FIXME: Perhaps this can be avoided completely by updating the + * indicators in teco_cmdline_insert(). + */ + gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); + teco_cmdline_ssm(SCI_SETINDICATORCURRENT, INDICATOR_RUBBEDOUT, 0); + teco_cmdline_ssm(SCI_INDICATORCLEARRANGE, 0, macro_len); + teco_cmdline_ssm(SCI_INDICATORFILLRANGE, effective_len, macro_len - effective_len); + + teco_cmdline_ssm(SCI_SCROLLCARET, 0, 0); + + /* + * FIXME: This gets reset repeatedly. + * Setting it once per keypress however means you can no longer customize + * the margin text. + */ + teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*"); +} + +void teco_cmdline_cleanup(void) { teco_machine_main_clear(&teco_cmdline.machine); - teco_string_clear(&teco_cmdline.str); + teco_view_free(teco_cmdline.view); teco_string_clear(&teco_last_cmdline); } @@ -448,11 +548,12 @@ teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent_ctx, gun if (teco_cmdline.modifier_enabled) { /* reinsert construct */ + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); do { if (!teco_cmdline_rubin(error)) return FALSE; } while (!ctx->current->is_start && - teco_cmdline.effective_len < teco_cmdline.str.len); + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len); } else { /* rubout construct */ do @@ -508,6 +609,9 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa case TECO_CTL_KEY('W'): /* rubout/reinsert command */ teco_interface_popup_clear(); + const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); + /* * This mimics the behavior of the `Y` command, * so it also rubs out no-op commands. @@ -517,9 +621,8 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa /* reinsert command */ /* @ and : are not separate states, but practically belong to the command */ while (ctx->parent.current->is_start && - teco_cmdline.effective_len < teco_cmdline.str.len && - (teco_cmdline.str.data[teco_cmdline.effective_len] == ':' || - teco_cmdline.str.data[teco_cmdline.effective_len] == '@')) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && + strchr(":@", macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]) != NULL) if (!teco_cmdline_rubin(error)) return FALSE; @@ -527,11 +630,11 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa if (!teco_cmdline_rubin(error)) return FALSE; } while (!ctx->parent.current->is_start && - teco_cmdline.effective_len < teco_cmdline.str.len); + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len); while (ctx->parent.current->is_start && - teco_cmdline.effective_len < teco_cmdline.str.len && - teco_is_noop(teco_cmdline.str.data[teco_cmdline.effective_len])) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && + teco_is_noop(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) if (!teco_cmdline_rubin(error)) return FALSE; @@ -540,8 +643,8 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa /* rubout command */ while (ctx->parent.current->is_start && - teco_cmdline.effective_len > 0 && - teco_is_noop(teco_cmdline.str.data[teco_cmdline.effective_len-1])) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) > 0 && + teco_is_noop(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); do @@ -555,7 +658,7 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa */ while (ctx->parent.current->is_start && (ctx->flags.modifier_at || ctx->flags.modifier_colon) && - teco_cmdline.effective_len > 0) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) > 0) teco_cmdline_rubout(); return TRUE; @@ -570,6 +673,9 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * { teco_state_t *current = ctx->parent.current; + const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); + switch (key) { case TECO_CTL_KEY('W'): { /* rubout/reinsert word */ teco_interface_popup_clear(); @@ -587,15 +693,15 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * if (teco_cmdline.modifier_enabled) { /* reinsert word chars */ while (ctx->parent.current == current && - teco_cmdline.effective_len < teco_cmdline.str.len && - teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len])) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && + teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) if (!teco_cmdline_rubin(error)) return FALSE; /* reinsert non-word chars */ while (ctx->parent.current == current && - teco_cmdline.effective_len < teco_cmdline.str.len && - !teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len])) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && + !teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) if (!teco_cmdline_rubin(error)) return FALSE; @@ -609,7 +715,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * * a result string even in parse-only mode. */ if (ctx->result && ctx->result->len > 0) { - gboolean is_wordchar = teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len-1]); + gboolean is_wordchar = teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]); teco_cmdline_rubout(); if (ctx->parent.current != current) { /* rub out string building command */ @@ -625,13 +731,13 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * */ if (!is_wordchar) { while (ctx->result->len > 0 && - !teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len-1])) + !teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); } /* rubout word chars */ while (ctx->result->len > 0 && - teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len-1])) + teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); return TRUE; @@ -648,8 +754,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * if (teco_cmdline.modifier_enabled) { /* reinsert string */ - while (ctx->parent.current == current && - teco_cmdline.effective_len < teco_cmdline.str.len) + while (ctx->parent.current == current && teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len) if (!teco_cmdline_rubin(error)) return FALSE; @@ -827,18 +932,21 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t case TECO_CTL_KEY('W'): /* rubout/reinsert file names including directories */ teco_interface_popup_clear(); + const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); + if (teco_cmdline.modifier_enabled) { /* reinsert one level of file name */ while (stringbuilding_ctx->parent.current == stringbuilding_current && - teco_cmdline.effective_len < teco_cmdline.str.len && - !G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len])) + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && + !G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) if (!teco_cmdline_rubin(error)) return FALSE; /* reinsert final directory separator */ if (stringbuilding_ctx->parent.current == stringbuilding_current && - teco_cmdline.effective_len < teco_cmdline.str.len && - G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len]) && + teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && + G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]) && !teco_cmdline_rubin(error)) return FALSE; @@ -847,12 +955,12 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t if (ctx->expectstring.string.len > 0) { /* rubout directory separator */ - if (G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len-1])) + if (G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); /* rubout one level of file name */ while (ctx->expectstring.string.len > 0 && - !G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len-1])) + !G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); return TRUE; diff --git a/src/cmdline.h b/src/cmdline.h index abe9b53..9123358 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -19,10 +19,13 @@ #include <glib.h> #include "sciteco.h" -#include "string-utils.h" #include "parser.h" +#include "view.h" #include "undo.h" +/** Indicator number used for the rubbed out part of the command line */ +#define INDICATOR_RUBBEDOUT (INDICATOR_CONTAINER+0) + typedef struct { /** * State machine used for interactive mode (commandline macro). @@ -34,16 +37,17 @@ typedef struct { teco_machine_main_t machine; /** - * String containing the current command line - * (both effective and rubbed out). - */ - teco_string_t str; - /** - * Effective command line length. - * The length of the rubbed out part of the command line - * is (teco_cmdline.str.len - teco_cmdline.effective_len). + * Command-line Scintilla view. + * It's document contains the current command line macro. + * The current position (cursor) marks the end of the + * "effective" command line, while everything afterwards + * is the rubbed out part of the command line. + * The rubbed out part should be highlighted with an indicator. */ - gsize effective_len; + teco_view_t *view; + + /** Height of the command line view in lines */ + guint height; /** Program counter within the command-line macro */ gsize pc; @@ -60,6 +64,30 @@ typedef struct { extern teco_cmdline_t teco_cmdline; +void teco_cmdline_init(void); + +static inline sptr_t +teco_cmdline_ssm(unsigned int iMessage, uptr_t wParam, sptr_t lParam) +{ + return teco_view_ssm(teco_cmdline.view, iMessage, wParam, lParam); +} + +/** + * Update scroll beavior on command line after window resizes. + * + * This should ensure that the caret jumps to the middle of the command line. + * + * @param width Window (command line view) width in pixels or columns. + * + * @fixme + * On the other hand this limits how you can customize the scroll behavior. + */ +static inline void +teco_cmdline_resized(guint width) +{ + teco_cmdline_ssm(SCI_SETXCARETPOLICY, CARET_SLOP | CARET_EVEN, width/2); +} + gboolean teco_cmdline_keypress(const gchar *data, gsize len, GError **error); typedef enum { @@ -84,6 +112,10 @@ teco_cmdline_keymacro_c(gchar key, GError **error) return TRUE; } +void teco_cmdline_update(void); + +void teco_cmdline_cleanup(void); + /* * Command states */ diff --git a/src/core-commands.c b/src/core-commands.c index 653a40f..e0b2f89 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -551,9 +551,11 @@ teco_state_start_cmdline_push(teco_machine_main_t *ctx, GError **error) !teco_qreg_table_edit_name(&teco_qreg_table_globals, "\e", 1, error)) return; + const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_CLEARALL, 0, 0); - teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)teco_cmdline.str.data); + teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)macro); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); /* @@ -771,8 +773,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) * for beginnings of command-lines? * It could also be used for a corresponding KEYMACRO mask. */ - if (teco_cmdline.effective_len == 1 && teco_cmdline.str.data[0] == '*') + if (teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) == 1 && + teco_cmdline_ssm(SCI_GETCHARAT, 0, 0) == '*') return &teco_state_save_cmdline; + /* treat as an operator */ break; case '<': @@ -2013,6 +2017,13 @@ TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control); * by the \(lqNerd Fonts\(rq project. * Changes to this flag in interactive mode may not become * effective immediately. + * .IP 1024: + * If set the default clipboard register \(lq~\(rq will refer + * to the primary clipboard (\(lq~P\(rq) instead of the + * clipboard selection (\(lq~C\(rq). + * .IP 2048: + * Enable/Disable redirection of Scintilla messages (\fBES\fP) + * to the command line's Scintilla view. * * The features controlled thus are discribed in other sections * of this manual. @@ -2135,8 +2146,11 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * The column after the last horizontal movement. * This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal * setting \fBSCI_CHOOSECARETX\fP. - * Unless most other settings, this is on purpose not restored on rubout, - * so it "survives" command line replacements. + * Unlike most other settings, this is on purpose not restored on rubout, + * so it \(lqsurvives\(rq command line replacements. + * .IP 5: + * Height of the command line view in lines. + * Must not be smaller than 1. * . * .IP -1: * Type of the last mouse event (\fBread-only\fP). @@ -2189,7 +2203,8 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) EJ_BUFFERS, EJ_MEMORY_LIMIT, EJ_INIT_COLOR, - EJ_CARETX + EJ_CARETX, + EJ_CMDLINE_HEIGHT }; static teco_int_t caret_x = 0; @@ -2226,9 +2241,20 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) break; case EJ_CARETX: + /* DON'T undo on rubout */ caret_x = value; break; + case EJ_CMDLINE_HEIGHT: + if (value < 1 || value > G_MAXUINT) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Invalid command line height %" TECO_INT_FORMAT " " + "for <EJ>", value); + return; + } + teco_undo_guint(teco_cmdline.height) = value; + break; + default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Cannot set property %" TECO_INT_FORMAT " " @@ -2284,6 +2310,10 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) teco_expressions_push(caret_x); break; + case EJ_CMDLINE_HEIGHT: + teco_expressions_push(teco_cmdline.height); + break; + default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Invalid property %" TECO_INT_FORMAT " " diff --git a/src/core-commands.h b/src/core-commands.h index cb28dce..4cc8747 100644 --- a/src/core-commands.h +++ b/src/core-commands.h @@ -125,6 +125,7 @@ TECO_DECLARE_STATE(teco_state_insert_indent); teco_state_t *teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error); TECO_DECLARE_STATE(teco_state_start); +TECO_DECLARE_STATE(teco_state_control); TECO_DECLARE_STATE(teco_state_escape); TECO_DECLARE_STATE(teco_state_ctlc); TECO_DECLARE_STATE(teco_state_ctlc_control); @@ -205,7 +205,8 @@ teco_doc_get_string(teco_doc_t *ctx, gchar **str, gsize *outlen, guint *codepage gsize len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0); if (str) { *str = g_malloc(len + 1); - teco_view_ssm(teco_qreg_view, SCI_GETTEXT, len + 1, (sptr_t)*str); + /* null-terminates the string */ + teco_view_ssm(teco_qreg_view, SCI_GETTEXT, len, (sptr_t)*str); } if (outlen) *outlen = len; diff --git a/src/file-utils.c b/src/file-utils.c index 75bcb48..db06ff3 100644 --- a/src/file-utils.c +++ b/src/file-utils.c @@ -109,6 +109,14 @@ teco_file_set_attributes(const gchar *filename, teco_file_attributes_t attrs) #ifdef G_OS_UNIX +/* + * NOTE: This version does not resolve symlinks to non-existing paths. + * It could be improved by repeating readlink() and g_canonicalize_filename(), + * but it would require glib v2.58.0. + * Alternatively we could also iteratively resolve all path components. + * Currently, we simply do not rely on successful canonicalization of + * yet non-existing paths. + */ gchar * teco_file_get_absolute_path(const gchar *path) { @@ -137,13 +145,12 @@ teco_file_is_visible(const gchar *path) #if GLIB_CHECK_VERSION(2,58,0) /* - * FIXME: This should perhaps be preferred on any platform. - * But it will complicate preprocessing. + * NOTE: Does not resolve symlinks. */ gchar * teco_file_get_absolute_path(const gchar *path) { - return g_canonicalize_filename(path, NULL); + return path ? g_canonicalize_filename(path, NULL) : NULL; } #else /* !GLIB_CHECK_VERSION(2,58,0) */ diff --git a/src/file-utils.h b/src/file-utils.h index 12a9b83..9a2f8d6 100644 --- a/src/file-utils.h +++ b/src/file-utils.h @@ -32,9 +32,7 @@ void teco_file_set_attributes(const gchar *filename, teco_file_attributes_t attr /** * Get absolute/full version of a possibly relative path. * The path is tried to be canonicalized so it does - * not contain relative components. - * Works with existing and non-existing paths (in the latter case, - * heuristics may be applied). + * not contain relative components and symlinks. * Depending on platform and existence of the path, * canonicalization might fail, but the path returned is * always absolute. diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c index 332d434..c51a99b 100644 --- a/src/interface-curses/curses-info-popup.c +++ b/src/interface-curses/curses-info-popup.c @@ -26,6 +26,7 @@ #include "list.h" #include "string-utils.h" #include "interface.h" +#include "cmdline.h" #include "curses-utils.h" #include "curses-info-popup.h" #include "curses-icons.h" @@ -71,7 +72,6 @@ teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_ static void teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) { - int cols = getmaxx(stdscr); /**! screen width */ int pad_lines; /**! pad height */ gint pad_cols; /**! entry columns */ gint pad_colwidth; /**! width per entry column */ @@ -82,10 +82,10 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) * Otherwise 2 characters after the entry. */ gint reserve = teco_ed & TECO_ED_ICONS ? 2+1 : 2; - pad_colwidth = MIN(ctx->longest + reserve, cols - 2); + pad_colwidth = MIN(ctx->longest + reserve, COLS - 2); /* pad_cols = floor((cols - 2) / pad_colwidth) */ - pad_cols = (cols - 2) / pad_colwidth; + pad_cols = (COLS - 2) / pad_colwidth; /* pad_lines = ceil(length / pad_cols) */ pad_lines = (ctx->length+pad_cols-1) / pad_cols; @@ -96,7 +96,7 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) * it will be drawn into the popup window which has left * and right borders. */ - ctx->pad = newpad(pad_lines, cols - 2); + ctx->pad = newpad(pad_lines, COLS - 2); /* * NOTE: attr could contain A_REVERSE on monochrome terminals, @@ -157,9 +157,6 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) /* nothing to display */ return; - int lines, cols; /* screen dimensions */ - getmaxyx(stdscr, lines, cols); - if (ctx->window) delwin(ctx->window); @@ -171,10 +168,10 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) * Popup window can cover all but one screen row. * Another row is reserved for the top border. */ - gint popup_lines = MIN(pad_lines + 1, lines - 1); + gint popup_lines = MIN(pad_lines + 1, LINES - teco_cmdline.height); /* window covers message, scintilla and info windows */ - ctx->window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0); + ctx->window = newwin(popup_lines, 0, LINES - teco_cmdline.height - popup_lines, 0); wattrset(ctx->window, attr); @@ -188,7 +185,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) copywin(ctx->pad, ctx->window, ctx->pad_first_line, 0, - 1, 1, popup_lines - 1, cols - 2, FALSE); + 1, 1, popup_lines - 1, COLS - 2, FALSE); if (pad_lines <= popup_lines - 1) /* no need for scrollbar */ @@ -200,13 +197,13 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) /* bar_y = floor(pad_first_line/pad_lines * (popup_lines-2)) + 1 */ gint bar_y = ctx->pad_first_line*(popup_lines-2) / pad_lines + 1; - mvwvline(ctx->window, 1, cols-1, ACS_CKBOARD, popup_lines-2); + mvwvline(ctx->window, 1, COLS-1, ACS_CKBOARD, popup_lines-2); /* * We do not use ACS_BLOCK here since it will not * always be drawn as a solid block (e.g. xterm). * Instead, simply draw reverse blanks. */ - wmove(ctx->window, bar_y, cols-1); + wmove(ctx->window, bar_y, COLS-1); wattrset(ctx->window, attr ^ A_REVERSE); wvline(ctx->window, ' ', bar_height); } @@ -227,7 +224,6 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) const teco_string_t * teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x) { - int cols = getmaxx(stdscr); /**! screen width */ gint pad_cols; /**! entry columns */ gint pad_colwidth; /**! width per entry column */ @@ -240,10 +236,10 @@ teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x) * Otherwise 2 characters after the entry. */ gint reserve = teco_ed & TECO_ED_ICONS ? 2+1 : 2; - pad_colwidth = MIN(ctx->longest + reserve, cols - 2); + pad_colwidth = MIN(ctx->longest + reserve, COLS - 2); /* pad_cols = floor((cols - 2) / pad_colwidth) */ - pad_cols = (cols - 2) / pad_colwidth; + pad_cols = (COLS - 2) / pad_colwidth; gint cur_col = 0; for (teco_stailq_entry_t *cur = ctx->list.first; cur != NULL; cur = cur->next) { @@ -265,9 +261,8 @@ teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x) void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx) { - gint lines = getmaxy(stdscr); gint pad_lines = getmaxy(ctx->pad); - gint popup_lines = MIN(pad_lines + 1, lines - 1); + gint popup_lines = MIN(pad_lines + 1, LINES - teco_cmdline.height); /* progress scroll position */ ctx->pad_first_line += popup_lines - 1; @@ -281,9 +276,8 @@ teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx) void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta) { - gint lines = getmaxy(stdscr); gint pad_lines = getmaxy(ctx->pad); - gint popup_lines = MIN(pad_lines + 1, lines - 1); + gint popup_lines = MIN(pad_lines + 1, LINES - teco_cmdline.height); ctx->pad_first_line = MAX(ctx->pad_first_line+delta, 0); if (pad_lines - ctx->pad_first_line < popup_lines - 1) diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 6c8c812..8f41f2a 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -166,19 +166,91 @@ static gint teco_interface_blocking_getch(void); #define COLOR_LCYAN COLOR_LIGHT(COLOR_CYAN) #define COLOR_LWHITE COLOR_LIGHT(COLOR_WHITE) +static struct { + /** + * Mapping of foreground and background curses color tuples + * (encoded into a pointer) to a color pair number. + */ + GHashTable *pair_table; + + /** + * Mapping of the first 16 curses color codes (that may or may not + * correspond with the standard terminal color codes) to + * Scintilla-compatible RGB values (red is LSB) to initialize after + * Curses startup. + * Negative values mean no color redefinition (keep the original + * palette entry). + */ + gint32 color_table[16]; + + /** + * Mapping of the first 16 curses color codes to their + * original values for restoring them on shutdown. + * Unfortunately, this may not be supported on all + * curses ports, so this array may be unused. + */ + struct { + gshort r, g, b; + } orig_color_table[16]; + + int stdin_orig, stdout_orig, stderr_orig; + SCREEN *screen; + FILE *screen_tty; + + WINDOW *info_window; + enum { + TECO_INFO_TYPE_BUFFER = 0, + TECO_INFO_TYPE_QREG + } info_type; + teco_string_t info_current; + gboolean info_dirty; + + WINDOW *msg_window; + + /** + * Pad used exclusively for wgetch() as it will not + * result in unwanted wrefresh(). + */ + WINDOW *input_pad; + GQueue *input_queue; + + teco_curses_info_popup_t popup; + gsize popup_prefix_len; + + /** + * GError "thrown" by teco_interface_event_loop_iter(). + * Having this in a variable avoids problems with EMScripten. + */ + GError *event_loop_error; +} teco_interface; + /** - * Returns the curses `COLOR_PAIR` for the given curses foreground and background `COLOR`s. - * This is used simply to enumerate every possible color combination. - * Note: only 256 combinations are possible due to curses portability. + * Returns the curses color pair for the given curses foreground and background colors. + * Initializes a new pair if necessary. + * + * Scinterm no longer initializes all color pairs for all combinations of + * the builtin foreground and background colors. + * Since curses guarantees only 256 color pairs, we cannot do that either. + * Instead we allocate color pairs beginnig at 128 on demand + * (similar to what Scinterm does). * - * @param fg The curses foreground `COLOR`. - * @param bg The curses background `COLOR`. - * @return number for defining a curses `COLOR_PAIR`. + * @param fg curses foreground color + * @param bg curses background color + * @return curses color pair number */ -static inline gshort +static gshort teco_color_pair(gshort fg, gshort bg) { - return bg * (COLORS < 16 ? 8 : 16) + fg + 1; + static gshort last_pair = 127; + + G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint)); + gpointer key = GUINT_TO_POINTER(((guint)fg << 16) | bg); + gpointer value = g_hash_table_lookup(teco_interface.pair_table, key); + if (G_LIKELY(value != NULL)) + return GPOINTER_TO_UINT(value); + init_pair(++last_pair, fg, bg); + g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(last_pair)); + return last_pair; } /** @@ -336,61 +408,6 @@ teco_view_free(teco_view_t *ctx) scintilla_delete(ctx); } -static struct { - /** - * Mapping of the first 16 curses color codes (that may or may not - * correspond with the standard terminal color codes) to - * Scintilla-compatible RGB values (red is LSB) to initialize after - * Curses startup. - * Negative values mean no color redefinition (keep the original - * palette entry). - */ - gint32 color_table[16]; - - /** - * Mapping of the first 16 curses color codes to their - * original values for restoring them on shutdown. - * Unfortunately, this may not be supported on all - * curses ports, so this array may be unused. - */ - struct { - gshort r, g, b; - } orig_color_table[16]; - - int stdin_orig, stdout_orig, stderr_orig; - SCREEN *screen; - FILE *screen_tty; - - WINDOW *info_window; - enum { - TECO_INFO_TYPE_BUFFER = 0, - TECO_INFO_TYPE_QREG - } info_type; - teco_string_t info_current; - gboolean info_dirty; - - WINDOW *msg_window; - - WINDOW *cmdline_window, *cmdline_pad; - guint cmdline_len, cmdline_rubout_len; - - /** - * Pad used exclusively for wgetch() as it will not - * result in unwanted wrefresh(). - */ - WINDOW *input_pad; - GQueue *input_queue; - - teco_curses_info_popup_t popup; - gsize popup_prefix_len; - - /** - * GError "thrown" by teco_interface_event_loop_iter(). - * Having this in a variable avoids problems with EMScripten. - */ - GError *event_loop_error; -} teco_interface; - static void teco_interface_init_color_safe(guint color, guint32 rgb); static void teco_interface_restore_colors(void); @@ -404,7 +421,6 @@ static void teco_interface_resize_all_windows(void); static void teco_interface_set_window_title(const gchar *title); static void teco_interface_draw_info(void); -static void teco_interface_draw_cmdline(void); void teco_interface_init(void) @@ -424,6 +440,15 @@ teco_interface_init(void) */ teco_string_init(&teco_interface.info_current, PACKAGE_NAME, strlen(PACKAGE_NAME)); + teco_cmdline_init(); + /* + * The default INDIC_STRIKE wouldn't be visible. + * Instead we use INDIC_STRAIGHTBOX, which will be rendered as underlined if + * the alpha is 0. + */ + teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_STRAIGHTBOX); + teco_cmdline_ssm(SCI_INDICSETALPHA, INDICATOR_RUBBEDOUT, 0); + /* * On all platforms except Curses/XTerm, it's * safe to initialize the clipboards now. @@ -561,7 +586,7 @@ teco_interface_init_color(guint color, guint32 rgb) ((color & 0x1) << 2) | ((color & 0x4) >> 2); #endif - if (teco_interface.cmdline_window) { + if (teco_interface.input_pad) { /* interactive mode */ if (!can_change_color()) return; @@ -717,6 +742,9 @@ teco_interface_init_interactive(GError **error) teco_interface_init_screen(); + teco_interface.pair_table = g_hash_table_new(g_direct_hash, g_direct_equal); + start_color(); + /* * On UNIX terminals, the escape key is usually * delivered as the escape character even though function @@ -768,8 +796,12 @@ teco_interface_init_interactive(GError **error) leaveok(stdscr, TRUE); teco_interface.info_window = newwin(1, 0, 0, 0); - teco_interface.msg_window = newwin(1, 0, LINES - 2, 0); - teco_interface.cmdline_window = newwin(0, 0, LINES - 1, 0); + teco_interface.msg_window = newwin(1, 0, LINES - teco_cmdline.height - 1, 0); + + WINDOW *cmdline_win = teco_view_get_window(teco_cmdline.view); + wresize(cmdline_win, teco_cmdline.height, COLS); + mvwin(cmdline_win, LINES - teco_cmdline.height, 0); + teco_cmdline_resized(COLS); teco_interface.input_pad = newpad(1, 1); /* @@ -861,39 +893,37 @@ teco_interface_restore_batch(void) #endif /* - * cmdline_window determines whether we're in batch mode. + * input_pad determines whether we're in batch mode. */ - if (teco_interface.cmdline_window) { - delwin(teco_interface.cmdline_window); - teco_interface.cmdline_window = NULL; + if (teco_interface.input_pad) { + delwin(teco_interface.input_pad); + teco_interface.input_pad = NULL; } } static void teco_interface_resize_all_windows(void) { - int lines, cols; /* screen dimensions */ - - getmaxyx(stdscr, lines, cols); - - wresize(teco_interface.info_window, 1, cols); + wresize(teco_interface.info_window, 1, COLS); wresize(teco_view_get_window(teco_interface_current_view), - lines - 3, cols); - wresize(teco_interface.msg_window, 1, cols); - mvwin(teco_interface.msg_window, lines - 2, 0); - wresize(teco_interface.cmdline_window, 1, cols); - mvwin(teco_interface.cmdline_window, lines - 1, 0); + LINES - 2 - teco_cmdline.height, COLS); + wresize(teco_interface.msg_window, 1, COLS); + mvwin(teco_interface.msg_window, LINES - 1 - teco_cmdline.height, 0); + + WINDOW *cmdline_win = teco_view_get_window(teco_cmdline.view); + wresize(cmdline_win, teco_cmdline.height, COLS); + mvwin(cmdline_win, LINES - teco_cmdline.height, 0); + teco_cmdline_resized(COLS); teco_interface_draw_info(); teco_interface_msg_clear(); /* FIXME: use saved message */ teco_interface_popup_clear(); - teco_interface_draw_cmdline(); } void teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) { - if (!teco_interface.cmdline_window) { /* batch mode */ + if (!teco_interface.input_pad) { /* batch mode */ teco_interface_stdio_msg(type, str, len); return; } @@ -936,7 +966,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) void teco_interface_msg_clear(void) { - if (!teco_interface.cmdline_window) /* batch mode */ + if (!teco_interface.input_pad) /* batch mode */ return; short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); @@ -950,7 +980,7 @@ teco_interface_msg_clear(void) teco_int_t teco_interface_getch(gboolean widechar) { - if (!teco_interface.cmdline_window) /* batch mode */ + if (!teco_interface.input_pad) /* batch mode */ return teco_interface_stdio_getch(widechar); teco_interface_refresh(FALSE); @@ -996,7 +1026,7 @@ teco_interface_show_view(teco_view_t *view) { teco_interface_current_view = view; - if (!teco_interface.cmdline_window) /* batch mode */ + if (!teco_interface.input_pad) /* batch mode */ return; WINDOW *current_view_win = teco_view_get_window(teco_interface_current_view); @@ -1005,9 +1035,7 @@ teco_interface_show_view(teco_view_t *view) * screen size might have changed since * this view's WINDOW was last active */ - int lines, cols; /* screen dimensions */ - getmaxyx(stdscr, lines, cols); - wresize(current_view_win, lines - 3, cols); + wresize(current_view_win, LINES - 2 - teco_cmdline.height, COLS); /* Set up window position: never changes */ mvwin(current_view_win, 1, 0); } @@ -1194,114 +1222,6 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer) /* NOTE: drawn in teco_interface_event_loop_iter() */ } -void -teco_interface_cmdline_update(const teco_cmdline_t *cmdline) -{ - /* - * Especially important on PDCurses, which can crash - * in newpad() when run with --fake-cmdline. - */ - if (!teco_interface.cmdline_window) /* batch mode */ - return; - - /* - * Replace entire pre-formatted command-line. - * We don't know if it is similar to the last one, - * so resizing makes no sense. - * We approximate the size of the new formatted command-line, - * wasting a few bytes for control characters and - * multi-byte Unicode sequences. - */ - if (teco_interface.cmdline_pad) - delwin(teco_interface.cmdline_pad); - - int max_cols = 1; - for (guint i = 0; i < cmdline->str.len; i++) - max_cols += TECO_IS_CTL(cmdline->str.data[i]) ? 3 : 1; - teco_interface.cmdline_pad = newpad(1, max_cols); - - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - wattrset(teco_interface.cmdline_pad, teco_color_attr(fg, bg)); - - /* format effective command line */ - teco_interface.cmdline_len = - teco_curses_format_str(teco_interface.cmdline_pad, - cmdline->str.data, cmdline->effective_len, -1); - - /* - * A_BOLD should result in either a bold font or a brighter - * color both on 8 and 16 color terminals. - * This is not quite color-scheme-agnostic, but works - * with both the `terminal` and `solarized` themes. - * This problem will be gone once we use a Scintilla view - * as command line, since we can then define a style - * for rubbed out parts of the command line which will - * be user-configurable. - * The attributes, supported by the terminal can theoretically - * be queried with term_attrs(). - */ - wattron(teco_interface.cmdline_pad, A_UNDERLINE | A_BOLD); - - /* - * Format rubbed-out command line. - * NOTE: This formatting will never be truncated since we're - * writing into the pad which is large enough. - */ - teco_interface.cmdline_rubout_len = - teco_curses_format_str(teco_interface.cmdline_pad, cmdline->str.data + cmdline->effective_len, - cmdline->str.len - cmdline->effective_len, -1); - - /* - * Highlight cursor after effective command line - * FIXME: This should use SCI_GETCARETFORE(). - */ - attr_t attr = A_NORMAL; - short pair = 0; - if (teco_interface.cmdline_rubout_len) { - wmove(teco_interface.cmdline_pad, 0, teco_interface.cmdline_len); - wattr_get(teco_interface.cmdline_pad, &attr, &pair, NULL); - wchgat(teco_interface.cmdline_pad, 1, - (attr & (A_UNDERLINE | A_REVERSE)) ^ A_REVERSE, pair, NULL); - } else { - teco_interface.cmdline_len++; - wattr_get(teco_interface.cmdline_pad, &attr, &pair, NULL); - wattr_set(teco_interface.cmdline_pad, (attr & ~(A_UNDERLINE | A_BOLD)) ^ A_REVERSE, pair, NULL); - waddch(teco_interface.cmdline_pad, ' '); - } - - teco_interface_draw_cmdline(); -} - -static void -teco_interface_draw_cmdline(void) -{ - /* total width available for command line */ - guint total_width = getmaxx(teco_interface.cmdline_window) - 1; - - /* beginning of command line to show */ - guint disp_offset = teco_interface.cmdline_len - - MIN(teco_interface.cmdline_len, - total_width/2 + teco_interface.cmdline_len % MAX(total_width/2, 1)); - /* - * length of command line to show - * - * NOTE: we do not use getmaxx(cmdline_pad) here since it may be - * larger than the text the pad contains. - */ - guint disp_len = MIN(total_width, teco_interface.cmdline_len + - teco_interface.cmdline_rubout_len - disp_offset); - - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - - wattrset(teco_interface.cmdline_window, teco_color_attr(fg, bg)); - mvwaddch(teco_interface.cmdline_window, 0, 0, '*' | A_BOLD); - teco_curses_clrtobot(teco_interface.cmdline_window); - copywin(teco_interface.cmdline_pad, teco_interface.cmdline_window, - 0, disp_offset, 0, 1, 0, disp_len, FALSE); -} - #if PDCURSES /* @@ -1771,7 +1691,7 @@ void teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize name_len, gboolean highlight) { - if (teco_interface.cmdline_window) + if (teco_interface.input_pad) /* interactive mode */ teco_curses_info_popup_add(&teco_interface.popup, type, name, name_len, highlight); } @@ -1779,7 +1699,7 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize void teco_interface_popup_show(gsize prefix_len) { - if (!teco_interface.cmdline_window) + if (!teco_interface.input_pad) /* batch mode */ return; @@ -1793,7 +1713,7 @@ teco_interface_popup_show(gsize prefix_len) void teco_interface_popup_scroll(void) { - if (!teco_interface.cmdline_window) + if (!teco_interface.input_pad) /* batch mode */ return; @@ -1880,15 +1800,13 @@ teco_interface_is_interrupted(void) void teco_interface_refresh(gboolean force) { - if (!teco_interface.cmdline_window) + if (!teco_interface.input_pad) /* batch mode */ return; #ifdef NETBSD_CURSES /* works around crashes in doupdate() */ - gint y, x; - getmaxyx(stdscr, y, x); - if (G_UNLIKELY(x <= 1 || y <= 1)) + if (G_UNLIKELY(COLS <= 1 || LINES <= 1)) return; #endif @@ -1905,7 +1823,7 @@ teco_interface_refresh(gboolean force) wnoutrefresh(teco_interface.info_window); teco_view_noutrefresh(teco_interface_current_view); wnoutrefresh(teco_interface.msg_window); - wnoutrefresh(teco_interface.cmdline_window); + teco_view_noutrefresh(teco_cmdline.view); teco_curses_info_popup_noutrefresh(&teco_interface.popup); doupdate(); } @@ -1952,7 +1870,7 @@ teco_interface_process_mevent(MEVENT *event, GError **error) teco_interface_popup_clear(); teco_interface_msg_clear(); - teco_interface_cmdline_update(&teco_cmdline); + teco_cmdline_update(); } return TRUE; @@ -2136,6 +2054,7 @@ teco_interface_event_loop_iter(void) const teco_view_t *last_view = teco_interface_current_view; sptr_t last_vpos = teco_interface_ssm(SCI_GETFIRSTVISIBLELINE, 0, 0); + guint last_cmdline_height = teco_cmdline.height; switch (key) { case ERR: @@ -2262,6 +2181,10 @@ teco_interface_event_loop_iter(void) } } + if (G_UNLIKELY(teco_cmdline.height != last_cmdline_height)) + /* command line height was changed with h,5EJ */ + teco_interface_resize_all_windows(); + /* * Scintilla has been patched to avoid any automatic scrolling since that * has been benchmarked to be a very costly operation. @@ -2286,8 +2209,6 @@ teco_interface_event_loop(GError **error) if (!teco_interface_init_interactive(error)) return FALSE; - static const teco_cmdline_t empty_cmdline; // FIXME - teco_interface_cmdline_update(&empty_cmdline); teco_interface_msg_clear(); teco_interface_ssm(SCI_SCROLLCARET, 0, 0); /* @@ -2342,10 +2263,6 @@ teco_interface_cleanup(void) teco_string_clear(&teco_interface.info_current); if (teco_interface.input_queue) g_queue_free(teco_interface.input_queue); - if (teco_interface.cmdline_window) - delwin(teco_interface.cmdline_window); - if (teco_interface.cmdline_pad) - delwin(teco_interface.cmdline_pad); if (teco_interface.msg_window) delwin(teco_interface.msg_window); if (teco_interface.input_pad) @@ -2370,4 +2287,7 @@ teco_interface_cleanup(void) close(teco_interface.stderr_orig); if (teco_interface.stdout_orig >= 0) close(teco_interface.stdout_orig); + + if (teco_interface.pair_table) + g_hash_table_destroy(teco_interface.pair_table); } diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index dcf3660..7aa9797 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -84,14 +84,6 @@ static gchar teco_interface_get_ansi_key(GdkEventKey *event); /** printf() format for CSS RGB colors given as guint32 */ #define CSS_COLOR_FORMAT "#%06" G_GINT32_MODIFIER "X" -/** Style used for the asterisk at the beginning of the command line */ -#define STYLE_ASTERISK 16 - -/** Indicator number used for control characters in the command line */ -#define INDIC_CONTROLCHAR (INDIC_CONTAINER+0) -/** Indicator number used for the rubbed out part of the command line */ -#define INDIC_RUBBEDOUT (INDIC_CONTAINER+1) - /** Convert Scintilla-style BGR color triple to RGB. */ static inline guint32 teco_bgr2rgb(guint32 bgr) @@ -125,7 +117,6 @@ static struct { GtkWidget *message_bar_widget; GtkWidget *message_widget; - teco_view_t *cmdline_view; GtkIMContext *input_method; GtkWidget *popup_widget; @@ -227,7 +218,7 @@ teco_interface_init(void) /* * Overlay widget will allow overlaying the Scintilla view * and message widgets with the info popup. - * Therefore overlay_vbox (containing the view and popup) + * Therefore overlay_vbox (containing the view and message line) * will be the main child of the overlay. */ GtkWidget *overlay_widget = gtk_overlay_new(); @@ -286,23 +277,9 @@ teco_interface_init(void) gtk_container_add(GTK_CONTAINER(overlay_widget), overlay_vbox); gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0); - teco_interface.cmdline_view = teco_view_new(); - teco_view_setup(teco_interface.cmdline_view); - teco_view_ssm(teco_interface.cmdline_view, SCI_SETUNDOCOLLECTION, FALSE, 0); - teco_view_ssm(teco_interface.cmdline_view, SCI_SETVSCROLLBAR, FALSE, 0); - teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINTYPEN, 1, SC_MARGIN_TEXT); - teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETSTYLE, 0, STYLE_ASTERISK); - teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINWIDTHN, 1, - teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTWIDTH, STYLE_ASTERISK, (sptr_t)"*")); - teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*"); - /* only required as long as we avoid ordinary character representations */ - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_CONTROLCHAR, INDIC_ROUNDBOX); - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETALPHA, INDIC_CONTROLCHAR, 128); - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_RUBBEDOUT, INDIC_STRIKE); - /* we will forward key events, so the view should only react to text insertion */ - teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALLCMDKEYS, 0, 0); - - GtkWidget *cmdline_widget = GTK_WIDGET(teco_interface.cmdline_view); + teco_cmdline_init(); + + GtkWidget *cmdline_widget = GTK_WIDGET(teco_cmdline.view); gtk_widget_set_name(cmdline_widget, "sciteco-cmdline"); g_signal_connect(cmdline_widget, "size-allocate", G_CALLBACK(teco_interface_cmdline_size_allocate_cb), NULL); @@ -337,10 +314,6 @@ teco_interface_init(void) */ gtk_widget_set_can_focus(teco_interface.message_widget, FALSE); gtk_widget_set_can_focus(teco_interface.info_name_widget, FALSE); - - teco_cmdline_t empty_cmdline; - memset(&empty_cmdline, 0, sizeof(empty_cmdline)); - teco_interface_cmdline_update(&empty_cmdline); } static void @@ -604,81 +577,6 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer) : TECO_INFO_TYPE_BUFFER; } -/** - * Insert a single character into the command line. - * - * @fixme - * Control characters should be inserted verbatim since the Scintilla - * representations of them should be preferred. - * However, Scintilla would break the line on every CR/LF and there is - * currently no way to prevent this. - * Scintilla needs to be patched. - * - * @see teco_view_set_representations() - * @see teco_curses_format_str() - */ -static void -teco_interface_cmdline_insert_c(gchar chr) -{ - gchar buffer[3+1] = ""; - - /* - * NOTE: This mapping is similar to teco_view_set_representations() - */ - switch (chr) { - case '\e': strcpy(buffer, "$"); break; - case '\r': strcpy(buffer, "CR"); break; - case '\n': strcpy(buffer, "LF"); break; - case '\t': strcpy(buffer, "TAB"); break; - default: - if (TECO_IS_CTL(chr)) { - buffer[0] = '^'; - buffer[1] = TECO_CTL_ECHO(chr); - buffer[2] = '\0'; - } - } - - if (*buffer) { - gsize len = strlen(buffer); - teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, len, (sptr_t)buffer); - teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_CONTROLCHAR, 0); - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE, - teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - len, len); - } else { - teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, 1, (sptr_t)&chr); - } -} - -void -teco_interface_cmdline_update(const teco_cmdline_t *cmdline) -{ - /* - * We don't know if the new command line is similar to - * the old one, so we can just as well rebuild it. - * - * NOTE: teco_view_ssm() already locks the GDK lock. - */ - teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALL, 0, 0); - - /* format effective command line */ - for (guint i = 0; i < cmdline->effective_len; i++) - teco_interface_cmdline_insert_c(cmdline->str.data[i]); - - /* cursor should be after effective command line */ - guint pos = teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0); - teco_view_ssm(teco_interface.cmdline_view, SCI_GOTOPOS, pos, 0); - - /* format rubbed out command line */ - for (guint i = cmdline->effective_len; i < cmdline->str.len; i++) - teco_interface_cmdline_insert_c(cmdline->str.data[i]); - - teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_RUBBEDOUT, 0); - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE, pos, - teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - pos); - - teco_view_ssm(teco_interface.cmdline_view, SCI_SCROLLCARET, 0, 0); -} - static GdkAtom teco_interface_get_selection_by_name(const gchar *name) { @@ -885,40 +783,6 @@ teco_interface_set_css_variables(teco_view_t *view) guint32 calltip_bg_color = teco_view_ssm(view, SCI_STYLEGETBACK, STYLE_CALLTIP, 0); /* - * FIXME: Font and colors of Scintilla views cannot be set via CSS. - * But some day, there will be a way to send messages to the commandline view - * from SciTECO code via ES. - * Configuration will then be in the hands of color schemes. - * - * NOTE: We don't actually know apriori how large the font_size buffer should be, - * but luckily SCI_STYLEGETFONT with a sptr==0 will return only the size. - * This is undocumented in the Scintilla docs. - */ - g_autofree gchar *font_name = g_malloc(teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, 0) + 1); - teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, (sptr_t)font_name); - - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_DEFAULT, default_fg_color); - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_DEFAULT, default_bg_color); - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)font_name); - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETSIZE, STYLE_DEFAULT, - teco_view_ssm(view, SCI_STYLEGETSIZE, STYLE_DEFAULT, 0)); - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLECLEARALL, 0, 0); - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_CALLTIP, calltip_fg_color); - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_CALLTIP, calltip_bg_color); - teco_view_ssm(teco_interface.cmdline_view, SCI_SETCARETFORE, - teco_view_ssm(view, SCI_GETCARETFORE, 0, 0), 0); - /* used for the asterisk at the beginning of the command line */ - teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBOLD, STYLE_ASTERISK, TRUE); - /* used for character representations */ - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_CONTROLCHAR, default_fg_color); - /* used for the rubbed out command line */ - teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_RUBBEDOUT, default_fg_color); - /* this somehow gets reset */ - teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*"); - - guint text_height = teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTHEIGHT, 0, 0); - - /* * Generates a CSS that sets some predefined color variables. * This effectively "exports" Scintilla styles into the CSS * world. @@ -944,12 +808,13 @@ teco_interface_set_css_variables(teco_view_t *view) gtk_css_provider_load_from_data(teco_interface.css_var_provider, css, -1, NULL); /* - * The font and size of the commandline view might have changed, + * The font and size and height of the command-line view might have changed, * so we resize it. * This cannot be done via CSS or Scintilla messages. - * Currently, it is always exactly one line high in order to mimic the Curses UI. */ - gtk_widget_set_size_request(GTK_WIDGET(teco_interface.cmdline_view), -1, text_height); + g_assert(teco_cmdline.height > 0); + gtk_widget_set_size_request(GTK_WIDGET(teco_cmdline.view), -1, + teco_cmdline.height*teco_cmdline_ssm(SCI_TEXTHEIGHT, 0, 0)); } static void @@ -1420,17 +1285,12 @@ teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data) teco_interface_set_cursor(widget, "text"); } -/** - * Called when the commandline widget is resized. - * This should ensure that the caret jumps to the middle of the command line, - * imitating the behaviour of the current Curses command line. - */ +/** Called when the commandline widget is resized */ static void teco_interface_cmdline_size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) { - teco_view_ssm(teco_interface.cmdline_view, SCI_SETXCARETPOLICY, - CARET_SLOP | CARET_EVEN, allocation->width/2); + teco_cmdline_resized(allocation->width); } static gboolean @@ -1606,7 +1466,7 @@ teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpoint !machine->current->insert_completion_cb(machine, &insert, NULL)) return; teco_interface_popup_clear(); - teco_interface_cmdline_update(&teco_cmdline); + teco_cmdline_update(); teco_interface_update(teco_interface_current_view != last_view); } diff --git a/src/interface.h b/src/interface.h index 9531d37..8ab66ef 100644 --- a/src/interface.h +++ b/src/interface.h @@ -118,9 +118,6 @@ void undo__teco_interface_info_update_qreg(const teco_qreg_t *); void undo__teco_interface_info_update_buffer(const teco_buffer_t *); /** @pure */ -void teco_interface_cmdline_update(const teco_cmdline_t *cmdline); - -/** @pure */ gboolean teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error); void teco_interface_undo_set_clipboard(const gchar *name, gchar *str, gsize len); diff --git a/src/lexer.c b/src/lexer.c index 2f43b76..787fe89 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -26,6 +26,7 @@ #include "sciteco.h" #include "view.h" #include "parser.h" +#include "core-commands.h" #include "lexer.h" static teco_style_t @@ -37,16 +38,21 @@ teco_lexer_getstyle(teco_view_t *view, teco_machine_main_t *machine, /* * FIXME: At least this special workaround for numbers might be * unnecessary once we get a special parser state for parsing numbers. - * - * FIXME: What about ^* and ^/? - * They are currently highlighted as commands. */ if (machine->parent.current->keymacro_mask & TECO_KEYMACRO_MASK_START && chr <= 0xFF) { if (g_ascii_isdigit(chr)) style = SCE_SCITECO_NUMBER; - else if (strchr("+-*/#&", chr)) + else if (strchr(",+-*/#&()", chr)) style = SCE_SCITECO_OPERATOR; + } else if (machine->parent.current == &teco_state_control) { + /* + * Two-character operators must always begin with caret + * They get a separate style, so we can extend it back to + * the caret in teco_lexter_step. + */ + if (strchr("*/#", chr)) + style = SCE_SCITECO_OPERATOR2; } /* @@ -174,8 +180,9 @@ teco_lexer_step(teco_view_t *view, teco_machine_main_t *machine, /* * True comments begin with `!*` or `!!`, but only the second character gets * the correct style by default, so we extend it backwards. + * The same is true for two-letter operators. */ - if (style == SCE_SCITECO_COMMENT) + if (style == SCE_SCITECO_COMMENT || style == SCE_SCITECO_OPERATOR2) old_pc--; teco_view_ssm(view, SCI_STARTSTYLING, start+old_pc, 0); @@ -212,22 +219,35 @@ teco_lexer_style(teco_view_t *view, gsize end) gsize start = teco_view_ssm(view, SCI_GETENDSTYLED, 0, 0); guint start_line = teco_view_ssm(view, SCI_LINEFROMPOSITION, start, 0); - gint start_col = 0; /* * The line state stores the laster character (column) in bytes, * that starts from a fresh parser state. * It's -1 if the line does not have a clean parser state. - * Therefore we search for the first line before `start` that has a - * known clean parser state. + * If the cached position on start_line does not fit our needs, + * we backtrack and search in previous lines + * for a known clean parser state. + * + * NOTE: It's crucial to consider the line state of the first possible + * line since we might be styling for a single-line command line view. + * + * FIXME: During rubout of regular commands we will frequently have the + * situation that the cached line state points after the last styled position + * forcing us to restyle the entire command line macro. + * If this turns out to be problematic, we might detect that + * view == teco_cmdline.view and inspect teco_cmdline.machine. */ - if (start_line > 0) { + gint start_col = teco_view_ssm(view, SCI_GETLINESTATE, start_line, 0); + if (start_col > start - teco_view_ssm(view, SCI_POSITIONFROMLINE, start_line, 0)) + /* we are asked to style __before__ the last known start state */ + start_col = -1; + if (start_col < 0 && start_line > 0) { do start_line--; while ((start_col = teco_view_ssm(view, SCI_GETLINESTATE, start_line, 0)) < 0 && start_line > 0); - start_col = MAX(start_col, 0); } + start_col = MAX(start_col, 0); start = teco_view_ssm(view, SCI_POSITIONFROMLINE, start_line, 0) + start_col; g_assert(end > start); diff --git a/src/lexer.h b/src/lexer.h index 2b011be..e91cdd1 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -25,12 +25,14 @@ typedef enum { SCE_SCITECO_DEFAULT = 0, SCE_SCITECO_COMMAND = 1, SCE_SCITECO_OPERATOR = 2, - SCE_SCITECO_QREG = 3, - SCE_SCITECO_STRING = 4, - SCE_SCITECO_NUMBER = 5, - SCE_SCITECO_LABEL = 6, - SCE_SCITECO_COMMENT = 7, - SCE_SCITECO_INVALID = 8 + /** two-character operators */ + SCE_SCITECO_OPERATOR2 = 3, + SCE_SCITECO_QREG = 4, + SCE_SCITECO_STRING = 5, + SCE_SCITECO_NUMBER = 6, + SCE_SCITECO_LABEL = 7, + SCE_SCITECO_COMMENT = 8, + SCE_SCITECO_INVALID = 9 } teco_style_t; void teco_lexer_style(teco_view_t *view, gsize end); @@ -640,6 +640,7 @@ cleanup: teco_qreg_table_clear(&teco_qreg_table_globals); teco_qreg_stack_clear(); teco_view_free(teco_qreg_view); + teco_cmdline_cleanup(); #endif teco_interface_cleanup(); diff --git a/src/sciteco.h b/src/sciteco.h index cc43368..388d6c4 100644 --- a/src/sciteco.h +++ b/src/sciteco.h @@ -96,7 +96,8 @@ enum { TECO_ED_SHELLEMU = (1 << 7), TECO_ED_OSC52 = (1 << 8), TECO_ED_ICONS = (1 << 9), - TECO_ED_CLIP_PRIMARY = (1 << 10) + TECO_ED_CLIP_PRIMARY = (1 << 10), + TECO_ED_MINIBUF_SSM = (1 << 11) }; /* in main.c */ diff --git a/src/stdio-commands.c b/src/stdio-commands.c index 3a1b320..565f442 100644 --- a/src/stdio-commands.c +++ b/src/stdio-commands.c @@ -38,7 +38,7 @@ static inline gboolean teco_cmdline_is_executing(teco_machine_main_t *ctx) { return ctx == &teco_cmdline.machine && - ctx->macro_pc == teco_cmdline.effective_len; + ctx->macro_pc == teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0); } static gboolean is_executing = FALSE; diff --git a/src/symbols.c b/src/symbols.c index b5600e8..9cf1c35 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -38,6 +38,7 @@ #include "undo.h" #include "expressions.h" #include "interface.h" +#include "cmdline.h" #include "symbols.h" teco_symbol_list_t teco_symbol_list_scintilla = {NULL, 0}; @@ -166,6 +167,13 @@ teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, tec * Command states */ +static inline sptr_t +teco_scintilla_ssm(unsigned int iMessage, uptr_t wParam, sptr_t lParam) +{ + return teco_view_ssm(teco_ed & TECO_ED_MINIBUF_SSM ? teco_cmdline.view : teco_interface_current_view, + iMessage, wParam, lParam); +} + /* * FIXME: This state could be static. */ @@ -316,6 +324,13 @@ gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx * second string argument of \fBES\fP, i.e. it allows you * to look up style ids by name. * + * By default Scintilla messages are sent to the current buffer's + * view or the Q-register view \(em there is only one view for + * all Q-registers. + * If bit 11 is set in the \fBED\fP flags, the messages will be + * sent to the command-line view instead, which allows you to + * set up \*(ST syntax highlighting and other styles. + * * .BR Warning : * Almost all Scintilla messages may be dispatched using * this command. @@ -442,10 +457,10 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t * /* * FIXME: Should we cache the name to style id? */ - guint count = teco_interface_ssm(SCI_GETNAMEDSTYLES, 0, 0); + guint count = teco_scintilla_ssm(SCI_GETNAMEDSTYLES, 0, 0); for (guint id = 0; id < count; id++) { gchar style[128] = ""; - teco_interface_ssm(SCI_NAMEOFSTYLE, id, (sptr_t)style); + teco_scintilla_ssm(SCI_NAMEOFSTYLE, id, (sptr_t)style); if (!teco_string_cmp(str, style, strlen(style))) { teco_expressions_push(id); return &teco_state_start; @@ -491,7 +506,7 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t * lParam = v; } - teco_expressions_push(teco_interface_ssm(ctx->scintilla.iMessage, + teco_expressions_push(teco_scintilla_ssm(ctx->scintilla.iMessage, ctx->scintilla.wParam, lParam)); return &teco_state_start; @@ -145,7 +145,14 @@ teco_view_setup(teco_view_t *ctx) TECO_DEFINE_UNDO_CALL(teco_view_ssm, teco_view_t *, unsigned int, uptr_t, sptr_t); -/** @memberof teco_view_t */ +/** + * Configure typical TECO representations for control characters. + * + * You may have to SCI_SETVIEWEOL(TRUE) to see the CR and LF characters. + * In order to see the TAB character use SCI_SETTABDRAWMODE(SCTD_CONTROLCHAR). + * + * @memberof teco_view_t + */ void teco_view_set_representations(teco_view_t *ctx) { @@ -543,16 +550,16 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error) file_stat.st_gid = -1; #endif teco_file_attributes_t attributes = TECO_FILE_INVALID_ATTRIBUTES; + gboolean undo_remove_file = FALSE; if (teco_undo_enabled) { - if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { + undo_remove_file = !g_file_test(filename, G_FILE_TEST_IS_REGULAR); + if (!undo_remove_file) { #ifdef G_OS_UNIX g_stat(filename, &file_stat); #endif attributes = teco_file_get_attributes(filename); teco_make_savepoint(filename); - } else { - teco_undo_remove_file_push(filename); } } @@ -561,6 +568,18 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error) if (!channel) return FALSE; + if (undo_remove_file) { + /* + * The file is new, so has to be removed on undo. + * If `filename` is a symlink, it's crucial to resolve it now, + * since early canonicalization may have failed (for non-existent + * path segments). + * Now, `filename` is guaranteed to exist. + */ + g_autofree gchar *filename_canon = teco_file_get_absolute_path(filename); + teco_undo_remove_file_push(filename_canon); + } + /* * teco_view_save_to_channel() expects a buffered and blocking channel */ @@ -569,6 +588,7 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error) if (!teco_view_save_to_channel(ctx, channel, error)) { g_prefix_error(error, "Error writing file \"%s\": ", filename); + /* file might also be removed (in interactive mode) */ return FALSE; } |
