aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--doc/sciteco.7.template5
-rw-r--r--src/interface-curses.cpp329
-rw-r--r--src/interface-curses.h59
3 files changed, 260 insertions, 133 deletions
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index 54fdf57..c64fb4a 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -576,9 +576,8 @@ If no completion can be performed, the invocation will display a
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.
+completions, this is highlighted by the user interface
+using scroll bars.
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
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
index c3a5b33..b4d83ec 100644
--- a/src/interface-curses.cpp
+++ b/src/interface-curses.cpp
@@ -256,6 +256,193 @@ InterfaceCurses::InterfaceCurses() : stdout_orig(-1), stderr_orig(-1),
}
void
+InterfaceCurses::Popup::add(PopupEntryType type,
+ const gchar *name, bool highlight)
+{
+ gchar *entry = g_strconcat(highlight ? "*" : " ", name, NIL);
+
+ longest = MAX(longest, (gint)strlen(name));
+ length++;
+
+ /*
+ * Entries are added in reverse (constant time for GSList),
+ * so they will later have to be reversed.
+ */
+ list = g_slist_prepend(list, entry);
+}
+
+void
+InterfaceCurses::Popup::init_pad(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 */
+
+ gint cur_col;
+
+ /* reserve 2 spaces between columns */
+ pad_colwidth = MIN(longest + 2, cols - 2);
+
+ /* pad_cols = floor((cols - 2) / pad_colwidth) */
+ pad_cols = (cols - 2) / pad_colwidth;
+ /* pad_lines = ceil(length / pad_cols) */
+ pad_lines = (length+pad_cols-1) / pad_cols;
+
+ /*
+ * Render the entire autocompletion list into a pad
+ * which can be higher than the physical screen.
+ * The pad uses two columns less than the screen since
+ * it will be drawn into the popup window which has left
+ * and right borders.
+ */
+ pad = newpad(pad_lines, cols - 2);
+
+ wbkgd(pad, ' ' | attr);
+
+ /*
+ * 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;
+ for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
+ gchar *entry = (gchar *)cur->data;
+ gint cur_line = cur_col/pad_cols + 1;
+
+ wmove(pad, cur_line-1,
+ (cur_col % pad_cols)*pad_colwidth);
+
+ wattrset(pad, *entry == '*' ? A_BOLD : A_NORMAL);
+
+ if ((int)strlen(entry + 1) > cols - 2) {
+ /* truncate entry */
+ waddnstr(pad, entry + 1, cols - 2 - 3);
+ wattron(pad, A_BOLD);
+ /*
+ * A_UNDERLINE is not supported by PDCurses/win32
+ * and causes weird colors, so we better leave it away.
+ */
+#ifndef PDCURSES_WIN32
+ wattron(pad, A_UNDERLINE);
+#endif
+ waddstr(pad, "...");
+ } else {
+ waddstr(pad, entry + 1);
+ }
+
+ cur_col++;
+ }
+}
+
+void
+InterfaceCurses::Popup::show(attr_t attr)
+{
+ int lines, cols; /* screen dimensions */
+ gint pad_lines;
+ gint popup_lines;
+ gint bar_height, bar_y;
+
+ if (!length)
+ /* nothing to display */
+ return;
+
+ getmaxyx(stdscr, lines, cols);
+
+ if (window)
+ delwin(window);
+ else
+ /* reverse list only once */
+ list = g_slist_reverse(list);
+
+ if (!pad)
+ init_pad(attr);
+ pad_lines = getmaxy(pad);
+
+ /*
+ * Popup window can cover all but one screen row.
+ * Another row is reserved for the top border.
+ */
+ popup_lines = MIN(pad_lines + 1, lines - 1);
+
+ /* window covers message, scintilla and info windows */
+ window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
+
+ wbkgdset(window, ' ' | attr);
+
+ wborder(window,
+ ACS_VLINE,
+ ACS_VLINE, /* may be overwritten with scrollbar */
+ ACS_HLINE,
+ ' ', /* no bottom line */
+ ACS_ULCORNER, ACS_URCORNER,
+ ACS_VLINE, ACS_VLINE);
+
+ copywin(pad, window,
+ pad_first_line, 0,
+ 1, 1, popup_lines - 1, cols - 2, FALSE);
+
+ if (pad_lines <= popup_lines - 1)
+ /* no need for scrollbar */
+ return;
+
+ /* bar_height = ceil((popup_lines-1)/pad_lines * (popup_lines-2)) */
+ bar_height = ((popup_lines-1)*(popup_lines-2) + pad_lines-1) /
+ pad_lines;
+ /* bar_y = floor(pad_first_line/pad_lines * (popup_lines-2)) + 1 */
+ bar_y = pad_first_line*(popup_lines-2) / pad_lines + 1;
+
+ mvwvline(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(window, bar_y, cols-1);
+ wattron(window, A_REVERSE);
+ wvline(window, ' ', bar_height);
+
+ /* progress scroll position */
+ pad_first_line += popup_lines - 1;
+ /* wrap on last shown page */
+ pad_first_line %= pad_lines;
+ if (pad_lines - pad_first_line < popup_lines - 1)
+ /* show last page */
+ pad_first_line = pad_lines - (popup_lines - 1);
+}
+
+void
+InterfaceCurses::Popup::clear(void)
+{
+ g_slist_free_full(list, g_free);
+ list = NULL;
+ length = 0;
+ longest = 0;
+
+ pad_first_line = 0;
+
+ if (window) {
+ delwin(window);
+ window = NULL;
+ }
+
+ if (pad) {
+ delwin(pad);
+ pad = NULL;
+ }
+}
+
+InterfaceCurses::Popup::~Popup()
+{
+ if (window)
+ delwin(window);
+ if (pad)
+ delwin(pad);
+ if (list)
+ g_slist_free_full(list, g_free);
+}
+
+void
InterfaceCurses::main_impl(int &argc, char **&argv)
{
/*
@@ -981,127 +1168,40 @@ InterfaceCurses::draw_cmdline(void)
}
void
-InterfaceCurses::popup_add_impl(PopupEntryType type,
- const gchar *name, bool highlight)
-{
- gchar *entry;
-
- if (!cmdline_window) /* batch mode */
- return;
-
- entry = g_strconcat(highlight ? "*" : " ", name, NIL);
-
- popup.longest = MAX(popup.longest, (gint)strlen(name));
- popup.length++;
-
- popup.list = g_slist_prepend(popup.list, entry);
-}
-
-void
InterfaceCurses::popup_show_impl(void)
{
- int lines, cols; /* screen dimensions */
- int popup_lines;
- gint popup_cols;
- gint popup_colwidth;
- gint cur_col;
-
short fg, bg;
- if (!cmdline_window || !popup.length)
- /* batch mode or nothing to display */
+ if (!cmdline_window)
+ /* batch mode */
return;
- getmaxyx(stdscr, lines, cols);
-
- if (popup.window)
- delwin(popup.window);
- else
- /* reverse list only once */
- popup.list = g_slist_reverse(popup.list);
-
- 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
- * and "..." is displayed.
- */
- popup_lines = MIN(popup_lines, lines - 1);
-
- /* window covers message, scintilla and info windows */
- popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
-
fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_CALLTIP));
bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_CALLTIP));
- wbkgd(popup.window, ' ' | SCI_COLOR_ATTR(fg, bg));
-
- /*
- * 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_col % popup_cols)*popup_colwidth);
- cur_col++;
-
- 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, "...");
- break;
- }
-
- (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++;
- }
-
- redrawwin(info_window);
- /* scintilla window is redrawn by ViewCurses::refresh() */
- redrawwin(msg_window);
+ popup.show(SCI_COLOR_ATTR(fg, bg));
}
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);
- /* scintilla window is redrawn by ViewCurses::refresh() */
- redrawwin(msg_window);
+#ifdef __PDCURSES__
+ /*
+ * PDCurses will not redraw all windows that may be
+ * overlapped by the popup window correctly - at least
+ * not the info window.
+ * The Scintilla window is apparently always touched by
+ * scintilla_noutrefresh().
+ * Actually we would expect this to be necessary on any curses,
+ * but ncurses doesn't require this.
+ */
+ if (popup.is_shown()) {
+ touchwin(info_window);
+ touchwin(msg_window);
+ }
+#endif
- delwin(popup.window);
- popup.window = NULL;
+ popup.clear();
}
/**
@@ -1232,12 +1332,10 @@ event_loop_iter()
*/
interface.draw_info();
wnoutrefresh(interface.info_window);
- /* FIXME: this does wrefresh() internally */
- interface.current_view->refresh();
+ interface.current_view->noutrefresh();
wnoutrefresh(interface.msg_window);
wnoutrefresh(interface.cmdline_window);
- if (interface.popup.window)
- wnoutrefresh(interface.popup.window);
+ interface.popup.noutrefresh();
doupdate();
}
@@ -1252,10 +1350,9 @@ InterfaceCurses::event_loop_impl(void)
init_interactive();
/* initial refresh */
- /* FIXME: this does wrefresh() internally */
- current_view->refresh();
draw_info();
wnoutrefresh(info_window);
+ current_view->noutrefresh();
msg_clear();
wnoutrefresh(msg_window);
cmdline_update(&empty_cmdline);
@@ -1287,14 +1384,6 @@ InterfaceCurses::event_loop_impl(void)
#endif
}
-InterfaceCurses::Popup::~Popup()
-{
- if (window)
- delwin(window);
- if (list)
- g_slist_free_full(list, g_free);
-}
-
InterfaceCurses::~InterfaceCurses()
{
if (info_window)
diff --git a/src/interface-curses.h b/src/interface-curses.h
index b43069f..9bb17cd 100644
--- a/src/interface-curses.h
+++ b/src/interface-curses.h
@@ -52,6 +52,12 @@ public:
}
inline void
+ noutrefresh(void)
+ {
+ scintilla_noutrefresh(sci);
+ }
+
+ inline void
refresh(void)
{
scintilla_refresh(sci);
@@ -104,19 +110,44 @@ typedef class InterfaceCurses : public Interface<InterfaceCurses, ViewCurses> {
WINDOW *cmdline_window, *cmdline_pad;
gsize cmdline_len, cmdline_rubout_len;
- struct Popup {
- WINDOW *window;
+ class Popup {
+ WINDOW *window; /**! window showing part of pad */
+ WINDOW *pad; /**! full-height entry list */
+
GSList *list; /**! list of popup entries */
gint longest; /**! size of longest entry */
gint length; /**! total number of popup entries */
- GSList *cur_list; /**! next entry to display */
- gint cur_entry; /**! next entry to display (position) */
+ gint pad_first_line; /**! first line in pad to show */
+
+ public:
+ Popup() : window(NULL), pad(NULL),
+ list(NULL), longest(0), length(0),
+ pad_first_line(0) {}
+
+ void add(PopupEntryType type,
+ const gchar *name, bool highlight = false);
+
+ void show(attr_t attr);
+ inline bool
+ is_shown(void)
+ {
+ return window != NULL;
+ }
+
+ void clear(void);
+
+ inline void
+ noutrefresh(void)
+ {
+ if (window)
+ wnoutrefresh(window);
+ }
- Popup() : window(NULL), list(NULL),
- longest(3), length(0),
- cur_list(NULL), cur_entry(0) {}
~Popup();
+
+ private:
+ void init_pad(attr_t attr);
} popup;
public:
@@ -145,16 +176,24 @@ public:
void cmdline_update_impl(const Cmdline *cmdline);
/* implementation of Interface::popup_add() */
- void popup_add_impl(PopupEntryType type,
- const gchar *name, bool highlight = false);
+ inline void
+ popup_add_impl(PopupEntryType type,
+ const gchar *name, bool highlight = false)
+ {
+ if (cmdline_window)
+ /* interactive mode */
+ popup.add(type, name, highlight);
+ }
+
/* 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;
+ return popup.is_shown();
}
+
/* implementation of Interface::popup_clear() */
void popup_clear_impl(void);