diff --git a/config.def.h b/config.def.h index 9d491a0ed..dff70d529 100644 --- a/config.def.h +++ b/config.def.h @@ -827,6 +827,11 @@ enum { COLOR_COMMENT = COLOR_SYNTAX7, COLOR_IDENTIFIER = COLOR_SYNTAX8, COLOR_TYPE = COLOR_SYNTAX9, + COLOR_WHITESPACE = COLOR_COMMENT, + COLOR_SPACES = COLOR_WHITESPACE, + COLOR_TABS = COLOR_WHITESPACE, + COLOR_EOL = COLOR_WHITESPACE, + COLOR_EOF = COLOR_WHITESPACE, }; static Color colors[] = { @@ -894,6 +899,12 @@ static Color colors[] = { &colors[COLOR_PREPROCESSOR], \ } +#define SYNTAX_SPACES { "\xC2\xB7", &colors[COLOR_SPACES] } +#define SYNTAX_TABS { "\xE2\x96\xB6", &colors[COLOR_TABS] } +#define SYNTAX_TABS_FILL { " ", &colors[COLOR_TABS] } +#define SYNTAX_EOL { "\xE2\x8F\x8E", &colors[COLOR_EOL] } +#define SYNTAX_EOF { "~", &colors[COLOR_EOF] } + /* these rules are applied top to bottom, first match wins. Therefore more 'greedy' * rules such as for comments should be the first entries. * @@ -905,8 +916,16 @@ static Syntax syntaxes[] = {{ .settings = (const char*[]){ "set number", "set autoindent", + "set show spaces=0 tabs=1 newlines=1", NULL }, + .symbols = { + SYNTAX_SPACES, + SYNTAX_TABS, + SYNTAX_TABS_FILL, + SYNTAX_EOL, + SYNTAX_EOF, + }, .rules = { SYNTAX_MULTILINE_COMMENT, SYNTAX_SINGLE_LINE_COMMENT, diff --git a/syntax.h b/syntax.h index 13fcb8c2f..c28a7ea0a 100644 --- a/syntax.h +++ b/syntax.h @@ -15,12 +15,27 @@ typedef struct { regex_t regex; /* compiled form of the above rule */ } SyntaxRule; +typedef struct { + char *symbol; + Color *color; +} SyntaxSymbol; + +enum { + SYNTAX_SYMBOL_SPACE, + SYNTAX_SYMBOL_TAB, + SYNTAX_SYMBOL_TAB_FILL, + SYNTAX_SYMBOL_EOL, + SYNTAX_SYMBOL_EOF, + SYNTAX_SYMBOL_LAST, +}; + typedef struct Syntax Syntax; struct Syntax { /* a syntax definition */ char *name; /* syntax name */ char *file; /* apply to files matching this regex */ regex_t file_regex; /* compiled file name regex */ const char **settings;/* settings associated with this file type */ + SyntaxSymbol symbols[SYNTAX_SYMBOL_LAST]; /* symbols for white space handling */ SyntaxRule rules[24]; /* all rules for this file type */ }; diff --git a/ui-curses.c b/ui-curses.c index a999aefca..5dfb2992d 100644 --- a/ui-curses.c +++ b/ui-curses.c @@ -213,7 +213,7 @@ static void ui_window_draw_sidebar(UiCursesWin *win, const Line *line) { size_t cursor_lineno = view_cursor_getpos(win->view).line; werase(win->winside); for (const Line *l = line; l; l = l->next, i++) { - if (l->lineno != prev_lineno) { + if (l->lineno && l->lineno != prev_lineno) { if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) { mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno); } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) { @@ -376,6 +376,8 @@ static void ui_window_draw_text(UiWin *w, const Line *line) { wattrset(win->win, l->cells[x].attr); waddstr(win->win, l->cells[x].data); } + if (l->width != win->width - win->sidebar_width) + waddstr(win->win, "\n"); } wclrtoeol(win->win); } diff --git a/view.c b/view.c index d49c41760..a002cd582 100644 --- a/view.c +++ b/view.c @@ -51,9 +51,26 @@ struct View { /* viewable area, showing part of a file */ Line *line; /* used while drawing view content, line where next char will be drawn */ int col; /* used while drawing view content, column where next char will be drawn */ Syntax *syntax; /* syntax highlighting definitions for this view or NULL */ + SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */ int tabwidth; /* how many spaces should be used to display a tab character */ }; +static SyntaxSymbol symbols_none[] = { + { " " }, /* spaces */ + { " " }, /* tab first cell */ + { " " }, /* tab remaining cells */ + { "" }, /* eol */ + { "~" }, /* eof */ +}; + +static SyntaxSymbol symbols_default[] = { + { "\xC2\xB7" }, /* spaces */ + { "\xE2\x96\xB6" }, /* tab first cell */ + { " " }, /* tab remaining cells */ + { "\xE2\x8F\x8E" }, /* eol */ + { "~" }, /* eof */ +}; + static void view_clear(View *view); static bool view_addch(View *view, Cell *cell); static size_t view_cursor_update(View *view); @@ -134,6 +151,8 @@ static bool view_addch(View *view, Cell *cell) { switch (cell->data[0]) { case '\t': + cell->istab = true; + cell->width = 1; width = view->tabwidth - (view->col % view->tabwidth); for (int w = 0; w < width; w++) { if (view->col + 1 > view->width) { @@ -143,23 +162,18 @@ static bool view_addch(View *view, Cell *cell) { return false; view->line->lineno = lineno; } - if (w == 0) { - /* first cell of a tab has a length of 1 */ - view->line->cells[view->col].len = cell->len; - view->line->len += cell->len; - } else { - /* all remaining ones have a lenght of zero */ - view->line->cells[view->col].len = 0; - } - /* but all are marked as part of a tabstop */ - view->line->cells[view->col].width = 1; - view->line->cells[view->col].data[0] = ' '; - view->line->cells[view->col].data[1] = '\0'; - view->line->cells[view->col].istab = true; - view->line->cells[view->col].attr = cell->attr; - view->line->width++; + + cell->len = w == 0 ? 1 : 0; + int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL; + strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)); + if (view->symbols[t]->color) + cell->attr = view->symbols[t]->color->attr; + view->line->cells[view->col] = *cell; + view->line->len += cell->len; + view->line->width += cell->width; view->col++; } + cell->len = 1; return true; case '\n': cell->width = 1; @@ -170,6 +184,11 @@ static bool view_addch(View *view, Cell *cell) { return false; view->line->lineno = lineno; } + + strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)); + if (view->symbols[SYNTAX_SYMBOL_EOL]->color) + cell->attr = view->symbols[SYNTAX_SYMBOL_EOL]->color->attr; + view->line->cells[view->col] = *cell; view->line->len += cell->len; view->line->width += cell->width; @@ -193,6 +212,12 @@ static bool view_addch(View *view, Cell *cell) { }; } + if (cell->data[0] == ' ') { + strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)); + if (view->symbols[SYNTAX_SYMBOL_SPACE]->color) + cell->attr = view->symbols[SYNTAX_SYMBOL_SPACE]->color->attr; + } + if (view->col + cell->width > view->width) { for (int i = view->col; i < view->width; i++) view->line->cells[i] = empty; @@ -452,7 +477,15 @@ void view_draw(View *view) { /* set end of vieviewg region */ view->end = pos; view->lastline = view->line ? view->line : view->bottomline; - view->lastline->next = NULL; + + for (Line *l = view->lastline->next; l; l = l->next) { + strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data)); + if (view->symbols[SYNTAX_SYMBOL_EOF]->color) + l->cells[0].attr =view->symbols[SYNTAX_SYMBOL_EOF]->color->attr; + l->width = 1; + l->len = 0; + } + view_cursor_sync(view); if (view->ui) view->ui->draw_text(view->ui, view->topline); @@ -505,6 +538,7 @@ View *view_new(Text *text, ViewEvent *events) { view->text = text; view->events = events; view->tabwidth = 8; + view_symbols_set(view, 0); if (!view_resize(view, 1, 1)) { view_free(view); @@ -776,12 +810,40 @@ void view_selection_start(View *view) { void view_syntax_set(View *view, Syntax *syntax) { view->syntax = syntax; + for (int i = 0; i < LENGTH(view->symbols); i++) { + if (syntax && syntax->symbols[i].symbol) + view->symbols[i] = &syntax->symbols[i]; + else + view->symbols[i] = &symbols_none[i]; + } } Syntax *view_syntax_get(View *view) { return view->syntax; } +void view_symbols_set(View *view, int flags) { + for (int i = 0; i < LENGTH(view->symbols); i++) { + if (flags & (1 << i)) { + if (view->syntax && view->syntax->symbols[i].symbol) + view->symbols[i] = &view->syntax->symbols[i]; + else + view->symbols[i] = &symbols_default[i]; + } else { + view->symbols[i] = &symbols_none[i]; + } + } +} + +int view_symbols_get(View *view) { + int flags = 0; + for (int i = 0; i < LENGTH(view->symbols); i++) { + if (view->symbols[i] != &symbols_none[i]) + flags |= (1 << i); + } + return flags; +} + size_t view_screenline_goto(View *view, int n) { size_t pos = view->start; for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next) diff --git a/view.h b/view.h index 049ed4c47..1d10110fb 100644 --- a/view.h +++ b/view.h @@ -115,5 +115,7 @@ bool view_viewport_down(View *view, int n); /* associate a set of syntax highlighting rules to this window. */ void view_syntax_set(View*, Syntax*); Syntax *view_syntax_get(View*); +void view_symbols_set(View*, int flags); +int view_symbols_get(View*); #endif diff --git a/vis.c b/vis.c index 63c574ea1..6f1fb278a 100644 --- a/vis.c +++ b/vis.c @@ -1339,6 +1339,7 @@ static bool cmd_set(Filerange *range, enum CmdOpt cmdopt, const char *argv[]) { OPTION_EXPANDTAB, OPTION_TABWIDTH, OPTION_SYNTAX, + OPTION_SHOW, OPTION_NUMBER, OPTION_NUMBER_RELATIVE, }; @@ -1349,6 +1350,7 @@ static bool cmd_set(Filerange *range, enum CmdOpt cmdopt, const char *argv[]) { [OPTION_EXPANDTAB] = { { "expandtab", "et" }, OPTION_TYPE_BOOL }, [OPTION_TABWIDTH] = { { "tabwidth", "tw" }, OPTION_TYPE_NUMBER }, [OPTION_SYNTAX] = { { "syntax" }, OPTION_TYPE_STRING, true }, + [OPTION_SHOW] = { { "show" }, OPTION_TYPE_STRING }, [OPTION_NUMBER] = { { "numbers", "nu" }, OPTION_TYPE_BOOL }, [OPTION_NUMBER_RELATIVE] = { { "relativenumbers", "rnu" }, OPTION_TYPE_BOOL }, }; @@ -1448,6 +1450,36 @@ static bool cmd_set(Filerange *range, enum CmdOpt cmdopt, const char *argv[]) { else editor_info_show(vis, "Unknown syntax definition: `%s'", argv[2]); break; + case OPTION_SHOW: + if (!argv[2]) { + editor_info_show(vis, "Expecting: spaces, tabs, newlines"); + return false; + } + char *keys[] = { "spaces", "tabs", "newlines" }; + int values[] = { + 1 << SYNTAX_SYMBOL_SPACE, + (1 << SYNTAX_SYMBOL_TAB)|(1 << SYNTAX_SYMBOL_TAB_FILL), + 1 << SYNTAX_SYMBOL_EOL + }; + int flags = view_symbols_get(vis->win->view); + for (const char **args = &argv[2]; *args; args++) { + for (int i = 0; i < LENGTH(keys); i++) { + if (strcmp(*args, keys[i]) == 0) { + flags |= values[i]; + } else if (strstr(*args, keys[i]) == *args) { + bool show; + const char *v = *args + strlen(keys[i]); + if (*v == '=' && parse_bool(v+1, &show)) { + if (show) + flags |= values[i]; + else + flags &= ~values[i]; + } + } + } + } + view_symbols_set(vis->win->view, flags); + break; case OPTION_NUMBER: editor_window_options(vis->win, arg.b ? UI_OPTION_LINE_NUMBERS_ABSOLUTE : UI_OPTION_LINE_NUMBERS_NONE);