/* * Copyright (c) 2014 Marc André Tanner * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _XOPEN_SOURCE #include #include #include #include #include #include "editor.h" #include "text.h" #include "text-motions.h" #include "text-objects.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 */ Line *line; /* screen line on which cursor currently resides */ } Cursor; typedef struct Win Win; struct Win { /* window showing part of a file */ Editor *editor; Text *text; /* underlying text management */ WINDOW *win; /* curses window for the text area */ WINDOW *statuswin; /* curses window for the statusbar */ int width, height; /* text area size *not* including the statusbar */ Filepos start, end; /* currently displayed area [start, end] in bytes from the start of the file */ 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 */ Line *line; // TODO: rename to something more descriptive, these are the current drawing pos int col; Syntax *syntax; /* syntax highlighting definitions for this window or NULL */ int tabwidth; /* how many spaces should be used to display a tab character */ Win *prev, *next; /* neighbouring windows */ }; struct Editor { int width, height; /* terminal size, available for all windows */ editor_statusbar_t statusbar; Win *windows; /* list of windows */ Win *win; /* currently active window */ Syntax *syntaxes; /* NULL terminated array of syntax definitions */ void (*windows_arrange)(Editor*); }; static void editor_windows_arrange(Editor *ed); static void editor_windows_arrange_horizontal(Editor *ed); static void editor_windows_arrange_vertical(Editor *ed); static Filerange window_selection_get(Win *win); static void window_statusbar_draw(Win *win); static void editor_windows_invalidate(Editor *ed, size_t pos, size_t len); static void editor_windows_insert(Editor *ed, size_t pos, const char *c, size_t len); static void editor_windows_delete(Editor *ed, size_t pos, size_t len); static void editor_search_forward(Editor *ed, Regex *regex); static void editor_search_backward(Editor *ed, Regex *regex); static void window_selection_clear(Win *win); static void window_clear(Win *win); static bool window_addch(Win *win, Char *c); static size_t window_cursor_update(Win *win); static size_t window_line_begin(Win *win); static size_t cursor_move_to(Win *win, size_t pos); static size_t cursor_move_to_line(Win *win, size_t pos); static void window_draw(Win *win); static bool window_resize(Win *win, int width, int height); static void window_move(Win *win, int x, int y); static void window_free(Win *win); static Win *window_new(Editor *ed, const char *filename, int x, int y, int w, int h, int pos); static size_t pos_by_line(Win *win, Line *line); static size_t cursor_offset(Cursor *cursor); static bool scroll_line_down(Win *win, int n); static bool scroll_line_up(Win *win, int n); static void window_selection_clear(Win *win) { win->sel.start = win->sel.end = (size_t)-1; window_draw(win); window_cursor_update(win); } static void window_clear(Win *win) { size_t line_size = sizeof(Line) + win->width*sizeof(Cell); win->topline = win->lines; win->topline->lineno = text_lineno_by_pos(win->text, win->start); win->lastline = win->topline; Line *prev = NULL; for (int i = 0; i < win->height; i++) { Line *line = (Line*)(((char*)win->lines) + i*line_size); line->len = 0; line->width = 0; line->prev = prev; if (prev) prev->next = line; prev = line; } win->bottomline = prev; win->line = win->topline; win->col = 0; } static Filerange window_selection_get(Win *win) { Filerange sel = win->sel; if (sel.start == (size_t)-1) { sel.end = (size_t)-1; } else if (sel.end == (size_t)-1) { sel.start = (size_t)-1; } else if (sel.start > sel.end) { size_t tmp = sel.start; sel.start = sel.end; sel.end = tmp; } return sel; } /* try to add another character to the window, return whether there was space left */ static bool window_addch(Win *win, Char *c) { if (!win->line) return false; Cell empty = {}; int width; size_t lineno = win->line->lineno; switch (c->wchar) { case '\t': width = win->tabwidth - (win->col % win->tabwidth); for (int w = 0; w < width; w++) { if (win->col + 1 > win->width) { win->line = win->line->next; win->col = 0; if (!win->line) return false; win->line->lineno = lineno; } 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; } 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->width++; waddch(win->win, ' '); } return true; case '\n': width = 1; if (win->col + width > win->width) { win->line = win->line->next; win->col = 0; if (!win->line) return false; win->line->lineno = lineno + 1; } win->line->cells[win->col].len = c->len; win->line->len += c->len; win->line->cells[win->col].data = '\n'; 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)) { /* 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; } } if (win->col + width > win->width) { for (int i = win->col; i < win->width; i++) win->line->cells[i] = empty; win->line = win->line->next; win->col = 0; } if (win->line) { win->line->width += width; win->line->len += c->len; win->line->lineno = lineno; win->line->cells[win->col].len = c->len; win->line->cells[win->col].data = c->c[0]; win->col++; /* set cells of a character which uses multiple columns */ for (int i = 1; i < width; i++) win->line->cells[win->col++] = empty; waddstr(win->win, c->c); return true; } return false; } } static void window_statusbar_draw(Win *win) { if (!win->editor->statusbar) return; Cursor *cursor = &win->cursor; Line *line = cursor->line; size_t lineno = line->lineno; int col = cursor->col; while (line->prev && line->prev->lineno == lineno) { line = line->prev; col += line->width; } win->editor->statusbar(win->statuswin, win->editor->win == win, text_filename(win->text), cursor->line->lineno, col+1); } /* place the cursor according to the screen coordinates in win->{row,col} and * update the statusbar. if a selection is active, redraw the window to reflect * its changes. */ static size_t window_cursor_update(Win *win) { Cursor *cursor = &win->cursor; if (win->sel.start != (size_t)-1) { win->sel.end = cursor->pos; window_draw(win); } wmove(win->win, cursor->row, cursor->col); window_statusbar_draw(win); return cursor->pos; } /* move the cursor to the character at pos bytes from the begining of the file. * if pos is not in the current viewport, redraw the window to make it visible */ static size_t cursor_move_to(Win *win, size_t pos) { Line *line = win->topline; int row = 0; int col = 0; size_t max = text_size(win->text); if (pos > max) pos = max > 0 ? max - 1 : 0; if (pos < win->start || pos > win->end) { win->start = pos; window_draw(win); } else { size_t cur = win->start; while (line && line != win->lastline && cur < pos) { if (cur + line->len > pos) break; cur += line->len; line = line->next; row++; } if (line) { int max_col = MIN(win->width, line->width); while (cur < pos && col < max_col) { cur += line->cells[col].len; col++; } while (col < max_col && line->cells[col].data == '\t') col++; } else { line = win->bottomline; row = win->height - 1; } } win->cursor.line = line; win->cursor.row = row; win->cursor.col = col; win->cursor.pos = pos; return window_cursor_update(win); } /* move cursor to pos, make sure the whole line is visible */ static size_t cursor_move_to_line(Win *win, size_t pos) { if (pos < win->start || pos > win->end) { win->cursor.pos = pos; window_line_begin(win); } return cursor_move_to(win, 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 */ static void window_draw(Win *win) { 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 */ // TODO read smaller junks size_t text_len = win->width * win->height; /* current buffer to work with */ char text[text_len+1]; /* remaining bytes to process in buffer*/ size_t rem = text_bytes_get(win->text, pos, text_len, text); /* NUL terminate because regex(3) function expect it */ 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); /* matched tokens for each syntax rule */ regmatch_t match[SYNTAX_REGEX_RULES][1]; if (win->syntax) { for (int i = 0; i < LENGTH(win->syntax->rules); i++) { SyntaxRule *rule = &win->syntax->rules[i]; if (!rule->rule) break; if (regexec(&rule->regex, cur, 1, match[i], 0) || match[i][0].rm_so == match[i][0].rm_eo) { match[i][0].rm_so = -1; match[i][0].rm_eo = -1; } } } while (rem > 0) { int attrs = COLOR_PAIR(0) | A_NORMAL; if (win->syntax) { size_t off = cur - text; /* number of already processed bytes */ for (int i = 0; i < LENGTH(win->syntax->rules); i++) { SyntaxRule *rule = &win->syntax->rules[i]; if (!rule->rule) break; if (match[i][0].rm_so == -1) continue; /* no match on whole text */ if (off >= (size_t)match[i][0].rm_eo) { /* past match, continue search from current position */ if (regexec(&rule->regex, cur, 1, match[i], 0) || match[i][0].rm_so == match[i][0].rm_eo) { match[i][0].rm_so = -1; match[i][0].rm_eo = -1; continue; } match[i][0].rm_so += off; match[i][0].rm_eo += off; } if (text + match[i][0].rm_so <= cur && cur < text + match[i][0].rm_eo) { /* within matched expression */ attrs = rule->color.attr; } } } if (sel.start <= pos && pos < sel.end) attrs |= A_REVERSE; // TODO: make configurable size_t len = mbrtowc(&c.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 }; } else if (len == (size_t)-2) { /* not enough bytes available to convert to a * wide character. advance file position and read * another junk into buffer. */ rem = text_bytes_get(win->text, pos, text_len, text); text[rem] = '\0'; cur = text; continue; } else if (len == 0) { /* NUL byte encountered, store it and continue */ len = 1; c = (Char){ .c = "\x00", .wchar = 0x00, .len = len }; } else { for (size_t i = 0; i < len; i++) c.c[i] = cur[i]; c.c[len] = '\0'; c.len = len; } if (cur[0] == '\n' && rem > 1 && cur[1] == '\r') { /* convert windows style newline \n\r into a single char with len = 2 */ c.len = len = 2; } wattrset(win->win, attrs); if (!window_addch(win, &c)) break; rem -= len; cur += len; pos += 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); } static bool window_resize(Win *win, int width, int height) { height--; // statusbar if (wresize(win->win, height, width) == ERR || wresize(win->statuswin, 1, width) == ERR) return false; // TODO: only grow memory area win->height = height; win->width = width; free(win->lines); size_t line_size = sizeof(Line) + width*sizeof(Cell); if (!(win->lines = calloc(height, line_size))) return false; window_draw(win); cursor_move_to(win, win->cursor.pos); return true; } static void window_move(Win *win, int x, int y) { mvwin(win->win, y, x); mvwin(win->statuswin, y + win->height, x); } static void window_free(Win *win) { if (!win) return; if (win->win) delwin(win->win); if (win->statuswin) delwin(win->statuswin); text_free(win->text); free(win); } static Win *window_new(Editor *ed, const char *filename, int x, int y, int w, int h, int pos) { Win *win = calloc(1, sizeof(Win)); if (!win) return NULL; win->editor = ed; win->start = pos; win->tabwidth = 8; // TODO make configurable if (filename) { for (Syntax *syn = ed->syntaxes; syn && syn->name; syn++) { if (!regexec(&syn->file_regex, filename, 0, NULL, 0)) { win->syntax = syn; break; } } } if (!(win->text = text_load(filename)) || !(win->win = newwin(h-1, w, y, x)) || !(win->statuswin = newwin(1, w, y+h-1, x)) || !window_resize(win, w, h)) { window_free(win); return NULL; } window_selection_clear(win); cursor_move_to(win, pos); return win; } Editor *editor_new(int width, int height, editor_statusbar_t statusbar) { Editor *ed = calloc(1, sizeof(Editor)); if (!ed) return NULL; ed->width = width; ed->height = height; ed->statusbar = statusbar; ed->windows_arrange = editor_windows_arrange_horizontal; return ed; } bool editor_load(Editor *ed, const char *filename) { Win *win = window_new(ed, filename, 0, 0, ed->width, ed->height, 0); if (!win) return false; if (ed->windows) ed->windows->prev = win; win->next = ed->windows; win->prev = NULL; ed->windows = win; ed->win = win; return true; } static size_t pos_by_line(Win *win, Line *line) { size_t pos = win->start; for (Line *cur = win->topline; cur && cur != line; cur = cur->next) pos += cur->len; return pos; } size_t editor_char_prev(Editor *ed) { Win *win = ed->win; Cursor *cursor = &win->cursor; Line *line = cursor->line; do { if (cursor->col == 0) { if (!line->prev) return cursor->pos; cursor->line = line = line->prev; cursor->col = MIN(line->width, win->width - 1); cursor->row--; } else { cursor->col--; } } while (line->cells[cursor->col].len == 0); cursor->pos -= line->cells[cursor->col].len; return window_cursor_update(win); } size_t editor_char_next(Editor *ed) { Win *win = ed->win; Cursor *cursor = &win->cursor; Line *line = cursor->line; do { cursor->pos += line->cells[cursor->col].len; if ((line->width == win->width && cursor->col == win->width - 1) || cursor->col == line->width) { if (!line->next) return cursor->pos; cursor->line = line = line->next; cursor->row++; cursor->col = 0; } else { cursor->col++; } } while (line->cells[cursor->col].len == 0); return window_cursor_update(win); } /* calculate the line offset in bytes of a given cursor position, used after * the cursor changes line. this assumes cursor->line already points to the new * line, but cursor->col still has the old column position of the previous * line based on which the new column position is calculated */ static size_t cursor_offset(Cursor *cursor) { Line *line = cursor->line; int col = cursor->col; int off = 0; /* 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') col--; while (col < line->width && line->cells[col].data == '\t') col++; for (int i = 0; i < col; i++) off += line->cells[i].len; cursor->col = col; return off; } static bool scroll_line_down(Win *win, int n) { Line *line; if (win->end == text_size(win->text)) return false; for (line = win->topline; line && n > 0; line = line->next, n--) win->start += line->len; window_draw(win); /* try to place the cursor at the same column */ Cursor *cursor = &win->cursor; cursor->pos = win->end - win->lastline->len + cursor_offset(cursor); window_cursor_update(win); return true; } static bool scroll_line_up(Win *win, int n) { /* scrolling up is somewhat tricky because we do not yet know where * the lines start, therefore scan backwards but stop at a reasonable * maximum in case we are dealing with a file without any newlines */ if (win->start == 0) return false; size_t max = win->width * win->height / 2; char c; Iterator it = text_iterator_get(win->text, win->start - 1); if (!text_iterator_byte_get(&it, &c)) return false; size_t off = 0; /* skip newlines immediately before display area */ if (c == '\r' && text_iterator_byte_prev(&it, &c)) off++; if (c == '\n' && text_iterator_byte_prev(&it, &c)) off++; do { if ((c == '\n' || c == '\r') && --n == 0) break; if (++off > max) break; } while (text_iterator_byte_prev(&it, &c)); win->start -= off; window_draw(win); Cursor *cursor = &win->cursor; cursor->pos = win->start + cursor_offset(cursor); window_cursor_update(win); return true; } size_t editor_file_begin(Editor *ed) { return cursor_move_to(ed->win, 0); } size_t editor_file_end(Editor *ed) { Win *win = ed->win; size_t size = text_size(win->text); if (win->end == size) return cursor_move_to(win, win->end); win->start = size - 1; scroll_line_up(win, win->height / 2); return cursor_move_to(win, win->end); } size_t editor_page_up(Editor *ed) { Win *win = ed->win; if (!scroll_line_up(win, win->height)) cursor_move_to(win, win->start); return win->cursor.pos; } size_t editor_page_down(Editor *ed) { Win *win = ed->win; Cursor *cursor = &win->cursor; if (win->end == text_size(win->text)) return cursor_move_to(win, win->end); win->start = win->end; window_draw(win); int col = cursor->col; cursor_move_to(win, win->start); cursor->col = col; cursor->pos += cursor_offset(cursor); return window_cursor_update(win); } size_t editor_line_up(Editor *ed) { Win *win = ed->win; Cursor *cursor = &win->cursor; if (!cursor->line->prev) { scroll_line_up(win, 1); return cursor->pos; } cursor->row--; cursor->line = cursor->line->prev; cursor->pos = pos_by_line(win, cursor->line) + cursor_offset(cursor); return window_cursor_update(win); } size_t editor_line_down(Editor *ed) { Win *win = ed->win; Cursor *cursor = &win->cursor; if (!cursor->line->next) { if (cursor->line == win->bottomline) scroll_line_down(ed->win, 1); return cursor->pos; } cursor->row++; cursor->line = cursor->line->next; cursor->pos = pos_by_line(win, cursor->line) + cursor_offset(cursor); return window_cursor_update(win); } static size_t window_line_begin(Win *win) { return cursor_move_to(win, text_line_begin(win->text, win->cursor.pos)); } size_t editor_line_begin(Editor *ed) { return window_line_begin(ed->win); } size_t editor_line_start(Editor *ed) { Win *win = ed->win; size_t pos = text_line_start(win->text, win->cursor.pos); while (win->end < pos && scroll_line_down(win, 1)); return cursor_move_to(win, pos); } size_t editor_line_end(Editor *ed) { Win *win = ed->win; size_t pos = text_line_end(win->text, win->cursor.pos); while (win->end < pos && scroll_line_down(win, 1)); return cursor_move_to(win, pos); } size_t editor_line_finish(Editor *ed) { Win *win = ed->win; size_t pos = text_line_finish(win->text, win->cursor.pos); return cursor_move_to(win, pos); } size_t editor_word_end_prev(Editor *ed) { Win *win = ed->win; size_t pos = text_word_end_prev(win->text, win->cursor.pos); while (win->start > pos && scroll_line_up(win, 1)); return cursor_move_to(win, pos); } size_t editor_word_end_next(Editor *ed) { Win *win = ed->win; size_t pos = text_word_end_next(win->text, win->cursor.pos); while (win->end < pos && scroll_line_down(win, 1)); return cursor_move_to(win, pos); } size_t editor_word_start_next(Editor *ed) { Win *win = ed->win; size_t pos = text_word_start_next(win->text, win->cursor.pos); while (win->end < pos && scroll_line_down(win, 1)); return cursor_move_to(win, pos); } size_t editor_word_start_prev(Editor *ed) { Win *win = ed->win; size_t pos = text_word_start_prev(win->text, win->cursor.pos); while (win->start > pos && scroll_line_up(win, 1)); return cursor_move_to(win, pos); } size_t editor_sentence_next(Editor *ed) { Win *win = ed->win; return cursor_move_to_line(win, text_sentence_next(win->text, win->cursor.pos)); } size_t editor_paragraph_next(Editor *ed) { Win *win = ed->win; return cursor_move_to_line(win, text_paragraph_next(win->text, win->cursor.pos)); } size_t editor_sentence_prev(Editor *ed) { Win *win = ed->win; return cursor_move_to_line(win, text_sentence_prev(win->text, win->cursor.pos)); } size_t editor_paragraph_prev(Editor *ed) { Win *win = ed->win; return cursor_move_to_line(win, text_paragraph_prev(win->text, win->cursor.pos)); } size_t editor_bracket_match(Editor *ed) { Win *win = ed->win; return cursor_move_to_line(win, text_bracket_match(win->text, win->cursor.pos)); } void editor_draw(Editor *ed) { for (Win *win = ed->windows; win; win = win->next) { if (ed->win != win) { window_draw(win); cursor_move_to(win, win->cursor.pos); } } window_draw(ed->win); cursor_move_to(ed->win, ed->win->cursor.pos); } void editor_update(Editor *ed) { for (Win *win = ed->windows; win; win = win->next) { if (ed->win != win) { wnoutrefresh(win->statuswin); wnoutrefresh(win->win); } } wnoutrefresh(ed->win->statuswin); wnoutrefresh(ed->win->win); } void editor_free(Editor *ed) { if (!ed) return; window_free(ed->win); free(ed); } bool editor_resize(Editor *ed, int width, int height) { ed->width = width; ed->height = height; editor_windows_arrange(ed); return true; } bool editor_syntax_load(Editor *ed, Syntax *syntaxes, Color *colors) { bool success = true; ed->syntaxes = syntaxes; for (Syntax *syn = syntaxes; syn && syn->name; syn++) { if (regcomp(&syn->file_regex, syn->file, REG_EXTENDED|REG_NOSUB|REG_ICASE|REG_NEWLINE)) success = false; Color *color = colors; for (int j = 0; j < LENGTH(syn->rules); j++) { SyntaxRule *rule = &syn->rules[j]; if (!rule->rule) break; if (rule->color.fg == 0 && color && color->fg != 0) rule->color = *color++; if (rule->color.attr == 0) rule->color.attr = A_NORMAL; if (rule->color.fg != 0) rule->color.attr |= COLOR_PAIR(editor_color_reserve(rule->color.fg, rule->color.bg)); if (regcomp(&rule->regex, rule->rule, REG_EXTENDED|rule->cflags)) success = false; } } return success; } void editor_syntax_unload(Editor *ed) { for (Syntax *syn = ed->syntaxes; syn && syn->name; syn++) { regfree(&syn->file_regex); for (int j = 0; j < LENGTH(syn->rules); j++) { SyntaxRule *rule = &syn->rules[j]; if (!rule->rule) break; regfree(&rule->regex); } } ed->syntaxes = NULL; } static void editor_windows_invalidate(Editor *ed, size_t pos, size_t len) { size_t end = pos + len; for (Win *win = ed->windows; win; win = win->next) { if (ed->win != win && ed->win->text == win->text && ((win->start <= pos && pos <= win->end) || (win->start <= end && end <= win->end))) { window_draw(win); cursor_move_to(win, win->cursor.pos); } } } static void editor_windows_insert(Editor *ed, size_t pos, const char *c, size_t len) { text_insert_raw(ed->win->text, pos, c, len); editor_windows_invalidate(ed, pos, len); } static void editor_windows_delete(Editor *ed, size_t pos, size_t len) { text_delete(ed->win->text, pos, len); editor_windows_invalidate(ed, pos, len); } size_t editor_delete(Editor *ed) { Cursor *cursor = &ed->win->cursor; Line *line = cursor->line; size_t len = line->cells[cursor->col].len; editor_windows_delete(ed, cursor->pos, len); window_draw(ed->win); return cursor_move_to(ed->win, cursor->pos); } size_t editor_backspace(Editor *ed) { Win *win = ed->win; Cursor *cursor = &win->cursor; if (win->start == cursor->pos) { if (win->start == 0) return cursor->pos; /* if we are on the top left most position in the window * first scroll up so that the to be deleted character is * visible then proceed as normal */ size_t pos = cursor->pos; scroll_line_up(win, 1); cursor_move_to(win, pos); } editor_char_prev(ed); size_t pos = cursor->pos; size_t len = cursor->line->cells[cursor->col].len; editor_windows_delete(ed, pos, len); window_draw(win); return cursor_move_to(ed->win, pos); } size_t editor_insert(Editor *ed, const char *c, size_t len) { Win *win = ed->win; size_t pos = win->cursor.pos; editor_windows_insert(ed, pos, c, len); if (win->cursor.line == win->bottomline && memchr(c, '\n', len)) scroll_line_down(win, 1); else window_draw(win); return cursor_move_to(win, pos + len); } size_t editor_replace(Editor *ed, const char *c, size_t len) { Win *win = ed->win; Cursor *cursor = &win->cursor; 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') { size_t oldlen = line->cells[cursor->col].len; text_delete(win->text, pos, oldlen); } editor_windows_insert(ed, pos, c, len); if (cursor->line == win->bottomline && memchr(c, '\n', len)) scroll_line_down(win, 1); else window_draw(win); return cursor_move_to(win, pos + len); } size_t editor_cursor_get(Editor *ed) { return ed->win->cursor.pos; } size_t editor_selection_start(Editor *ed) { return ed->win->sel.start = editor_cursor_get(ed); } size_t editor_selection_end(Editor *ed) { return ed->win->sel.end = editor_cursor_get(ed); } Filerange editor_selection_get(Editor *ed) { return window_selection_get(ed->win); } void editor_selection_clear(Editor *ed) { window_selection_clear(ed->win); } size_t editor_line_goto(Editor *ed, size_t lineno) { size_t pos = text_pos_by_lineno(ed->win->text, lineno); return cursor_move_to(ed->win, pos); } static void editor_search_forward(Editor *ed, Regex *regex) { Win *win = ed->win; Cursor *cursor = &win->cursor; int pos = cursor->pos + 1; int end = text_size(win->text); RegexMatch match[1]; bool found = false; if (text_search_forward(win->text, pos, end - pos, regex, 1, match, 0)) { pos = 0; end = cursor->pos; if (!text_search_forward(win->text, pos, end, regex, 1, match, 0)) found = true; } else { found = true; } if (found) cursor_move_to_line(win, match[0].start); } static void editor_search_backward(Editor *ed, Regex *regex) { Win *win = ed->win; Cursor *cursor = &win->cursor; int pos = 0; int end = cursor->pos; RegexMatch match[1]; bool found = false; if (text_search_backward(win->text, pos, end, regex, 1, match, 0)) { pos = cursor->pos + 1; end = text_size(win->text); if (!text_search_backward(win->text, pos, end - pos, regex, 1, match, 0)) found = true; } else { found = true; } if (found) cursor_move_to_line(win, match[0].start); } void editor_search(Editor *ed, const char *s, int direction) { Regex *regex = text_regex_new(); if (!regex) return; if (!text_regex_compile(regex, s, REG_EXTENDED)) { if (direction >= 0) editor_search_forward(ed, regex); else editor_search_backward(ed, regex); } text_regex_free(regex); } void editor_snapshot(Editor *ed) { text_snapshot(ed->win->text); } void editor_undo(Editor *ed) { Win *win = ed->win; if (text_undo(win->text)) window_draw(win); } void editor_redo(Editor *ed) { Win *win = ed->win; if (text_redo(win->text)) window_draw(win); } static void editor_windows_arrange(Editor *ed) { erase(); wnoutrefresh(stdscr); ed->windows_arrange(ed); } static void editor_windows_arrange_horizontal(Editor *ed) { int n = 0, x = 0, y = 0; for (Win *win = ed->windows; win; win = win->next) n++; int height = ed->height / n; for (Win *win = ed->windows; win; win = win->next) { window_resize(win, ed->width, win->next ? height : ed->height - y); window_move(win, x, y); y += height; } } static void editor_windows_arrange_vertical(Editor *ed) { int n = 0, x = 0, y = 0; for (Win *win = ed->windows; win; win = win->next) n++; int width = ed->width / n; for (Win *win = ed->windows; win; win = win->next) { window_resize(win, win->next ? width : ed->width - x, ed->height); window_move(win, x, y); x += width; } } static void editor_window_split_internal(Editor *ed, const char *filename) { Win *sel = ed->win; editor_load(ed, filename); if (sel && !filename) { Win *win = ed->win; text_free(win->text); win->text = sel->text; win->start = sel->start; win->syntax = sel->syntax; win->cursor.pos = sel->cursor.pos; // TODO show begin of line instead of cursor->pos } } void editor_window_split(Editor *ed, const char *filename) { editor_window_split_internal(ed, filename); ed->windows_arrange = editor_windows_arrange_horizontal; editor_windows_arrange(ed); } void editor_window_vsplit(Editor *ed, const char *filename) { editor_window_split_internal(ed, filename); ed->windows_arrange = editor_windows_arrange_vertical; editor_windows_arrange(ed); } void editor_window_next(Editor *ed) { Win *sel = ed->win; if (!sel) return; ed->win = ed->win->next; if (!ed->win) ed->win = ed->windows; window_statusbar_draw(sel); window_statusbar_draw(ed->win); } void editor_window_prev(Editor *ed) { Win *sel = ed->win; if (!sel) return; ed->win = ed->win->prev; if (!ed->win) for (ed->win = ed->windows; ed->win->next; ed->win = ed->win->next); window_statusbar_draw(sel); window_statusbar_draw(ed->win); } void editor_mark_set(Editor *ed, Mark mark, size_t pos) { text_mark_set(ed->win->text, mark, pos); } void editor_mark_goto(Editor *ed, Mark mark) { Win *win = ed->win; size_t pos = text_mark_get(win->text, mark); if (pos != (size_t)-1) cursor_move_to_line(win, pos); } void editor_mark_clear(Editor *ed, Mark mark) { text_mark_clear(ed->win->text, mark); }