diff options
| author | Marc André Tanner <mat@brain-dump.org> | 2015-03-30 23:04:54 +0200 |
|---|---|---|
| committer | Marc André Tanner <mat@brain-dump.org> | 2015-04-03 23:52:06 +0200 |
| commit | 9915b9fd0b8e59bda9e334eb9485c473b06055c9 (patch) | |
| tree | 768f0a04df7e6cfc6d1379de8f19d1d4acdbf3a2 | |
| parent | b3b1abc76a832e53159b1191120bef56fe3a7041 (diff) | |
| download | vis-9915b9fd0b8e59bda9e334eb9485c473b06055c9.tar.gz vis-9915b9fd0b8e59bda9e334eb9485c473b06055c9.tar.xz | |
Preliminary user interface separation
In theory only ui-curses.[hc] should depend on curses, however in
practice keyboard input is still handled in vis.c. Furthermore the
syntax definitions as well as keyboard bindings and selection code
in window.c still depends on some curses constants.
There is also a slight regression in that the window status bar
does not show the current mode name. This and related global state
should be eliminated in the future.
| -rw-r--r-- | config.def.h | 22 | ||||
| -rw-r--r-- | editor.c | 388 | ||||
| -rw-r--r-- | editor.h | 39 | ||||
| -rw-r--r-- | ui-curses.c | 568 | ||||
| -rw-r--r-- | ui-curses.h | 9 | ||||
| -rw-r--r-- | ui.h | 58 | ||||
| -rw-r--r-- | vis.c | 89 | ||||
| -rw-r--r-- | window.c | 273 | ||||
| -rw-r--r-- | window.h | 30 |
9 files changed, 839 insertions, 637 deletions
diff --git a/config.def.h b/config.def.h index 2a9e258..b8657a4 100644 --- a/config.def.h +++ b/config.def.h @@ -1,5 +1,4 @@ /** start by reading from the top of vis.c up until config.h is included */ -#define DEFAULT_TERM "xterm" /* default term to use if $TERM isn't set */ /* macros used to specify keys for key bindings */ #define ESC 0x1B #define NONE(k) { .str = { k }, .code = 0 } @@ -71,26 +70,6 @@ static Command cmds[] = { { /* array terminator */ }, }; -/* draw a statubar, do whatever you want with win->statuswin curses window */ -static void statusbar(EditorWin *win) { - bool focused = vis->win == win || vis->prompt->editor == win; - const char *filename = text_filename_get(win->text); - CursorPos pos = window_cursor_getpos(win->win); - wattrset(win->statuswin, focused ? A_REVERSE|A_BOLD : A_REVERSE); - mvwhline(win->statuswin, 0, 0, ' ', win->width); - mvwprintw(win->statuswin, 0, 0, "%s %s %s %s", - mode->name && mode->name[0] == '-' ? mode->name : "", - filename ? filename : "[No Name]", - text_modified(win->text) ? "[+]" : "", - vis->recording ? "recording": ""); - char buf[win->width + 1]; - int len = snprintf(buf, win->width, "%zd, %zd", pos.line, pos.col); - if (len > 0) { - buf[len] = '\0'; - mvwaddstr(win->statuswin, 0, win->width - len - 1, buf); - } -} - /* called before any other keybindings are checked, if the function returns false * the key is completely ignored. */ static bool vis_keypress(Key *key) { @@ -796,7 +775,6 @@ static Config editors[] = { { .name = "vis", .mode = &vis_modes[VIS_MODE_NORMAL], - .statusbar = statusbar, .keypress = vis_keypress, }, }; @@ -5,91 +5,12 @@ #include "editor.h" #include "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 - static EditorWin *editor_window_new_text(Editor *ed, Text *text); -static void editor_window_free(Editor *ed, EditorWin *win); +static void editor_window_free(EditorWin *win); static void editor_windows_invalidate(Editor *ed, size_t start, size_t end); -static void editor_window_draw(EditorWin *win); -static void windows_arrange_horizontal(Editor *ed); -static void windows_arrange_vertical(Editor *ed); - -static Prompt *editor_prompt_new(); -static void editor_prompt_free(Prompt *prompt); -static void editor_prompt_clear(Prompt *prompt); -static void editor_prompt_resize(Prompt *prompt, int width, int height); -static void editor_prompt_move(Prompt *prompt, int x, int y); -static void editor_prompt_draw(Prompt *prompt); -static void editor_prompt_update(Prompt *prompt); -static void editor_info_draw(Editor *ed); - -static void editor_window_resize(EditorWin *win, int width, int height) { - window_resize(win->win, width, win->statuswin ? height - 1 : height); - if (win->statuswin) - wresize(win->statuswin, 1, width); - win->width = width; - win->height = height; -} - -static void editor_window_move(EditorWin *win, int x, int y) { - window_move(win->win, x, y); - if (win->statuswin) - mvwin(win->statuswin, y + win->height - 1, x); -} - -static void editor_window_statusbar_draw(EditorWin *win) { - if (win->statuswin && win->editor->statusbar) - win->editor->statusbar(win); -} - -static void editor_window_cursor_moved(Win *winwin, void *data) { - EditorWin *win = data; - Filerange sel = window_selection_get(winwin); - if (text_range_valid(&sel) && sel.start != sel.end) { - text_mark_intern_set(win->text, MARK_SELECTION_START, sel.start); - text_mark_intern_set(win->text, MARK_SELECTION_END, sel.end); - } - editor_window_statusbar_draw(win); -} -void editor_statusbar_set(Editor *ed, void (*statusbar)(EditorWin*)) { - ed->statusbar = statusbar; -} - -static void windows_arrange_horizontal(Editor *ed) { - int n = 0, x = 0, y = 0; - for (EditorWin *win = ed->windows; win; win = win->next) - n++; - int height = ed->height / MAX(1, n); - for (EditorWin *win = ed->windows; win; win = win->next) { - editor_window_resize(win, ed->width, win->next ? height : ed->height - y); - editor_window_move(win, x, y); - y += height; - } -} - -static void windows_arrange_vertical(Editor *ed) { - int n = 0, x = 0, y = 0; - for (EditorWin *win = ed->windows; win; win = win->next) - n++; - int width = (ed->width / MAX(1, n)) - 1; - for (EditorWin *win = ed->windows; win; win = win->next) { - editor_window_resize(win, win->next ? width : ed->width - x, ed->height); - editor_window_move(win, x, y); - x += width; - if (win->next) - mvvline(0, x++, ACS_VLINE, ed->height); - } +void editor_windows_arrange(Editor *ed, enum UiLayout layout) { + ed->ui->arrange(ed->ui, layout); } bool editor_window_reload(EditorWin *win) { @@ -115,16 +36,6 @@ bool editor_window_reload(EditorWin *win) { return true; } -void editor_windows_arrange_vertical(Editor *ed) { - ed->windows_arrange = windows_arrange_vertical; - editor_draw(ed); -} - -void editor_windows_arrange_horizontal(Editor *ed) { - ed->windows_arrange = windows_arrange_horizontal; - editor_draw(ed); -} - bool editor_window_split(EditorWin *original) { EditorWin *win = editor_window_new_text(original->editor, original->text); if (!win) @@ -201,18 +112,8 @@ size_t editor_window_changelist_next(EditorWin *win) { return win->changelist.pos; } -void editor_resize(Editor *ed, int width, int height) { - ed->width = width; - ed->height = height; - if (ed->info[0]) { - ed->height--; - } else if (ed->prompt->active) { - ed->height--; - editor_prompt_resize(ed->prompt, ed->width, 1); - editor_prompt_move(ed->prompt, 0, ed->height); - editor_prompt_draw(ed->prompt); - } - editor_draw(ed); +void editor_resize(Editor *ed) { + ed->ui->resize(ed->ui); } void editor_window_next(Editor *ed) { @@ -222,8 +123,7 @@ void editor_window_next(Editor *ed) { ed->win = ed->win->next; if (!ed->win) ed->win = ed->windows; - editor_window_statusbar_draw(sel); - editor_window_statusbar_draw(ed->win); + ed->ui->window_focus(ed->win->ui); } void editor_window_prev(Editor *ed) { @@ -233,8 +133,7 @@ void editor_window_prev(Editor *ed) { ed->win = ed->win->prev; if (!ed->win) for (ed->win = ed->windows; ed->win->next; ed->win = ed->win->next); - editor_window_statusbar_draw(sel); - editor_window_statusbar_draw(ed->win); + ed->ui->window_focus(ed->win->ui); } static void editor_windows_invalidate(Editor *ed, size_t start, size_t end) { @@ -243,10 +142,10 @@ static void editor_windows_invalidate(Editor *ed, size_t start, size_t end) { Filerange view = window_viewport_get(win->win); if ((view.start <= start && start <= view.end) || (view.start <= end && end <= view.end)) - editor_window_draw(win); + win->ui->draw(win->ui); } } - editor_window_draw(ed->win); + ed->win->ui->draw(ed->win->ui); } int editor_tabwidth_get(Editor *ed) { @@ -268,7 +167,7 @@ bool editor_syntax_load(Editor *ed, Syntax *syntaxes, Color *colors) { for (Color *color = colors; color && color->fg; color++) { if (color->attr == 0) color->attr = A_NORMAL; - color->attr |= COLOR_PAIR(editor_color_get(color->fg, color->bg)); + color->attr |= COLOR_PAIR(ed->ui->color_get(color->fg, color->bg)); } for (Syntax *syn = syntaxes; syn && syn->name; syn++) { @@ -303,49 +202,24 @@ void editor_syntax_unload(Editor *ed) { ed->syntaxes = NULL; } -static void editor_window_draw(EditorWin *win) { - // TODO window_draw draw should restore cursor position - window_draw(win->win); - window_cursor_to(win->win, window_cursor_get(win->win)); -} - void editor_draw(Editor *ed) { - erase(); - if (ed->windows) { - ed->windows_arrange(ed); - for (EditorWin *win = ed->windows; win; win = win->next) { - if (ed->win != win) - editor_window_draw(win); - } - editor_window_draw(ed->win); - } - if (ed->info[0]) - editor_info_draw(ed); - wnoutrefresh(stdscr); + ed->ui->draw(ed->ui); } void editor_update(Editor *ed) { - for (EditorWin *win = ed->windows; win; win = win->next) { - if (ed->win != win) { - if (win->statuswin) - wnoutrefresh(win->statuswin); - window_update(win->win); - } - } + ed->ui->update(ed->ui); +} - if (ed->win->statuswin) - wnoutrefresh(ed->win->statuswin); - if (ed->prompt && ed->prompt->active) - editor_prompt_update(ed->prompt); - window_update(ed->win->win); +void editor_suspend(Editor *ed) { + ed->ui->suspend(ed->ui); } -static void editor_window_free(Editor *ed, EditorWin *win) { +static void editor_window_free(EditorWin *win) { if (!win) return; - window_free(win->win); - if (win->statuswin) - delwin(win->statuswin); + Editor *ed = win->editor; + if (ed && ed->ui) + ed->ui->window_free(win->ui); ringbuf_free(win->jumplist); bool needed = false; for (EditorWin *w = ed ? ed->windows : NULL; w; w = w->next) { @@ -365,14 +239,13 @@ static EditorWin *editor_window_new_text(Editor *ed, Text *text) { return NULL; win->editor = ed; win->text = text; - win->win = window_new(win->text); - win->statuswin = newwin(1, ed->width, 0, 0); win->jumplist = ringbuf_alloc(31); - if (!win->win || !win->statuswin) { - editor_window_free(ed, win); + win->ui = ed->ui->window_new(ed->ui, text); + if (!win->jumplist || !win->ui) { + editor_window_free(win); return NULL; } - window_cursor_watch(win->win, editor_window_cursor_moved, win); + win->win = win->ui->view_get(win->ui); window_tabwidth_set(win->win, ed->tabwidth); if (ed->windows) ed->windows->prev = win; @@ -380,6 +253,7 @@ static EditorWin *editor_window_new_text(Editor *ed, Text *text) { win->prev = NULL; ed->windows = win; ed->win = win; + ed->ui->window_focus(win->ui); return win; } @@ -441,38 +315,41 @@ bool editor_window_new_fd(Editor *ed, int fd) { return true; } -static void editor_window_detach(Editor *ed, EditorWin *win) { +void editor_window_close(EditorWin *win) { + Editor *ed = win->editor; if (win->prev) win->prev->next = win->next; if (win->next) win->next->prev = win->prev; if (ed->windows == win) ed->windows = win->next; - win->next = win->prev = NULL; -} - -void editor_window_close(EditorWin *win) { - Editor *ed = win->editor; if (ed->win == win) ed->win = win->next ? win->next : win->prev; - editor_window_detach(ed, win); - editor_window_free(ed, win); + if (ed->win) + ed->ui->window_focus(ed->win->ui); + editor_window_free(win); editor_draw(ed); } -Editor *editor_new(int width, int height) { +Editor *editor_new(Ui *ui) { + if (!ui) + return NULL; Editor *ed = calloc(1, sizeof(Editor)); if (!ed) return NULL; - if (!(ed->prompt = editor_prompt_new())) + ed->ui = ui; + ed->ui->init(ed->ui, ed); + ed->tabwidth = 8; + ed->expandtab = false; + if (!(ed->prompt = calloc(1, sizeof(EditorWin)))) + goto err; + if (!(ed->prompt->text = text_load(NULL))) + goto err; + if (!(ed->prompt->ui = ed->ui->prompt_new(ed->ui, ed->prompt->text))) goto err; if (!(ed->search_pattern = text_regex_new())) goto err; - ed->width = width; - ed->height = height; - ed->tabwidth = 8; - ed->expandtab = false; - ed->windows_arrange = windows_arrange_horizontal; + ed->prompt->win = ed->prompt->ui->view_get(ed->prompt->ui); return ed; err: editor_free(ed); @@ -484,11 +361,12 @@ void editor_free(Editor *ed) { return; while (ed->windows) editor_window_close(ed->windows); - editor_prompt_free(ed->prompt); + editor_window_free(ed->prompt); text_regex_free(ed->search_pattern); for (int i = 0; i < REG_LAST; i++) register_free(&ed->registers[i]); editor_syntax_unload(ed); + ed->ui->free(ed->ui); free(ed); } @@ -528,187 +406,39 @@ void editor_delete(Editor *ed, size_t pos, size_t len) { editor_windows_invalidate(ed, pos, pos + len); } -static void editor_prompt_free(Prompt *prompt) { - if (!prompt) - return; - editor_window_free(NULL, prompt->win); - if (prompt->titlewin) - delwin(prompt->titlewin); - free(prompt->title); - free(prompt); -} - -static Prompt *editor_prompt_new() { - Text *text = text_load(NULL); - if (!text) - return NULL; - Prompt *prompt = calloc(1, sizeof(Prompt)); - if (!prompt) - goto err; - - if (!(prompt->win = calloc(1, sizeof(EditorWin)))) - goto err; - - if (!(prompt->win->win = window_new(text))) - goto err; - - prompt->win->text = text; - - if (!(prompt->titlewin = newwin(0, 0, 0, 0))) - goto err; - - return prompt; -err: - if (!prompt || !prompt->win) - text_free(text); - editor_prompt_free(prompt); - return NULL; -} - -static void editor_prompt_resize(Prompt *prompt, int width, int height) { - size_t title_width = strlen(prompt->title); - wresize(prompt->titlewin, height, title_width); - editor_window_resize(prompt->win, width - title_width, height); -} - -static void editor_prompt_move(Prompt *prompt, int x, int y) { - size_t title_width = strlen(prompt->title); - mvwin(prompt->titlewin, y, x); - editor_window_move(prompt->win, x + title_width, y); -} - void editor_prompt_show(Editor *ed, const char *title, const char *text) { - Prompt *prompt = ed->prompt; - if (prompt->active) + if (ed->prompt_window) return; - prompt->active = true; - prompt->editor = ed->win; - free(prompt->title); - prompt->title = strdup(title); - text_insert(prompt->win->text, 0, text, strlen(text)); - window_cursor_to(prompt->win->win, text_size(prompt->win->text)); - ed->win = prompt->win; - editor_resize(ed, ed->width, ed->height); -} - -static void editor_prompt_draw(Prompt *prompt) { - mvwaddstr(prompt->titlewin, 0, 0, prompt->title); -} - -static void editor_prompt_update(Prompt *prompt) { - wnoutrefresh(prompt->titlewin); -} - -static void editor_prompt_clear(Prompt *prompt) { - Text *text = prompt->win->text; - while (text_undo(text) != EPOS); - window_cursor_to(prompt->win->win, 0); + ed->prompt_window = ed->win; + ed->win = ed->prompt; + ed->prompt_type = title[0]; + ed->ui->prompt(ed->ui, title, text); } void editor_prompt_hide(Editor *ed) { - Prompt *prompt = ed->prompt; - if (!prompt->active) + if (!ed->prompt_window) return; - prompt->active = false; - ed->win = prompt->editor; - prompt->editor = NULL; - ed->height++; - editor_prompt_clear(prompt); - editor_draw(ed); -} - -void editor_prompt_set(Editor *ed, const char *line) { - Text *text = ed->prompt->win->text; - editor_prompt_clear(ed->prompt); - text_insert(text, 0, line, strlen(line)); - editor_window_draw(ed->prompt->win); + ed->ui->prompt_hide(ed->ui); + ed->win = ed->prompt_window; + ed->prompt_window = NULL; } char *editor_prompt_get(Editor *ed) { - Text *text = ed->prompt->win->text; - char *buf = malloc(text_size(text) + 1); - if (!buf) - return NULL; - size_t len = text_bytes_get(text, 0, text_size(text), buf); - buf[len] = '\0'; - return buf; + return ed->ui->prompt_input(ed->ui); } void editor_info_show(Editor *ed, const char *msg, ...) { va_list ap; va_start(ap, msg); - vsnprintf(ed->info, sizeof(ed->info), msg, ap); + ed->ui->info(ed->ui, msg, ap); va_end(ap); - editor_resize(ed, ed->width, ed->height); } void editor_info_hide(Editor *ed) { - if (!ed->info[0]) - return; - ed->info[0] = '\0'; - ed->height++; - editor_draw(ed); + ed->ui->info_hide(ed->ui); } -static void editor_info_draw(Editor *ed) { - attrset(A_BOLD); - mvaddstr(ed->height, 0, ed->info); -} - -static unsigned int color_hash(short fg, short bg) -{ - if (fg == -1) - fg = COLORS; - if (bg == -1) - bg = COLORS + 1; - return fg * (COLORS + 2) + bg; -} - -short editor_color_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_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_hash(oldfg, oldbg); - if (init_pair(color_pair_current, fg, bg) == OK) { - color2palette[old_index] = 0; - color2palette[index] = color_pair_current; - } - } - - return color2palette[index]; +void editor_window_options(EditorWin *win, enum UiOption options) { + win->ui->options(win->ui, options); } + @@ -4,14 +4,17 @@ #include <curses.h> #include <stddef.h> #include <stdbool.h> + +typedef struct Editor Editor; +typedef struct EditorWin EditorWin; + +#include "ui.h" #include "window.h" #include "register.h" #include "macro.h" #include "syntax.h" #include "ring-buffer.h" -typedef struct Editor Editor; -typedef struct EditorWin EditorWin; typedef struct { size_t index; /* #number of changes */ @@ -20,23 +23,14 @@ typedef struct { struct EditorWin { Editor *editor; /* editor instance to which this window belongs */ + UiWin *ui; Text *text; /* underlying text management */ Win *win; /* window for the text area */ RingBuffer *jumplist; /* LRU jump management */ ChangeList changelist; /* state for iterating through least recently changes */ - WINDOW *statuswin; /* curses window for the statusbar */ - int width, height; /* window size including the statusbar */ EditorWin *prev, *next; /* neighbouring windows */ }; -typedef struct { - EditorWin *win; /* 1-line height editor window used for the prompt */ - EditorWin *editor; /* active editor window before prompt is shown */ - char *title; /* title displayed to the left of the prompt */ - WINDOW *titlewin; /* the curses window holding the prompt title */ - bool active; /* whether the prompt is currently shown or not */ -} Prompt; - enum Reg { REG_a, REG_b, @@ -100,28 +94,29 @@ enum Mark { }; struct Editor { - int width, height; /* terminal size, available for all windows */ + Ui *ui; EditorWin *windows; /* list of windows */ EditorWin *win; /* currently active window */ Syntax *syntaxes; /* NULL terminated array of syntax definitions */ Register registers[REG_LAST]; /* register used for copy and paste */ Macro macros[26]; /* recorded macros */ Macro *recording, *last_recording;/* currently and least recently recorded macro */ - Prompt *prompt; /* used to get user input */ - char info[255]; /* a user message currently being displayed */ + EditorWin *prompt; /* 1-line height window to get user input */ + EditorWin *prompt_window; /* window which was focused before prompt was shown */ + char prompt_type; /* command ':' or search '/','?' prompt */ Regex *search_pattern; /* last used search pattern */ void (*windows_arrange)(Editor*); /* current layout which places the windows */ - void (*statusbar)(EditorWin*); /* configurable user hook to draw statusbar */ int tabwidth; /* how many spaces should be used to display a tab */ bool expandtab; /* whether typed tabs should be converted to spaces */ bool autoindent; /* whether indentation should be copied from previous line on newline */ }; -Editor *editor_new(int width, int height); +Editor *editor_new(Ui*); void editor_free(Editor*); -void editor_resize(Editor*, int width, int height); +void editor_resize(Editor*); void editor_draw(Editor*); void editor_update(Editor*); +void editor_suspend(Editor*); /* these function operate on the currently focused window but make sure * that all windows which show the affected region are redrawn too. */ @@ -168,8 +163,7 @@ void editor_window_jumplist_invalidate(EditorWin*); size_t editor_window_changelist_prev(EditorWin*); size_t editor_window_changelist_next(EditorWin*); /* rearrange all windows either vertically or horizontally */ -void editor_windows_arrange_vertical(Editor*); -void editor_windows_arrange_horizontal(Editor*); +void editor_windows_arrange(Editor*, enum UiLayout); /* display a user prompt with a certain title and default text */ void editor_prompt_show(Editor*, const char *title, const char *text); /* hide the user prompt if it is currently shown */ @@ -183,10 +177,7 @@ void editor_prompt_set(Editor*, const char *line); /* display a message to the user */ void editor_info_show(Editor*, const char *msg, ...); void editor_info_hide(Editor*); - -/* register a callback which is called whenever the statusbar needs to - * be redrawn */ -void editor_statusbar_set(Editor*, void (*statusbar)(EditorWin*)); +void editor_window_options(EditorWin*, enum UiOption options); /* look up a curses color pair for the given combination of fore and * background color */ diff --git a/ui-curses.c b/ui-curses.c new file mode 100644 index 0000000..4b351e5 --- /dev/null +++ b/ui-curses.c @@ -0,0 +1,568 @@ +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <curses.h> +#include <locale.h> +#include <sys/ioctl.h> + +#include "ui.h" +#include "ui-curses.h" +#include "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 + +#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 + +typedef struct UiCursesWin UiCursesWin; + +typedef struct { + Ui ui; /* generic ui interface, has to be the first struct member */ + Editor *ed; /* editor instance to which this ui belongs */ + UiCursesWin *windows; /* all windows managed by this ui */ + UiCursesWin *selwin; /* the currently selected layout */ + char prompt_title[255]; /* prompt_title[0] == '\0' if prompt isn't shown */ + UiCursesWin *prompt_win; /* like a normal window but without a status bar */ + char info[255]; /* 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 */ +} UiCurses; + +struct UiCursesWin { + UiWin uiwin; /* generic interface, has to be the first struct member */ + UiCurses *ui; /* ui which manages this window */ + Text *text; /* underlying text management */ + Win *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 */ +}; + +static unsigned int color_hash(short fg, short bg) { + if (fg == -1) + fg = COLORS; + if (bg == -1) + bg = COLORS + 1; + return fg * (COLORS + 2) + bg; +} + +static short color_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_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_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 void ui_window_resize(UiCursesWin *win, int width, int 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); + window_resize(win->view, width - win->sidebar_width, win->winstatus ? height - 1 : height); +} + +static void ui_window_move(UiCursesWin *win, int x, int 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 void ui_window_draw_status(UiWin *w) { + UiCursesWin *win = (UiCursesWin*)w; + if (!win->winstatus) + return; + UiCurses *uic = win->ui; + Editor *vis = uic->ed; + bool focused = uic->selwin == win; + const char *filename = text_filename_get(win->text); + CursorPos pos = window_cursor_getpos(win->view); + wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE); + mvwhline(win->winstatus, 0, 0, ' ', win->width); + mvwprintw(win->winstatus, 0, 0, "%s %s %s %s", + "", // TODO mode->name && mode->name[0] == '-' ? mode->name : "", + filename ? filename : "[No Name]", + text_modified(win->text) ? "[+]" : "", + vis->recording ? "recording": ""); + char buf[win->width + 1]; + int len = snprintf(buf, win->width, "%zd, %zd", pos.line, pos.col); + if (len > 0) { + buf[len] = '\0'; + mvwaddstr(win->winstatus, 0, win->width - len - 1, buf); + } +} + +static void ui_window_draw(UiWin *w) { + UiCursesWin *win = (UiCursesWin*)w; + if (win->winstatus) + ui_window_draw_status((UiWin*)win); + window_draw(win->view); + window_cursor_to(win->view, window_cursor_get(win->view)); +} + +static void ui_window_reload(UiWin *w, Text *text) { + UiCursesWin *win = (UiCursesWin*)w; + win->text = text; + win->sidebar_width = 0; + ui_window_draw(w); +} + +static void ui_window_draw_sidebar(UiCursesWin *win, const Line *line) { + if (!win->winside) + return; + 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); + } else { + int i = 0; + size_t prev_lineno = 0; + werase(win->winside); + for (const Line *l = line; l; l = l->next, i++) { + if (l->lineno != prev_lineno) + mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno); + prev_lineno = l->lineno; + } + mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1); + } +} + +static void ui_window_update(UiCursesWin *win) { + if (win->winstatus) + wnoutrefresh(win->winstatus); + if (win->winside) + wnoutrefresh(win->winside); + wnoutrefresh(win->win); +} + +static void update(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + for (UiCursesWin *win = uic->windows; win; win = win->next) { + if (win != uic->selwin) + ui_window_update(win); + } + + if (uic->selwin) + ui_window_update(uic->selwin); + if (uic->prompt_title[0]) { + wnoutrefresh(uic->prompt_win->win); + ui_window_update(uic->prompt_win); + } + doupdate(); +} + +static void arrange(Ui *ui, enum UiLayout layout) { + UiCurses *uic = (UiCurses*)ui; + uic->layout = layout; + int n = 0, x = 0, y = 0; + for (UiCursesWin *win = uic->windows; win; win = win->next) + n++; + int max_height = uic->height - !!(uic->prompt_title[0] || uic->info[0]); + 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 (layout == UI_LAYOUT_HORIZONTAL) { + ui_window_resize(win, uic->width, win->next ? height : max_height - y); + ui_window_move(win, x, y); + y += height; + } else { + ui_window_resize(win, win->next ? width : uic->width - x, max_height); + ui_window_move(win, x, y); + x += width; + if (win->next) + mvvline(0, x++, ACS_VLINE, max_height); + } + } +} + +static void draw(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + erase(); + 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); + } + + if (uic->prompt_title[0]) { + attrset(A_NORMAL); + mvaddstr(uic->height-1, 0, uic->prompt_title); + ui_window_draw((UiWin*)uic->prompt_win); + } + + wnoutrefresh(stdscr); +} + +static void ui_resize_to(Ui *ui, int width, int height) { + UiCurses *uic = (UiCurses*)ui; + uic->width = width; + uic->height = height; + if (uic->prompt_title[0]) { + size_t title_width = strlen(uic->prompt_title); + ui_window_resize(uic->prompt_win, width - title_width, 1); + ui_window_move(uic->prompt_win, title_width, height-1); + } + draw(ui); +} + +static void ui_resize(Ui *ui) { + struct winsize ws; + int width, height; + + if (ioctl(0, 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_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); + window_free(win->view); + free(win); +} + +static Win *ui_window_view_get(UiWin *win) { + UiCursesWin *cwin = (UiCursesWin*)win; + return cwin->view; +} + +static void ui_window_cursor_to(UiWin *w, int x, int y) { + UiCursesWin *win = (UiCursesWin*)w; + wmove(win->win, y, x); + ui_window_draw_status(w); +} + +static void ui_window_draw_text(UiWin *w, const Line *line) { + UiCursesWin *win = (UiCursesWin*)w; + wmove(win->win, 0, 0); + attr_t attr = 0; + for (const Line *l = line; l; l = l->next) { + /* add a single space in an otherwise empty line to make + * the selection cohorent */ + if (l->width == 0) + waddch(win->win, ' '); + + for (int x = 0; x < l->width; x++) { + attr_t newattr = l->cells[x].attr; + if (newattr != attr) { + wattrset(win->win, newattr); + attr = newattr; + } + waddstr(win->win, l->cells[x].data); + } + wclrtoeol(win->win); + } + wclrtobot(win->win); + + ui_window_draw_sidebar(win, line); +} + +static void ui_window_focus(UiWin *w) { + UiCursesWin *win = (UiCursesWin*)w; + UiCursesWin *oldsel = win->ui->selwin; + win->ui->selwin = win; + if (oldsel) + ui_window_draw_status((UiWin*)oldsel); + ui_window_draw_status(w); +} + +static void ui_window_options(UiWin *w, enum UiOption options) { + UiCursesWin *win = (UiCursesWin*)w; + win->options = options; + switch (options) { + case UI_OPTION_LINE_NUMBERS_NONE: + if (win->winside) { + delwin(win->winside); + win->winside = NULL; + win->sidebar_width = 0; + } + break; + case UI_OPTION_LINE_NUMBERS_ABSOLUTE: + if (!win->winside) + win->winside = newwin(1, 1, 1, 1); + break; + } + ui_window_draw(w); +} + +static UiWin *ui_window_new(Ui *ui, Text *text) { + UiCurses *uic = (UiCurses*)ui; + UiCursesWin *win = calloc(1, sizeof(UiCursesWin)); + if (!win) + return NULL; + + win->uiwin = (UiWin) { + .draw = ui_window_draw, + .draw_status = ui_window_draw_status, + .draw_text = ui_window_draw_text, + .cursor_to = ui_window_cursor_to, + .view_get = ui_window_view_get, + .options = ui_window_options, + .reload = ui_window_reload, + }; + + if (!(win->view = window_new(text, &win->uiwin, uic->width, uic->height)) || + !(win->win = newwin(0, 0, 0, 0)) || + !(win->winstatus = newwin(1, 0, 0, 0))) { + ui_window_free((UiWin*)win); + return NULL; + } + + win->ui = uic; + win->text = text; + if (uic->windows) + uic->windows->prev = win; + win->next = uic->windows; + uic->windows = win; + + return &win->uiwin; +} + +static void info(Ui *ui, const char *msg, va_list ap) { + UiCurses *uic = (UiCurses*)ui; + vsnprintf(uic->info, sizeof(uic->info), msg, ap); + draw(ui); +} + +static void info_hide(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + if (uic->info[0]) { + uic->info[0] = '\0'; + draw(ui); + } +} + +static UiWin *prompt_new(Ui *ui, Text *text) { + UiCurses *uic = (UiCurses*)ui; + if (uic->prompt_win) + return (UiWin*)uic->prompt_win; + UiWin *uiwin = ui_window_new(ui, text); + UiCursesWin *win = (UiCursesWin*)uiwin; + if (!win) + return NULL; + uic->windows = win->next; + if (uic->windows) + uic->windows->prev = NULL; + if (win->winstatus) + delwin(win->winstatus); + if (win->winside) + delwin(win->winside); + win->winstatus = NULL; + win->winside = NULL; + uic->prompt_win = win; + return uiwin; +} + +static void prompt(Ui *ui, const char *title, const char *text) { + UiCurses *uic = (UiCurses*)ui; + if (uic->prompt_title[0]) + return; + size_t text_len = strlen(text); + strncpy(uic->prompt_title, title, sizeof(uic->prompt_title)-1); + text_insert(uic->prompt_win->text, 0, text, text_len); + window_cursor_to(uic->prompt_win->view, text_len); + ui_resize_to(ui, uic->width, uic->height); +} + +static char *prompt_input(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + if (!uic->prompt_win) + return NULL; + Text *text = uic->prompt_win->text; + char *buf = malloc(text_size(text) + 1); + if (!buf) + return NULL; + size_t len = text_bytes_get(text, 0, text_size(text), buf); + buf[len] = '\0'; + return buf; +} + +static void prompt_hide(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + uic->prompt_title[0] = '\0'; + if (uic->prompt_win) { + while (text_undo(uic->prompt_win->text) != EPOS); + window_cursor_to(uic->prompt_win->view, 0); + } + ui_resize_to(ui, uic->width, uic->height); +} + +static bool ui_init(Ui *ui, Editor *ed) { + UiCurses *uic = (UiCurses*)ui; + uic->ed = ed; + return true; +} + +static void ui_suspend(Ui *ui) { + endwin(); + raise(SIGSTOP); +} + +Ui *ui_curses_new(void) { + setlocale(LC_CTYPE, ""); + if (!getenv("ESCDELAY")) + set_escdelay(50); + char *term = getenv("TERM"); + if (!term) + term = "xterm"; + if (!newterm(term, stderr, stdin)) + return NULL; + start_color(); + raw(); + noecho(); + keypad(stdscr, TRUE); + meta(stdscr, TRUE); + /* needed because we use getch() which implicitly calls refresh() which + would clear the screen (overwrite it with an empty / unused stdscr */ + refresh(); + + UiCurses *uic = calloc(1, sizeof(UiCurses)); + Ui *ui = (Ui*)uic; + if (!uic) + return NULL; + + *ui = (Ui) { + .init = ui_init, + .free = ui_curses_free, + .suspend = ui_suspend, + .resume = ui_resize, + .resize = ui_resize, + .update = update, + .window_new = ui_window_new, + .window_free = ui_window_free, + .window_focus = ui_window_focus, + .prompt_new = prompt_new, + .prompt = prompt, + .prompt_input = prompt_input, + .prompt_hide = prompt_hide, + .draw = draw, + .arrange = arrange, + .info = info, + .info_hide = info_hide, + .color_get = color_get, + }; + + ui_resize(ui); + + return ui; +} + +void ui_curses_free(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + if (!uic) + return; + ui_window_free((UiWin*)uic->prompt_win); + while (uic->windows) + ui_window_free((UiWin*)uic->windows); + endwin(); + free(uic); +} + diff --git a/ui-curses.h b/ui-curses.h new file mode 100644 index 0000000..74cb920 --- /dev/null +++ b/ui-curses.h @@ -0,0 +1,9 @@ +#ifndef UI_CURSES_H +#define UI_CURSES_H + +#include "ui.h" + +Ui *ui_curses_new(void); +void ui_curses_free(Ui*); + +#endif @@ -0,0 +1,58 @@ +#ifndef UI_H +#define UI_H + +typedef struct Ui Ui; +typedef struct UiWin UiWin; + +enum UiLayout { + UI_LAYOUT_HORIZONTAL, + UI_LAYOUT_VERTICAL, +}; + +enum UiOption { + UI_OPTION_LINE_NUMBERS_NONE, + UI_OPTION_LINE_NUMBERS_ABSOLUTE, +}; + +#include <stdbool.h> +#include <stdarg.h> +#include "text.h" +#include "window.h" +#include "editor.h" + +struct Ui { + bool (*init)(Ui*, Editor*); + void (*free)(Ui*); + short (*color_get)(short fg, short bg); + void (*resize)(Ui*); + UiWin* (*window_new)(Ui*, Text*); + void (*window_free)(UiWin*); + void (*window_focus)(UiWin*); + UiWin* (*prompt_new)(Ui*, Text*); + void (*prompt)(Ui*, const char *title, const char *value); + char* (*prompt_input)(Ui*); + void (*prompt_hide)(Ui*); + void (*info)(Ui*, const char *msg, va_list ap); + void (*info_hide)(Ui*); + void (*arrange)(Ui*, enum UiLayout); + void (*draw)(Ui*); + void (*update)(Ui*); + void (*suspend)(Ui*); + void (*resume)(Ui*); +/* TODO main loop integration, signal handling + Key getkey(void); + bool haskey(void); +*/ +}; + +struct UiWin { + void (*draw)(UiWin*); + void (*draw_text)(UiWin*, const Line*); + void (*draw_status)(UiWin*); + void (*cursor_to)(UiWin*, int x, int y); + void (*reload)(UiWin*, Text*); + void (*options)(UiWin*, enum UiOption); + Win* (*view_get)(UiWin*); +}; + +#endif @@ -19,6 +19,8 @@ #include <string.h> #include <strings.h> #include <signal.h> +#include <stdarg.h> +#include <stdio.h> #include <errno.h> #include <fcntl.h> #include <limits.h> @@ -29,6 +31,7 @@ #include <sys/ioctl.h> #include <sys/mman.h> +#include "ui-curses.h" #include "editor.h" #include "text-motions.h" #include "text-objects.h" @@ -84,7 +87,6 @@ struct Mode { typedef struct { char *name; /* is used to match against argv[0] to enable this config */ Mode *mode; /* default mode in which the editor should start in */ - void (*statusbar)(EditorWin*); /* routine which is called whenever the cursor is moved within a window */ bool (*keypress)(Key*); /* called before any other keybindings are checked, * return value decides whether key should be ignored */ } Config; @@ -155,6 +157,7 @@ typedef struct { /* command definitions for the ':'-prompt */ /** global variables */ static volatile bool running = true; /* exit main loop once this becomes false */ +static volatile sig_atomic_t need_resize; static Editor *vis; /* global editor instance, keeps track of all windows etc. */ static Mode *mode; /* currently active mode, used to search for keybindings */ static Mode *mode_prev; /* previsouly active user mode */ @@ -801,11 +804,13 @@ static size_t window_lines_top(const Arg *arg) { } static size_t window_lines_middle(const Arg *arg) { - return window_screenline_goto(vis->win->win, vis->win->height / 2); + int h = window_height_get(vis->win->win); + return window_screenline_goto(vis->win->win, h/2); } static size_t window_lines_bottom(const Arg *arg) { - return window_screenline_goto(vis->win->win, vis->win->height - action.count); + int h = window_height_get(vis->win->win); + return window_screenline_goto(vis->win->win, h-action.count); } /** key bindings functions of type: void (*func)(const Arg*) */ @@ -865,8 +870,7 @@ static void macro_replay(const Arg *arg) { } static void suspend(const Arg *arg) { - endwin(); - raise(SIGSTOP); + editor_suspend(vis); } static void repeat(const Arg *arg) { @@ -1055,7 +1059,7 @@ static void prompt_enter(const Arg *arg) { * on vis->win. */ switchmode_to(mode_before_prompt); - if (s && *s && exec_command(vis->prompt->title[0], s) && running) + if (s && *s && exec_command(vis->prompt_type, s) && running) switchmode(&(const Arg){ .i = VIS_MODE_NORMAL }); free(s); editor_draw(vis); @@ -1102,10 +1106,10 @@ static int argi2lines(const Arg *arg) { switch (arg->i) { case -PAGE: case +PAGE: - return vis->win->height-1; + return window_height_get(vis->win->win); case -PAGE_HALF: case +PAGE_HALF: - return vis->win->height/2; + return window_height_get(vis->win->win)/2; default: if (action.count > 0) return action.count; @@ -1330,7 +1334,7 @@ static void switchmode_to(Mode *new_mode) { mode_prev = mode; mode = new_mode; if (mode == config->mode || (mode->name && mode->name[0] == '-')) - statusbar(vis->win); + vis->win->ui->draw_status(vis->win->ui); if (mode->enter) mode->enter(mode_prev); } @@ -1467,7 +1471,8 @@ static bool cmd_set(Filerange *range, enum CmdOpt cmdopt, const char *argv[]) { editor_info_show(vis, "Unknown syntax definition: `%s'", argv[2]); break; case OPTION_NUMBER: - window_line_numbers_show(vis->win->win, arg.b); + editor_window_options(vis->win, arg.b ? UI_OPTION_LINE_NUMBERS_ABSOLUTE : + UI_OPTION_LINE_NUMBERS_NONE); break; } @@ -1611,26 +1616,26 @@ static bool openfiles(const char **files) { } static bool cmd_split(Filerange *range, enum CmdOpt opt, const char *argv[]) { - editor_windows_arrange_horizontal(vis); + editor_windows_arrange(vis, UI_LAYOUT_HORIZONTAL); if (!argv[1]) return vis_window_split(vis->win); return openfiles(&argv[1]); } static bool cmd_vsplit(Filerange *range, enum CmdOpt opt, const char *argv[]) { - editor_windows_arrange_vertical(vis); + editor_windows_arrange(vis, UI_LAYOUT_VERTICAL); if (!argv[1]) - return editor_window_split(vis->win); + return vis_window_split(vis->win); return openfiles(&argv[1]); } static bool cmd_new(Filerange *range, enum CmdOpt opt, const char *argv[]) { - editor_windows_arrange_horizontal(vis); + editor_windows_arrange(vis, UI_LAYOUT_HORIZONTAL); return vis_window_new(NULL); } static bool cmd_vnew(Filerange *range, enum CmdOpt opt, const char *argv[]) { - editor_windows_arrange_vertical(vis); + editor_windows_arrange(vis, UI_LAYOUT_VERTICAL); return vis_window_new(NULL); } @@ -1883,15 +1888,9 @@ static bool vis_window_split(EditorWin *win) { return true; } -typedef struct Screen Screen; -static struct Screen { - int w, h; - bool need_resize; -} screen = { .need_resize = true }; - static void die(const char *errstr, ...) { va_list ap; - endwin(); + editor_free(vis); va_start(ap, errstr); vfprintf(stderr, errstr, ap); va_end(ap); @@ -1899,43 +1898,10 @@ static void die(const char *errstr, ...) { } static void sigwinch_handler(int sig) { - screen.need_resize = true; -} - -static void resize_screen(Screen *screen) { - struct winsize ws; - - if (ioctl(0, TIOCGWINSZ, &ws) == -1) { - getmaxyx(stdscr, screen->h, screen->w); - } else { - screen->w = ws.ws_col; - screen->h = ws.ws_row; - } - - resizeterm(screen->h, screen->w); - wresize(stdscr, screen->h, screen->w); - screen->need_resize = false; + need_resize = true; } static void setup() { - setlocale(LC_CTYPE, ""); - if (!getenv("ESCDELAY")) - set_escdelay(50); - char *term = getenv("TERM"); - if (!term) - term = DEFAULT_TERM; - if (!newterm(term, stderr, stdin)) - die("Can not initialize terminal\n"); - start_color(); - raw(); - noecho(); - keypad(stdscr, TRUE); - meta(stdscr, TRUE); - resize_screen(&screen); - /* needed because we use getch() which implicitly calls refresh() which - would clear the screen (overwrite it with an empty / unused stdscr */ - refresh(); - struct sigaction sa; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); @@ -2046,9 +2012,9 @@ static void mainloop() { editor_draw(vis); while (running) { - if (screen.need_resize) { - resize_screen(&screen); - editor_resize(vis, screen.w, screen.h); + if (need_resize) { + editor_resize(vis); + need_resize = false; } fd_set fds; @@ -2056,7 +2022,6 @@ static void mainloop() { FD_SET(STDIN_FILENO, &fds); editor_update(vis); - doupdate(); idle.tv_sec = mode->idle_timeout; int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset); if (r == -1 && errno == EINTR) @@ -2113,11 +2078,10 @@ int main(int argc, char *argv[]) { mode_prev = mode = config->mode; setup(); - if (!vis_init() || !(vis = editor_new(screen.w, screen.h))) + if (!vis_init() || !(vis = editor_new(ui_curses_new()))) die("Could not allocate editor core\n"); if (!editor_syntax_load(vis, syntaxes, colors)) die("Could not load syntax highlighting definitions\n"); - editor_statusbar_set(vis, config->statusbar); char *cmd = NULL; for (int i = 1; i < argc; i++) { @@ -2157,6 +2121,5 @@ int main(int argc, char *argv[]) { mainloop(); editor_free(vis); vis_shutdown(); - endwin(); return 0; } @@ -19,7 +19,6 @@ #include <ctype.h> #include <errno.h> #include <regex.h> -#include <curses.h> #include "editor.h" #include "window.h" #include "syntax.h" @@ -27,29 +26,6 @@ #include "text-motions.h" #include "util.h" -typedef struct { /* used to calculate the display width of a character */ - char c[7]; /* utf8 encoded bytes */ - size_t len; /* number of bytes of the multibyte sequence */ - wchar_t wchar; /* equivalent converted wide character, needed for wcwidth(3) */ -} Char; - -typedef struct { - unsigned char len; /* number of bytes the character displayed in this cell uses, for - character which use more than 1 column to display, their lenght - is stored in the leftmost cell wheras all following cells - occupied by the same character have a length of 0. */ - char data; /* first byte of the utf8 sequence, used for white space handling */ -} Cell; - -typedef struct Line Line; -struct Line { /* a line on the screen, *not* in the file */ - Line *prev, *next; /* pointer to neighbouring screen lines */ - size_t len; /* line length in terms of bytes */ - size_t lineno; /* line number from start of file */ - int width; /* zero based position of last used column cell */ - Cell cells[]; /* win->width cells storing information about the displayed characters */ -}; - typedef struct { /* cursor position */ Filepos pos; /* in bytes from the start of the file */ int row, col; /* in terms of zero based screen coordinates */ @@ -58,19 +34,16 @@ typedef struct { /* cursor position */ struct Win { /* window showing part of a file */ Text *text; /* underlying text management */ - WINDOW *win; /* curses window for the text area */ - WINDOW *winnum; /* curses window for the line numbers area */ + UiWin *ui; int width, height; /* window text area size */ - int w, h, x, y; /* complete window position & dimension (including line numbers) */ Filepos start, end; /* currently displayed area [start, end] in bytes from the start of the file */ + size_t lines_size; /* number of allocated bytes for lines (grows only) */ Line *lines; /* win->height number of lines representing window content */ Line *topline; /* top of the window, first line currently shown */ Line *lastline; /* last currently used line, always <= bottomline */ Line *bottomline; /* bottom of screen, might be unused if lastline < bottomline */ Filerange sel; /* selected text range in bytes from start of file */ Cursor cursor; /* current window cursor position */ - void (*cursor_moved)(Win*, void *); /* registered callback, fires whenever the cursor moved */ - void *cursor_moved_data; /* user supplied data, passed as second argument to the above callback */ Line *line; /* used while drawing window content, line where next char will be drawn */ int col; /* used while drawing window content, column where next char will be drawn */ Syntax *syntax; /* syntax highlighting definitions for this window or NULL */ @@ -78,7 +51,7 @@ struct Win { /* window showing part of a file */ }; static void window_clear(Win *win); -static bool window_addch(Win *win, Char *c); +static bool window_addch(Win *win, Cell *cell); static size_t window_cursor_update(Win *win); /* set/move current cursor position to a given (line, column) pair */ static size_t window_cursor_set(Win *win, Line *line, int col); @@ -105,55 +78,22 @@ void window_selection_clear(Win *win) { curs_set(1); } -static int window_numbers_width(Win *win) { - if (!win->winnum) - return 0; - return snprintf(NULL, 0, "%zd", win->topline->lineno + win->height - 1) + 1; -} - -static void window_numbers_draw(Win *win) { - if (!win->winnum) - return; - werase(win->winnum); - size_t prev_lineno = -1; - int width = window_numbers_width(win), i = 0; - char fmt[32]; - snprintf(fmt, sizeof fmt, "%%%dd", width - 1); - for (Line *l = win->topline; l && l <= win->lastline; l = l->next, i++) { - if (l->lineno != prev_lineno) - mvwprintw(win->winnum, i, 0, fmt, l->lineno); - prev_lineno = l->lineno; - } - mvwvline(win->winnum, 0, width-1, ACS_VLINE, win->height); -} - /* reset internal window data structures (cell matrix, line offsets etc.) */ static void window_clear(Win *win) { /* calculate line number of first line */ + // TODO move elsewhere win->topline = win->lines; win->topline->lineno = text_lineno_by_pos(win->text, win->start); win->lastline = win->topline; - /* based on which we can calculate how much space is needed to display line numbers */ - int lineno_width = window_numbers_width(win); - int width = win->w - lineno_width; - if (win->winnum) { - wresize(win->winnum, win->h, lineno_width); - mvwin(win->winnum, win->y, win->x); - } - - wresize(win->win, win->height, width); - mvwin(win->win, win->y, win->x + lineno_width); - win->width = width; - /* reset all other lines */ size_t line_size = sizeof(Line) + win->width*sizeof(Cell); size_t end = win->height * line_size; Line *prev = NULL; for (size_t i = 0; i < end; i += line_size) { Line *line = (Line*)(((char*)win->lines) + i); - line->len = 0; line->width = 0; + line->len = 0; line->prev = prev; if (prev) prev->next = line; @@ -190,15 +130,15 @@ Filerange window_viewport_get(Win *win) { } /* try to add another character to the window, return whether there was space left */ -static bool window_addch(Win *win, Char *c) { +static bool window_addch(Win *win, Cell *cell) { if (!win->line) return false; int width; - Cell empty = { .len = 0, .data = '\0' }; + static Cell empty; size_t lineno = win->line->lineno; - switch (c->wchar) { + switch (cell->data[0]) { case '\t': width = win->tabwidth - (win->col % win->tabwidth); for (int w = 0; w < width; w++) { @@ -211,68 +151,53 @@ static bool window_addch(Win *win, Char *c) { } if (w == 0) { /* first cell of a tab has a length of 1 */ - win->line->cells[win->col].len = c->len; - win->line->len += c->len; + win->line->cells[win->col].len = cell->len; + win->line->len += cell->len; } else { /* all remaining ones have a lenght of zero */ win->line->cells[win->col].len = 0; } /* but all are marked as part of a tabstop */ - win->line->cells[win->col].data = '\t'; - win->col++; + win->line->cells[win->col].width = 1; + win->line->cells[win->col].data[0] = ' '; + win->line->cells[win->col].data[1] = '\0'; + win->line->cells[win->col].istab = true; win->line->width++; - waddch(win->win, ' '); + win->col++; } return true; case '\n': - width = 1; - if (win->col + width > win->width) { + cell->width = 1; + if (win->col + cell->width > win->width) { win->line = win->line->next; win->col = 0; if (!win->line) return false; - win->line->lineno = lineno + 1; + win->line->lineno = lineno; } - win->line->cells[win->col].len = c->len; - win->line->len += c->len; - win->line->cells[win->col].data = '\n'; + win->line->cells[win->col] = *cell; + win->line->len += cell->len; + win->line->width += cell->width; for (int i = win->col + 1; i < win->width; i++) win->line->cells[i] = empty; - if (win->line == win->bottomline) { - /* XXX: curses bug? the wclrtoeol(win->win); implied by waddch(win->win, '\n') - * doesn't seem to work on the last line!? - * - * Thus explicitly clear the remaining of the line. - */ - for (int i = win->col; i < win->width; i++) - waddch(win->win, ' '); - } else if (win->line->width == 0) { - /* add a single space in an otherwise empty line, makes selection cohorent */ - waddch(win->win, ' '); - } - - waddch(win->win, '\n'); win->line = win->line->next; if (win->line) win->line->lineno = lineno + 1; win->col = 0; return true; default: - if (c->wchar < 128 && !isprint(c->wchar)) { + if ((unsigned char)cell->data[0] < 128 && !isprint((unsigned char)cell->data[0])) { /* non-printable ascii char, represent it as ^(char + 64) */ - Char s = { .c = "^_", .len = 1 }; - s.c[1] = c->c[0] + 64; - *c = s; - width = 2; - } else { - if ((width = wcwidth(c->wchar)) == -1) { - /* this should never happen */ - width = 1; - } + *cell = (Cell) { + .data = { '^', cell->data[0] + 64, '\0' }, + .len = 1, + .width = 2, + .istab = false, + }; } - if (win->col + width > win->width) { + if (win->col + cell->width > win->width) { for (int i = win->col; i < win->width; i++) win->line->cells[i] = empty; win->line = win->line->next; @@ -280,16 +205,14 @@ static bool window_addch(Win *win, Char *c) { } if (win->line) { - win->line->width += width; - win->line->len += c->len; + win->line->width += cell->width; + win->line->len += cell->len; win->line->lineno = lineno; - win->line->cells[win->col].len = c->len; - win->line->cells[win->col].data = c->c[0]; + win->line->cells[win->col] = *cell; win->col++; /* set cells of a character which uses multiple columns */ - for (int i = 1; i < width; i++) + for (int i = 1; i < cell->width; i++) win->line->cells[win->col++] = empty; - waddstr(win->win, c->c); return true; } return false; @@ -317,9 +240,7 @@ static size_t window_cursor_update(Win *win) { win->sel.end = cursor->pos; window_draw(win); } - wmove(win->win, cursor->row, cursor->col); - if (win->cursor_moved) - win->cursor_moved(win, win->cursor_moved_data); + win->ui->cursor_to(win->ui, cursor->col, cursor->row); return cursor->pos; } @@ -385,9 +306,7 @@ void window_cursor_to(Win *win, size_t pos) { /* redraw the complete with data starting from win->start bytes into the file. * stop once the screen is full, update win->end, win->lastline */ void window_draw(Win *win) { - int old_width = win->width; window_clear(win); - wmove(win->win, 0, 0); /* current absolute file position */ size_t pos = win->start; /* number of bytes to read in one go */ @@ -400,8 +319,6 @@ void window_draw(Win *win) { text[rem] = '\0'; /* current position into buffer from which to interpret a character */ char *cur = text; - /* current 'parsed' character' */ - Char c; /* current selection */ Filerange sel = window_selection_get(win); /* syntax definition to use */ @@ -414,6 +331,11 @@ void window_draw(Win *win) { while (rem > 0) { + /* current 'parsed' character' */ + wchar_t wchar; + Cell cell; + memset(&cell, 0, sizeof cell); + if (syntax) { if (matched && cur >= text + matched->rm_eo) { /* end of current match */ @@ -460,13 +382,13 @@ void window_draw(Win *win) { } } - size_t len = mbrtowc(&c.wchar, cur, rem, NULL); + size_t len = mbrtowc(&wchar, cur, rem, NULL); if (len == (size_t)-1 && errno == EILSEQ) { /* ok, we encountered an invalid multibyte sequence, * replace it with the Unicode Replacement Character * (FFFD) and skip until the start of the next utf8 char */ for (len = 1; rem > len && !ISUTF8(cur[len]); len++); - c = (Char){ .c = "\xEF\xBF\xBD", .wchar = 0xFFFD, .len = len }; + cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1, .istab = false }; } else if (len == (size_t)-2) { /* not enough bytes available to convert to a * wide character. advance file position and read @@ -478,74 +400,65 @@ void window_draw(Win *win) { continue; } else if (len == 0) { /* NUL byte encountered, store it and continue */ - len = 1; - c = (Char){ .c = "\x00", .wchar = 0x00, .len = len }; + cell = (Cell){ .data = "\x00", .len = 1, .width = 0, .istab = false }; } else { for (size_t i = 0; i < len; i++) - c.c[i] = cur[i]; - c.c[len] = '\0'; - c.len = len; + cell.data[i] = cur[i]; + cell.data[len] = '\0'; + cell.istab = false; + cell.len = len; + cell.width = wcwidth(wchar); + if (cell.width == -1) + cell.width = 1; } if (cur[0] == '\r' && rem > 1 && cur[1] == '\n') { /* convert windows style newline \r\n into a single char with len = 2 */ - len = 2; - c = (Char){ .c = "\n", .wchar = L'\n', .len = len }; + cell = (Cell){ .data = "\n", .len = 2, .width = 1, .istab = false }; } + cell.attr = attrs; if (sel.start <= pos && pos < sel.end) - wattrset(win->win, attrs | A_REVERSE); - else - wattrset(win->win, attrs); - - if (!window_addch(win, &c)) + cell.attr |= A_REVERSE; + if (!window_addch(win, &cell)) break; - rem -= len; - cur += len; - pos += len; + rem -= cell.len; + cur += cell.len; + pos += cell.len; } /* set end of viewing region */ win->end = pos; win->lastline = win->line ? win->line : win->bottomline; win->lastline->next = NULL; - /* and clear the rest of the unused window */ - wclrtobot(win->win); - /* if the text area width has changed because of the line numbers - * we have to (re)sync the cursor->line pointer */ - if (win->width != old_width) - window_cursor_sync(win); - /* draw line numbers */ - window_numbers_draw(win); + window_cursor_sync(win); + win->ui->draw_text(win->ui, win->topline); } bool window_resize(Win *win, int width, int height) { - // TODO: only grow memory area - size_t line_size = sizeof(Line) + width*sizeof(Cell); - Line *lines = calloc(height, line_size); - if (!lines) - return false; - free(win->lines); - win->w = win->width = width; - win->h = win->height = height; - win->lines = lines; - window_clear(win); + size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell)); + if (lines_size > win->lines_size) { + Line *lines = malloc(lines_size); + if (!lines) + return false; + win->lines = lines; + win->lines_size = lines_size; + } + win->width = width; + win->height = height; + memset(win->lines, 0, win->lines_size); + window_draw(win); return true; } -void window_move(Win *win, int x, int y) { - win->x = x; - win->y = y; +int window_height_get(Win *win) { + return win->height; } void window_free(Win *win) { if (!win) return; - if (win->winnum) - delwin(win->winnum); - if (win->win) - delwin(win->win); free(win->lines); free(win); } @@ -554,27 +467,23 @@ void window_reload(Win *win, Text *text) { win->text = text; window_selection_clear(win); window_cursor_to(win, 0); + win->ui->reload(win->ui, text); } -Win *window_new(Text *text) { - if (!text) +Win *window_new(Text *text, UiWin *ui, int width, int height) { + if (!text || !ui) return NULL; Win *win = calloc(1, sizeof(Win)); - if (!win || !(win->win = newwin(0, 0, 0, 0))) { - window_free(win); + if (!win) return NULL; - } win->text = text; + win->ui = ui; win->tabwidth = 8; - - int width, height; - getmaxyx(win->win, height, width); if (!window_resize(win, width, height)) { window_free(win); return NULL; } - window_selection_clear(win); window_cursor_to(win, 0); @@ -633,9 +542,9 @@ static size_t window_cursor_set(Win *win, Line *line, int col) { } /* for characters which use more than 1 column, make sure we are on the left most */ - while (col > 0 && line->cells[col].len == 0 && line->cells[col].data != '\t') + while (col > 0 && line->cells[col].len == 0) col--; - while (col < line->width && line->cells[col].data == '\t') + while (col < line->width && line->cells[col].istab) col++; /* calculate offset within the line */ @@ -845,12 +754,6 @@ size_t window_screenline_end(Win *win) { return window_cursor_set(win, cursor->line, col >= 0 ? col : 0); } -void window_update(Win *win) { - if (win->winnum) - wnoutrefresh(win->winnum); - wnoutrefresh(win->win); -} - size_t window_delete_key(Win *win) { Cursor *cursor = &win->cursor; Line *line = cursor->line; @@ -899,7 +802,7 @@ size_t window_replace_key(Win *win, const char *c, size_t len) { Line *line = cursor->line; size_t pos = cursor->pos; /* do not overwrite new line which would merge the two lines */ - if (line->cells[cursor->col].data != '\n') { + if (line->cells[cursor->col].data[0] != '\n') { size_t oldlen = line->cells[cursor->col].len; text_delete(win->text, pos, oldlen); } @@ -939,25 +842,9 @@ Syntax *window_syntax_get(Win *win) { return win->syntax; } -void window_cursor_watch(Win *win, void (*cursor_moved)(Win*, void *), void *data) { - win->cursor_moved = cursor_moved; - win->cursor_moved_data = data; -} - size_t window_screenline_goto(Win *win, int n) { size_t pos = win->start; for (Line *line = win->topline; --n > 0 && line != win->lastline; line = line->next) pos += line->len; return pos; } - -void window_line_numbers_show(Win *win, bool show) { - if (show) { - if (!win->winnum) - win->winnum = newwin(0, 0, 0, 0); - } else { - if (win->winnum) - delwin(win->winnum); - win->winnum = NULL; - } -} @@ -4,16 +4,38 @@ #include <stddef.h> #include <stdbool.h> #include "text.h" +#include "ui.h" #include "syntax.h" typedef struct Win Win; typedef struct { + int width; /* display width i.e. number of columns ocupied by this character */ + size_t len; /* number of bytes the character displayed in this cell uses, for + character which use more than 1 column to display, their lenght + is stored in the leftmost cell wheras all following cells + occupied by the same character have a length of 0. */ + char data[8]; /* utf8 encoded character displayed in this cell might not be the + the same as in the underlying text, for example tabs get expanded */ + bool istab; + unsigned int attr; +} Cell; + +typedef struct Line Line; +struct Line { /* a line on the screen, *not* in the file */ + Line *prev, *next; /* pointer to neighbouring screen lines */ + size_t len; /* line length in terms of bytes */ + size_t lineno; /* line number from start of file */ + int width; /* zero based position of last used column cell */ + Cell cells[]; /* win->width cells storing information about the displayed characters */ +}; + +typedef struct { size_t line; size_t col; } CursorPos; -Win *window_new(Text*); +Win *window_new(Text*, UiWin*, int width, int height); /* change associated text displayed in this window */ void window_reload(Win*, Text*); void window_free(Win*); @@ -25,10 +47,8 @@ size_t window_backspace_key(Win*); size_t window_delete_key(Win*); bool window_resize(Win*, int width, int height); -void window_move(Win*, int x, int y); +int window_height_get(Win*); void window_draw(Win*); -/* flush all changes made to the ncurses windows to the screen */ -void window_update(Win*); /* changes how many spaces are used for one tab (must be >0), redraws the window */ void window_tabwidth_set(Win*, int tabwidth); @@ -83,8 +103,6 @@ Filerange window_viewport_get(Win*); /* associate a set of syntax highlighting rules to this window. */ void window_syntax_set(Win*, Syntax*); Syntax *window_syntax_get(Win*); -/* whether to show line numbers to the left of the text content */ -void window_line_numbers_show(Win*, bool show); /* register a user defined function which will be called whenever the cursor has moved */ void window_cursor_watch(Win *win, void (*cursor_moved)(Win*, void*), void *data); |
