From 9bcf2667e7e239873597b7ec2172206a9af18071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Andr=C3=A9=20Tanner?= Date: Tue, 14 Mar 2017 16:53:53 +0100 Subject: [PATCH] Restructure display code Use pull instead of push based model for display code. Previously view.c was calling into the ui frontend code, with the new scheme this switches around: the necessary data is fetched by the ui as necessary. The UI independent display code is moved out of view.c/ui-curses.c into vis.c. The cell styles are now directly embedded into the Cell struct. New UI styles are introduced for: - status bar (focused / non-focused) - info message - window separator - EOF symbol You will have to update your color themes. The terminal output code is further abstracted into a generic ui-terminal.c part which keeps track of the whole in-memory cell matrix and #includes ui-terminal-curses.c for the actual terminal output. This architecture currently assumes that there are no overlapping windows. It will also allow non-curses based terminal user interfaces. --- Makefile | 2 +- lua/themes/dark-16.lua | 7 +- lua/themes/light-16.lua | 5 + lua/themes/solarized.lua | 5 + lua/vis-std.lua | 5 + main.c | 5 +- ui-curses.c | 970 --------------------------------------- ui-curses.h | 10 - ui-terminal-curses.c | 286 ++++++++++++ ui-terminal.c | 727 +++++++++++++++++++++++++++++ ui-terminal.h | 9 + ui.h | 25 +- view.c | 147 ++---- view.h | 29 +- vis-core.h | 2 +- vis-lua.c | 5 + vis.c | 187 +++++++- vis.h | 2 + 18 files changed, 1306 insertions(+), 1122 deletions(-) delete mode 100644 ui-curses.c delete mode 100644 ui-curses.h create mode 100644 ui-terminal-curses.c create mode 100644 ui-terminal.c create mode 100644 ui-terminal.h diff --git a/Makefile b/Makefile index ebf31305a..5c92cda77 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ REGEX_SRC ?= text-regex.c SRC = array.c buffer.c libutf.c main.c map.c register.c ring-buffer.c \ sam.c text.c text-motions.c text-objects.c text-util.c \ - ui-curses.c view.c vis.c vis-lua.c vis-modes.c vis-motions.c \ + ui-terminal.c view.c vis.c vis-lua.c vis-modes.c vis-motions.c \ vis-operators.c vis-prompt.c vis-text-objects.c $(REGEX_SRC) ELF = vis vis-menu vis-digraph diff --git a/lua/themes/dark-16.lua b/lua/themes/dark-16.lua index 986c4b335..fcfc1f8f7 100644 --- a/lua/themes/dark-16.lua +++ b/lua/themes/dark-16.lua @@ -1,7 +1,7 @@ -- Eight-color scheme local lexers = vis.lexers -- dark -lexers.STYLE_DEFAULT = 'back:black,fore:white' +lexers.STYLE_DEFAULT ='back:black,fore:white' lexers.STYLE_NOTHING = 'back:black' lexers.STYLE_CLASS = 'fore:yellow,bold' lexers.STYLE_COMMENT = 'fore:blue,bold' @@ -29,3 +29,8 @@ lexers.STYLE_CURSOR_PRIMARY = lexers.STYLE_CURSOR..',fore:yellow' lexers.STYLE_CURSOR_LINE = 'underlined' lexers.STYLE_COLOR_COLUMN = 'back:red' lexers.STYLE_SELECTION = 'back:white' +lexers.STYLE_STATUS = 'reverse' +lexers.STYLE_STATUS_FOCUSED = 'reverse,bold' +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = '' diff --git a/lua/themes/light-16.lua b/lua/themes/light-16.lua index b4ba39187..cf72f7fef 100644 --- a/lua/themes/light-16.lua +++ b/lua/themes/light-16.lua @@ -29,3 +29,8 @@ lexers.STYLE_CURSOR_PRIMARY = lexers.STYLE_CURSOR..',fore:yellow' lexers.STYLE_CURSOR_LINE = 'underlined' lexers.STYLE_COLOR_COLUMN = 'back:red' lexers.STYLE_SELECTION = 'back:black' +lexers.STYLE_STATUS = 'reverse' +lexers.STYLE_STATUS_FOCUSED = 'reverse,bold' +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = '' diff --git a/lua/themes/solarized.lua b/lua/themes/solarized.lua index 70ad51f50..04540ec18 100644 --- a/lua/themes/solarized.lua +++ b/lua/themes/solarized.lua @@ -57,3 +57,8 @@ lexers.STYLE_CURSOR_LINE = 'back:'..colors.base02 lexers.STYLE_COLOR_COLUMN = 'back:'..colors.base02 -- lexers.STYLE_SELECTION = 'back:'..colors.base02 lexers.STYLE_SELECTION = 'back:white' +lexers.STYLE_STATUS = 'back:black,fore:white' +lexers.STYLE_STATUS_FOCUSED = lexers.STYLE_STATUS..',bold' +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = '' diff --git a/lua/vis-std.lua b/lua/vis-std.lua index a0b84f607..4492fcc55 100644 --- a/lua/vis-std.lua +++ b/lua/vis-std.lua @@ -32,6 +32,11 @@ vis.events.subscribe(vis.events.WIN_SYNTAX, function(win, name) win:style_define(win.STYLE_SELECTION, lexers.STYLE_SELECTION or '') win:style_define(win.STYLE_LINENUMBER, lexers.STYLE_LINENUMBER or '') win:style_define(win.STYLE_COLOR_COLUMN, lexers.STYLE_COLOR_COLUMN or '') + win:style_define(win.STYLE_STATUS, lexers.STYLE_STATUS or '') + win:style_define(win.STYLE_STATUS_FOCUSED, lexers.STYLE_STATUS_FOCUSED or '') + win:style_define(win.STYLE_SEPARATOR, lexers.STYLE_SEPARATOR or '') + win:style_define(win.STYLE_INFO, lexers.STYLE_INFO or '') + win:style_define(win.STYLE_EOF, lexers.STYLE_EOF or '') if name == nil then return true end diff --git a/main.c b/main.c index 6751fa1eb..db6b99422 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,7 +9,7 @@ #include #include -#include "ui-curses.h" +#include "ui-terminal.h" #include "vis.h" #include "vis-lua.h" #include "text-util.h" @@ -2001,7 +2002,7 @@ int main(int argc, char *argv[]) { .win_status = vis_lua_win_status, }; - vis = vis_new(ui_curses_new(), &event); + vis = vis_new(ui_term_new(), &event); if (!vis) return EXIT_FAILURE; diff --git a/ui-curses.c b/ui-curses.c deleted file mode 100644 index 81a075fcb..000000000 --- a/ui-curses.c +++ /dev/null @@ -1,970 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ui-curses.h" -#include "vis.h" -#include "vis-core.h" -#include "text.h" -#include "util.h" -#include "text-util.h" - -#ifdef NCURSES_VERSION -# ifndef NCURSES_EXT_COLORS -# define NCURSES_EXT_COLORS 0 -# endif -# if !NCURSES_EXT_COLORS -# define MAX_COLOR_PAIRS 256 -# endif -#endif -#ifndef MAX_COLOR_PAIRS -# define MAX_COLOR_PAIRS COLOR_PAIRS -#endif - -#ifndef DEBUG_UI -#define DEBUG_UI 0 -#endif - -#if DEBUG_UI -#define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0) -#else -#define debug(...) do { } while (0) -#endif - -#if 0 -#define wresize(win, y, x) do { \ - if (wresize(win, y, x) == ERR) { \ - printf("ERROR resizing: %d x %d\n", x, y); \ - } else { \ - printf("OK resizing: %d x %d\n", x, y); \ - } \ - fflush(stdout); \ -} while (0); - -#define mvwin(win, y, x) do { \ - if (mvwin(win, y, x) == ERR) { \ - printf("ERROR moving: %d x %d\n", x, y); \ - } else { \ - printf("OK moving: %d x %d\n", x, y); \ - } \ - fflush(stdout); \ -} while (0); -#endif - -#define MAX_COLOR_CLOBBER 240 -static short color_clobber_idx = 0; -static uint32_t clobbering_colors[MAX_COLOR_CLOBBER]; -static int change_colors = -1; - -typedef struct { - attr_t attr; - short fg, bg; -} CellStyle; - -typedef struct UiCursesWin UiCursesWin; - -typedef struct { - Ui ui; /* generic ui interface, has to be the first struct member */ - Vis *vis; /* editor instance to which this ui belongs */ - UiCursesWin *windows; /* all windows managed by this ui */ - UiCursesWin *selwin; /* the currently selected layout */ - char info[512]; /* info message displayed at the bottom of the screen */ - int width, height; /* terminal dimensions available for all windows */ - enum UiLayout layout; /* whether windows are displayed horizontally or vertically */ - TermKey *termkey; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */ -} UiCurses; - -struct UiCursesWin { - UiWin uiwin; /* generic interface, has to be the first struct member */ - UiCurses *ui; /* ui which manages this window */ - File *file; /* file being displayed in this window */ - View *view; /* current viewport */ - WINDOW *win; /* curses window for the text area */ - WINDOW *winstatus; /* curses window for the status bar */ - WINDOW *winside; /* curses window for the side bar (line numbers) */ - int width, height; /* window dimension including status bar */ - int x, y; /* window position */ - int sidebar_width; /* width of the sidebar showing line numbers etc. */ - UiCursesWin *next, *prev; /* pointers to neighbouring windows */ - enum UiOption options; /* display settings for this window */ - CellStyle styles[UI_STYLE_MAX]; -}; - -__attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) { - UiCurses *uic = (UiCurses*)ui; - endwin(); - if (uic->termkey) - termkey_stop(uic->termkey); - vfprintf(stderr, msg, ap); - exit(EXIT_FAILURE); -} - -__attribute__((noreturn)) static void ui_die_msg(Ui *ui, const char *msg, ...) { - va_list ap; - va_start(ap, msg); - ui_die(ui, msg, ap); - va_end(ap); -} - -/* Calculate r,g,b components of one of the standard upper 240 colors */ -static void get_6cube_rgb(unsigned int n, int *r, int *g, int *b) -{ - if (n < 16) { - return; - } else if (n < 232) { - n -= 16; - *r = (n / 36) ? (n / 36) * 40 + 55 : 0; - *g = ((n / 6) % 6) ? ((n / 6) % 6) * 40 + 55 : 0; - *b = (n % 6) ? (n % 6) * 40 + 55 : 0; - } else if (n < 256) { - n -= 232; - *r = n * 10 + 8; - *g = n * 10 + 8; - *b = n * 10 + 8; - } -} - -/* Reset color palette to default values using OSC 104 */ -static void undo_palette(void) -{ - fputs("\033]104;\a", stderr); - fflush(stderr); -} - -/* Work out the nearest color from the 256 color set, or perhaps exactly. */ -static int color_find_rgb(UiCurses *ui, unsigned char r, unsigned char g, unsigned char b) -{ - if (change_colors == -1) - change_colors = ui->vis->change_colors && can_change_color() && COLORS >= 256; - if (change_colors) { - uint32_t hexrep = ((r << 16) | (g << 8) | b) + 1; - for (short i = 0; i < MAX_COLOR_CLOBBER; ++i) { - if (clobbering_colors[i] == hexrep) - return i + 16; - else if (!clobbering_colors[i]) - break; - } - - short i = color_clobber_idx; - clobbering_colors[i] = hexrep; - init_color(i + 16, (r * 1000) / 0xff, (g * 1000) / 0xff, - (b * 1000) / 0xff); - - /* in the unlikely case a user requests this many colors, reuse old slots */ - if (++color_clobber_idx >= MAX_COLOR_CLOBBER) - color_clobber_idx = 0; - - return i + 16; - } - - static const unsigned char color_256_to_16[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4, - 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, - 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12, - 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, - 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1, - 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12, - 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5, - 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3, - 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, - 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, - 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10, - 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, - 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, - 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8, - 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15 - }; - - int i = 0; - if ((!r || (r - 55) % 40 == 0) && - (!g || (g - 55) % 40 == 0) && - (!b || (b - 55) % 40 == 0)) { - i = 16; - i += r ? ((r - 55) / 40) * 36 : 0; - i += g ? ((g - 55) / 40) * 6 : 0; - i += g ? ((b - 55) / 40) : 0; - } else if (r == g && g == b && (r - 8) % 10 == 0 && r < 239) { - i = 232 + ((r - 8) / 10); - } else { - unsigned lowest = UINT_MAX; - for (int j = 16; j < 256; ++j) { - int jr = 0, jg = 0, jb = 0; - get_6cube_rgb(j, &jr, &jg, &jb); - int dr = jr - r; - int dg = jg - g; - int db = jb - b; - unsigned int distance = dr * dr + dg * dg + db * db; - if (distance < lowest) { - lowest = distance; - i = j; - } - } - } - - if (COLORS <= 16) - return color_256_to_16[i]; - return i; -} - -/* Convert color from string. */ -static int color_fromstring(UiCurses *ui, const char *s) -{ - if (!s) - return -1; - if (*s == '#' && strlen(s) == 7) { - const char *cp; - unsigned char r, g, b; - for (cp = s + 1; isxdigit((unsigned char)*cp); cp++); - if (*cp != '\0') - return -1; - int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b); - if (n != 3) - return -1; - return color_find_rgb(ui, r, g, b); - } else if ('0' <= *s && *s <= '9') { - int col = atoi(s); - return (col <= 0 || col > 255) ? -1 : col; - } - - if (strcasecmp(s, "black") == 0) - return 0; - if (strcasecmp(s, "red") == 0) - return 1; - if (strcasecmp(s, "green") == 0) - return 2; - if (strcasecmp(s, "yellow") == 0) - return 3; - if (strcasecmp(s, "blue") == 0) - return 4; - if (strcasecmp(s, "magenta") == 0) - return 5; - if (strcasecmp(s, "cyan") == 0) - return 6; - if (strcasecmp(s, "white") == 0) - return 7; - return -1; -} - -static inline unsigned int color_pair_hash(short fg, short bg) { - if (fg == -1) - fg = COLORS; - if (bg == -1) - bg = COLORS + 1; - return fg * (COLORS + 2) + bg; -} - -static short color_pair_get(short fg, short bg) { - static bool has_default_colors; - static short *color2palette, default_fg, default_bg; - static short color_pairs_max, color_pair_current; - - if (!color2palette) { - pair_content(0, &default_fg, &default_bg); - if (default_fg == -1) - default_fg = COLOR_WHITE; - if (default_bg == -1) - default_bg = COLOR_BLACK; - has_default_colors = (use_default_colors() == OK); - color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS); - if (COLORS) - color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short)); - } - - if (fg >= COLORS) - fg = default_fg; - if (bg >= COLORS) - bg = default_bg; - - if (!has_default_colors) { - if (fg == -1) - fg = default_fg; - if (bg == -1) - bg = default_bg; - } - - if (!color2palette || (fg == -1 && bg == -1)) - return 0; - - unsigned int index = color_pair_hash(fg, bg); - if (color2palette[index] == 0) { - short oldfg, oldbg; - if (++color_pair_current >= color_pairs_max) - color_pair_current = 1; - pair_content(color_pair_current, &oldfg, &oldbg); - unsigned int old_index = color_pair_hash(oldfg, oldbg); - if (init_pair(color_pair_current, fg, bg) == OK) { - color2palette[old_index] = 0; - color2palette[index] = color_pair_current; - } - } - - return color2palette[index]; -} - -static inline attr_t style_to_attr(CellStyle *style) { - return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg)); -} - -static bool ui_window_syntax_style(UiWin *w, int id, const char *style) { - UiCursesWin *win = (UiCursesWin*)w; - if (id >= UI_STYLE_MAX) - return false; - if (!style) - return true; - CellStyle cell_style = win->styles[UI_STYLE_DEFAULT]; - char *style_copy = strdup(style), *option = style_copy, *next, *p; - while (option) { - if ((next = strchr(option, ','))) - *next++ = '\0'; - if ((p = strchr(option, ':'))) - *p++ = '\0'; - if (!strcasecmp(option, "reverse")) { - cell_style.attr |= A_REVERSE; - } else if (!strcasecmp(option, "bold")) { - cell_style.attr |= A_BOLD; - } else if (!strcasecmp(option, "notbold")) { - cell_style.attr &= ~A_BOLD; -#ifdef A_ITALIC - } else if (!strcasecmp(option, "italics")) { - cell_style.attr |= A_ITALIC; - } else if (!strcasecmp(option, "notitalics")) { - cell_style.attr &= ~A_ITALIC; -#endif - } else if (!strcasecmp(option, "underlined")) { - cell_style.attr |= A_UNDERLINE; - } else if (!strcasecmp(option, "notunderlined")) { - cell_style.attr &= ~A_UNDERLINE; - } else if (!strcasecmp(option, "blink")) { - cell_style.attr |= A_BLINK; - } else if (!strcasecmp(option, "notblink")) { - cell_style.attr &= ~A_BLINK; - } else if (!strcasecmp(option, "fore")) { - cell_style.fg = color_fromstring(win->ui, p); - } else if (!strcasecmp(option, "back")) { - cell_style.bg = color_fromstring(win->ui, p); - } - option = next; - } - win->styles[id] = cell_style; - free(style_copy); - return true; -} - -static void ui_window_resize(UiCursesWin *win, int width, int height) { - debug("ui-win-resize[%s]: %dx%d\n", win->file->name ? win->file->name : "noname", width, height); - win->width = width; - win->height = height; - if (win->winstatus) - wresize(win->winstatus, 1, width); - wresize(win->win, win->winstatus ? height - 1 : height, width - win->sidebar_width); - if (win->winside) - wresize(win->winside, height-1, win->sidebar_width); - view_resize(win->view, width - win->sidebar_width, win->winstatus ? height - 1 : height); -} - -static void ui_window_move(UiCursesWin *win, int x, int y) { - debug("ui-win-move[%s]: (%d, %d)\n", win->file->name ? win->file->name : "noname", x, y); - win->x = x; - win->y = y; - mvwin(win->win, y, x + win->sidebar_width); - if (win->winside) - mvwin(win->winside, y, x); - if (win->winstatus) - mvwin(win->winstatus, y + win->height - 1, x); -} - -static bool ui_window_draw_sidebar(UiCursesWin *win) { - if (!win->winside) - return true; - const Line *line = view_lines_get(win->view); - int sidebar_width = snprintf(NULL, 0, "%zd", line->lineno + win->height - 2) + 1; - if (win->sidebar_width != sidebar_width) { - win->sidebar_width = sidebar_width; - ui_window_resize(win, win->width, win->height); - ui_window_move(win, win->x, win->y); - return false; - } else { - int i = 0; - size_t prev_lineno = 0; - const Line *cursor_line = view_line_get(win->view); - size_t cursor_lineno = cursor_line->lineno; - werase(win->winside); - wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT])); - wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER])); - for (const Line *l = line; l; l = l->next, i++) { - if (l->lineno && l->lineno != prev_lineno) { - if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) { - mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno); - } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) { - size_t rel = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno; - if (l->lineno > cursor_lineno) - rel = l->lineno - cursor_lineno; - else if (l->lineno < cursor_lineno) - rel = cursor_lineno - l->lineno; - mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel); - } - } - prev_lineno = l->lineno; - } - mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1); - return true; - } -} - -static void ui_window_status(UiWin *w, const char *status) { - UiCursesWin *win = (UiCursesWin*)w; - if (!win->winstatus) - return; - UiCurses *uic = win->ui; - bool focused = uic->selwin == win; - wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE); - mvwhline(win->winstatus, 0, 0, ' ', win->width); - if (status) - mvwprintw(win->winstatus, 0, 0, "%s", status); -} - -static void ui_window_draw(UiWin *w) { - UiCursesWin *win = (UiCursesWin*)w; - if (!ui_window_draw_sidebar(win)) - return; - - debug("ui-win-draw[%s]\n", win->file->name ? win->file->name : "noname"); - wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT])); - wmove(win->win, 0, 0); - int width = view_width_get(win->view); - CellStyle *prev_style = NULL; - size_t cursor_lineno = -1; - - if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) { - Filerange selection = view_selection_get(win->view); - if (!view_cursors_multiple(win->view) && !text_range_valid(&selection)) { - const Line *line = view_line_get(win->view); - cursor_lineno = line->lineno; - } - } - - short selection_bg = win->styles[UI_STYLE_SELECTION].bg; - short cul_bg = win->styles[UI_STYLE_CURSOR_LINE].bg; - attr_t cul_attr = win->styles[UI_STYLE_CURSOR_LINE].attr; - bool multiple_cursors = view_cursors_multiple(win->view); - attr_t attr = A_NORMAL; - - for (const Line *l = view_lines_get(win->view); l; l = l->next) { - bool cursor_line = l->lineno == cursor_lineno; - for (int x = 0; x < width; x++) { - enum UiStyle style_id = l->cells[x].style; - if (style_id == 0) - style_id = UI_STYLE_DEFAULT; - CellStyle *style = &win->styles[style_id]; - - if (l->cells[x].cursor && win->ui->selwin == win) { - if (multiple_cursors && l->cells[x].cursor_primary) - attr = style_to_attr(&win->styles[UI_STYLE_CURSOR_PRIMARY]); - else - attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]); - prev_style = NULL; - } else if (l->cells[x].selected) { - if (style->fg == selection_bg) - attr = style->attr | A_REVERSE; - else - attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg)); - prev_style = NULL; - } else if (cursor_line) { - attr = cul_attr | (style->attr & ~A_COLOR) | COLOR_PAIR(color_pair_get(style->fg, cul_bg)); - prev_style = NULL; - } else if (style != prev_style) { - attr = style_to_attr(style); - prev_style = style; - } - wattrset(win->win, attr); - waddstr(win->win, l->cells[x].data); - } - /* try to fixup display issues, in theory we should always output a full line */ - int x, y; - getyx(win->win, y, x); - (void)y; - wattrset(win->win, A_NORMAL); - for (; 0 < x && x < width; x++) - waddstr(win->win, " "); - } - - wclrtobot(win->win); -} - -static void ui_window_reload(UiWin *w, File *file) { - UiCursesWin *win = (UiCursesWin*)w; - win->file = file; - win->sidebar_width = 0; - view_reload(win->view, file->text); - ui_window_draw(w); -} - -static void ui_window_update(UiCursesWin *win) { - debug("ui-win-update[%s]\n", win->file->name ? win->file->name : "noname"); - if (win->winstatus) - wnoutrefresh(win->winstatus); - if (win->winside) - wnoutrefresh(win->winside); - wnoutrefresh(win->win); -} - -static void ui_arrange(Ui *ui, enum UiLayout layout) { - debug("ui-arrange\n"); - UiCurses *uic = (UiCurses*)ui; - uic->layout = layout; - int n = 0, m = !!uic->info[0], x = 0, y = 0; - for (UiCursesWin *win = uic->windows; win; win = win->next) { - if (win->options & UI_OPTION_ONELINE) - m++; - else - n++; - } - int max_height = uic->height - m; - int width = (uic->width / MAX(1, n)) - 1; - int height = max_height / MAX(1, n); - for (UiCursesWin *win = uic->windows; win; win = win->next) { - if (win->options & UI_OPTION_ONELINE) - continue; - n--; - if (layout == UI_LAYOUT_HORIZONTAL) { - int h = n ? height : max_height - y; - ui_window_resize(win, uic->width, h); - ui_window_move(win, x, y); - y += h; - } else { - int w = n ? width : uic->width - x; - ui_window_resize(win, w, max_height); - ui_window_move(win, x, y); - x += w; - if (n) - mvvline(0, x++, ACS_VLINE, max_height); - } - } - - if (layout == UI_LAYOUT_VERTICAL) - y = max_height; - - for (UiCursesWin *win = uic->windows; win; win = win->next) { - if (!(win->options & UI_OPTION_ONELINE)) - continue; - ui_window_resize(win, uic->width, 1); - ui_window_move(win, 0, y++); - } -} - -static void ui_draw(Ui *ui) { - debug("ui-draw\n"); - UiCurses *uic = (UiCurses*)ui; - erase(); - ui_arrange(ui, uic->layout); - - for (UiCursesWin *win = uic->windows; win; win = win->next) - ui_window_draw((UiWin*)win); - - if (uic->info[0]) { - attrset(A_BOLD); - mvaddstr(uic->height-1, 0, uic->info); - } - - wnoutrefresh(stdscr); -} - -static void ui_redraw(Ui *ui) { - clear(); - ui_draw(ui); -} - -static void ui_resize_to(Ui *ui, int width, int height) { - UiCurses *uic = (UiCurses*)ui; - uic->width = width; - uic->height = height; - ui_draw(ui); -} - -static void ui_resize(Ui *ui) { - struct winsize ws; - int width, height; - - if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) { - getmaxyx(stdscr, height, width); - } else { - width = ws.ws_col; - height = ws.ws_row; - } - - resizeterm(height, width); - wresize(stdscr, height, width); - ui_resize_to(ui, width, height); -} - -static void ui_update(Ui *ui) { - UiCurses *uic = (UiCurses*)ui; - for (UiCursesWin *win = uic->windows; win; win = win->next) - ui_window_update(win); - debug("ui-doupdate\n"); - doupdate(); -} - -static void ui_window_free(UiWin *w) { - UiCursesWin *win = (UiCursesWin*)w; - if (!win) - return; - UiCurses *uic = win->ui; - if (win->prev) - win->prev->next = win->next; - if (win->next) - win->next->prev = win->prev; - if (uic->windows == win) - uic->windows = win->next; - if (uic->selwin == win) - uic->selwin = NULL; - win->next = win->prev = NULL; - if (win->winstatus) - delwin(win->winstatus); - if (win->winside) - delwin(win->winside); - if (win->win) - delwin(win->win); - free(win); -} - -static void ui_window_focus(UiWin *w) { - UiCursesWin *win = (UiCursesWin*)w; - UiCursesWin *oldsel = win->ui->selwin; - win->ui->selwin = win; - if (oldsel) { - view_draw(oldsel->view); - ui_window_draw((UiWin*)oldsel); - } - view_draw(win->view); - ui_window_draw(w); -} - -static void ui_window_options_set(UiWin *w, enum UiOption options) { - UiCursesWin *win = (UiCursesWin*)w; - win->options = options; - if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) { - if (!win->winside) - win->winside = newwin(1, 1, 1, 1); - } else { - if (win->winside) { - delwin(win->winside); - win->winside = NULL; - win->sidebar_width = 0; - } - } - if (options & UI_OPTION_STATUSBAR) { - if (!win->winstatus) - win->winstatus = newwin(1, 0, 0, 0); - } else { - if (win->winstatus) - delwin(win->winstatus); - win->winstatus = NULL; - } - - if (options & UI_OPTION_ONELINE) { - /* move the new window to the end of the list */ - UiCurses *uic = win->ui; - UiCursesWin *last = uic->windows; - while (last->next) - last = last->next; - if (last != win) { - if (win->prev) - win->prev->next = win->next; - if (win->next) - win->next->prev = win->prev; - if (uic->windows == win) - uic->windows = win->next; - last->next = win; - win->prev = last; - win->next = NULL; - } - } - - ui_draw((Ui*)win->ui); -} - -static enum UiOption ui_window_options_get(UiWin *w) { - UiCursesWin *win = (UiCursesWin*)w; - return win->options; -} - -static int ui_window_width(UiWin *win) { - return ((UiCursesWin*)win)->width; -} - -static int ui_window_height(UiWin *win) { - return ((UiCursesWin*)win)->height; -} - -static void ui_window_swap(UiWin *aw, UiWin *bw) { - UiCursesWin *a = (UiCursesWin*)aw; - UiCursesWin *b = (UiCursesWin*)bw; - if (a == b || !a || !b) - return; - UiCurses *ui = a->ui; - UiCursesWin *tmp = a->next; - a->next = b->next; - b->next = tmp; - if (a->next) - a->next->prev = a; - if (b->next) - b->next->prev = b; - tmp = a->prev; - a->prev = b->prev; - b->prev = tmp; - if (a->prev) - a->prev->next = a; - if (b->prev) - b->prev->next = b; - if (ui->windows == a) - ui->windows = b; - else if (ui->windows == b) - ui->windows = a; - if (ui->selwin == a) - ui_window_focus(bw); - else if (ui->selwin == b) - ui_window_focus(aw); -} - -static UiWin *ui_window_new(Ui *ui, View *view, File *file, enum UiOption options) { - UiCurses *uic = (UiCurses*)ui; - UiCursesWin *win = calloc(1, sizeof(UiCursesWin)); - if (!win) - return NULL; - - win->uiwin = (UiWin) { - .draw = ui_window_draw, - .status = ui_window_status, - .options_set = ui_window_options_set, - .options_get = ui_window_options_get, - .reload = ui_window_reload, - .syntax_style = ui_window_syntax_style, - .window_width = ui_window_width, - .window_height = ui_window_height, - }; - - if (!(win->win = newwin(0, 0, 0, 0))) { - ui_window_free((UiWin*)win); - return NULL; - } - - - for (int i = 0; i < UI_STYLE_MAX; i++) { - win->styles[i] = (CellStyle) { - .fg = -1, .bg = -1, .attr = A_NORMAL, - }; - } - - win->styles[UI_STYLE_CURSOR].attr |= A_REVERSE; - win->styles[UI_STYLE_CURSOR_PRIMARY].attr |= A_REVERSE|A_BLINK; - win->styles[UI_STYLE_SELECTION].attr |= A_REVERSE; - win->styles[UI_STYLE_COLOR_COLUMN].attr |= A_REVERSE; - - win->ui = uic; - win->view = view; - win->file = file; - view_ui(view, &win->uiwin); - - if (uic->windows) - uic->windows->prev = win; - win->next = uic->windows; - uic->windows = win; - - if (text_size(file->text) > UI_LARGE_FILE_SIZE) { - options |= UI_OPTION_LARGE_FILE; - options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE; - } - - ui_window_options_set((UiWin*)win, options); - - return &win->uiwin; -} - -static void ui_info(Ui *ui, const char *msg, va_list ap) { - UiCurses *uic = (UiCurses*)ui; - vsnprintf(uic->info, sizeof(uic->info), msg, ap); - ui_draw(ui); -} - -static void ui_info_hide(Ui *ui) { - UiCurses *uic = (UiCurses*)ui; - if (uic->info[0]) { - uic->info[0] = '\0'; - ui_draw(ui); - } -} - -static TermKey *ui_termkey_new(int fd) { - TermKey *termkey = termkey_new(fd, TERMKEY_FLAG_UTF8); - if (termkey) - termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS); - return termkey; -} - -static TermKey *ui_termkey_reopen(Ui *ui, int fd) { - int tty = open("/dev/tty", O_RDWR); - if (tty == -1) - return NULL; - if (tty != fd && dup2(tty, fd) == -1) { - close(tty); - return NULL; - } - close(tty); - return ui_termkey_new(fd); -} - -static TermKey *ui_termkey_get(Ui *ui) { - UiCurses *uic = (UiCurses*)ui; - return uic->termkey; -} - -static void ui_suspend(Ui *ui) { - if (change_colors == 1) - undo_palette(); - endwin(); - kill(0, SIGSTOP); -} - -static bool ui_getkey(Ui *ui, TermKeyKey *key) { - UiCurses *uic = (UiCurses*)ui; - TermKeyResult ret = termkey_getkey(uic->termkey, key); - - if (ret == TERMKEY_RES_EOF) { - termkey_destroy(uic->termkey); - errno = 0; - if (!(uic->termkey = ui_termkey_reopen(ui, STDIN_FILENO))) - ui_die_msg(ui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : ""); - return false; - } - - if (ret == TERMKEY_RES_AGAIN) { - struct pollfd fd; - fd.fd = STDIN_FILENO; - fd.events = POLLIN; - if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0) - ret = termkey_getkey_force(uic->termkey, key); - } - - return ret == TERMKEY_RES_KEY; -} - -static void ui_terminal_save(Ui *ui) { - UiCurses *uic = (UiCurses*)ui; - curs_set(1); - reset_shell_mode(); - termkey_stop(uic->termkey); -} - -static void ui_terminal_restore(Ui *ui) { - UiCurses *uic = (UiCurses*)ui; - termkey_start(uic->termkey); - reset_prog_mode(); - wclear(stdscr); - curs_set(0); -} - -static int ui_colors(Ui *ui) { - return COLORS; -} - -static bool ui_init(Ui *ui, Vis *vis) { - UiCurses *uic = (UiCurses*)ui; - uic->vis = vis; - - setlocale(LC_CTYPE, ""); - - char *term = getenv("TERM"); - if (!term) - term = "xterm"; - - errno = 0; - if (!(uic->termkey = ui_termkey_new(STDIN_FILENO))) { - /* work around libtermkey bug which fails if stdin is /dev/null */ - if (errno == EBADF && !isatty(STDIN_FILENO)) { - errno = 0; - if (!(uic->termkey = ui_termkey_reopen(ui, STDIN_FILENO)) && errno == ENXIO) - uic->termkey = termkey_new_abstract(term, TERMKEY_FLAG_UTF8); - } - if (!uic->termkey) - goto err; - } - - if (!newterm(term, stderr, stdin)) { - snprintf(uic->info, sizeof(uic->info), "Warning: unknown term `%s'", term); - if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin)) - goto err; - } - start_color(); - use_default_colors(); - raw(); - noecho(); - nonl(); - keypad(stdscr, TRUE); - meta(stdscr, TRUE); - curs_set(0); - - ui_resize(ui); - return true; -err: - ui_die_msg(ui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : ""); - return false; -} - -Ui *ui_curses_new(void) { - - Ui *ui = calloc(1, sizeof(UiCurses)); - if (!ui) - return NULL; - - *ui = (Ui) { - .init = ui_init, - .free = ui_curses_free, - .termkey_get = ui_termkey_get, - .suspend = ui_suspend, - .resize = ui_resize, - .update = ui_update, - .window_new = ui_window_new, - .window_free = ui_window_free, - .window_focus = ui_window_focus, - .window_swap = ui_window_swap, - .draw = ui_draw, - .redraw = ui_redraw, - .arrange = ui_arrange, - .die = ui_die, - .info = ui_info, - .info_hide = ui_info_hide, - .getkey = ui_getkey, - .terminal_save = ui_terminal_save, - .terminal_restore = ui_terminal_restore, - .colors = ui_colors, - }; - - return ui; -} - -void ui_curses_free(Ui *ui) { - UiCurses *uic = (UiCurses*)ui; - if (!uic) - return; - while (uic->windows) - ui_window_free((UiWin*)uic->windows); - if (change_colors == 1) - undo_palette(); - endwin(); - if (uic->termkey) - termkey_destroy(uic->termkey); - free(uic); -} diff --git a/ui-curses.h b/ui-curses.h deleted file mode 100644 index 34897ee91..000000000 --- a/ui-curses.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef UI_CURSES_H -#define UI_CURSES_H - -#include -#include "ui.h" - -Ui *ui_curses_new(void); -void ui_curses_free(Ui*); - -#endif diff --git a/ui-terminal-curses.c b/ui-terminal-curses.c new file mode 100644 index 000000000..5f0319b85 --- /dev/null +++ b/ui-terminal-curses.c @@ -0,0 +1,286 @@ +/* This file is included from ui-terminal.c */ +#include +#include + +#define ui_term_backend_init ui_curses_init +#define ui_term_backend_blit ui_curses_blit +#define ui_term_backend_clear ui_curses_clear +#define ui_term_backend_colors ui_curses_colors +#define ui_term_backend_resize ui_curses_resize +#define ui_term_backend_restore ui_curses_restore +#define ui_term_backend_save ui_curses_save +#define ui_term_backend_new ui_curses_new +#define ui_term_backend_resume ui_curses_resume +#define ui_term_backend_suspend ui_curses_suspend +#define ui_term_backend_free ui_curses_suspend + +#define CELL_COLOR_BLACK COLOR_BLACK +#define CELL_COLOR_RED COLOR_RED +#define CELL_COLOR_GREEN COLOR_GREEN +#define CELL_COLOR_YELLOW COLOR_YELLOW +#define CELL_COLOR_BLUE COLOR_BLUE +#define CELL_COLOR_MAGENTA COLOR_MAGENTA +#define CELL_COLOR_CYAN COLOR_CYAN +#define CELL_COLOR_WHITE COLOR_WHITE +#define CELL_COLOR_DEFAULT (-1) + +#ifndef A_ITALIC +#define A_ITALIC A_NORMAL +#endif +#define CELL_ATTR_NORMAL A_NORMAL +#define CELL_ATTR_UNDERLINE A_UNDERLINE +#define CELL_ATTR_REVERSE A_REVERSE +#define CELL_ATTR_BLINK A_BLINK +#define CELL_ATTR_BOLD A_BOLD +#define CELL_ATTR_ITALIC A_ITALIC + +#ifdef NCURSES_VERSION +# ifndef NCURSES_EXT_COLORS +# define NCURSES_EXT_COLORS 0 +# endif +# if !NCURSES_EXT_COLORS +# define MAX_COLOR_PAIRS 256 +# endif +#endif +#ifndef MAX_COLOR_PAIRS +# define MAX_COLOR_PAIRS COLOR_PAIRS +#endif + +#define MAX_COLOR_CLOBBER 240 + +static short color_clobber_idx = 0; +static uint32_t clobbering_colors[MAX_COLOR_CLOBBER]; +static int change_colors = -1; + +/* Calculate r,g,b components of one of the standard upper 240 colors */ +static void get_6cube_rgb(unsigned int n, int *r, int *g, int *b) +{ + if (n < 16) { + return; + } else if (n < 232) { + n -= 16; + *r = (n / 36) ? (n / 36) * 40 + 55 : 0; + *g = ((n / 6) % 6) ? ((n / 6) % 6) * 40 + 55 : 0; + *b = (n % 6) ? (n % 6) * 40 + 55 : 0; + } else if (n < 256) { + n -= 232; + *r = n * 10 + 8; + *g = n * 10 + 8; + *b = n * 10 + 8; + } +} + +/* Reset color palette to default values using OSC 104 */ +static void undo_palette(void) +{ + fputs("\033]104;\a", stderr); + fflush(stderr); +} + +/* Work out the nearest color from the 256 color set, or perhaps exactly. */ +static CellColor color_rgb(UiTerm *ui, uint8_t r, uint8_t g, uint8_t b) +{ + if (change_colors == -1) + change_colors = ui->vis->change_colors && can_change_color() && COLORS >= 256; + if (change_colors) { + uint32_t hexrep = ((r << 16) | (g << 8) | b) + 1; + for (short i = 0; i < MAX_COLOR_CLOBBER; ++i) { + if (clobbering_colors[i] == hexrep) + return i + 16; + else if (!clobbering_colors[i]) + break; + } + + short i = color_clobber_idx; + clobbering_colors[i] = hexrep; + init_color(i + 16, (r * 1000) / 0xff, (g * 1000) / 0xff, + (b * 1000) / 0xff); + + /* in the unlikely case a user requests this many colors, reuse old slots */ + if (++color_clobber_idx >= MAX_COLOR_CLOBBER) + color_clobber_idx = 0; + + return i + 16; + } + + static const unsigned char color_256_to_16[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4, + 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, + 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12, + 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, + 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1, + 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12, + 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5, + 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3, + 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, + 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, + 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10, + 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, + 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, + 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8, + 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15 + }; + + int i = 0; + if ((!r || (r - 55) % 40 == 0) && + (!g || (g - 55) % 40 == 0) && + (!b || (b - 55) % 40 == 0)) { + i = 16; + i += r ? ((r - 55) / 40) * 36 : 0; + i += g ? ((g - 55) / 40) * 6 : 0; + i += g ? ((b - 55) / 40) : 0; + } else if (r == g && g == b && (r - 8) % 10 == 0 && r < 239) { + i = 232 + ((r - 8) / 10); + } else { + unsigned lowest = UINT_MAX; + for (int j = 16; j < 256; ++j) { + int jr = 0, jg = 0, jb = 0; + get_6cube_rgb(j, &jr, &jg, &jb); + int dr = jr - r; + int dg = jg - g; + int db = jb - b; + unsigned int distance = dr * dr + dg * dg + db * db; + if (distance < lowest) { + lowest = distance; + i = j; + } + } + } + + if (COLORS <= 16) + return color_256_to_16[i]; + return i; +} + +static CellColor color_terminal(UiTerm *ui, uint8_t index) { + return index; +} + +static inline unsigned int color_pair_hash(short fg, short bg) { + if (fg == -1) + fg = COLORS; + if (bg == -1) + bg = COLORS + 1; + return fg * (COLORS + 2) + bg; +} + +static short color_pair_get(short fg, short bg) { + static bool has_default_colors; + static short *color2palette, default_fg, default_bg; + static short color_pairs_max, color_pair_current; + + if (!color2palette) { + pair_content(0, &default_fg, &default_bg); + if (default_fg == -1) + default_fg = CELL_COLOR_WHITE; + if (default_bg == -1) + default_bg = CELL_COLOR_BLACK; + has_default_colors = (use_default_colors() == OK); + color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS); + if (COLORS) + color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short)); + } + + if (fg >= COLORS) + fg = default_fg; + if (bg >= COLORS) + bg = default_bg; + + if (!has_default_colors) { + if (fg == -1) + fg = default_fg; + if (bg == -1) + bg = default_bg; + } + + if (!color2palette || (fg == -1 && bg == -1)) + return 0; + + unsigned int index = color_pair_hash(fg, bg); + if (color2palette[index] == 0) { + short oldfg, oldbg; + if (++color_pair_current >= color_pairs_max) + color_pair_current = 1; + pair_content(color_pair_current, &oldfg, &oldbg); + unsigned int old_index = color_pair_hash(oldfg, oldbg); + if (init_pair(color_pair_current, fg, bg) == OK) { + color2palette[old_index] = 0; + color2palette[index] = color_pair_current; + } + } + + return color2palette[index]; +} + +static inline attr_t style_to_attr(CellStyle *style) { + return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg)); +} + +static void ui_curses_blit(UiTerm *tui) { + int w = tui->width, h = tui->height; + Cell *cell = tui->cells; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + attrset(style_to_attr(&cell->style)); + mvaddstr(y, x, cell->data); + cell++; + } + } + wnoutrefresh(stdscr); + doupdate(); +} + +static void ui_curses_clear(UiTerm *tui) { + clear(); +} + +static void ui_curses_resize(UiTerm *tui, int width, int height) { + resizeterm(height, width); + wresize(stdscr, height, width); +} + +static void ui_curses_save(UiTerm *tui) { + curs_set(1); + reset_shell_mode(); +} + +static void ui_curses_restore(UiTerm *tui) { + reset_prog_mode(); + wclear(stdscr); + curs_set(0); +} + +static int ui_curses_colors(Ui *ui) { + return COLORS; +} + +static bool ui_curses_init(UiTerm *tui, const char *term) { + if (!newterm(term, stderr, stdin)) { + snprintf(tui->info, sizeof(tui->info), "Warning: unknown term `%s'", term); + if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin)) + return false; + } + start_color(); + use_default_colors(); + raw(); + noecho(); + nonl(); + keypad(stdscr, TRUE); + meta(stdscr, TRUE); + curs_set(0); + return true; +} + +static UiTerm *ui_curses_new(void) { + return calloc(1, sizeof(UiTerm)); +} + +static void ui_curses_resume(UiTerm *term) { } + +static void ui_curses_suspend(UiTerm *term) { + if (change_colors == 1) + undo_palette(); + endwin(); +} + diff --git a/ui-terminal.c b/ui-terminal.c new file mode 100644 index 000000000..67cabba8c --- /dev/null +++ b/ui-terminal.c @@ -0,0 +1,727 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui-terminal.h" +#include "vis.h" +#include "vis-core.h" +#include "text.h" +#include "util.h" +#include "text-util.h" + +#ifndef DEBUG_UI +#define DEBUG_UI 0 +#endif + +#if DEBUG_UI +#define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0) +#else +#define debug(...) do { } while (0) +#endif + +#define MAX_WIDTH 1024 +#define MAX_HEIGHT 1024 +typedef struct UiTermWin UiTermWin; + +typedef struct { + Ui ui; /* generic ui interface, has to be the first struct member */ + Vis *vis; /* editor instance to which this ui belongs */ + UiTermWin *windows; /* all windows managed by this ui */ + UiTermWin *selwin; /* the currently selected layout */ + char info[MAX_WIDTH]; /* info message displayed at the bottom of the screen */ + int width, height; /* terminal dimensions available for all windows */ + enum UiLayout layout; /* whether windows are displayed horizontally or vertically */ + TermKey *termkey; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */ + size_t ids; /* bit mask of in use window ids */ + size_t styles_size; /* #bytes allocated for styles array */ + CellStyle *styles; /* each window has UI_STYLE_MAX different style definitions */ + size_t cells_size; /* #bytes allocated for 2D grid (grows only) */ + Cell *cells; /* 2D grid of cells, at least as large as current terminal size */ +} UiTerm; + +struct UiTermWin { + UiWin uiwin; /* generic interface, has to be the first struct member */ + UiTerm *ui; /* ui which manages this window */ + Win *win; /* editor window being displayed */ + int id; /* unique identifier for this window */ + int width, height; /* window dimension including status bar */ + int x, y; /* window position */ + int sidebar_width; /* width of the sidebar showing line numbers etc. */ + UiTermWin *next, *prev; /* pointers to neighbouring windows */ + enum UiOption options; /* display settings for this window */ +}; + +#include "ui-terminal-curses.c" + +__attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) { + UiTerm *tui = (UiTerm*)ui; + ui_term_backend_free(tui); + if (tui->termkey) + termkey_stop(tui->termkey); + vfprintf(stderr, msg, ap); + exit(EXIT_FAILURE); +} + +__attribute__((noreturn)) static void ui_die_msg(Ui *ui, const char *msg, ...) { + va_list ap; + va_start(ap, msg); + ui_die(ui, msg, ap); + va_end(ap); +} + +static void ui_window_resize(UiTermWin *win, int width, int height) { + debug("ui-win-resize[%s]: %dx%d\n", win->win->file->name ? win->win->file->name : "noname", width, height); + bool status = win->options & UI_OPTION_STATUSBAR; + win->width = width; + win->height = height; + view_resize(win->win->view, width - win->sidebar_width, status ? height - 1 : height); +} + +static void ui_window_move(UiTermWin *win, int x, int y) { + debug("ui-win-move[%s]: (%d, %d)\n", win->win->file->name ? win->win->file->name : "noname", x, y); + win->x = x; + win->y = y; +} + +/* Convert color from string. */ +static bool color_fromstring(UiTerm *ui, CellColor *color, const char *s) +{ + if (!s) + return false; + if (*s == '#' && strlen(s) == 7) { + const char *cp; + unsigned char r, g, b; + for (cp = s + 1; isxdigit((unsigned char)*cp); cp++); + if (*cp != '\0') + return false; + int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b); + if (n != 3) + return false; + *color = color_rgb(ui, r, g, b); + return true; + } else if ('0' <= *s && *s <= '9') { + int index = atoi(s); + if (index <= 0 || index > 255) + return false; + *color = color_terminal(ui, index); + return true; + } + + struct { + const char *name; + CellColor color; + } color_names[] = { + { "black", CELL_COLOR_BLACK }, + { "red", CELL_COLOR_RED }, + { "green", CELL_COLOR_GREEN }, + { "yellow", CELL_COLOR_YELLOW }, + { "blue", CELL_COLOR_BLUE }, + { "magenta", CELL_COLOR_MAGENTA }, + { "cyan", CELL_COLOR_CYAN }, + { "white", CELL_COLOR_WHITE }, + { "default", CELL_COLOR_DEFAULT }, + }; + + for (size_t i = 0; i < LENGTH(color_names); i++) { + if (strcasecmp(color_names[i].name, s) == 0) { + *color = color_names[i].color; + return true; + } + } + + return false; +} + +static bool ui_style_define(UiWin *w, int id, const char *style) { + UiTermWin *win = (UiTermWin*)w; + UiTerm *tui = win->ui; + if (id >= UI_STYLE_MAX) + return false; + if (!style) + return true; + CellStyle cell_style = tui->styles[win->id * UI_STYLE_MAX + UI_STYLE_DEFAULT]; + char *style_copy = strdup(style), *option = style_copy, *next, *p; + while (option) { + if ((next = strchr(option, ','))) + *next++ = '\0'; + if ((p = strchr(option, ':'))) + *p++ = '\0'; + if (!strcasecmp(option, "reverse")) { + cell_style.attr |= CELL_ATTR_REVERSE; + } else if (!strcasecmp(option, "bold")) { + cell_style.attr |= CELL_ATTR_BOLD; + } else if (!strcasecmp(option, "notbold")) { + cell_style.attr &= ~CELL_ATTR_BOLD; + } else if (!strcasecmp(option, "italics")) { + cell_style.attr |= CELL_ATTR_ITALIC; + } else if (!strcasecmp(option, "notitalics")) { + cell_style.attr &= ~CELL_ATTR_ITALIC; + } else if (!strcasecmp(option, "underlined")) { + cell_style.attr |= CELL_ATTR_UNDERLINE; + } else if (!strcasecmp(option, "notunderlined")) { + cell_style.attr &= ~CELL_ATTR_UNDERLINE; + } else if (!strcasecmp(option, "blink")) { + cell_style.attr |= CELL_ATTR_BLINK; + } else if (!strcasecmp(option, "notblink")) { + cell_style.attr &= ~CELL_ATTR_BLINK; + } else if (!strcasecmp(option, "fore")) { + color_fromstring(win->ui, &cell_style.fg, p); + } else if (!strcasecmp(option, "back")) { + color_fromstring(win->ui, &cell_style.bg, p); + } + option = next; + } + tui->styles[win->id * UI_STYLE_MAX + id] = cell_style; + free(style_copy); + return true; +} + +static void ui_style(UiTerm *tui, int x, int y, int len, UiTermWin *win, enum UiStyle style_id) { + CellStyle style = tui->styles[(win ? win->id : 0)*UI_STYLE_MAX + style_id]; + Cell (*cells)[tui->width] = (void*)tui->cells; + int end = x + len; + if (end > tui->width) + end = tui->width; + while (x < end) + cells[y][x++].style = style; +} + +static void ui_draw_string(UiTerm *tui, int x, int y, const char *str, UiTermWin *win, enum UiStyle style_id) { + debug("draw-string: [%d][%d]\n", y, x); + CellStyle style = tui->styles[(win ? win->id : 0)*UI_STYLE_MAX + style_id]; + // FIXME: does not handle double width characters etc, share code with view.c? + Cell (*cells)[tui->width] = (void*)tui->cells; + const size_t cell_size = sizeof(cells[0][0].data)-1; + for (const char *next = str; *str && x < tui->width; str = next) { + do next++; while (!ISUTF8(*next)); + size_t len = next - str; + if (!len) + break; + len = MIN(len, cell_size); + strncpy(cells[y][x].data, str, len); + cells[y][x].data[len] = '\0'; + cells[y][x].style = style; + x++; + } +} + +static void ui_window_draw(UiWin *w) { + UiTermWin *win = (UiTermWin*)w; + UiTerm *ui = win->ui; + View *view = win->win->view; + Cell (*cells)[ui->width] = (void*)ui->cells; + int width = win->width, height = win->height; + const Line *line = view_lines_first(view); + bool status = win->options & UI_OPTION_STATUSBAR; + bool nu = win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE; + bool rnu = win->options & UI_OPTION_LINE_NUMBERS_RELATIVE; + bool sidebar = nu || rnu; + int sidebar_width = sidebar ? snprintf(NULL, 0, "%zd ", line->lineno + height - 2) : 0; + if (sidebar_width != win->sidebar_width) { + view_resize(view, width - sidebar_width, status ? height - 1 : height); + win->sidebar_width = sidebar_width; + } + vis_window_draw(win->win); + line = view_lines_first(view); + size_t prev_lineno = 0; + Cursor *cursor = view_cursors_primary_get(view); + const Line *cursor_line = view_cursors_line_get(cursor); + size_t cursor_lineno = cursor_line->lineno; + char buf[sidebar_width+1]; + int x = win->x, y = win->y; + int view_width = view_width_get(view); + if (x + sidebar_width + view_width > ui->width) + view_width = ui->width - x - sidebar_width; + for (const Line *l = line; l; l = l->next) { + if (sidebar) { + if (!l->lineno || !l->len || l->lineno == prev_lineno) { + memset(buf, ' ', sizeof(buf)); + buf[sidebar_width] = '\0'; + } else { + size_t number = l->lineno; + if (rnu) { + number = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno; + if (l->lineno > cursor_lineno) + number = l->lineno - cursor_lineno; + else if (l->lineno < cursor_lineno) + number = cursor_lineno - l->lineno; + } + snprintf(buf, sizeof buf, "%*zu ", sidebar_width-1, number); + } + ui_draw_string(ui, x, y, buf, win, UI_STYLE_LINENUMBER); + prev_lineno = l->lineno; + } + debug("draw-window: [%d][%d] ... cells[%d][%d]\n", y, x+sidebar_width, y, view_width); + memcpy(&cells[y++][x+sidebar_width], l->cells, sizeof(Cell) * view_width); + } +} + +static CellStyle ui_window_style_get(UiWin *w, enum UiStyle style) { + UiTermWin *win = (UiTermWin*)w; + UiTerm *tui = win->ui; + return tui->styles[win->id * UI_STYLE_MAX + style]; +} + +static void ui_window_status(UiWin *w, const char *status) { + UiTermWin *win = (UiTermWin*)w; + if (!(win->options & UI_OPTION_STATUSBAR)) + return; + UiTerm *ui = win->ui; + enum UiStyle style = ui->selwin == win ? UI_STYLE_STATUS_FOCUSED : UI_STYLE_STATUS; + ui_draw_string(ui, win->x, win->y + win->height - 1, status, win, style); +} + +static void ui_arrange(Ui *ui, enum UiLayout layout) { + debug("ui-arrange\n"); + UiTerm *tui = (UiTerm*)ui; + tui->layout = layout; + Cell (*cells)[tui->width] = (void*)tui->cells; + int n = 0, m = !!tui->info[0], x = 0, y = 0; + for (UiTermWin *win = tui->windows; win; win = win->next) { + if (win->options & UI_OPTION_ONELINE) + m++; + else + n++; + } + int max_height = tui->height - m; + int width = (tui->width / MAX(1, n)) - 1; + int height = max_height / MAX(1, n); + for (UiTermWin *win = tui->windows; win; win = win->next) { + if (win->options & UI_OPTION_ONELINE) + continue; + n--; + if (layout == UI_LAYOUT_HORIZONTAL) { + int h = n ? height : max_height - y; + ui_window_resize(win, tui->width, h); + ui_window_move(win, x, y); + y += h; + } else { + int w = n ? width : tui->width - x; + ui_window_resize(win, w, max_height); + ui_window_move(win, x, y); + x += w; + if (n) { + for (int i = 0; i < max_height; i++) { + strcpy(cells[i][x].data,"│"); + cells[i][x].style = tui->styles[UI_STYLE_SEPARATOR]; + } + x++; + } + } + } + + if (layout == UI_LAYOUT_VERTICAL) + y = max_height; + + for (UiTermWin *win = tui->windows; win; win = win->next) { + if (!(win->options & UI_OPTION_ONELINE)) + continue; + ui_window_resize(win, tui->width, 1); + ui_window_move(win, 0, y++); + } +} + +static void ui_draw(Ui *ui) { + debug("ui-draw\n"); + UiTerm *tui = (UiTerm*)ui; + ui_arrange(ui, tui->layout); + + for (UiTermWin *win = tui->windows; win; win = win->next) + ui_window_draw((UiWin*)win); + if (tui->info[0]) { + ui_draw_string(tui, 0, tui->height-1, tui->info, NULL, UI_STYLE_INFO); + ui_style(tui, 0, tui->height-1, tui->width, NULL, UI_STYLE_INFO); + } +} + +static void ui_redraw(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + ui_term_backend_clear(tui); + for (UiTermWin *win = tui->windows; win; win = win->next) + view_dirty(win->win->view); +} + +static bool ui_resize_to(Ui *ui, int width, int height) { + UiTerm *tui = (UiTerm*)ui; + width = MAX(width, 1); + width = MIN(width, MAX_WIDTH); + height = MAX(height, 1); + height = MIN(height, MAX_HEIGHT); + ui_term_backend_resize(tui, width, height); + size_t size = width*height*sizeof(Cell); + if (size > tui->cells_size) { + Cell *cells = realloc(tui->cells, size); + if (!cells) + return false; + tui->cells_size = size; + tui->cells = cells; + } + tui->width = width; + tui->height = height; + ui_draw(ui); + return true; +} + +static void ui_resize(Ui *ui) { + struct winsize ws; + int width = 80, height = 24; + + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1) { + width = ws.ws_col; + height = ws.ws_row; + } + + ui_resize_to(ui, width, height); +} + +static void ui_update(Ui *ui) { + debug("ui-doupdate\n"); + UiTerm *tui = (UiTerm*)ui; + ui_draw(ui); + ui_term_backend_blit(tui); +} + +static void ui_window_free(UiWin *w) { + UiTermWin *win = (UiTermWin*)w; + if (!win) + return; + UiTerm *tui = win->ui; + if (win->prev) + win->prev->next = win->next; + if (win->next) + win->next->prev = win->prev; + if (tui->windows == win) + tui->windows = win->next; + if (tui->selwin == win) + tui->selwin = NULL; + win->next = win->prev = NULL; + tui->ids &= ~(1UL << win->id); + free(win); +} + +static void ui_window_focus(UiWin *w) { + UiTermWin *new = (UiTermWin*)w; + UiTermWin *old = new->ui->selwin; + if (new->options & UI_OPTION_STATUSBAR) + new->ui->selwin = new; + if (old) + view_dirty(old->win->view); + view_dirty(new->win->view); +} + +static void ui_window_options_set(UiWin *w, enum UiOption options) { + UiTermWin *win = (UiTermWin*)w; + win->options = options; + if (options & UI_OPTION_ONELINE) { + /* move the new window to the end of the list */ + UiTerm *tui = win->ui; + UiTermWin *last = tui->windows; + while (last->next) + last = last->next; + if (last != win) { + if (win->prev) + win->prev->next = win->next; + if (win->next) + win->next->prev = win->prev; + if (tui->windows == win) + tui->windows = win->next; + last->next = win; + win->prev = last; + win->next = NULL; + } + } + + ui_draw((Ui*)win->ui); +} + +static enum UiOption ui_window_options_get(UiWin *win) { + return ((UiTermWin*)win)->options; +} + +static int ui_window_width(UiWin *win) { + return ((UiTermWin*)win)->width; +} + +static int ui_window_height(UiWin *win) { + return ((UiTermWin*)win)->height; +} + +static void ui_window_swap(UiWin *aw, UiWin *bw) { + UiTermWin *a = (UiTermWin*)aw; + UiTermWin *b = (UiTermWin*)bw; + if (a == b || !a || !b) + return; + UiTerm *tui = a->ui; + UiTermWin *tmp = a->next; + a->next = b->next; + b->next = tmp; + if (a->next) + a->next->prev = a; + if (b->next) + b->next->prev = b; + tmp = a->prev; + a->prev = b->prev; + b->prev = tmp; + if (a->prev) + a->prev->next = a; + if (b->prev) + b->prev->next = b; + if (tui->windows == a) + tui->windows = b; + else if (tui->windows == b) + tui->windows = a; + if (tui->selwin == a) + ui_window_focus(bw); + else if (tui->selwin == b) + ui_window_focus(aw); +} + +static UiWin *ui_window_new(Ui *ui, Win *w, enum UiOption options) { + UiTerm *tui = (UiTerm*)ui; + /* get rightmost zero bit, i.e. highest available id */ + size_t bit = ~tui->ids & (tui->ids + 1); + size_t id = 0; + for (size_t tmp = bit; tmp >>= 1; id++); + if (id >= sizeof(size_t) * 8) + return NULL; + size_t styles_size = (id + 1) * UI_STYLE_MAX * sizeof(CellStyle); + if (styles_size > tui->styles_size) { + CellStyle *styles = realloc(tui->styles, styles_size); + if (!styles) + return NULL; + tui->styles = styles; + tui->styles_size = styles_size; + } + UiTermWin *win = calloc(1, sizeof(UiTermWin)); + if (!win) + return NULL; + + win->uiwin = (UiWin) { + .style_get = ui_window_style_get, + .status = ui_window_status, + .options_set = ui_window_options_set, + .options_get = ui_window_options_get, + .style_define = ui_style_define, + .window_width = ui_window_width, + .window_height = ui_window_height, + }; + + tui->ids |= bit; + win->id = id; + win->ui = tui; + win->win = w; + + CellStyle *styles = &tui->styles[win->id * UI_STYLE_MAX]; + for (int i = 0; i < UI_STYLE_MAX; i++) { + styles[i] = (CellStyle) { + .fg = CELL_COLOR_DEFAULT, + .bg = CELL_COLOR_DEFAULT, + .attr = CELL_ATTR_NORMAL, + }; + } + + styles[UI_STYLE_CURSOR].attr |= CELL_ATTR_REVERSE; + styles[UI_STYLE_CURSOR_PRIMARY].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BLINK; + styles[UI_STYLE_SELECTION].attr |= CELL_ATTR_REVERSE; + styles[UI_STYLE_COLOR_COLUMN].attr |= CELL_ATTR_REVERSE; + styles[UI_STYLE_STATUS].attr |= CELL_ATTR_REVERSE; + styles[UI_STYLE_STATUS_FOCUSED].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BOLD; + styles[UI_STYLE_INFO].attr |= CELL_ATTR_BOLD; + view_ui(w->view, &win->uiwin); + + if (tui->windows) + tui->windows->prev = win; + win->next = tui->windows; + tui->windows = win; + + if (text_size(w->file->text) > UI_LARGE_FILE_SIZE) { + options |= UI_OPTION_LARGE_FILE; + options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE; + } + + ui_window_options_set((UiWin*)win, options); + + return &win->uiwin; +} + +static void ui_info(Ui *ui, const char *msg, va_list ap) { + UiTerm *tui = (UiTerm*)ui; + vsnprintf(tui->info, sizeof(tui->info), msg, ap); + ui_draw(ui); +} + +static void ui_info_hide(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + if (tui->info[0]) { + tui->info[0] = '\0'; + ui_draw(ui); + } +} + +static TermKey *ui_termkey_new(int fd) { + TermKey *termkey = termkey_new(fd, TERMKEY_FLAG_UTF8); + if (termkey) + termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS); + return termkey; +} + +static TermKey *ui_termkey_reopen(Ui *ui, int fd) { + int tty = open("/dev/tty", O_RDWR); + if (tty == -1) + return NULL; + if (tty != fd && dup2(tty, fd) == -1) { + close(tty); + return NULL; + } + close(tty); + return ui_termkey_new(fd); +} + +static TermKey *ui_termkey_get(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + return tui->termkey; +} + +static void ui_suspend(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + ui_term_backend_suspend(tui); + termkey_stop(tui->termkey); + kill(0, SIGSTOP); +} + +static void ui_resume(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + termkey_start(tui->termkey); + ui_term_backend_resume(tui); +} + +static bool ui_getkey(Ui *ui, TermKeyKey *key) { + UiTerm *tui = (UiTerm*)ui; + TermKeyResult ret = termkey_getkey(tui->termkey, key); + + if (ret == TERMKEY_RES_EOF) { + termkey_destroy(tui->termkey); + errno = 0; + if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO))) + ui_die_msg(ui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : ""); + return false; + } + + if (ret == TERMKEY_RES_AGAIN) { + struct pollfd fd; + fd.fd = STDIN_FILENO; + fd.events = POLLIN; + if (poll(&fd, 1, termkey_get_waittime(tui->termkey)) == 0) + ret = termkey_getkey_force(tui->termkey, key); + } + + return ret == TERMKEY_RES_KEY; +} + +static void ui_terminal_save(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + ui_term_backend_save(tui); + termkey_stop(tui->termkey); +} + +static void ui_terminal_restore(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + termkey_start(tui->termkey); + ui_term_backend_restore(tui); +} + +static bool ui_init(Ui *ui, Vis *vis) { + UiTerm *tui = (UiTerm*)ui; + tui->vis = vis; + + setlocale(LC_CTYPE, ""); + + char *term = getenv("TERM"); + if (!term) + term = "xterm"; + + errno = 0; + if (!(tui->termkey = ui_termkey_new(STDIN_FILENO))) { + /* work around libtermkey bug which fails if stdin is /dev/null */ + if (errno == EBADF && !isatty(STDIN_FILENO)) { + errno = 0; + if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)) && errno == ENXIO) + tui->termkey = termkey_new_abstract(term, TERMKEY_FLAG_UTF8); + } + if (!tui->termkey) + goto err; + } + + if (!ui_term_backend_init(tui, term)) + goto err; + ui_resize(ui); + return true; +err: + ui_die_msg(ui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : ""); + return false; +} + +Ui *ui_term_new(void) { + size_t styles_size = UI_STYLE_MAX * sizeof(CellStyle); + CellStyle *styles = calloc(1, styles_size); + if (!styles) + return NULL; + UiTerm *tui = ui_term_backend_new(); + if (!tui) { + free(styles); + return NULL; + } + tui->styles_size = styles_size; + tui->styles = styles; + Ui *ui = (Ui*)tui; + *ui = (Ui) { + .init = ui_init, + .free = ui_term_free, + .termkey_get = ui_termkey_get, + .suspend = ui_suspend, + .resume = ui_resume, + .resize = ui_resize, + .update = ui_update, + .window_new = ui_window_new, + .window_free = ui_window_free, + .window_focus = ui_window_focus, + .window_swap = ui_window_swap, + .draw = ui_draw, + .redraw = ui_redraw, + .arrange = ui_arrange, + .die = ui_die, + .info = ui_info, + .info_hide = ui_info_hide, + .getkey = ui_getkey, + .terminal_save = ui_terminal_save, + .terminal_restore = ui_terminal_restore, + .colors = ui_term_backend_colors, + }; + + return ui; +} + +void ui_term_free(Ui *ui) { + UiTerm *tui = (UiTerm*)ui; + if (!tui) + return; + while (tui->windows) + ui_window_free((UiWin*)tui->windows); + ui_term_backend_free(tui); + if (tui->termkey) + termkey_destroy(tui->termkey); + free(tui->cells); + free(tui->styles); + free(tui); +} diff --git a/ui-terminal.h b/ui-terminal.h new file mode 100644 index 000000000..45286abff --- /dev/null +++ b/ui-terminal.h @@ -0,0 +1,9 @@ +#ifndef UI_TERMINAL_H +#define UI_TERMINAL_H + +#include "ui.h" + +Ui *ui_term_new(void); +void ui_term_free(Ui*); + +#endif diff --git a/ui.h b/ui.h index dd663b8a1..41ef97513 100644 --- a/ui.h +++ b/ui.h @@ -42,9 +42,26 @@ enum UiStyle { UI_STYLE_SELECTION, UI_STYLE_LINENUMBER, UI_STYLE_COLOR_COLUMN, + UI_STYLE_STATUS, + UI_STYLE_STATUS_FOCUSED, + UI_STYLE_SEPARATOR, + UI_STYLE_INFO, + UI_STYLE_EOF, UI_STYLE_MAX, }; +typedef uint64_t CellAttr; +typedef short CellColor; + +static inline bool cell_color_equal(CellColor c1, CellColor c2) { + return c1 == c2; +} + +typedef struct { + CellAttr attr; + CellColor fg, bg; +} CellStyle; + #include "vis.h" #include "text.h" #include "view.h" @@ -53,7 +70,7 @@ struct Ui { bool (*init)(Ui*, Vis*); void (*free)(Ui*); void (*resize)(Ui*); - UiWin* (*window_new)(Ui*, View*, File*, enum UiOption); + UiWin* (*window_new)(Ui*, Win*, enum UiOption); void (*window_free)(UiWin*); void (*window_focus)(UiWin*); void (*window_swap)(UiWin*, UiWin*); @@ -65,6 +82,7 @@ struct Ui { void (*redraw)(Ui*); void (*update)(Ui*); void (*suspend)(Ui*); + void (*resume)(Ui*); bool (*getkey)(Ui*, TermKeyKey*); void (*terminal_save)(Ui*); void (*terminal_restore)(Ui*); @@ -73,12 +91,11 @@ struct Ui { }; struct UiWin { - void (*draw)(UiWin*); + CellStyle (*style_get)(UiWin*, enum UiStyle); void (*status)(UiWin*, const char *txt); - void (*reload)(UiWin*, File*); void (*options_set)(UiWin*, enum UiOption); enum UiOption (*options_get)(UiWin*); - bool (*syntax_style)(UiWin*, int id, const char *style); + bool (*style_define)(UiWin*, int id, const char *style); int (*window_width)(UiWin*); int (*window_height)(UiWin*); }; diff --git a/view.c b/view.c index d6a3f3f90..ea6b70cc3 100644 --- a/view.c +++ b/view.c @@ -12,7 +12,6 @@ typedef struct { char *symbol; - int style; } SyntaxSymbol; enum { @@ -20,7 +19,6 @@ enum { SYNTAX_SYMBOL_TAB, SYNTAX_SYMBOL_TAB_FILL, SYNTAX_SYMBOL_EOL, - SYNTAX_SYMBOL_EOF, SYNTAX_SYMBOL_LAST, }; @@ -70,6 +68,7 @@ struct Cursor { /* cursor position */ struct View { Text *text; /* underlying text management */ UiWin *ui; + Cell cell_blank; /* used for empty/blank cells */ int width, height; /* size of display area */ size_t start, end; /* currently displayed area [start, end] in bytes from the start of the file */ size_t start_last; /* previously used start of visible area, used to update the mark */ @@ -93,7 +92,6 @@ struct View { bool need_update; /* whether view has been redrawn */ bool large_file; /* optimize for displaying large files */ int colorcolumn; - ViewEvent *events; }; static const SyntaxSymbol symbols_none[] = { @@ -101,7 +99,6 @@ static const SyntaxSymbol symbols_none[] = { [SYNTAX_SYMBOL_TAB] = { " " }, [SYNTAX_SYMBOL_TAB_FILL] = { " " }, [SYNTAX_SYMBOL_EOL] = { " " }, - [SYNTAX_SYMBOL_EOF] = { "~" }, }; static const SyntaxSymbol symbols_default[] = { @@ -109,15 +106,12 @@ static const SyntaxSymbol symbols_default[] = { [SYNTAX_SYMBOL_TAB] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ }, [SYNTAX_SYMBOL_TAB_FILL] = { " " }, [SYNTAX_SYMBOL_EOL] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ }, - [SYNTAX_SYMBOL_EOF] = { "~" }, }; static Cell cell_unused; -static Cell cell_blank = { .data = " " }; static void view_clear(View *view); static bool view_addch(View *view, Cell *cell); -static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol); static void view_cursors_free(Cursor *c); /* set/move current cursor position to a given (line, column) pair */ static size_t cursor_set(Cursor *cursor, Line *line, int col); @@ -178,6 +172,7 @@ static bool view_addch(View *view, Cell *cell) { int width; size_t lineno = view->line->lineno; unsigned char ch = (unsigned char)cell->data[0]; + cell->style = view->cell_blank.style; switch (ch) { case '\t': @@ -195,7 +190,6 @@ static bool view_addch(View *view, Cell *cell) { cell->len = w == 0 ? 1 : 0; int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL; strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1); - cell->style = view->symbols[t]->style; view->line->cells[view->col] = *cell; view->line->len += cell->len; view->line->width += cell->width; @@ -214,13 +208,12 @@ static bool view_addch(View *view, Cell *cell) { } strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1); - cell->style = view->symbols[SYNTAX_SYMBOL_EOL]->style; view->line->cells[view->col] = *cell; view->line->len += cell->len; view->line->width += cell->width; for (int i = view->col + 1; i < view->width; i++) - view->line->cells[i] = cell_blank; + view->line->cells[i] = view->cell_blank; view->line = view->line->next; if (view->line) @@ -240,13 +233,12 @@ static bool view_addch(View *view, Cell *cell) { if (ch == ' ') { strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1); - cell->style = view->symbols[SYNTAX_SYMBOL_SPACE]->style; } if (view->col + cell->width > view->width) { for (int i = view->col; i < view->width; i++) - view->line->cells[i] = cell_blank; + view->line->cells[i] = view->cell_blank; view->line = view->line->next; view->col = 0; } @@ -287,7 +279,7 @@ static void cursor_to(Cursor *c, size_t pos) { view_draw(c->view); } -static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) { +bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) { int row = 0, col = 0; size_t cur = view->start; Line *line = view->topline; @@ -350,7 +342,7 @@ void view_draw(View *view) { /* start from known multibyte state */ mbstate_t mbstate = { 0 }; - Cell cell = { 0 }, prev_cell = { 0 }; + Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell; while (rem > 0) { @@ -412,10 +404,11 @@ void view_draw(View *view) { if (prev_cell.len && view_addch(view, &prev_cell)) pos += prev_cell.len; - /* set end of vieviewg region */ + /* set end of viewing region */ view->end = pos; if (view->line) { - if (view->line->len == 0 && view->end == text_size(view->text) && view->line->prev) + bool eof = view->end == text_size(view->text); + if (view->line->len == 0 && eof && view->line->prev) view->lastline = view->line->prev; else view->lastline = view->line; @@ -426,23 +419,14 @@ void view_draw(View *view) { /* clear remaining of line, important to show cursor at end of file */ if (view->line) { for (int x = view->col; x < view->width; x++) - view->line->cells[x] = cell_blank; + view->line->cells[x] = view->cell_blank; } /* resync position of cursors within visible area */ for (Cursor *c = view->cursors; c; c = c->next) { size_t pos = view_cursors_pos(c); - if (view_coord_get(view, pos, &c->line, &c->row, &c->col)) { - c->line->cells[c->col].cursor = true; - c->line->cells[c->col].cursor_primary = (c == view->cursor); - if (view->ui && !c->sel) { - Line *line_match; int col_match; - size_t pos_match = text_bracket_match_symbol(view->text, pos, "(){}[]\"'`"); - if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) { - line_match->cells[col_match].selected = true; - } - } - } else if (c == view->cursor) { + if (!view_coord_get(view, pos, &c->line, &c->row, &c->col) && + c == view->cursor) { c->line = view->topline; c->row = 0; c->col = 0; @@ -452,77 +436,19 @@ void view_draw(View *view) { view->need_update = true; } -void view_update(View *view) { - if (!view->need_update) - return; - - if (view->events->draw) - view->events->draw(view->events->data); - - if (view->colorcolumn > 0) { - size_t lineno = 0; - int line_cols = 0; /* Track the number of columns we've passed on each line */ - bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */ - - for (Line *l = view->topline; l; l = l->next) { - if (l->lineno != lineno) { - line_cols = 0; - line_cc_set = false; - } - - if (!line_cc_set) { - line_cols += view->width; - - /* This screen line contains the cell we want to highlight */ - if (line_cols >= view->colorcolumn) { - l->cells[(view->colorcolumn - 1) % view->width].style = UI_STYLE_COLOR_COLUMN; - line_cc_set = true; - } - } - - lineno = l->lineno; - } - } +void view_dirty(View *view) { + view->need_update = true; +} +bool view_update(View *view) { + if (!view->need_update) + return false; for (Line *l = view->lastline->next; l; l = l->next) { - strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data)); - l->cells[0].style = view->symbols[SYNTAX_SYMBOL_EOF]->style; - for (int x = 1; x < view->width; x++) - l->cells[x] = cell_blank; - l->width = 1; - l->len = 0; - } - - for (Selection *s = view->selections; s; s = s->next) { - Filerange sel = view_selections_get(s); - if (text_range_valid(&sel)) { - Line *start_line; int start_col; - Line *end_line; int end_col; - view_coord_get(view, sel.start, &start_line, NULL, &start_col); - view_coord_get(view, sel.end, &end_line, NULL, &end_col); - if (start_line || end_line) { - if (!start_line) { - start_line = view->topline; - start_col = 0; - } - if (!end_line) { - end_line = view->lastline; - end_col = end_line->width; - } - for (Line *l = start_line; l != end_line->next; l = l->next) { - int col = (l == start_line) ? start_col : 0; - int end = (l == end_line) ? end_col : l->width; - while (col < end) { - l->cells[col++].selected = true; - } - } - } - } + for (int x = 0; x < view->width; x++) + l->cells[x] = view->cell_blank; } - - if (view->ui) - view->ui->draw(view->ui); view->need_update = false; + return true; } bool view_resize(View *view, int width, int height) { @@ -544,8 +470,6 @@ bool view_resize(View *view, int width, int height) { view->height = height; memset(view->lines, 0, view->lines_size); view_draw(view); - if (view->ui) - view_update(view); return true; } @@ -574,7 +498,7 @@ void view_reload(View *view, Text *text) { view_cursor_to(view, 0); } -View *view_new(Text *text, ViewEvent *events) { +View *view_new(Text *text) { if (!text) return NULL; View *view = calloc(1, sizeof(View)); @@ -585,6 +509,11 @@ View *view_new(Text *text, ViewEvent *events) { return NULL; } + view->cell_blank = (Cell) { + .width = 0, + .len = 0, + .data = " ", + }; view->text = text; view->tabwidth = 8; view_options_set(view, 0); @@ -595,13 +524,13 @@ View *view_new(Text *text, ViewEvent *events) { } view_cursor_to(view, 0); - view->events = events; return view; } void view_ui(View *view, UiWin* ui) { view->ui = ui; + view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT); } static size_t cursor_set(Cursor *cursor, Line *line, int col) { @@ -886,12 +815,16 @@ size_t view_cursor_get(View *view) { return view_cursors_pos(view->cursor); } -const Line *view_lines_get(View *view) { +Line *view_lines_first(View *view) { return view->topline; } -const Line *view_line_get(View *view) { - return view->cursor->line; +Line *view_lines_last(View *view) { + return view->lastline; +} + +Line *view_cursors_line_get(Cursor *c) { + return c->line; } void view_scroll_to(View *view, size_t pos) { @@ -904,7 +837,6 @@ void view_options_set(View *view, enum UiOption options) { [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB, [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL, [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL, - [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF, }; for (int i = 0; i < LENGTH(mapping); i++) { @@ -917,8 +849,10 @@ void view_options_set(View *view, enum UiOption options) { view->large_file = (options & UI_OPTION_LARGE_FILE); - if (view->ui) + if (view->ui) { view->ui->options_set(view->ui, options); + view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT); + } } enum UiOption view_options_get(View *view) { @@ -1428,13 +1362,14 @@ Text *view_text(View *view) { } bool view_style_define(View *view, enum UiStyle id, const char *style) { - return view->ui->syntax_style(view->ui, id, style); + return view->ui->style_define(view->ui, id, style); } -void view_style(View *view, enum UiStyle style, size_t start, size_t end) { +void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) { if (end < view->start || start > view->end) return; + CellStyle style = view->ui->style_get(view->ui, style_id); size_t pos = view->start; Line *line = view->topline; diff --git a/view.h b/view.h index 4f25588c1..6c30b51eb 100644 --- a/view.h +++ b/view.h @@ -13,23 +13,15 @@ typedef struct Selection Selection; #include "register.h" typedef struct { - void *data; - void (*draw)(void *data); -} ViewEvent; - -typedef struct { - int width; /* display width i.e. number of columns occupied by this character */ + char data[16]; /* utf8 encoded character displayed in this cell (might be more than + one Unicode codepoint. might also not be the same as in the + underlying text, for example tabs get expanded */ size_t len; /* number of bytes the character displayed in this cell uses, for characters which use more than 1 column to display, their length is stored in the leftmost cell whereas all following cells occupied by the same character have a length of 0. */ - char data[16]; /* utf8 encoded character displayed in this cell (might be more than - one Unicode codepoint. might also not be the same as in the - underlying text, for example tabs get expanded */ - enum UiStyle style; /* style id used to display this cell */ - bool selected; /* whether this cell is part of a selected region */ - bool cursor; /* whether a cursor is currently located on the cell */ - bool cursor_primary;/* whether it is the primary cursor located on the cell */ + int width; /* display width i.e. number of columns occupied by this character */ + CellStyle style; /* colors and attributes used to display this cell */ } Cell; typedef struct Line Line; @@ -41,7 +33,7 @@ struct Line { /* a line on the screen, *not* in the file */ Cell cells[]; /* win->width cells storing information about the displayed characters */ }; -View *view_new(Text*, ViewEvent*); +View *view_new(Text*); void view_ui(View*, UiWin*); /* change associated text displayed in this window */ void view_reload(View*, Text*); @@ -50,8 +42,9 @@ void view_free(View*); bool view_resize(View*, int width, int height); int view_height_get(View*); int view_width_get(View*); +void view_dirty(View*); void view_draw(View*); -void view_update(View*); +bool view_update(View*); /* changes how many spaces are used for one tab (must be >0), redraws the window */ void view_tabwidth_set(View*, int tabwidth); @@ -80,8 +73,9 @@ size_t view_scroll_halfpage_down(View*); /* place the cursor at the start ot the n-th window line, counting from 1 */ size_t view_screenline_goto(View*, int n); -const Line *view_lines_get(View*); -const Line *view_line_get(View*); +Line *view_lines_first(View*); +Line *view_lines_last(View*); +Line *view_cursors_line_get(Cursor*); /* redraw current cursor line at top/center/bottom of window */ void view_redraw_top(View*); void view_redraw_center(View*); @@ -100,6 +94,7 @@ enum UiOption view_options_get(View*); void view_colorcolumn_set(View*, int col); int view_colorcolumn_get(View*); +bool view_coord_get(View*, size_t pos, Line **retline, int *retrow, int *retcol); /* A view can manage multiple cursors, one of which (the main cursor) is always * placed within the visible viewport. All functions named view_cursor_* operate * on this cursor. Additional cursor can be created and manipulated using the diff --git a/vis-core.h b/vis-core.h index 435079417..0739e55aa 100644 --- a/vis-core.h +++ b/vis-core.h @@ -147,7 +147,6 @@ struct Win { Mode modes[VIS_MODE_INVALID]; /* overlay mods used for per window key bindings */ Win *parent; /* window which was active when showing the command prompt */ Mode *parent_mode; /* mode which was active when showing the command prompt */ - ViewEvent event; /* callbacks from view.[ch] */ char *lexer_name; /* corresponds to filename in lexers/ subdirectory */ size_t horizon; /* max bytes to consider for syntax coloring before viewport */ Win *prev, *next; /* neighbouring windows */ @@ -195,6 +194,7 @@ struct Vis { volatile sig_atomic_t cancel_filter; /* abort external command/filter (SIGINT occured) */ volatile sig_atomic_t sigbus; /* one of the memory mapped region became unavailable (SIGBUS) */ volatile sig_atomic_t need_resize; /* need to resize UI (SIGWINCH occured) */ + volatile sig_atomic_t resume; /* need to resume UI (SIGCONT occured) */ volatile sig_atomic_t terminate; /* need to terminate we were being killed by SIGTERM */ sigjmp_buf sigbus_jmpbuf; /* used to jump back to a known good state in the mainloop after (SIGBUS) */ Map *actions; /* registered editor actions / special keys commands */ diff --git a/vis-lua.c b/vis-lua.c index 0590f55bc..47b1dcd42 100644 --- a/vis-lua.c +++ b/vis-lua.c @@ -2430,6 +2430,11 @@ void vis_lua_init(Vis *vis) { { UI_STYLE_SELECTION, "STYLE_SELECTION" }, { UI_STYLE_LINENUMBER, "STYLE_LINENUMBER" }, { UI_STYLE_COLOR_COLUMN, "STYLE_COLOR_COLUMN" }, + { UI_STYLE_STATUS, "STYLE_STATUS" }, + { UI_STYLE_STATUS_FOCUSED, "STYLE_STATUS_FOCUSED" }, + { UI_STYLE_SEPARATOR, "STYLE_SEPARATOR" }, + { UI_STYLE_INFO, "STYLE_INFO" }, + { UI_STYLE_EOF, "STYLE_EOF" }, }; for (size_t i = 0; i < LENGTH(styles); i++) { diff --git a/vis.c b/vis.c index 8757ec174..850f85001 100644 --- a/vis.c +++ b/vis.c @@ -305,12 +305,176 @@ static void window_free(Win *win) { free(win); } -static void window_draw(void *ctx) { - Win *win = ctx; - if (!win->ui) +static void window_draw_colorcolumn(Win *win) { + View *view = win->view; + int cc = view_colorcolumn_get(view); + if (cc <= 0) + return; + CellStyle style = win->ui->style_get(win->ui, UI_STYLE_COLOR_COLUMN); + size_t lineno = 0; + int line_cols = 0; /* Track the number of columns we've passed on each line */ + bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */ + int width = view_width_get(view); + + for (Line *l = view_lines_first(view); l; l = l->next) { + if (l->lineno != lineno) { + line_cols = 0; + line_cc_set = false; + lineno = l->lineno; + } + + if (line_cc_set) + continue; + line_cols += width; + + /* This screen line contains the cell we want to highlight */ + if (line_cols >= cc) { + l->cells[(cc - 1) % width].style = style; + line_cc_set = true; + } + } +} + +static void window_draw_cursorline(Win *win) { + Vis *vis = win->vis; + View *view = win->view; + enum UiOption options = view_options_get(view); + if (!(options & UI_OPTION_CURSOR_LINE)) + return; + if (vis->mode->visual || vis->win != win) + return; + if (view_cursors_multiple(view)) + return; + + int width = view_width_get(view); + CellStyle style = win->ui->style_get(win->ui, UI_STYLE_CURSOR_LINE); + Cursor *cursor = view_cursors_primary_get(view); + size_t lineno = view_cursors_line_get(cursor)->lineno; + for (Line *l = view_lines_first(view); l; l = l->next) { + if (l->lineno == lineno) { + for (int x = 0; x < width; x++) { + l->cells[x].style.attr |= style.attr; + l->cells[x].style.bg = style.bg; + } + } else if (l->lineno > lineno) { + break; + } + } +} + +static void window_draw_selection(View *view, Cursor *cur, CellStyle *style) { + Filerange sel = view_cursors_selection_get(cur); + if (!text_range_valid(&sel)) + return; + Line *start_line; int start_col; + Line *end_line; int end_col; + view_coord_get(view, sel.start, &start_line, NULL, &start_col); + view_coord_get(view, sel.end, &end_line, NULL, &end_col); + if (!start_line && !end_line) + return; + if (!start_line) { + start_line = view_lines_first(view); + start_col = 0; + } + if (!end_line) { + end_line = view_lines_last(view); + end_col = end_line->width; + } + for (Line *l = start_line; l != end_line->next; l = l->next) { + int col = (l == start_line) ? start_col : 0; + int end = (l == end_line) ? end_col : l->width; + while (col < end) { + if (cell_color_equal(l->cells[col].style.fg, style->fg)) { + CellStyle old = l->cells[col].style; + l->cells[col].style.fg = old.bg; + l->cells[col].style.bg = old.fg; + } else { + l->cells[col].style.bg = style->bg; + } + col++; + } + } +} + +static void window_draw_cursor_matching(Win *win, Cursor *cur, CellStyle *style) { + if (win->vis->mode->visual) + return; + Line *line_match; int col_match; + size_t pos = view_cursors_pos(cur); + size_t pos_match = text_bracket_match_symbol(win->file->text, pos, "(){}[]\"'`"); + if (pos == pos_match) + return; + if (!view_coord_get(win->view, pos_match, &line_match, NULL, &col_match)) + return; + if (cell_color_equal(line_match->cells[col_match].style.fg, style->fg)) { + CellStyle old = line_match->cells[col_match].style; + line_match->cells[col_match].style.fg = old.bg; + line_match->cells[col_match].style.bg = old.fg; + } else { + line_match->cells[col_match].style.bg = style->bg; + } +} + +static void window_draw_cursor(Win *win, Cursor *cur, CellStyle *style, CellStyle *sel_style) { + if (win->vis->win != win) + return; + Line *line = view_cursors_line_get(cur); + int col = view_cursors_cell_get(cur); + if (!line || col == -1) + return; + line->cells[col].style = *style; + window_draw_cursor_matching(win, cur, sel_style); + return; +} + +static void window_draw_cursors(Win *win) { + View *view = win->view; + Filerange viewport = view_viewport_get(view); + bool multiple_cursors = view_cursors_multiple(view); + Cursor *cursor = view_cursors_primary_get(view); + CellStyle style_cursor = win->ui->style_get(win->ui, UI_STYLE_CURSOR); + CellStyle style_cursor_primary = win->ui->style_get(win->ui, UI_STYLE_CURSOR_PRIMARY); + CellStyle style_selection = win->ui->style_get(win->ui, UI_STYLE_SELECTION); + for (Cursor *c = view_cursors_prev(cursor); c; c = view_cursors_prev(c)) { + window_draw_selection(win->view, c, &style_selection); + size_t pos = view_cursors_pos(c); + if (pos < viewport.start) + break; + window_draw_cursor(win, c, &style_cursor, &style_selection); + } + window_draw_selection(win->view, cursor, &style_selection); + window_draw_cursor(win, cursor, multiple_cursors ? &style_cursor_primary : &style_cursor, &style_selection); + for (Cursor *c = view_cursors_next(cursor); c; c = view_cursors_next(c)) { + window_draw_selection(win->view, c, &style_selection); + size_t pos = view_cursors_pos(c); + if (pos > viewport.end) + break; + window_draw_cursor(win, c, &style_cursor, &style_selection); + } +} + +static void window_draw_eof(Win *win) { + View *view = win->view; + if (view_width_get(view) == 0) + return; + CellStyle style = win->ui->style_get(win->ui, UI_STYLE_EOF); + for (Line *l = view_lines_last(view)->next; l; l = l->next) { + strcpy(l->cells[0].data, "~"); + l->cells[0].style = style; + } +} + +void vis_window_draw(Win *win) { + if (!win->ui || !view_update(win->view)) return; Vis *vis = win->vis; vis_event_emit(vis, VIS_EVENT_WIN_HIGHLIGHT, win); + + window_draw_colorcolumn(win); + window_draw_cursorline(win); + window_draw_cursors(win); + window_draw_eof(win); + vis_event_emit(vis, VIS_EVENT_WIN_STATUS, win); } @@ -321,11 +485,9 @@ Win *window_new_file(Vis *vis, File *file, enum UiOption options) { win->vis = vis; win->file = file; win->jumplist = ringbuf_alloc(31); - win->event.data = win; - win->event.draw = window_draw; win->horizon = 1 << 15; - win->view = view_new(file->text, &win->event); - win->ui = vis->ui->window_new(vis->ui, win->view, file, options); + win->view = view_new(file->text); + win->ui = vis->ui->window_new(vis->ui, win, options); if (!win->jumplist || !win->view || !win->ui) { window_free(win); return NULL; @@ -358,7 +520,7 @@ bool vis_window_reload(Win *win) { file_free(win->vis, win->file); file->refcount = 1; win->file = file; - win->ui->reload(win->ui, file); + view_reload(win->view, file->text); return true; } @@ -411,6 +573,7 @@ const char *vis_window_syntax_get(Win *win) { bool vis_window_syntax_set(Win *win, const char *syntax) { if (!vis_event_emit(win->vis, VIS_EVENT_WIN_SYNTAX, win, syntax)) return false; + view_options_set(win->view, view_options_get(win->view)); free(win->lexer_name); win->lexer_name = syntax ? strdup(syntax) : NULL; return !syntax || win->lexer_name; @@ -435,8 +598,6 @@ void vis_redraw(Vis *vis) { } void vis_update(Vis *vis) { - for (Win *win = vis->windows; win; win = win->next) - view_update(win->view); vis->ui->update(vis->ui); } @@ -1136,6 +1297,8 @@ bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const vo vis->cancel_filter = true; return true; case SIGCONT: + vis->resume = true; + /* fall through */ case SIGWINCH: vis->need_resize = true; return true; @@ -1191,6 +1354,10 @@ int vis_run(Vis *vis, int argc, char *argv[]) { if (vis->terminate) vis_die(vis, "Killed by SIGTERM\n"); + if (vis->resume) { + vis->ui->resume(vis->ui); + vis->resume = false; + } if (vis->need_resize) { vis->ui->resize(vis->ui); vis->need_resize = false; diff --git a/vis.h b/vis.h index fb9f4b113..46123eaac 100644 --- a/vis.h +++ b/vis.h @@ -96,6 +96,7 @@ void vis_redraw(Vis*); void vis_update(Vis*); /* temporarily supsend the editor process, resumes upon receiving SIGCONT */ void vis_suspend(Vis*); +void vis_resume(Vis*); /* creates a new window, and loads the given file. if filename is NULL * an unamed / empty buffer is created. If the given file is already opened @@ -118,6 +119,7 @@ void vis_window_close(Win*); bool vis_window_split(Win*); /* change status message of this window */ void vis_window_status(Win*, const char *status); +void vis_window_draw(Win*); /* focus the next / previous window */ void vis_window_next(Vis*); void vis_window_prev(Vis*);