diff options
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | doc/sciteco.7.template | 10 | ||||
-rw-r--r-- | src/cmdline.cpp | 80 | ||||
-rw-r--r-- | src/interface-curses.cpp | 109 | ||||
-rw-r--r-- | src/interface-curses.h | 19 | ||||
-rw-r--r-- | src/interface-gtk.h | 7 | ||||
-rw-r--r-- | src/interface.h | 5 |
7 files changed, 162 insertions, 70 deletions
@@ -43,8 +43,6 @@ Features: influence tab-completion and relative file names. Optimizations: - * prevent command-line flickering when using function key macros. - * check if Scinterm refreshing is optimal * refactor search commands (create proper base class) * refactor match-char state machine using MicroStateMachine class * simplify parser (static jump tables are unnecessary!) diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template index bad98cc..d5ef0a9 100644 --- a/doc/sciteco.7.template +++ b/doc/sciteco.7.template @@ -365,8 +365,16 @@ so in a manner similar to Posix shells. Upon first invocation they try to fully or partially complete the file name (or token). If no completion can be performed, the invocation will display a -list of file names (or tokens) beginning with the token to complete +list of file names (or tokens) that begin with the token to complete in \*(ST's popup area. +If the popup area is not large enough to display all possible +completions, this is highlighted by the user interface either +by showing an ellipsis (\(lq...\(rq) in the bottom right corner +of the popup area or by its scrollbars. +If the immediate editing command is invoked again, the next page +of file names or tokens is displayed in the popup area. +I.e. it is possible to cycle through long lists of possible +auto-completions. .LP When completing file names, hidden files are not considered for completion unless a prefix of the hidden file's name has already diff --git a/src/cmdline.cpp b/src/cmdline.cpp index 0907106..b0f5155 100644 --- a/src/cmdline.cpp +++ b/src/cmdline.cpp @@ -74,13 +74,13 @@ cmdline_keypress(gchar key) gchar *echo; /* - * Cleanup messages, popups, etc... + * Cleanup messages,etc... */ - interface.popup_clear(); interface.msg_clear(); /* - * Process immediate editing commands + * Process immediate editing commands. + * It may clear/hide the popup. */ insert = process_edit_cmd(key); @@ -149,6 +149,7 @@ process_edit_cmd(gchar key) { static gchar insert[255]; gint cmdline_len = cmdline ? strlen(cmdline) : 0; + bool clear_popup = true; insert[0] = key; insert[1] = '\0'; @@ -197,10 +198,19 @@ process_edit_cmd(gchar key) case CTL_KEY('T'): if (States::is_string()) { + *insert = '\0'; + if (interface.popup_is_shown()) { + /* cycle through popup pages */ + interface.popup_show(); + clear_popup = false; + break; + } + const gchar *filename = last_occurrence(strings[0]); gchar *new_chars = filename_complete(filename); - *insert = '\0'; + clear_popup = !interface.popup_is_shown(); + if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); @@ -209,32 +219,61 @@ process_edit_cmd(gchar key) case '\t': if (States::is_file()) { + *insert = '\0'; + if (interface.popup_is_shown()) { + /* cycle through popup pages */ + interface.popup_show(); + clear_popup = false; + break; + } + gchar complete = escape_char == '{' ? ' ' : escape_char; gchar *new_chars = filename_complete(strings[0], complete); - *insert = '\0'; + clear_popup = !interface.popup_is_shown(); + if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); } 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; + } + const gchar *filename = last_occurrence(strings[0]); gchar *new_chars = filename_complete(filename); - *insert = '\0'; + clear_popup = !interface.popup_is_shown(); + if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); } 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; + } + const gchar *symbol = last_occurrence(strings[0], ","); SymbolList &list = symbol == strings[0] ? Symbols::scintilla : Symbols::scilexer; gchar *new_chars = symbol_complete(list, symbol, ','); - *insert = '\0'; + clear_popup = !interface.popup_is_shown(); + if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); @@ -286,6 +325,9 @@ process_edit_cmd(gchar key) #endif } + if (clear_popup) + interface.popup_clear(); + return insert; } @@ -364,7 +406,7 @@ filename_complete(const gchar *filename, gchar completed) gchar *dirname; const gchar *basename, *cur_basename; GDir *dir; - GList *files = NULL; + GSList *files = NULL; guint files_len = 0; gchar *insert = NULL; gsize filename_len; @@ -404,10 +446,10 @@ filename_complete(const gchar *filename, gchar completed) if (g_file_test(cur_filename, G_FILE_TEST_IS_DIR)) String::append(cur_filename, G_DIR_SEPARATOR_S); - files = g_list_prepend(files, cur_filename); + files = g_slist_prepend(files, cur_filename); - if (g_list_next(files)) { - const gchar *other_file = (gchar *)g_list_next(files)->data; + if (g_slist_next(files)) { + const gchar *other_file = (gchar *)g_slist_next(files)->data; gsize len = String::diff(other_file + filename_len, cur_filename + filename_len); if (len < prefix_len) @@ -425,24 +467,22 @@ filename_complete(const gchar *filename, gchar completed) g_dir_close(dir); if (!insert && files_len > 1) { - files = g_list_sort(files, (GCompareFunc)g_strcmp0); + files = g_slist_sort(files, (GCompareFunc)g_strcmp0); - for (GList *file = g_list_first(files); - file != NULL; - file = g_list_next(file)) { + for (GSList *file = files; file; file = g_slist_next(file)) { InterfaceCurrent::PopupEntryType type; - bool in_buffer = false; + bool is_buffer = false; if (filename_is_dir((gchar *)file->data)) { type = InterfaceCurrent::POPUP_DIRECTORY; } else { type = InterfaceCurrent::POPUP_FILE; /* FIXME: inefficient */ - in_buffer = ring.find((gchar *)file->data); + is_buffer = ring.find((gchar *)file->data); } interface.popup_add(type, (gchar *)file->data, - in_buffer); + is_buffer); } interface.popup_show(); @@ -450,9 +490,7 @@ filename_complete(const gchar *filename, gchar completed) String::append(insert, completed); } - for (GList *file = g_list_first(files); file; file = g_list_next(file)) - g_free(file->data); - g_list_free(files); + g_slist_free_full(files, g_free); return insert; } diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp index 6732494..1cbcef3 100644 --- a/src/interface-curses.cpp +++ b/src/interface-curses.cpp @@ -97,11 +97,6 @@ InterfaceCurses::main_impl(int &argc, char **&argv) cmdline_window = newwin(0, 0, LINES - 1, 0); cmdline_current = NULL; - draw_info(); - /* scintilla will be refreshed in event loop */ - msg_clear(); - cmdline_update(""); - #ifdef EMSCRIPTEN nodelay(cmdline_window, TRUE); #else @@ -163,8 +158,8 @@ InterfaceCurses::resize_all_windows(void) mvwin(cmdline_window, lines - 1, 0); draw_info(); - /* scintilla will be refreshed in event loop */ msg_clear(); /* FIXME: use saved message */ + popup_clear(); cmdline_update(); } @@ -193,8 +188,6 @@ InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) wbkgdset(msg_window, ' ' | type2attr[type]); vw_printw(msg_window, fmt, ap); wclrtoeol(msg_window); - - wrefresh(msg_window); } void @@ -206,8 +199,6 @@ InterfaceCurses::msg_clear(void) wmove(msg_window, 0, 0); wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); wclrtoeol(msg_window); - - wrefresh(msg_window); } void @@ -236,8 +227,6 @@ InterfaceCurses::draw_info(void) wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); waddstr(info_window, info_current); wclrtoeol(info_window); - - wrefresh(info_window); } void @@ -283,8 +272,6 @@ InterfaceCurses::cmdline_update_impl(const gchar *cmdline) waddstr(cmdline_window, line); waddch(cmdline_window, ' ' | A_REVERSE); wclrtoeol(cmdline_window); - - wrefresh(cmdline_window); } void @@ -310,21 +297,34 @@ InterfaceCurses::popup_show_impl(void) int lines, cols; /* screen dimensions */ int popup_lines; gint popup_cols; - gint cur_entry; + gint popup_colwidth; + gint cur_col; - if (isendwin()) /* batch mode */ - goto cleanup; + if (isendwin() || !popup.length) + /* batch mode or nothing to display */ + return; getmaxyx(stdscr, lines, cols); - /* reserve 2 space characters between columns */ - popup.longest += 2; - popup.list = g_slist_reverse(popup.list); + if (popup.window) + delwin(popup.window); + else + /* reverse list only once */ + popup.list = g_slist_reverse(popup.list); - /* popup_cols = floor(cols / popup.longest) */ - popup_cols = MAX(cols / popup.longest, 1); - /* popup_lines = ceil(popup.length / popup_cols) */ - popup_lines = (popup.length+popup_cols-1) / popup_cols; + if (!popup.cur_list) { + /* start from beginning of list */ + popup.cur_list = popup.list; + popup.cur_entry = 0; + } + + /* reserve 2 spaces between columns */ + popup_colwidth = popup.longest + 2; + + /* popup_cols = floor(cols / popup_colwidth) */ + popup_cols = MAX(cols / popup_colwidth, 1); + /* popup_lines = ceil((popup.length-popup.cur_entry) / popup_cols) */ + popup_lines = (popup.length-popup.cur_entry+popup_cols-1) / popup_cols; /* * Popup window can cover all but one screen row. * If it does not fit, the list of tokens is truncated @@ -336,17 +336,22 @@ InterfaceCurses::popup_show_impl(void) popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0); wbkgd(popup.window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLUE)); - cur_entry = 0; - for (GSList *cur = popup.list; cur; cur = g_slist_next(cur)) { - gchar *entry = (gchar *)cur->data; - gint cur_line = cur_entry/popup_cols + 1; + /* + * cur_col is the row currently written. + * It does not wrap but grows indefinitely. + * Therefore the real current row is (cur_col % popup_cols) + */ + cur_col = 0; + while (popup.cur_list) { + gchar *entry = (gchar *)popup.cur_list->data; + gint cur_line = cur_col/popup_cols + 1; wmove(popup.window, - cur_line-1, (cur_entry % popup_cols)*popup.longest); - cur_entry++; + cur_line-1, (cur_col % popup_cols)*popup_colwidth); + cur_col++; - if (cur_line == popup_lines && !(cur_entry % popup_cols) && - cur_entry < popup.length) { + if (cur_line == popup_lines && !(cur_col % popup_cols) && + g_slist_next(popup.cur_list) != NULL) { /* truncate entries in the popup's very last column */ (void)wattrset(popup.window, A_BOLD); waddstr(popup.window, "..."); @@ -355,26 +360,34 @@ InterfaceCurses::popup_show_impl(void) (void)wattrset(popup.window, *entry == '*' ? A_BOLD : A_NORMAL); waddstr(popup.window, entry + 1); + + popup.cur_list = g_slist_next(popup.cur_list); + popup.cur_entry++; } -cleanup: - g_slist_free_full(popup.list, g_free); - popup.list = NULL; - popup.longest = popup.length = 0; + redrawwin(info_window); + /* scintilla window is redrawn by ViewCurses::refresh() */ + redrawwin(msg_window); } void InterfaceCurses::popup_clear_impl(void) { + g_slist_free_full(popup.list, g_free); + popup.list = NULL; + popup.length = 0; + /* reserve at least 3 characters for "..." */ + popup.longest = 3; + + popup.cur_list = NULL; + popup.cur_entry = 0; + if (!popup.window) return; redrawwin(info_window); - wrefresh(info_window); - redrawwin(current_view->get_window()); - current_view->refresh(); + /* scintilla window is redrawn by ViewCurses::refresh() */ redrawwin(msg_window); - wrefresh(msg_window); delwin(popup.window); popup.window = NULL; @@ -460,17 +473,29 @@ event_loop_iter() sigint_occurred = FALSE; + wnoutrefresh(interface.info_window); + /* FIXME: this does wrefresh() internally */ interface.current_view->refresh(); + wnoutrefresh(interface.msg_window); + wnoutrefresh(interface.cmdline_window); if (interface.popup.window) - wrefresh(interface.popup.window); + wnoutrefresh(interface.popup.window); + doupdate(); } void InterfaceCurses::event_loop_impl(void) { - /* initial refresh: window might have been changed in batch mode */ + /* initial refresh */ + /* FIXME: this does wrefresh() internally */ current_view->refresh(); draw_info(); + wnoutrefresh(info_window); + msg_clear(); + wnoutrefresh(msg_window); + cmdline_update(""); + wnoutrefresh(cmdline_window); + doupdate(); #ifdef EMSCRIPTEN PDC_emscripten_set_handler(event_loop_iter, TRUE); diff --git a/src/interface-curses.h b/src/interface-curses.h index 35f7edb..70e5300 100644 --- a/src/interface-curses.h +++ b/src/interface-curses.h @@ -80,11 +80,16 @@ typedef class InterfaceCurses : public Interface<InterfaceCurses, ViewCurses> { struct Popup { WINDOW *window; - GSList *list; - gint longest; - gint length; + GSList *list; /**! list of popup entries */ + gint longest; /**! size of longest entry */ + gint length; /**! total number of popup entries */ - Popup() : window(NULL), list(NULL), longest(0), length(0) {} + GSList *cur_list; /**! next entry to display */ + gint cur_entry; /**! next entry to display (position) */ + + Popup() : window(NULL), list(NULL), + longest(3), length(0), + cur_list(NULL), cur_entry(0) {} ~Popup(); } popup; @@ -122,6 +127,12 @@ public: const gchar *name, bool highlight = false); /* implementation of Interface::popup_show() */ void popup_show_impl(void); + /* implementation of Interface::popup_is_shown() */ + inline bool + popup_is_shown_impl(void) + { + return popup.window != NULL; + } /* implementation of Interface::popup_clear() */ void popup_clear_impl(void); diff --git a/src/interface-gtk.h b/src/interface-gtk.h index 6c8e055..ac242f2 100644 --- a/src/interface-gtk.h +++ b/src/interface-gtk.h @@ -116,8 +116,15 @@ public: inline void popup_show_impl(void) { + /* FIXME: scroll through popup contents */ gtk_widget_show(popup_widget); } + /* implementation of Interface::popup_is_shown() */ + inline bool + popup_is_shown_impl(void) + { + return gtk_widget_get_visible(popup_widget); + } /* implementation of Interface::popup_clear() */ void popup_clear_impl(void); diff --git a/src/interface.h b/src/interface.h index 4b5de7f..dcf72ae 100644 --- a/src/interface.h +++ b/src/interface.h @@ -301,6 +301,11 @@ public: { impl().popup_show_impl(); } + inline bool + popup_is_shown(void) + { + return impl().popup_is_shown_impl(); + } inline void popup_clear(void) { |