diff options
-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; |