diff --git a/config.def.h b/config.def.h index 2a9e25896..b8657a4c8 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, }, }; diff --git a/editor.c b/editor.c index a8330d6bf..668062c4d 100644 --- a/editor.c +++ b/editor.c @@ -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); } + diff --git a/editor.h b/editor.h index 2be3fa627..ba7d3aabb 100644 --- a/editor.h +++ b/editor.h @@ -4,14 +4,17 @@ #include #include #include + +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 000000000..4b351e57b --- /dev/null +++ b/ui-curses.c @@ -0,0 +1,568 @@ +#include +#include +#include +#include +#include +#include + +#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 000000000..74cb92088 --- /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 diff --git a/ui.h b/ui.h new file mode 100644 index 000000000..92123db06 --- /dev/null +++ b/ui.h @@ -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 +#include +#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 diff --git a/vis.c b/vis.c index 11cf1afed..22b61bb67 100644 --- a/vis.c +++ b/vis.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -29,6 +31,7 @@ #include #include +#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; } diff --git a/window.c b/window.c index 47fad1ac2..81bb7b5f4 100644 --- a/window.c +++ b/window.c @@ -19,7 +19,6 @@ #include #include #include -#include #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; - } -} diff --git a/window.h b/window.h index 686f67fbf..8be069e9e 100644 --- a/window.h +++ b/window.h @@ -4,16 +4,38 @@ #include #include #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);