summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc André Tanner <mat@brain-dump.org>2017-03-14 16:53:53 +0100
committerMarc André Tanner <mat@brain-dump.org>2017-03-14 19:04:21 +0100
commit9bcf2667e7e239873597b7ec2172206a9af18071 (patch)
tree7e9ccb42fa665ba01be65b93fc995fa76719aaf7
parentbed289a96e1ed17e4b9fa4f9e22227fcf13cc818 (diff)
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.
-rw-r--r--Makefile2
-rw-r--r--lua/themes/dark-16.lua7
-rw-r--r--lua/themes/light-16.lua5
-rw-r--r--lua/themes/solarized.lua5
-rw-r--r--lua/vis-std.lua5
-rw-r--r--main.c5
-rw-r--r--ui-curses.c970
-rw-r--r--ui-curses.h10
-rw-r--r--ui-terminal-curses.c286
-rw-r--r--ui-terminal.c727
-rw-r--r--ui-terminal.h9
-rw-r--r--ui.h25
-rw-r--r--view.c147
-rw-r--r--view.h29
-rw-r--r--vis-core.h2
-rw-r--r--vis-lua.c5
-rw-r--r--vis.c187
-rw-r--r--vis.h2
18 files changed, 1306 insertions, 1122 deletions
diff --git a/Makefile b/Makefile
index ebf3130..5c92cda 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 986c4b3..fcfc1f8 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 b4ba391..cf72f7f 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 70ad51f..04540ec 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 a0b84f6..4492fcc 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 6751fa1..db6b994 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,7 @@
#include <signal.h>
#include <limits.h>
#include <string.h>
+#include <stdio.h>
#include <wchar.h>
#include <ctype.h>
#include <errno.h>
@@ -8,7 +9,7 @@
#include <sys/stat.h>
#include <sys/types.h>
-#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 81a075f..0000000
--- a/ui-curses.c
+++ /dev/null
@@ -1,970 +0,0 @@
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-#include <limits.h>
-#include <ctype.h>
-#include <locale.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <errno.h>
-
-#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 34897ee..0000000
--- a/ui-curses.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef UI_CURSES_H
-#define UI_CURSES_H
-
-#include <curses.h>
-#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 0000000..5f0319b
--- /dev/null
+++ b/ui-terminal-curses.c
@@ -0,0 +1,286 @@
+/* This file is included from ui-terminal.c */
+#include <stdio.h>
+#include <curses.h>
+
+#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 0000000..67cabba
--- /dev/null
+++ b/ui-terminal.c
@@ -0,0 +1,727 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <limits.h>
+#include <ctype.h>
+#include <locale.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <errno.h>
+
+#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 0000000..45286ab
--- /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 dd663b8..41ef975 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 d6a3f3f..ea6b70c 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 4f25588..6c30b51 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 4350794..0739e55 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 0590f55..47b1dcd 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 8757ec1..850f850 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 fb9f4b1..46123ea 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*);