diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-03-01 17:56:34 +0100 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-03-01 18:20:27 +0100 |
commit | 83ebaea1abc39cae990e0fd22b6e4b428abfa95f (patch) | |
tree | d297dab707836d05b9d46bad621b8e02d6506ab9 /src | |
parent | 68eb8e5c427877abae43f2e2aba7fcca5a3471de (diff) | |
download | sciteco-83ebaea1abc39cae990e0fd22b6e4b428abfa95f.tar.gz |
keep rubbed out command line for later re-insertion and massive Cmdline cleanup/refactoring
* characters rubbed out are not totally removed from the command line,
but only from the *effective* command line.
* The rubbed out command line is displayed after the command line cursor.
On Curses it is grey and underlined.
* When characters are inserted that are on the rubbed out part of the command line,
the cursor simply moves forward.
NOTE: There's currently no immediate editing command for reinserting the
next character/word from the rubbed out command line.
* Characters resulting in errors are no longer simply discarded but rubbed out,
so they will stay in the rubbed out part of the command line, reminding you
which character caused the error.
* Improved Cmdline formatting on Curses UI:
* Asterisk is printed bold
* Control characters are printed in REVERSE style, similar to what
Scinterm does. The controll character formatting has thus been moved
from macro_echo() in cmdline.cpp to the UI implementations.
* Updated the GTK+ UI (UNTESTED): I did only, the most important API
adaptions. The command line still does not use any colors.
* Refactored entire command line handling:
* The command line is now a class (Cmdline), and most functions
in cmdline.cpp have been converted to methods.
* Esp. process_edit_cmd() (now Cmdline::process_edit_cmd()) has been
simplified. There is no longer the possibility of a buffer overflow
because of static insertion buffer sizes
* Cleaned up usage of the cmdline_pos variable (now Cmdline::pc) which
is really a program counter that used a different origin as macro_pc
which was really confusing.
* The new Cmdline class is theoretically 8-bit clean. However all of this
will change again when we introduce Scintilla views for the command line.
* Added 8-bit clean (null-byte aware) versions of QRegisterData::set_string()
and QRegisterData::append_string()
Diffstat (limited to 'src')
-rw-r--r-- | src/cmdline.cpp | 349 | ||||
-rw-r--r-- | src/cmdline.h | 72 | ||||
-rw-r--r-- | src/error.cpp | 12 | ||||
-rw-r--r-- | src/error.h | 9 | ||||
-rw-r--r-- | src/interface-curses.cpp | 133 | ||||
-rw-r--r-- | src/interface-curses.h | 18 | ||||
-rw-r--r-- | src/interface-gtk.cpp | 88 | ||||
-rw-r--r-- | src/interface-gtk.h | 8 | ||||
-rw-r--r-- | src/interface.h | 16 | ||||
-rw-r--r-- | src/parser.cpp | 16 | ||||
-rw-r--r-- | src/parser.h | 5 | ||||
-rw-r--r-- | src/qregisters.cpp | 13 | ||||
-rw-r--r-- | src/qregisters.h | 21 | ||||
-rw-r--r-- | src/undo.cpp | 20 | ||||
-rw-r--r-- | src/undo.h | 8 |
15 files changed, 493 insertions, 295 deletions
diff --git a/src/cmdline.cpp b/src/cmdline.cpp index 7c74db4..b1f3805 100644 --- a/src/cmdline.cpp +++ b/src/cmdline.cpp @@ -44,9 +44,6 @@ namespace SciTECO { -static inline const gchar *process_edit_cmd(gchar key); -static gchar *macro_echo(const gchar *macro); - static gchar *filename_complete(const gchar *filename, gchar completed = ' '); static gchar *symbol_complete(SymbolList &list, const gchar *symbol, gchar completed = ' '); @@ -56,9 +53,11 @@ static const gchar *last_occurrence(const gchar *str, static inline gboolean filename_is_dir(const gchar *filename); static inline gchar derive_dir_separator(const gchar *filename); -gchar *cmdline = NULL; -gint cmdline_pos = 0; -static gchar *last_cmdline = NULL; +/** Current command line. */ +Cmdline cmdline; + +/** Last terminated command line */ +static Cmdline last_cmdline; bool quit_requested = false; @@ -66,14 +65,59 @@ namespace States { StateSaveCmdline save_cmdline; } +#if 0 +Cmdline * +copy(void) const +{ + Cmdline *c = new Cmdline(); + + if (str) + c->str = g_memdup(str, len+rubout_len); + c->len = len; + c->rubout_len = rubout_len; + + return c; +} +#endif + +/** + * Throws a command line based on the command line + * replacement register. + * It is catched by Cmdline::keypress() to actually + * perform the command line update. + */ +void +Cmdline::replace(void) +{ + QRegister *cmdline_reg = QRegisters::globals["\x1B"]; + /* use heap object to avoid copy constructors etc. */ + Cmdline *new_cmdline = new Cmdline(); + + /* FIXME: does not handle null bytes */ + new_cmdline->str = cmdline_reg->get_string(); + new_cmdline->len = strlen(new_cmdline->str); + new_cmdline->rubout_len = 0; + + /* + * Search for first differing character in old and + * new command line. This avoids unnecessary rubouts + * and insertions when the command line is updated. + */ + for (new_cmdline->pc = 0; + new_cmdline->pc < len && new_cmdline->pc < new_cmdline->len && + str[new_cmdline->pc] == new_cmdline->str[new_cmdline->pc]; + new_cmdline->pc++); + + throw new_cmdline; +} + void -cmdline_keypress(gchar key) +Cmdline::keypress(gchar key) { - gchar *old_cmdline = NULL; - gint repl_pos = 0; + Cmdline old_cmdline; - const gchar *insert; - gchar *echo; + gsize old_len = len; + guint repl_pc = 0; /* * Cleanup messages,etc... @@ -81,44 +125,59 @@ cmdline_keypress(gchar key) interface.msg_clear(); /* - * Process immediate editing commands. - * It may clear/hide the popup. + * Process immediate editing commands, inserting + * characters as necessary into the command line. */ - insert = process_edit_cmd(key); + if (process_edit_cmd(key)) + interface.popup_clear(); /* * Parse/execute characters, one at a time so * undo tokens get emitted for the corresponding characters. */ - cmdline_pos = cmdline ? strlen(cmdline)+1 : 1; - String::append(cmdline, insert); + macro_pc = pc = MIN(old_len, len); - while (cmdline[cmdline_pos-1]) { + while (pc < len) { try { - Execute::step(cmdline, cmdline_pos); - } catch (ReplaceCmdline &r) { - undo.pop(r.pos); - - old_cmdline = cmdline; - cmdline = r.new_cmdline; - cmdline_pos = repl_pos = r.pos; - macro_pc = r.pos-1; + Execute::step(str, pc+1); + } catch (Cmdline *new_cmdline) { + /* + * Result of command line replacement (}): + * Exchange command lines, avoiding + * deep copying + */ + undo.pop(new_cmdline->pc); + + old_cmdline = *this; + *this = *new_cmdline; + new_cmdline->str = NULL; + macro_pc = repl_pc = pc; + + delete new_cmdline; continue; } catch (Error &error) { error.add_frame(new Error::ToplevelFrame()); error.display_short(); - if (old_cmdline) { - undo.pop(repl_pos); - - g_free(cmdline); - cmdline = old_cmdline; - cmdline[strlen(cmdline)-1] = '\0'; - old_cmdline = NULL; - cmdline_pos = repl_pos; - macro_pc = repl_pos-1; + if (old_cmdline.str) { + /* + * Error during command-line replacement. + * Replay previous command-line. + * This avoids deep copying. + */ + undo.pop(repl_pc); + + g_free(str); + *this = old_cmdline; + old_cmdline.str = NULL; + macro_pc = pc = repl_pc; + + /* rubout cmdline replacement command */ + len--; + rubout_len++; continue; } + /* * Undo tokens may have been emitted * (or had to be) before the exception @@ -126,156 +185,151 @@ cmdline_keypress(gchar key) * as if the character had never been * inserted. */ - undo.pop(cmdline_pos); - cmdline[cmdline_pos-1] = '\0'; + undo.pop(pc); + rubout_len += len-pc; + len = pc; /* program counter could be messed up */ - macro_pc = cmdline_pos - 1; + macro_pc = len; break; } - cmdline_pos++; + pc++; } - g_free(old_cmdline); - /* * Echo command line */ - echo = macro_echo(cmdline); - interface.cmdline_update(echo); - g_free(echo); + interface.cmdline_update(this); } -static inline const gchar * -process_edit_cmd(gchar key) +void +Cmdline::insert(const gchar *src) { - static gchar insert[255]; - gint cmdline_len = cmdline ? strlen(cmdline) : 0; - bool clear_popup = true; - - insert[0] = key; - insert[1] = '\0'; + size_t src_len = strlen(src); + + if (src_len <= rubout_len && !strncmp(str+len, src, src_len)) { + len += src_len; + rubout_len -= src_len; + } else { + String::append(str, len, src); + len += src_len; + rubout_len = 0; + } +} +bool +Cmdline::process_edit_cmd(gchar key) +{ switch (key) { - case '\b': - if (cmdline_len) { - undo.pop(cmdline_len); - cmdline[cmdline_len - 1] = '\0'; - macro_pc--; - } - *insert = '\0'; + case '\b': /* rubout character */ + if (len) + rubout(); break; - case CTL_KEY('W'): + case CTL_KEY('W'): /* rubout word */ if (States::is_string()) { gchar wchars[interface.ssm(SCI_GETWORDCHARS)]; interface.ssm(SCI_GETWORDCHARS, 0, (sptr_t)wchars); /* rubout non-word chars */ while (strings[0] && strlen(strings[0]) > 0 && - !strchr(wchars, cmdline[macro_pc-1])) - undo.pop(macro_pc--); + !strchr(wchars, str[len-1])) + rubout(); /* rubout word chars */ while (strings[0] && strlen(strings[0]) > 0 && - strchr(wchars, cmdline[macro_pc-1])) - undo.pop(macro_pc--); - } else if (cmdline_len) { + strchr(wchars, str[len-1])) + rubout(); + } else if (len) { do - undo.pop(macro_pc--); + rubout(); while (States::current != &States::start); } - cmdline[macro_pc] = '\0'; - *insert = '\0'; break; - case CTL_KEY('U'): + case CTL_KEY('U'): /* rubout string */ if (States::is_string()) { while (strings[0] && strlen(strings[0]) > 0) - undo.pop(macro_pc--); - cmdline[macro_pc] = '\0'; - *insert = '\0'; + rubout(); + } else { + insert(key); } break; - case CTL_KEY('T'): + case CTL_KEY('T'): /* autocomplete file name */ + /* + * TODO: In insertion commands, we can autocomplete + * the string at the buffer cursor. + */ if (States::is_string()) { - *insert = '\0'; if (interface.popup_is_shown()) { /* cycle through popup pages */ interface.popup_show(); - clear_popup = false; - break; + return false; } const gchar *filename = last_occurrence(strings[0]); gchar *new_chars = filename_complete(filename); - clear_popup = !interface.popup_is_shown(); - - if (new_chars) - g_stpcpy(insert, new_chars); + insert(new_chars); g_free(new_chars); + + if (interface.popup_is_shown()) + return false; + } else { + insert(key); } break; - case '\t': - if (States::is_insertion()) { - if (!interface.ssm(SCI_GETUSETABS)) { - gint len = interface.ssm(SCI_GETTABWIDTH); + case '\t': /* autocomplete symbol or file name */ + if (States::is_insertion() && !interface.ssm(SCI_GETUSETABS)) { + gint spaces = interface.ssm(SCI_GETTABWIDTH); - len -= interface.ssm(SCI_GETCOLUMN, - interface.ssm(SCI_GETCURRENTPOS)) % len; + spaces -= interface.ssm(SCI_GETCOLUMN, + interface.ssm(SCI_GETCURRENTPOS)) % spaces; - memset(insert, ' ', len); - insert[len] = '\0'; - } + while (spaces--) + insert(' '); } else if (States::is_file()) { - *insert = '\0'; if (interface.popup_is_shown()) { /* cycle through popup pages */ interface.popup_show(); - clear_popup = false; - break; + return false; } gchar complete = escape_char == '{' ? ' ' : escape_char; gchar *new_chars = filename_complete(strings[0], complete); - clear_popup = !interface.popup_is_shown(); - - if (new_chars) - g_stpcpy(insert, new_chars); + insert(new_chars); g_free(new_chars); + + if (interface.popup_is_shown()) + return false; } else if (States::current == &States::executecommand) { /* * In the EC command, <TAB> completes files just like ^T * TODO: Implement shell-command completion by iterating * executables in $PATH */ - *insert = '\0'; if (interface.popup_is_shown()) { /* cycle through popup pages */ interface.popup_show(); - clear_popup = false; - break; + return false; } const gchar *filename = last_occurrence(strings[0]); gchar *new_chars = filename_complete(filename); - clear_popup = !interface.popup_is_shown(); - - if (new_chars) - g_stpcpy(insert, new_chars); + insert(new_chars); g_free(new_chars); + + if (interface.popup_is_shown()) + return false; } else if (States::current == &States::scintilla_symbols) { - *insert = '\0'; if (interface.popup_is_shown()) { /* cycle through popup pages */ interface.popup_show(); - clear_popup = false; - break; + return false; } const gchar *symbol = last_occurrence(strings[0], ","); @@ -284,20 +338,19 @@ process_edit_cmd(gchar key) : Symbols::scilexer; gchar *new_chars = symbol_complete(list, symbol, ','); - clear_popup = !interface.popup_is_shown(); - - if (new_chars) - g_stpcpy(insert, new_chars); + insert(new_chars); g_free(new_chars); - } + if (interface.popup_is_shown()) + return false; + } else { + insert(key); + } break; - case '\x1B': + case '\x1B': /* terminate command line */ if (States::current == &States::start && - cmdline && cmdline[cmdline_len - 1] == '\x1B') { - *insert = '\0'; - + str && str[len-1] == '\x1B') { if (Goto::skip_label) { interface.msg(InterfaceCurrent::MSG_ERROR, "Label \"%s\" not found", @@ -316,10 +369,16 @@ process_edit_cmd(gchar key) Goto::table->clear(); expressions.clear(); - g_free(last_cmdline); - last_cmdline = cmdline; - cmdline = NULL; - macro_pc = 0; + last_cmdline = *this; + str = NULL; + len = rubout_len = 0; + + /* + * FIXME: Perhaps to the malloc_trim() here + * instead of in UndoStack::clear() + */ + } else { + insert(key); } break; @@ -332,20 +391,20 @@ process_edit_cmd(gchar key) * terminal (GTK+) */ raise(SIGTSTP); - *insert = '\0'; break; #endif - } - if (clear_popup) - interface.popup_clear(); + default: + insert(key); + } - return insert; + return true; } void -cmdline_fnmacro(const gchar *name) +Cmdline::fnmacro(const gchar *name) { + /* FIXME: check again if function keys are enabled */ gchar macro_name[1 + strlen(name) + 1]; QRegister *reg; @@ -355,7 +414,7 @@ cmdline_fnmacro(const gchar *name) reg = QRegisters::globals[macro_name]; if (reg) { gchar *macro = reg->get_string(); - cmdline_keypress(macro); + keypress(macro); g_free(macro); } } @@ -375,44 +434,6 @@ get_eol(void) } static gchar * -macro_echo(const gchar *macro) -{ - gchar *result, *rp; - - if (!macro) - return g_strdup(""); - - rp = result = (gchar *)g_malloc(strlen(macro)*5 + 1); - - for (const gchar *p = macro; *p; p++) { - switch (*p) { - case '\x1B': - *rp++ = '$'; - break; - case '\r': - rp = g_stpcpy(rp, "<CR>"); - break; - case '\n': - rp = g_stpcpy(rp, "<LF>"); - break; - case '\t': - rp = g_stpcpy(rp, "<TAB>"); - break; - default: - if (IS_CTL(*p)) { - *rp++ = '^'; - *rp++ = CTL_ECHO(*p); - } else { - *rp++ = *p; - } - } - } - *rp = '\0'; - - return result; -} - -static gchar * filename_complete(const gchar *filename, gchar completed) { gchar *dirname; @@ -596,7 +617,7 @@ StateSaveCmdline::got_register(QRegister ®) BEGIN_EXEC(&States::start); reg.undo_set_string(); - reg.set_string(last_cmdline); + reg.set_string(last_cmdline.str, last_cmdline.len); return &States::start; } diff --git a/src/cmdline.h b/src/cmdline.h index 1a254e6..6095cbb 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -20,25 +20,73 @@ #include <glib.h> -#include "sciteco.h" #include "parser.h" #include "qregisters.h" +#include "undo.h" namespace SciTECO { -extern gchar *cmdline; -extern gint cmdline_pos; -extern bool quit_requested; +extern class Cmdline { +public: + /** + * String containing the current command line. + * It is not null-terminated and contains the effective + * command-line up to cmdline_len followed by the recently rubbed-out + * command-line of length cmdline_rubout_len. + */ + gchar *str; + /** Effective command line length */ + gsize len; + /** Length of the rubbed out command line */ + gsize rubout_len; + /** Program counter within the command-line macro */ + guint pc; -void cmdline_keypress(gchar key); -static inline void -cmdline_keypress(const gchar *keys) -{ - while (*keys) - cmdline_keypress(*keys++); -} + Cmdline() : str(NULL), len(0), rubout_len(0), pc(0) {} + inline + ~Cmdline() + { + g_free(str); + } + + inline gchar + operator [](guint i) const + { + return str[i]; + } + + void keypress(gchar key); + inline void + keypress(const gchar *keys) + { + while (*keys) + keypress(*keys++); + } + + void fnmacro(const gchar *name); -void cmdline_fnmacro(const gchar *name); + void replace(void) G_GNUC_NORETURN; + +private: + bool process_edit_cmd(gchar key); + + inline void + rubout(void) + { + undo.pop(--len); + rubout_len++; + } + + void insert(const gchar *src); + inline void + insert(gchar key) + { + gchar src[] = {key, '\0'}; + insert(src); + } +} cmdline; + +extern bool quit_requested; const gchar *get_eol(void); diff --git a/src/error.cpp b/src/error.cpp index 301626b..34819e3 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -25,21 +25,11 @@ #include <glib/gprintf.h> #include "sciteco.h" -#include "qregisters.h" #include "interface.h" -#include "cmdline.h" +#include "error.h" namespace SciTECO { -ReplaceCmdline::ReplaceCmdline() -{ - QRegister *cmdline_reg = QRegisters::globals["\x1B"]; - - new_cmdline = cmdline_reg->get_string(); - for (pos = 0; cmdline[pos] && cmdline[pos] == new_cmdline[pos]; pos++); - pos++; -} - Error::Frame * Error::QRegFrame::copy() const { diff --git a/src/error.h b/src/error.h index 7ee0ac6..c9cad00 100644 --- a/src/error.h +++ b/src/error.h @@ -29,15 +29,6 @@ namespace SciTECO { -/* thrown as exception, executed at cmdline macro level */ -class ReplaceCmdline { -public: - gchar *new_cmdline; - gint pos; - - ReplaceCmdline(); -}; - /* * Thrown as exception to signify that program * should be terminated. diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp index 6d2feb0..1f4506a 100644 --- a/src/interface-curses.cpp +++ b/src/interface-curses.cpp @@ -54,7 +54,7 @@ static void scintilla_notify(Scintilla *sci, int idFrom, #define UNNAMED_FILE "(Unnamed)" #define SCI_COLOR_ATTR(f, b) \ - ((chtype)COLOR_PAIR(SCI_COLOR_PAIR(f, b))) + ((attr_t)COLOR_PAIR(SCI_COLOR_PAIR(f, b))) void ViewCurses::initialize_impl(void) @@ -95,7 +95,6 @@ InterfaceCurses::main_impl(int &argc, char **&argv) msg_window = newwin(1, 0, LINES - 2, 0); cmdline_window = newwin(0, 0, LINES - 1, 0); - cmdline_current = NULL; #ifdef EMSCRIPTEN nodelay(cmdline_window, TRUE); @@ -160,13 +159,13 @@ InterfaceCurses::resize_all_windows(void) draw_info(); msg_clear(); /* FIXME: use saved message */ popup_clear(); - cmdline_update(); + draw_cmdline(); } void InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) { - static const chtype type2attr[] = { + static const attr_t type2attr[] = { SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE), /* MSG_USER */ SCI_COLOR_ATTR(COLOR_BLACK, COLOR_GREEN), /* MSG_INFO */ SCI_COLOR_ATTR(COLOR_BLACK, COLOR_YELLOW), /* MSG_WARNING */ @@ -230,7 +229,7 @@ InterfaceCurses::draw_info(void) } void -InterfaceCurses::info_update_impl(QRegister *reg) +InterfaceCurses::info_update_impl(const QRegister *reg) { g_free(info_current); info_current = g_strdup_printf("%s - <QRegister> %s", PACKAGE_NAME, @@ -240,7 +239,7 @@ InterfaceCurses::info_update_impl(QRegister *reg) } void -InterfaceCurses::info_update_impl(Buffer *buffer) +InterfaceCurses::info_update_impl(const Buffer *buffer) { g_free(info_current); info_current = g_strdup_printf("%s - <Buffer> %s%s", PACKAGE_NAME, @@ -251,27 +250,103 @@ InterfaceCurses::info_update_impl(Buffer *buffer) } void -InterfaceCurses::cmdline_update_impl(const gchar *cmdline) +InterfaceCurses::format_chr(chtype *&target, gchar chr, attr_t attr) { - size_t len; - int half_line = (getmaxx(stdscr) - 2) / 2; - const gchar *line; + /* + * NOTE: This mapping is similar to + * View::set_representations() + */ + switch (chr) { + case '\x1B': /* escape */ + *target++ = '$' | attr | A_REVERSE; + break; + case '\r': + *target++ = 'C' | attr | A_REVERSE; + *target++ = 'R' | attr | A_REVERSE; + break; + case '\n': + *target++ = 'L' | attr | A_REVERSE; + *target++ = 'F' | attr | A_REVERSE; + break; + case '\t': + *target++ = 'T' | attr | A_REVERSE; + *target++ = 'A' | attr | A_REVERSE; + *target++ = 'B' | attr | A_REVERSE; + break; + default: + if (IS_CTL(chr)) { + *target++ = '^' | attr | A_REVERSE; + *target++ = CTL_ECHO(chr) | attr | A_REVERSE; + } else { + *target++ = chr | attr; + } + } +} - if (cmdline) { - g_free(cmdline_current); - cmdline_current = g_strdup(cmdline); +void +InterfaceCurses::cmdline_update_impl(const Cmdline *cmdline) +{ + gsize alloc_len = 1; + chtype *p; + + /* + * Replace entire pre-formatted command-line. + * We don't know if it is similar to the last one, + * so realloc makes no sense. + * We approximate the size of the new formatted command-line, + * wasting a few bytes for control characters. + */ + delete[] cmdline_current; + for (guint i = 0; i < cmdline->len+cmdline->rubout_len; i++) + alloc_len += IS_CTL((*cmdline)[i]) ? 3 : 1; + p = cmdline_current = new chtype[alloc_len]; + + /* format effective command line */ + for (guint i = 0; i < cmdline->len; i++) + format_chr(p, (*cmdline)[i]); + cmdline_len = p - cmdline_current; + + /* + * Format rubbed-out command line. + * AFAIK bold black should be rendered grey by any + * common terminal. + * If not, this problem will be gone once we support + * a Scintilla view command line. + */ + for (guint i = cmdline->len; i < cmdline->len+cmdline->rubout_len; i++) + format_chr(p, (*cmdline)[i], + A_UNDERLINE | A_BOLD | + SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLACK)); + cmdline_rubout_len = p - cmdline_current - cmdline_len; + + /* highlight cursor after effective command line */ + if (cmdline_rubout_len) { + cmdline_current[cmdline_len] &= A_CHARTEXT | A_UNDERLINE; + cmdline_current[cmdline_len] |= A_REVERSE; } else { - cmdline = cmdline_current; + cmdline_current[cmdline_len++] = ' ' | A_REVERSE; } - len = strlen(cmdline); - /* FIXME: optimize */ - line = cmdline + len - MIN(len, half_line + len % half_line); + draw_cmdline(); +} - mvwaddch(cmdline_window, 0, 0, '*'); - waddstr(cmdline_window, line); - waddch(cmdline_window, ' ' | A_REVERSE); - wclrtoeol(cmdline_window); +void +InterfaceCurses::draw_cmdline(void) +{ + /* total width available for command line */ + guint total_width = getmaxx(stdscr) - 1; + /* beginning of command line to show */ + guint disp_offset; + /* length of command line to show */ + guint disp_len; + + disp_offset = cmdline_len - + MIN(cmdline_len, total_width/2 + cmdline_len % (total_width/2)); + disp_len = MIN(total_width, cmdline_len+cmdline_rubout_len - disp_offset); + + werase(cmdline_window); + mvwaddch(cmdline_window, 0, 0, '*' | A_BOLD); + waddchnstr(cmdline_window, cmdline_current+disp_offset, disp_len); } void @@ -430,18 +505,18 @@ event_loop_iter() #endif case 0x7F: /* DEL */ case KEY_BACKSPACE: - cmdline_keypress('\b'); + cmdline.keypress('\b'); break; case KEY_ENTER: case '\r': case '\n': - cmdline_keypress(get_eol()); + cmdline.keypress(get_eol()); break; /* * Function key macros */ -#define FN(KEY) case KEY_##KEY: cmdline_fnmacro(#KEY); break +#define FN(KEY) case KEY_##KEY: cmdline.fnmacro(#KEY); break #define FNS(KEY) FN(KEY); FN(S##KEY) FN(DOWN); FN(UP); FNS(LEFT); FNS(RIGHT); FNS(HOME); @@ -450,7 +525,7 @@ event_loop_iter() g_snprintf(macro_name, sizeof(macro_name), "F%d", key - KEY_F0); - cmdline_fnmacro(macro_name); + cmdline.fnmacro(macro_name); break; } FNS(DC); @@ -468,7 +543,7 @@ event_loop_iter() */ default: if (key <= 0xFF) - cmdline_keypress((gchar)key); + cmdline.keypress((gchar)key); } sigint_occurred = FALSE; @@ -486,6 +561,8 @@ event_loop_iter() void InterfaceCurses::event_loop_impl(void) { + static const Cmdline empty_cmdline; + /* initial refresh */ /* FIXME: this does wrefresh() internally */ current_view->refresh(); @@ -493,7 +570,7 @@ InterfaceCurses::event_loop_impl(void) wnoutrefresh(info_window); msg_clear(); wnoutrefresh(msg_window); - cmdline_update(""); + cmdline_update(&empty_cmdline); wnoutrefresh(cmdline_window); doupdate(); @@ -540,7 +617,7 @@ InterfaceCurses::~InterfaceCurses() g_free(info_current); if (cmdline_window) delwin(cmdline_window); - g_free(cmdline_current); + delete[] cmdline_current; if (msg_window) delwin(msg_window); diff --git a/src/interface-curses.h b/src/interface-curses.h index cab2031..8ee8811 100644 --- a/src/interface-curses.h +++ b/src/interface-curses.h @@ -74,9 +74,12 @@ typedef class InterfaceCurses : public Interface<InterfaceCurses, ViewCurses> { WINDOW *info_window; gchar *info_current; + WINDOW *msg_window; + WINDOW *cmdline_window; - gchar *cmdline_current; + chtype *cmdline_current; + gsize cmdline_len, cmdline_rubout_len; struct Popup { WINDOW *window; @@ -101,7 +104,8 @@ public: info_current(NULL), msg_window(NULL), cmdline_window(NULL), - cmdline_current(NULL) {} + cmdline_current(NULL), + cmdline_len(0), cmdline_rubout_len(0) {} ~InterfaceCurses(); /* implementation of Interface::main() */ @@ -116,11 +120,11 @@ public: void show_view_impl(ViewCurses *view); /* implementation of Interface::info_update() */ - void info_update_impl(QRegister *reg); - void info_update_impl(Buffer *buffer); + void info_update_impl(const QRegister *reg); + void info_update_impl(const Buffer *buffer); /* implementation of Interface::cmdline_update() */ - void cmdline_update_impl(const gchar *cmdline = NULL); + void cmdline_update_impl(const Cmdline *cmdline); /* implementation of Interface::popup_add() */ void popup_add_impl(PopupEntryType type, @@ -144,6 +148,10 @@ private: void resize_all_windows(void); void draw_info(void); + void format_chr(chtype *&target, gchar chr, + attr_t attr = 0); + void draw_cmdline(void); + friend void event_loop_iter(); } InterfaceCurrent; diff --git a/src/interface-gtk.cpp b/src/interface-gtk.cpp index 7931957..e7f6927 100644 --- a/src/interface-gtk.cpp +++ b/src/interface-gtk.cpp @@ -20,6 +20,7 @@ #endif #include <stdarg.h> +#include <string.h> #include <glib.h> #include <glib/gprintf.h> @@ -77,6 +78,7 @@ ViewGtk::initialize_impl(void) void InterfaceGtk::main_impl(int &argc, char **&argv) { + static const Cmdline empty_cmdline; GtkWidget *info_content; gtk_init(&argc, &argv); @@ -109,7 +111,7 @@ InterfaceGtk::main_impl(int &argc, char **&argv) gtk_widget_grab_focus(cmdline_widget); - cmdline_update(""); + cmdline_update(&empty_cmdline); } void @@ -164,7 +166,7 @@ InterfaceGtk::show_view_impl(ViewGtk *view) } void -InterfaceGtk::info_update_impl(QRegister *reg) +InterfaceGtk::info_update_impl(const QRegister *reg) { gchar buf[255]; @@ -174,7 +176,7 @@ InterfaceGtk::info_update_impl(QRegister *reg) } void -InterfaceGtk::info_update_impl(Buffer *buffer) +InterfaceGtk::info_update_impl(const Buffer *buffer) { gchar buf[255]; @@ -185,18 +187,66 @@ InterfaceGtk::info_update_impl(Buffer *buffer) } void -InterfaceGtk::cmdline_update_impl(const gchar *cmdline) +InterfaceGtk::cmdline_insert_chr(gint &pos, gchar chr) { - gint pos = 1; + gchar buffer[5+1]; - if (!cmdline) - /* widget automatically redrawn */ - return; + /* + * NOTE: This mapping is similar to + * View::set_representations() + */ + switch (chr) { + case '\x1B': /* escape */ + strcpy(buffer, "$"); + break; + case '\r': + strcpy(buffer, "<CR>"); + break; + case '\n': + strcpy(buffer, "<LF>"); + break; + case '\t': + strcpy(buffer, "<TAB>"); + break; + default: + if (IS_CTL(chr)) { + buffer[0] = '^'; + buffer[1] = CTL_ECHO(chr); + buffer[2] = '\0'; + } else { + buffer[0] = chr; + buffer[1] = '\0'; + } + } - gtk_entry_set_text(GTK_ENTRY(cmdline_widget), "*"); gtk_editable_insert_text(GTK_EDITABLE(cmdline_widget), - cmdline, -1, &pos); - gtk_editable_set_position(GTK_EDITABLE(cmdline_widget), pos); + buffer, -1, &pos); +} + +void +InterfaceGtk::cmdline_update_impl(const Cmdline *cmdline) +{ + gint pos = 1; + gint cmdline_len; + + /* + * We don't know if the new command line is similar to + * the old one, so we can just as well rebuild it. + */ + gtk_entry_set_text(GTK_ENTRY(cmdline_widget), "*"); + + /* format effective command line */ + for (guint i = 0; i < cmdline->len; i++) + cmdline_insert_chr(pos, (*cmdline)[i]); + /* save end of effective command line */ + cmdline_len = pos; + + /* format rubbed out command line */ + for (guint i = cmdline->len; i < cmdline->len+cmdline->rubout_len; i++) + cmdline_insert_chr(pos, (*cmdline)[i]); + + /* set cursor after effective command line */ + gtk_editable_set_position(GTK_EDITABLE(cmdline_widget), cmdline_len); } void @@ -258,25 +308,25 @@ handle_key_press(bool is_shift, bool is_ctl, guint keyval) { switch (keyval) { case GDK_Escape: - cmdline_keypress('\x1B'); + cmdline.keypress('\x1B'); break; case GDK_BackSpace: - cmdline_keypress('\b'); + cmdline.keypress('\b'); break; case GDK_Tab: - cmdline_keypress('\t'); + cmdline.keypress('\t'); break; case GDK_Return: - cmdline_keypress(get_eol()); + cmdline.keypress(get_eol()); break; /* * Function key macros */ #define FN(KEY, MACRO) \ - case GDK_##KEY: cmdline_fnmacro(#MACRO); break + case GDK_##KEY: cmdline.fnmacro(#MACRO); break #define FNS(KEY, MACRO) \ - case GDK_##KEY: cmdline_fnmacro(is_shift ? "S" #MACRO : #MACRO); break + case GDK_##KEY: cmdline.fnmacro(is_shift ? "S" #MACRO : #MACRO); break FN(Down, DOWN); FN(Up, UP); FNS(Left, LEFT); FNS(Right, RIGHT); FN(KP_Down, DOWN); FN(KP_Up, UP); @@ -287,7 +337,7 @@ handle_key_press(bool is_shift, bool is_ctl, guint keyval) g_snprintf(macro_name, sizeof(macro_name), "F%d", keyval - GDK_F1 + 1); - cmdline_fnmacro(macro_name); + cmdline.fnmacro(macro_name); break; } FNS(Delete, DC); @@ -317,7 +367,7 @@ handle_key_press(bool is_shift, bool is_ctl, guint keyval) if (is_ctl) key = CTL_KEY(g_ascii_toupper(key)); - cmdline_keypress(key); + cmdline.keypress(key); } } } diff --git a/src/interface-gtk.h b/src/interface-gtk.h index a9b57a8..4afac2f 100644 --- a/src/interface-gtk.h +++ b/src/interface-gtk.h @@ -103,11 +103,11 @@ public: void show_view_impl(ViewGtk *view); /* implementation of Interface::info_update() */ - void info_update_impl(QRegister *reg); - void info_update_impl(Buffer *buffer); + void info_update_impl(const QRegister *reg); + void info_update_impl(const Buffer *buffer); /* implementation of Interface::cmdline_update() */ - void cmdline_update_impl(const gchar *cmdline = NULL); + void cmdline_update_impl(const Cmdline *cmdline); /* implementation of Interface::popup_add() */ void popup_add_impl(PopupEntryType type, @@ -138,6 +138,8 @@ public: private: static void widget_set_font(GtkWidget *widget, const gchar *font_name); + + void cmdline_insert_chr(gint &pos, gchar chr); } InterfaceCurrent; } /* namespace SciTECO */ diff --git a/src/interface.h b/src/interface.h index 8f37668..6c847f4 100644 --- a/src/interface.h +++ b/src/interface.h @@ -32,6 +32,7 @@ namespace SciTECO { /* avoid include dependency conflict */ class QRegister; class Buffer; +class Cmdline; extern sig_atomic_t sigint_occurred; /** @@ -170,10 +171,10 @@ class Interface { template <class Type> class UndoTokenInfoUpdate : public UndoTokenWithSize< UndoTokenInfoUpdate<Type> > { - Type *obj; + const Type *obj; public: - UndoTokenInfoUpdate(Type *_obj) + UndoTokenInfoUpdate(const Type *_obj) : obj(_obj) {} void run(void); @@ -257,30 +258,29 @@ public: * by the deriving class. */ inline void - info_update(QRegister *reg) + info_update(const QRegister *reg) { impl().info_update_impl(reg); } inline void - info_update(Buffer *buffer) + info_update(const Buffer *buffer) { impl().info_update_impl(buffer); } inline void - undo_info_update(QRegister *reg) + undo_info_update(const QRegister *reg) { undo.push(new UndoTokenInfoUpdate<QRegister>(reg)); } inline void - undo_info_update(Buffer *buffer) + undo_info_update(const Buffer *buffer) { undo.push(new UndoTokenInfoUpdate<Buffer>(buffer)); } - /* NULL means to redraw the current cmdline if necessary */ inline void - cmdline_update(const gchar *cmdline = NULL) + cmdline_update(const Cmdline *cmdline) { impl().cmdline_update_impl(cmdline); } diff --git a/src/parser.cpp b/src/parser.cpp index e84edb5..0193e19 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -79,13 +79,14 @@ static gint nest_level = 0; gchar *strings[2] = {NULL, NULL}; gchar escape_char = '\x1B'; -/* - * handles all expected exceptions, converting them to - * SciTECO::Error and preparing them for stack frame insertion +/** + * Handles all expected exceptions, converting them to + * SciTECO::Error and preparing them for stack frame insertion. + * This method will only throw SciTECO::Error and + * SciTECO::Cmdline *. */ void Execute::step(const gchar *macro, gint stop_pos) - throw (Error, ReplaceCmdline) { while (macro_pc < stop_pos) { #ifdef DEBUG @@ -698,7 +699,7 @@ StateStart::custom(gchar chr) break; case '*': - if (!g_strcmp0(cmdline, "*")) + if (cmdline.len == 1 && cmdline[0] == '*') /* special save last commandline command */ return &States::save_cmdline; @@ -1028,7 +1029,7 @@ StateStart::custom(gchar chr) interface.ssm(SCI_BEGINUNDOACTION); interface.ssm(SCI_CLEARALL); - interface.ssm(SCI_ADDTEXT, cmdline_pos-1, (sptr_t)cmdline); + interface.ssm(SCI_ADDTEXT, cmdline.pc, (sptr_t)cmdline.str); interface.ssm(SCI_SCROLLCARET); interface.ssm(SCI_ENDUNDOACTION); @@ -1046,7 +1047,8 @@ StateStart::custom(gchar chr) "editing the replacement register"); /* replace cmdline in the outer macro environment */ - throw ReplaceCmdline(); + cmdline.replace(); + /* never reached */ /* * modifiers diff --git a/src/parser.h b/src/parser.h index 4092435..a6a18bb 100644 --- a/src/parser.h +++ b/src/parser.h @@ -20,9 +20,9 @@ #include <glib.h> +#include "sciteco.h" #include "undo.h" #include "error.h" -#include "sciteco.h" namespace SciTECO { @@ -300,8 +300,7 @@ extern gchar *strings[2]; extern gchar escape_char; namespace Execute { - void step(const gchar *macro, gint stop_pos) - throw (Error, ReplaceCmdline); + void step(const gchar *macro, gint stop_pos); void macro(const gchar *macro, bool locals = true); void file(const gchar *filename, bool locals = true); } diff --git a/src/qregisters.cpp b/src/qregisters.cpp index 9cda9ca..615fc77 100644 --- a/src/qregisters.cpp +++ b/src/qregisters.cpp @@ -71,7 +71,7 @@ namespace QRegisters { static QRegister *register_argument = NULL; void -QRegisterData::set_string(const gchar *str) +QRegisterData::set_string(const gchar *str, gsize len) { if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); @@ -80,8 +80,9 @@ QRegisterData::set_string(const gchar *str) string.edit(QRegisters::view); QRegisters::view.ssm(SCI_BEGINUNDOACTION); - QRegisters::view.ssm(SCI_SETTEXT, 0, - (sptr_t)(str ? : "")); + QRegisters::view.ssm(SCI_CLEARALL); + QRegisters::view.ssm(SCI_APPENDTEXT, + len, (sptr_t)(str ? : "")); QRegisters::view.ssm(SCI_ENDUNDOACTION); if (QRegisters::current) @@ -110,7 +111,7 @@ QRegisterData::undo_set_string(void) } void -QRegisterData::append_string(const gchar *str) +QRegisterData::append_string(const gchar *str, gsize len) { /* * NOTE: Will not create undo action @@ -118,7 +119,7 @@ QRegisterData::append_string(const gchar *str) * Also, appending preserves the string's * parameters. */ - if (!str || !*str) + if (!len) return; if (QRegisters::current) @@ -128,7 +129,7 @@ QRegisterData::append_string(const gchar *str) QRegisters::view.ssm(SCI_BEGINUNDOACTION); QRegisters::view.ssm(SCI_APPENDTEXT, - strlen(str), (sptr_t)str); + len, (sptr_t)str); QRegisters::view.ssm(SCI_ENDUNDOACTION); if (QRegisters::current) diff --git a/src/qregisters.h b/src/qregisters.h index 8414439..ff4da87 100644 --- a/src/qregisters.h +++ b/src/qregisters.h @@ -18,6 +18,8 @@ #ifndef __QREGISTERS_H #define __QREGISTERS_H +#include <string.h> + #include <bsd/sys/queue.h> #include <glib.h> @@ -94,9 +96,20 @@ public: return integer; } - virtual void set_string(const gchar *str); + virtual void set_string(const gchar *str, gsize len); + inline void + set_string(const gchar *str) + { + set_string(str, str ? strlen(str) : 0); + } virtual void undo_set_string(void); - virtual void append_string(const gchar *str); + + virtual void append_string(const gchar *str, gsize len); + inline void + append_string(const gchar *str) + { + append_string(str, str ? strlen(str) : 0); + } virtual inline void undo_append_string(void) { @@ -155,9 +168,9 @@ public: tecoInt get_integer(void); - void set_string(const gchar *str) {} + void set_string(const gchar *str, gsize len) {} void undo_set_string(void) {} - void append_string(const gchar *str) {} + void append_string(const gchar *str, gsize len) {} void undo_append_string(void) {} gchar *get_string(void); diff --git a/src/undo.cpp b/src/undo.cpp index c1e842a..21fae61 100644 --- a/src/undo.cpp +++ b/src/undo.cpp @@ -33,18 +33,14 @@ #include "error.h" #include "undo.h" +#define SIZE_TO_MB(SIZE) ((gdouble)(SIZE))/(1024*1024) + namespace SciTECO { //#define DEBUG UndoStack undo; -static inline gdouble -size_to_mb(gsize s) -{ - return ((gdouble)s)/(1024*1024); -} - void UndoStack::set_memory_limit(gsize new_limit) { @@ -61,8 +57,8 @@ UndoStack::set_memory_limit(gsize new_limit) if (memory_usage > new_limit) throw Error("Cannot set undo memory limit (%gmb): " "Current stack too large (%gmb).", - size_to_mb(new_limit), - size_to_mb(memory_usage)); + SIZE_TO_MB(new_limit), + SIZE_TO_MB(memory_usage)); } push_var(memory_limit) = new_limit; @@ -83,7 +79,7 @@ UndoStack::push(UndoToken *token) delete token; throw Error("Undo stack memory limit (%gmb) exceeded. " "See <EJ> command.", - size_to_mb(memory_limit)); + SIZE_TO_MB(memory_limit)); } memory_usage += token_size; @@ -92,14 +88,14 @@ UndoStack::push(UndoToken *token) #ifdef DEBUG g_printf("UNDO PUSH %p\n", token); #endif - token->pos = cmdline_pos; + token->pc = cmdline.pc; SLIST_INSERT_HEAD(&head, token, tokens); } void -UndoStack::pop(gint pos) +UndoStack::pop(gint pc) { - while (!SLIST_EMPTY(&head) && SLIST_FIRST(&head)->pos >= pos) { + while (!SLIST_EMPTY(&head) && SLIST_FIRST(&head)->pc >= pc) { UndoToken *top = SLIST_FIRST(&head); #ifdef DEBUG g_printf("UNDO POP %p\n", top); @@ -41,8 +41,8 @@ public: SLIST_ENTRY(UndoToken) tokens; /** - * Command-line character position corresponding - * to this token. + * Command-line character position (program counter) + * corresponding to this token. * * @todo This wastes memory in macro calls and loops * because all undo tokens will have the same @@ -50,7 +50,7 @@ public: * stack data structure - as a list/array pointing * to undo stacks per character. */ - gint pos; + gint pc; virtual ~UndoToken() {} @@ -240,7 +240,7 @@ public: return push_obj<Type>(variable, variable); } - void pop(gint pos); + void pop(gint pc); void clear(void); } undo; |