├── .gitignore ├── Makefile ├── README ├── cmd.c ├── conf.c ├── conf.h ├── dir.c ├── ecmd.sh ├── ex.c ├── kmap.h ├── lbuf.c ├── led.c ├── mot.c ├── reg.c ├── regex.c ├── regex.h ├── ren.c ├── rset.c ├── rstr.c ├── sbuf.c ├── stag.c ├── syn.c ├── tag.c ├── term.c ├── test.sh ├── test ├── e00.sh ├── e01.sh ├── e02.sh ├── e03.sh ├── e04.sh ├── e05.sh ├── e06.sh ├── e07.sh ├── e08.sh ├── e09.sh ├── e0a.sh ├── e0b.sh ├── e0c.sh ├── e0d.sh ├── e0e.sh ├── e0f.sh ├── e10.sh ├── e11.sh ├── e12.sh ├── e13.sh ├── e14.sh ├── e15.sh ├── e16.sh ├── e17.sh ├── v00.sh ├── v01.sh ├── v02.sh ├── v03.sh ├── v04.sh ├── v05.sh ├── v06.sh ├── v07.sh ├── v08.sh ├── v09.sh ├── v0a.sh ├── v0b.sh ├── v0c.sh ├── v0d.sh ├── v0e.sh ├── v0f.sh ├── v10.sh ├── v11.sh ├── v12.sh ├── v13.sh ├── v14.sh ├── v15.sh ├── v16.sh ├── v17.sh ├── v18.sh ├── v19.sh ├── v1a.sh ├── v1b.sh ├── v1c.sh ├── v1d.sh ├── v1e.sh ├── v1f.sh ├── v20.sh ├── v21.sh ├── v22.sh └── v23.sh ├── uc.c ├── vi.c └── vi.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | vi 3 | tags 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | CFLAGS = -Wall -O2 3 | LDFLAGS = 4 | 5 | OBJS = vi.o ex.o lbuf.o mot.o sbuf.o ren.o dir.o syn.o reg.o led.o \ 6 | uc.o term.o rset.o rstr.o regex.o cmd.o tag.o conf.o 7 | STAG = stag.o regex.o 8 | 9 | all: vi 10 | 11 | conf.o: conf.h kmap.h 12 | 13 | %.o: %.c vi.h 14 | $(CC) -c $(CFLAGS) $< 15 | vi: $(OBJS) 16 | $(CC) -o $@ $(OBJS) $(LDFLAGS) 17 | stag: $(STAG) 18 | $(CC) -o $@ $(STAG) $(LDFLAGS) 19 | clean: 20 | rm -f *.o vi stag 21 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NEATVI 2 | ====== 3 | 4 | Neatvi is a small vi/ex editor for editing UTF-8 text. It supports 5 | syntax highlighting, multiple windows, right-to-left languages, and 6 | keymaps. 7 | 8 | CONFIGURATION 9 | ------------- 10 | 11 | Edit conf.h to adjust syntax highlighting rules and text direction 12 | patterns. To define a new keymap, create a new array in kmap.h, like 13 | kmap_fa, and add it to kmaps array in the same header (the first entry 14 | of the new array specifies its name). The current keymap may be 15 | changed with :cm ex command. When in input mode, ^e activates the 16 | English keymap and ^f switches to the alternate keymap (the last 17 | keymap specified with :cm). 18 | 19 | Sadly, VTE-based terminals such as GNOME's implement a 20 | backward-incompatible extension of virtual terminal escape codes to 21 | render bidirectional text by default. When using these terminal, the 22 | value of LNPREF macro in conf.h needs to be changed to "\33[8l". 23 | 24 | COMMANDS 25 | -------- 26 | 27 | Commands not available in ex(1): 28 | 29 | :cm[ap][!] [kmap] 30 | Without kmap, prints the current keymap name. 31 | When kmap is specified, sets the alternate keymap to 32 | kmap and, unless ! is given, switches to this keymap. 33 | :ft [filetype] 34 | Without filetype, prints the current file type. 35 | When filetype is specified, sets the file type of the 36 | current ex buffer. 37 | :ta[g] tag 38 | Jumps to tag (tags file: TAGPATH environment variable or ./tags). 39 | :tn[ext] 40 | Jumps to the next matching tag. 41 | :tp[rev] 42 | Jumps to the previous matching tag. 43 | :po[p] 44 | Pops tag stack. 45 | :b[uffer] [buf] 46 | Without buf, prints buffer list. Switches to the given buffer 47 | if buf is a buffer number or alias. Also, buf can be -, +, !, 48 | and ~ to switch to the previous buffer, switch to the next buffer, 49 | delete the current buffer, and renumber buffers, respectively. 50 | :rs reg 51 | Reads dot-terminated lines (similar to :a command) from ex input 52 | and copies them to the given yank buffer. 53 | :rx reg cmd 54 | Like :! command, executes cmd. However, the contents of the 55 | specified yank buffer is given to the command as input, and the 56 | output of the command is written to the same buffer. 57 | :rk reg path 58 | Connects to a unix socket, writes the contents of the given buffer 59 | to it, and reads from the socket into the buffer. 60 | :ra reg 61 | Similar to :@ command, executes the contents of the given buffer. 62 | Before executing the buffer, however, it replaces ^rx with the 63 | contents of buffer x, and ^vx with x, in which x is any character. 64 | :ec[ho] msg 65 | Prints the given message (useful in ex scripts or q-commands). 66 | 67 | New key mappings: 68 | - ^a in normal mode: searches for the word under the cursor. 69 | - ^p in insert mode: inserts the contents of the default yank buffer. 70 | - ^rX in insert mode: inserts the contents of yank buffer X. 71 | - z>, z<, 2z>, and 2z< in normal mode: changes the value of td option. 72 | - ^e and ^f in insert mode: switches to the English and alternate keymap. 73 | - ze and zf in normal mode: switches to the English and alternate keymap. 74 | - gu, gU, and g~ in normal mode: switches character case. 75 | - ^l in normal mode: updates terminal dimensions (after resizing it). 76 | - ^] and ^t in normal mode: jumps to tag and pops tag stack. 77 | - gf in normal mode: edits the file whose address is under the cursor. 78 | - gl in normal mode: like gf, but it reads line and column numbers too. 79 | - ^ws, ^wo, ^wc, ^wj, ^wk, ^wx in normal mode: manages windows. 80 | - ^wgf, ^wgl, ^w^] in normal mode: executes gf, gl, ^] in a new window. 81 | - zj, zk, zD: equivalent to :b+, :b-, :b !. 82 | - zJ, zK: equivalent to :next, :prev. 83 | - q in normal mode: see quick access section. 84 | - ^a in ex, search, and pipe prompts: inserts from history lines. 85 | 86 | Other noteworthy differences with vi(1): 87 | - Neatvi assumes POSIX extended regular expressions (ERE) in search 88 | patterns, conf.h variables, and even tags files. 89 | - If paths start with =, they are assumed to be relative to the 90 | directory of the current file. 91 | - Neatvi highlights files whose names end with ls as directory 92 | listings; the gl command edits the file under the cursor. For 93 | instance, git ls-files >ls && neatvi ls. 94 | - In addition to the standard single-letter yank buffers, Neatvi 95 | supports a set of extended buffers whose two-letter names begin 96 | with a backslash, like \x. 97 | - If EXINIT environment variable is defined as "so /path/to/exrc", 98 | Neatvi executes the ex commands in this file at startup. 99 | 100 | Note that in :rs command, input lines are read from ex input stream 101 | (unlike :a), to make it usable in @ commands and ex scripts (files 102 | passed to :so). This allows setting the value of yank buffers in ex 103 | files, as the following example shows: 104 | 105 | rs a 106 | :!git add % 107 | . 108 | 109 | OPTIONS 110 | ------- 111 | 112 | To improve Neatvi's performance, shaping, character reordering, and 113 | syntax highlighting can be disabled by defining the EXINIT environment 114 | variable as "set noshape | set noorder | set nohl | set td=+2". 115 | 116 | Options supported in Neatvi: 117 | 118 | td, textdirection 119 | Current direction context. The following values are meaningful: 120 | * +2: always left-to-right. 121 | * +1: follow conf.h's dircontexts[]; left-to-right for others. 122 | * -1: follow conf.h's dircontexts[]; right-to-left for others. 123 | * -2: always right-to-left. 124 | The default value is 0, which assumes left-to-right if the first 125 | character of the line is single-byte; otherwise, it follows 126 | dircontexts[]. 127 | shape 128 | If set, Arabic/Farsi letter shaping will be performed. 129 | order 130 | If set, characters will be reordered based on the rules defined 131 | in conf.h. If the value is 1, only lines with at least one 132 | multi-byte UTF-8 character are reordered. If it is 2, all lines 133 | are reordered. 134 | hl, highlight 135 | If set, text will be highlighted based on syntax highlighting 136 | rules in conf.h. 137 | hll, highlightline 138 | If set, the current line will be highlighted. 139 | lim, linelimit 140 | Lines longer than this value are not reordered or highlighted. 141 | ru, ruler 142 | Indicates when to redraw the status line: 143 | * 0: never. 144 | * 1: always. 145 | * 2: when multiple windows are visible. 146 | * 4: when the current file changes. 147 | hist, history 148 | Indicates the number of lines remembered for ex, search, and 149 | pipe prompts. Zero disables command history. 150 | ai, autoindent 151 | As in vi(1). 152 | aw, autowrite 153 | As in vi(1). 154 | ic, ignorecase 155 | As in vi(1). 156 | wa, writeany 157 | As in vi(1). 158 | 159 | MARKS AND BUFFERS 160 | ----------------- 161 | 162 | Special marks: 163 | - * the position of the previous change 164 | - [ the first line of the previous change 165 | - ] the last line of the previous change 166 | 167 | Special yank buffers: 168 | - / the previous search keyword 169 | - : the previous ex command 170 | - ! the previous pipe command 171 | - % the name of the current file 172 | - " the default yank buffer 173 | - ; the current line 174 | - . the last vi command 175 | - # cursor line number 176 | - ^ cursor line offset 177 | - \/ search history 178 | - \: ex command history 179 | - \! pipe command history 180 | 181 | QUICK ACCESS 182 | ------------ 183 | 184 | When q is pressed in normal mode, Neatvi prints the list of buffers at 185 | the bottom of the screen and waits for a key. If the key is a digit, 186 | it switches to its corresponding buffer. If it is a letter and the 187 | extended buffer with that letter is defined, the contents of that 188 | buffer is executed. Otherwise, Neatvi executes ECMD (defined in 189 | conf.h) with the following parameters: i) the letter, ii) the current 190 | file, iii) the current line number, and iv) the current line offset. 191 | 192 | What ECMD writes to its standard output, Neatvi executes as ex 193 | commands. Q-commands can be used to add interesting features to 194 | Neatvi, such as language-dependent IDE features, for instance by 195 | connecting to an LSP (language server protocol) server. ecmd.sh is an 196 | example ECMD shell script. 197 | 198 | AUTO-COMPLETION 199 | --------------- 200 | 201 | When the hist option is nonzero, Neatvi suggests the most recent 202 | matching entry when reading user input for :, /, and ! prompts; ^a 203 | completes the input using the suggestion. 204 | 205 | On the other hand, when in insert mode, ^a changes the value of the 206 | buffer named ~ to the contents of the current line up to the cursor, 207 | and executes the contents of \~ buffer (as :ra \~). The buffer can 208 | execute any ex command (for instance, :ec to print a message), and 209 | change the contents of ~ to suggest a completion; a second ^a inserts 210 | the suggestion. The following lines define \~ to demonstrate how it 211 | works. 212 | 213 | rs \~ 214 | ec completing '~' 215 | rs ~ 216 | completion 217 | . 218 | . 219 | 220 | In a real implementation, the \~ buffer can use external commands 221 | (with rx or rk) to compute the completions. 222 | -------------------------------------------------------------------------------- /cmd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "vi.h" 12 | 13 | static int cmd_make(char **argv, int *ifd, int *ofd) 14 | { 15 | int pid; 16 | int pipefds0[2]; 17 | int pipefds1[2]; 18 | if (ifd) 19 | pipe(pipefds0); 20 | if (ofd) 21 | pipe(pipefds1); 22 | if (!(pid = fork())) { 23 | if (ifd) { /* setting up stdin */ 24 | close(0); 25 | dup(pipefds0[0]); 26 | close(pipefds0[1]); 27 | close(pipefds0[0]); 28 | } 29 | if (ofd) { /* setting up stdout and stderr */ 30 | close(1); 31 | dup(pipefds1[1]); 32 | close(2); 33 | dup(pipefds1[1]); 34 | close(pipefds1[0]); 35 | close(pipefds1[1]); 36 | } 37 | execvp(argv[0], argv); 38 | exit(1); 39 | } 40 | if (ifd) 41 | close(pipefds0[0]); 42 | if (ofd) 43 | close(pipefds1[1]); 44 | if (pid < 0) { 45 | if (ifd) 46 | close(pipefds0[1]); 47 | if (ofd) 48 | close(pipefds1[0]); 49 | return -1; 50 | } 51 | if (ifd) 52 | *ifd = pipefds0[1]; 53 | if (ofd) 54 | *ofd = pipefds1[0]; 55 | return pid; 56 | } 57 | 58 | /* 59 | * Execute a shell command. 60 | * 61 | * If ibuf is given, it is passed as standard input to the process. 62 | * Otherwise, the process reads from the terminal. 63 | * 64 | * If oproc is 0, the process writes directly to the terminal. If it 65 | * is 1, process' output is saved and returned. If it is 2, in addition 66 | * to returning the output, it is written to the terminal. 67 | */ 68 | char *cmd_pipe(char *cmd, char *ibuf, int oproc) 69 | { 70 | char *argv[] = {"/bin/sh", "-c", cmd, NULL}; 71 | struct pollfd fds[3]; 72 | struct sbuf *sb = NULL; 73 | char buf[512]; 74 | int ifd = -1, ofd = -1; 75 | int slen = ibuf != NULL ? strlen(ibuf) : 0; 76 | int nw = 0; 77 | int pid = cmd_make(argv, ibuf != NULL ? &ifd : NULL, oproc ? &ofd : NULL); 78 | if (pid <= 0) 79 | return NULL; 80 | if (oproc) 81 | sb = sbuf_make(); 82 | if (ibuf == NULL) { 83 | signal(SIGINT, SIG_IGN); 84 | term_done(); 85 | } 86 | fcntl(ifd, F_SETFL, fcntl(ifd, F_GETFL, 0) | O_NONBLOCK); 87 | fds[0].fd = ofd; 88 | fds[0].events = POLLIN; 89 | fds[1].fd = ifd; 90 | fds[1].events = POLLOUT; 91 | fds[2].fd = isatty(0) && ibuf != NULL ? 0 : -1; 92 | fds[2].events = POLLIN; 93 | while ((fds[0].fd >= 0 || fds[1].fd >= 0) && poll(fds, 3, 200) >= 0) { 94 | if (fds[0].revents & POLLIN) { 95 | int ret = read(fds[0].fd, buf, sizeof(buf)); 96 | if (ret > 0 && oproc == 2) 97 | write(1, buf, ret); 98 | if (ret > 0) 99 | sbuf_mem(sb, buf, ret); 100 | if (ret <= 0) { 101 | close(fds[0].fd); 102 | fds[0].fd = -1; 103 | } 104 | } else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { 105 | close(fds[0].fd); 106 | fds[0].fd = -1; 107 | } 108 | if (fds[1].revents & POLLOUT) { 109 | int ret = write(fds[1].fd, ibuf + nw, slen - nw); 110 | if (ret > 0) 111 | nw += ret; 112 | if (ret <= 0 || nw == slen) { 113 | close(fds[1].fd); 114 | fds[1].fd = -1; 115 | } 116 | } else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) { 117 | close(fds[1].fd); 118 | fds[1].fd = -1; 119 | } 120 | if (fds[2].revents & POLLIN) { 121 | int ret = read(fds[2].fd, buf, sizeof(buf)); 122 | int i; 123 | for (i = 0; i < ret; i++) 124 | if ((unsigned char) buf[i] == TK_CTL('c')) 125 | kill(pid, SIGINT); 126 | } else if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) { 127 | fds[2].fd = -1; 128 | } 129 | } 130 | close(fds[0].fd); 131 | close(fds[1].fd); 132 | waitpid(pid, NULL, 0); 133 | if (ibuf == NULL) { 134 | term_init(); 135 | signal(SIGINT, SIG_DFL); 136 | } 137 | if (oproc) 138 | return sbuf_done(sb); 139 | return NULL; 140 | } 141 | 142 | int cmd_exec(char *cmd) 143 | { 144 | cmd_pipe(cmd, NULL, 0); 145 | return 0; 146 | } 147 | 148 | char *cmd_unix(char *path, char *ibuf) 149 | { 150 | char buf[512]; 151 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); 152 | struct sockaddr_un addr; 153 | long nw = 0, nc = 0; 154 | long len = strlen(ibuf); 155 | struct sbuf *sb; 156 | if (fd < 0) 157 | return NULL; 158 | memset(&addr, 0, sizeof(addr)); 159 | addr.sun_family = AF_UNIX; 160 | snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path); 161 | if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 162 | close(fd); 163 | return NULL; 164 | } 165 | while (nw < len && (nc = write(fd, ibuf + nw, len - nw)) >= 0) 166 | nw += nc; 167 | shutdown(fd, SHUT_WR); 168 | sb = sbuf_make(); 169 | while ((nc = read(fd, buf, sizeof(buf))) > 0) 170 | sbuf_mem(sb, buf, nc); 171 | close(fd); 172 | return sbuf_done(sb); 173 | } 174 | -------------------------------------------------------------------------------- /conf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "vi.h" 4 | #include "conf.h" 5 | #include "kmap.h" 6 | 7 | int conf_dirmark(int idx, char **pat, int *ctx, int *dir, int *grp) 8 | { 9 | if (idx < 0 || idx >= LEN(dirmarks)) 10 | return 1; 11 | if (pat) 12 | *pat = dirmarks[idx].pat; 13 | if (ctx) 14 | *ctx = dirmarks[idx].ctx; 15 | if (dir) 16 | *dir = dirmarks[idx].dir; 17 | if (grp) 18 | *grp = dirmarks[idx].grp; 19 | return 0; 20 | } 21 | 22 | int conf_dircontext(int idx, char **pat, int *ctx) 23 | { 24 | if (idx < 0 || idx >= LEN(dircontexts)) 25 | return 1; 26 | if (pat) 27 | *pat = dircontexts[idx].pat; 28 | if (ctx) 29 | *ctx = dircontexts[idx].dir; 30 | return 0; 31 | } 32 | 33 | int conf_placeholder(int idx, char **s, char **d, int *wid) 34 | { 35 | if (idx < 0 || idx >= LEN(placeholders)) 36 | return 1; 37 | if (s) 38 | *s = placeholders[idx].s; 39 | if (d) 40 | *d = placeholders[idx].d; 41 | if (wid) 42 | *wid = placeholders[idx].wid; 43 | return 0; 44 | } 45 | 46 | int conf_highlight(int idx, char **ft, int **att, char **pat, int *end) 47 | { 48 | if (idx < 0 || idx >= LEN(highlights)) 49 | return 1; 50 | if (ft) 51 | *ft = highlights[idx].ft; 52 | if (att) 53 | *att = highlights[idx].att; 54 | if (pat) 55 | *pat = highlights[idx].pat; 56 | if (end) 57 | *end = highlights[idx].end; 58 | return 0; 59 | } 60 | 61 | int conf_filetype(int idx, char **ft, char **pat) 62 | { 63 | if (idx < 0 || idx >= LEN(filetypes)) 64 | return 1; 65 | if (ft) 66 | *ft = filetypes[idx].ft; 67 | if (pat) 68 | *pat = filetypes[idx].pat; 69 | return 0; 70 | } 71 | 72 | int conf_hlrev(void) 73 | { 74 | return SYN_REVDIR; 75 | } 76 | 77 | int conf_hlline(void) 78 | { 79 | return SYN_LINE; 80 | } 81 | 82 | int conf_mode(void) 83 | { 84 | return MKFILE_MODE; 85 | } 86 | 87 | char **conf_kmap(int id) 88 | { 89 | return kmaps[id]; 90 | } 91 | 92 | int conf_kmapfind(char *name) 93 | { 94 | int i; 95 | for (i = 0; i < LEN(kmaps); i++) 96 | if (name && kmaps[i][0] && !strcmp(name, kmaps[i][0])) 97 | return i; 98 | return 0; 99 | } 100 | 101 | char *conf_digraph(int c1, int c2) 102 | { 103 | int i; 104 | for (i = 0; i < LEN(digraphs); i++) 105 | if (digraphs[i][0][0] == c1 && digraphs[i][0][1] == c2) 106 | return digraphs[i][1]; 107 | return NULL; 108 | } 109 | 110 | char *conf_lnpref(void) 111 | { 112 | return LNPREF; 113 | } 114 | 115 | char *conf_definition(char *ft) 116 | { 117 | int i; 118 | for (i = 0; i < LEN(filetypes); i++) 119 | if (!strcmp(ft, filetypes[i].ft) && filetypes[i].def) 120 | return filetypes[i].def; 121 | return "^%s\\>"; 122 | } 123 | 124 | char *conf_section(char *ft) 125 | { 126 | int i; 127 | for (i = 0; i < LEN(filetypes); i++) 128 | if (!strcmp(ft, filetypes[i].ft) && filetypes[i].sec) 129 | return filetypes[i].sec; 130 | return "^\\{"; 131 | } 132 | 133 | char *conf_ecmd(void) 134 | { 135 | return ECMD; 136 | } 137 | -------------------------------------------------------------------------------- /conf.h: -------------------------------------------------------------------------------- 1 | /* neatvi configuration file */ 2 | 3 | /* access mode of new files */ 4 | #define MKFILE_MODE 0600 5 | 6 | /* map file names to file types */ 7 | static struct filetype { 8 | char *ft; /* file type */ 9 | char *pat; /* file name pattern */ 10 | char *def; /* pattern for global definitions (for gd command) */ 11 | char *sec; /* section start pattern (for [[ and ]] commands) */ 12 | } filetypes[] = { 13 | {"c", "\\.[hc]$", "^([a-zA-Z_].*)?\\<%s\\>"}, 14 | {"roff", "\\.(ms|me|mom|tr|roff|tmac|[1-9])$", "^\\.(de|nr|ds) +%s\\>"}, 15 | {"tex", "\\.tex$"}, 16 | {"msg", "letter$|mbox$|mail$"}, 17 | {"mk", "Makefile$|makefile$|\\.mk$", "^%s:"}, 18 | {"sh", "\\.sh$", "^(function +)?%s(\\(\\))? *\\{", "^(function +)?[a-zA-Z_0-9]+(\\(\\))? *\\{"}, 19 | {"go", "\\.go$", "^(func|var|const|type)( +\\(.*\\))? +%s\\>", "^(func|type)\\>.*\\{$"}, 20 | {"py", "\\.py$", "^(def|class) +\\<%s\\>", "^(def|class) "}, 21 | {"bib", "bib$"}, 22 | {"nm", "\\.nm$"}, 23 | {"diff", "\\.(patch|diff)$"}, 24 | {"ls", "ls$"}, 25 | {"txt", "$"}, /* matches everything; must be the last pattern */ 26 | }; 27 | 28 | /* colours used in highlights[] for programming languages */ 29 | #define CKWD (3 | SYN_BD) /* general keywords */ 30 | #define CCON (CKWD) /* control flow keywords */ 31 | #define CPRE (2 | SYN_BD) /* preprocessor directives */ 32 | #define CIMP (4 | SYN_BD) /* imported packages */ 33 | #define CTYP (3) /* built-in types and values */ 34 | #define CBIN (3) /* built-in functions */ 35 | #define CCMT (4 | SYN_IT) /* comments */ 36 | #define CDEF (4 | SYN_BD) /* top-level definition */ 37 | #define CFUN (SYN_BD) /* called functions */ 38 | #define CNUM 4 /* numerical constants */ 39 | #define CSTR 4 /* string literals */ 40 | #define CVAR 3 /* macros */ 41 | #define CIDN 0 /* identifiers */ 42 | 43 | /* syntax highlighting patterns */ 44 | static struct highlight { 45 | char *ft; /* the filetype of this pattern */ 46 | int att[16]; /* attributes of the matched groups */ 47 | char *pat; /* regular expression */ 48 | int end; /* the group ending this pattern */ 49 | } highlights[] = { 50 | /* status bar */ 51 | {"---", {SYN_BGMK(0) | 7 | SYN_BD, 2, 1}, "^(\".*\").*(\\[[wr]\\]).*$"}, 52 | {"---", {SYN_BGMK(0) | 7 | SYN_BD, 2, 5, 7}, "^(\".*\").*=.*(L[0-9]+) +(C[0-9]+).*$"}, 53 | {"---", {SYN_BGMK(0) | 7}, "^(\".*\").*-.*(L[0-9]+) +(C[0-9]+).*$"}, 54 | {"---", {SYN_BGMK(0) | 7 | SYN_BD, 5, 7, 5}, "^\\[([0-9])\\](.*/)*([^/]*)$"}, 55 | {"---", {SYN_BGMK(0) | 2 | SYN_BD}, "^.*$\n?"}, 56 | /* ex mode */ 57 | {"-ex", {SYN_BGMK(0) | 7 | SYN_BD}, "^[:/!].*$"}, 58 | {"-ex", {SYN_BGMK(0) | 7}, "^.*$\n?"}, 59 | 60 | /* C */ 61 | {"c", {CTYP}, "\\<(signed|unsigned|char|short|int|long|float|double|void|struct|enum|union|typedef)\\>"}, 62 | {"c", {CKWD}, "\\<(static|extern|register)\\>"}, 63 | {"c", {CCON}, "\\<(return|for|while|if|else|do|sizeof|goto|switch|case|default|break|continue)\\>"}, 64 | {"c", {CCMT}, "//.*$"}, 65 | {"c", {CCMT}, "/\\*([^*]|\\*+[^*/])*\\*+/"}, 66 | {"c", {CIMP, CPRE}, "^#([ \t]*include).*"}, 67 | {"c", {CIMP, CPRE}, "^#([ \t]*[a-zA-Z0-9_]+)"}, 68 | {"c", {0, CFUN}, "([a-zA-Z][a-zA-Z0-9_]+)\\(", 1}, 69 | {"c", {CSTR}, "\"([^\"\\]|\\\\.)*\""}, 70 | {"c", {CNUM}, "'([^\\]|\\\\.)'"}, 71 | {"c", {CNUM}, "[-+]?\\<(0[xX][0-9a-fA-F]+|[0-9]+)\\>"}, 72 | {"c", {CCMT}, "^\t*(/\\*.*|\t* \\*.*|\t* \\*\\//)$"}, 73 | {"c", {CIDN}, "[a-zA-Z][a-zA-Z0-9_]*"}, 74 | 75 | /* troff */ 76 | {"roff", {0, CKWD, CDEF}, "^[.'][ \t]*(SH)(.*)$"}, 77 | {"roff", {0, CKWD, CDEF}, "^[.'][ \t]*de (.*)$"}, 78 | {"roff", {0, CFUN, CSTR}, "^[.']([^ \t\\]{2,})?(.*)$", 1}, 79 | {"roff", {CCMT}, "\\\\\".*$"}, 80 | {"roff", {CFUN}, "\\\\{1,2}[*$fgkmns]([^[(]|\\(..|\\[[^]]*\\])"}, 81 | {"roff", {CVAR}, "\\\\([^[(*$fgkmns]|\\(..|\\[[^]]*\\])"}, 82 | {"roff", {CSTR}, "\\$[^$]+\\$"}, 83 | 84 | /* tex */ 85 | {"tex", {0, CKWD, 0, 0, CIMP, 0, CFUN}, 86 | "\\\\([^[{ \t]+)((\\[([^][]+)\\])|(\\{([^}]*)\\}))*"}, 87 | {"tex", {CSTR}, "\\$[^$]+\\$"}, 88 | {"tex", {CCMT}, "%.*$"}, 89 | 90 | /* mail */ 91 | {"msg", {6 | SYN_BD}, "^From .*20..$"}, 92 | {"msg", {6 | SYN_BD, 4 | SYN_BD}, "^Subject: (.*)$"}, 93 | {"msg", {6 | SYN_BD, 2 | SYN_BD}, "^From: (.*)$"}, 94 | {"msg", {6 | SYN_BD, 5 | SYN_BD}, "^To: (.*)$"}, 95 | {"msg", {6 | SYN_BD, 5 | SYN_BD}, "^Cc: (.*)$"}, 96 | {"msg", {6 | SYN_BD}, "^[A-Z][-A-Za-z]+: .+$"}, 97 | {"msg", {2 | SYN_IT}, "^> .*$"}, 98 | 99 | /* makefile */ 100 | {"mk", {0, CDEF}, "([A-Za-z_][A-Za-z0-9_]*)[ \t]*="}, 101 | {"mk", {CVAR}, "\\$\\([a-zA-Z0-9_]+\\)"}, 102 | {"mk", {CCMT}, "#.*$"}, 103 | {"mk", {0, CFUN}, "([A-Za-z_%.]+):"}, 104 | 105 | /* shell script */ 106 | {"sh", {0, CKWD, CDEF}, "^(function +)?([a-zA-Z_0-9]+) *(\\(\\))? *\\{"}, 107 | {"sh", {CCON}, "\\<(break|case|continue|do|done|elif|else|esac|fi|for|if|in|then|until|while|return)\\>"}, 108 | {"sh", {CSTR}, "\"([^\"\\]|\\\\.)*\""}, 109 | {"sh", {CSTR}, "'[^']*'"}, 110 | {"sh", {CSTR}, "`([^`\\]|\\\\.)*`"}, 111 | {"sh", {CVAR}, "\\$(\\{[^}]+\\}|[a-zA-Z_0-9]+|[!#$?*@-])"}, 112 | {"sh", {CVAR}, "\\$\\([^()]+\\)"}, 113 | {"sh", {CFUN}, "^\\. .*$"}, 114 | {"sh", {CCMT}, "#.*$"}, 115 | 116 | /* go */ 117 | {"go", {0, CKWD, CIDN, CDEF}, "^\\<(func) (\\([^()]+\\) )?([a-zA-Z0-9_]+)\\>"}, 118 | {"go", {CKWD}, "\\<(func|type|var|const|package)\\>"}, 119 | {"go", {CPRE, CIMP}, "^import[ \t]+([^ ]+)"}, 120 | {"go", {CKWD}, "\\<(import|interface|struct)\\>"}, 121 | {"go", {CCON}, "\\<(break|case|chan|continue|default|defer|else|fallthrough|for|go|goto|if|map|range|return|select|switch)\\>"}, 122 | {"go", {0, CBIN}, "\\<(append|copy|delete|len|cap|make|new|complex|real|imag|close|panic|recover|print|println|int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|uintptr|float32|float64|complex128|complex64|bool|byte|rune|string|error)\\>\\("}, 123 | {"go", {CTYP}, "\\<(true|false|iota|nil|int8|int16|int32|int64|int|uint8|uint16|uint32|uint64|uint|uintptr|float32|float64|complex128|complex64|bool|byte|rune|string|error)\\>"}, 124 | {"go", {CCMT}, "//.*$"}, 125 | {"go", {CCMT}, "/\\*([^*]|\\*+[^*/])*\\*+/"}, 126 | {"go", {0, CFUN}, "([a-zA-Z][a-zA-Z0-9_]*)\\(", 1}, 127 | {"go", {CIDN}, "[a-zA-Z][a-zA-Z0-9_]*"}, 128 | {"go", {CSTR}, "\"([^\"\\]|\\\\.)*\""}, 129 | {"go", {CNUM}, "'([^']|\\\\')*'"}, 130 | {"go", {CSTR}, "`([^`]|\\\\`)*`"}, 131 | {"go", {CNUM}, "[-+]?\\<(0[xX][0-9a-fA-F]+|[0-9.]+)\\>"}, 132 | 133 | /* refer */ 134 | {"bib", {0, SYN_BD, SYN_BGMK(3) | SYN_BD}, "^(%L) +(.*)$", 1}, 135 | {"bib", {0, SYN_BD, 4 | SYN_BD}, "^(%A) (.*)$", 1}, 136 | {"bib", {0, SYN_BD, 5 | SYN_BD}, "^(%T) (.*)$", 1}, 137 | {"bib", {0, SYN_BD, 2 | SYN_BD}, "^(%[JB]) (.*)$", 1}, 138 | {"bib", {0, SYN_BD, 5 | SYN_BD}, "^(%D) (.*)$", 1}, 139 | {"bib", {0, SYN_BD, 7}, "^(%O) (.*)$", 1}, 140 | {"bib", {0, SYN_BD, SYN_BD}, "^(%[A-Z]) (.*)$", 1}, 141 | {"bib", {4}, "^#.*$", 1}, 142 | 143 | /* python */ 144 | {"py", {CCMT}, "#.*$"}, 145 | {"py", {CKWD}, "\\<(class|def)\\>"}, 146 | {"py", {CKWD}, "\\<(and|or|not|is|in)\\>"}, 147 | {"py", {0, 0, CPRE, CIDN, CPRE, CIMP}, "((from)[ \t]+([^ ]+)[ \t]+)?(import)[ \t]+([^ ]+)"}, 148 | {"py", {CKWD}, "\\<(import|from|global|lambda|del)\\>"}, 149 | {"py", {CCON}, "\\<(for|while|if|elif|else|pass|return|break|continue)\\>"}, 150 | {"py", {CCON}, "\\<(try|except|as|raise|finally|with)\\>"}, 151 | {"py", {0, CFUN}, "([a-zA-Z][a-zA-Z0-9_]+)\\(", 1}, 152 | {"py", {CSTR}, "[\"']([^\"'\\]|\\\\.)*[\"']"}, 153 | {"py", {CIDN}, "[a-zA-Z][a-zA-Z0-9_]*"}, 154 | 155 | /* neatmail listing */ 156 | {"nm", {0, 6 | SYN_BD, 4 | SYN_BD, 3, 5, SYN_BD}, 157 | "^([ROU])([0-9]+)(@[^ ]*)? *\t([^\t]*)\t([^\t]*)"}, 158 | {"nm", {0 | SYN_BD | SYN_BGMK(6)}, "^[N].*$"}, 159 | {"nm", {0 | SYN_BD | SYN_BGMK(5)}, "^[A-Z][HA].*$"}, 160 | {"nm", {0 | SYN_BD | SYN_BGMK(3)}, "^[A-Z][MB].*$"}, 161 | {"nm", {7}, "^[A-Z][LC].*$"}, 162 | {"nm", {0 | SYN_BD | SYN_BGMK(7)}, "^[F].*$"}, 163 | {"nm", {7 | SYN_IT}, "^\t.*$"}, 164 | {"nm", {SYN_BD}, "^:.*$"}, 165 | 166 | /* diff */ 167 | {"diff", {1}, "^-.*$"}, 168 | {"diff", {2}, "^\\+.*$"}, 169 | {"diff", {6}, "^@.*$"}, 170 | {"diff", {SYN_BD}, "^diff .*$"}, 171 | 172 | /* directory listing */ 173 | {"ls", {7, 3, SYN_FGMK(0) | SYN_BD, 2, 6}, "^/?([-a-zA-Z0-9_.]+/)*([-a-zA-Z0-9_.]+)\\>(:[0-9]*:)?(.*)$"}, 174 | {"ls", {CCMT}, "^#.*$"}, 175 | }; 176 | 177 | /* how to highlight current line (hll option) */ 178 | #define SYN_LINE (SYN_BGMK(11)) 179 | 180 | /* how to highlight text in the reverse direction */ 181 | #define SYN_REVDIR (SYN_RV) 182 | 183 | /* define it as "\33[8l" to disable BiDi in vte-based terminals */ 184 | #define LNPREF "" 185 | 186 | /* right-to-left characters (used only in dircontexts[] and dirmarks[]) */ 187 | #define CR2L "ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىييپچژکگی‌‍؛،»«؟ًٌٍَُِّْٔ" 188 | /* neutral characters (used only in dircontexts[] and dirmarks[]) */ 189 | #define CNEUT "-!\"#$%&'()*+,./:;<=>?@^_`{|}~ " 190 | 191 | /* direction context; specifies the base direction of lines */ 192 | static struct dircontext { 193 | int dir; 194 | char *pat; 195 | } dircontexts[] = { 196 | {-1, "^[" CR2L "]"}, 197 | {+1, "^[a-zA-Z_0-9]"}, 198 | }; 199 | 200 | /* direction marks; the direction of contiguous characters in a line */ 201 | static struct dirmark { 202 | int ctx; /* the direction context for this mark; 0 means any */ 203 | int dir; /* the direction of the matched text */ 204 | int grp; /* the nested subgroup; 0 means no groups */ 205 | char *pat; 206 | } dirmarks[] = { 207 | {+0, +1, 1, "\\\\\\*\\[([^]]+)\\]"}, 208 | {+1, -1, 0, "[" CR2L "][" CNEUT CR2L "]*[" CR2L "]"}, 209 | {-1, +1, 0, "[a-zA-Z0-9_][^" CR2L "\\\\`$']*[a-zA-Z0-9_]"}, 210 | {+0, +1, 0, "\\$([^$]+)\\$"}, 211 | {+0, +1, 1, "\\\\[a-zA-Z0-9_]+\\{([^}]+)\\}"}, 212 | {-1, +1, 0, "\\\\[^ \t" CR2L "]+"}, 213 | }; 214 | 215 | /* character placeholders */ 216 | static struct placeholder { 217 | char *s; /* the source character */ 218 | char *d; /* the placeholder */ 219 | int wid; /* the width of the placeholder */ 220 | } placeholders[] = { 221 | {"‌", "-", 1}, 222 | {"‍", "-", 1}, 223 | {"ً", "ـً", 1}, 224 | {"ٌ", "ـٌ", 1}, 225 | {"ٍ", "ـٍ", 1}, 226 | {"َ", "ـَ", 1}, 227 | {"ُ", "ـُ", 1}, 228 | {"ِ", "ـِ", 1}, 229 | {"ّ", "ـّ", 1}, 230 | {"ْ", "ـْ", 1}, 231 | {"ٓ", "ـٓ", 1}, 232 | {"ٔ", "ـٔ", 1}, 233 | {"ٕ", "ـٕ", 1}, 234 | {"ٰ", "ـٰ", 1}, 235 | }; 236 | 237 | /* external commands */ 238 | #define ECMD "neatvi.sh" 239 | -------------------------------------------------------------------------------- /dir.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "vi.h" 5 | 6 | static struct rset *dir_rslr; /* pattern of marks for left-to-right strings */ 7 | static struct rset *dir_rsrl; /* pattern of marks for right-to-left strings */ 8 | static struct rset *dir_rsctx; /* direction context patterns */ 9 | 10 | static int dir_match(char **chrs, int beg, int end, int ctx, int *rec, 11 | int *r_beg, int *r_end, int *c_beg, int *c_end, int *dir) 12 | { 13 | int subs[16 * 2]; 14 | struct rset *rs = ctx < 0 ? dir_rsrl : dir_rslr; 15 | struct sbuf *str = sbuf_make(); 16 | int grp; 17 | int flg = (beg ? RE_NOTBOL : 0) | (chrs[end][0] ? RE_NOTEOL : 0); 18 | int found = -1; 19 | sbuf_mem(str, chrs[beg], chrs[end] - chrs[beg]); 20 | if (rs) 21 | found = rset_find(rs, sbuf_buf(str), LEN(subs) / 2, subs, flg); 22 | if (found >= 0 && r_beg && r_end && c_beg && c_end) { 23 | char *s = sbuf_buf(str); 24 | conf_dirmark(found, NULL, NULL, dir, &grp); 25 | *r_beg = beg + uc_off(s, subs[0]); 26 | *r_end = beg + uc_off(s, subs[1]); 27 | *c_beg = subs[grp * 2 + 0] >= 0 ? 28 | beg + uc_off(s, subs[grp * 2 + 0]) : *r_beg; 29 | *c_end = subs[grp * 2 + 1] >= 0 ? 30 | beg + uc_off(s, subs[grp * 2 + 1]) : *r_end; 31 | *rec = grp > 0; 32 | } 33 | sbuf_free(str); 34 | return found < 0; 35 | } 36 | 37 | static void dir_reverse(int *ord, int beg, int end) 38 | { 39 | end--; 40 | while (beg < end) { 41 | int tmp = ord[beg]; 42 | ord[beg] = ord[end]; 43 | ord[end] = tmp; 44 | beg++; 45 | end--; 46 | } 47 | } 48 | 49 | /* reorder the characters based on direction marks and characters */ 50 | static void dir_fix(char **chrs, int *ord, int dir, int beg, int end) 51 | { 52 | int r_beg, r_end, c_beg, c_end; 53 | int c_dir, c_rec; 54 | while (beg < end && !dir_match(chrs, beg, end, dir, &c_rec, 55 | &r_beg, &r_end, &c_beg, &c_end, &c_dir)) { 56 | if (dir < 0) 57 | dir_reverse(ord, r_beg, r_end); 58 | if (c_dir < 0) 59 | dir_reverse(ord, c_beg, c_end); 60 | if (c_beg == r_beg) 61 | c_beg++; 62 | if (c_rec) 63 | dir_fix(chrs, ord, c_dir, c_beg, c_end); 64 | beg = r_end; 65 | } 66 | } 67 | 68 | /* return the direction context of the given line */ 69 | int dir_context(char *s) 70 | { 71 | int found = -1; 72 | int dir; 73 | if (xtd > +1) 74 | return +1; 75 | if (xtd < -1) 76 | return -1; 77 | if (xtd == 0 && ~((unsigned char) *s) & 0x80) 78 | return +1; 79 | if (dir_rsctx) 80 | found = rset_find(dir_rsctx, s ? s : "", 0, NULL, 0); 81 | if (!conf_dircontext(found, NULL, &dir)) 82 | return dir; 83 | return xtd < 0 ? -1 : +1; 84 | } 85 | 86 | /* reorder the characters in s */ 87 | void dir_reorder(char *s, int *ord) 88 | { 89 | int n; 90 | char **chrs = uc_chop(s, &n); 91 | int dir = dir_context(s); 92 | if (n && chrs[n - 1][0] == '\n') { 93 | ord[n - 1] = n - 1; 94 | n--; 95 | } 96 | dir_fix(chrs, ord, dir, 0, n); 97 | free(chrs); 98 | } 99 | 100 | void dir_init(void) 101 | { 102 | char *relr[128]; 103 | char *rerl[128]; 104 | char *ctx[128]; 105 | int curctx, i; 106 | char *pat; 107 | for (i = 0; !conf_dirmark(i, &pat, &curctx, NULL, NULL); i++) { 108 | relr[i] = curctx >= 0 ? pat : NULL; 109 | rerl[i] = curctx <= 0 ? pat : NULL; 110 | } 111 | dir_rslr = rset_make(i, relr, 0); 112 | dir_rsrl = rset_make(i, rerl, 0); 113 | for (i = 0; !conf_dircontext(i, &pat, NULL); i++) 114 | ctx[i] = pat; 115 | dir_rsctx = rset_make(i, ctx, 0); 116 | } 117 | 118 | void dir_done(void) 119 | { 120 | if (dir_rslr) 121 | rset_free(dir_rslr); 122 | if (dir_rsrl) 123 | rset_free(dir_rsrl); 124 | if (dir_rsctx) 125 | rset_free(dir_rsctx); 126 | } 127 | -------------------------------------------------------------------------------- /ecmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is an example ECMD script for Neatvi (see conf.h). 3 | # 4 | # When q is pressed in normal mode, Neatvi prints the list of buffers 5 | # and waits for a key. If the key is a digit, it switches to its 6 | # corresponding buffer. If it is a letter and the extended buffer 7 | # with that letter is defined, the contents of that buffer is executed. 8 | # Otherwise, Neatvi executes ECMD with the following parameters: 9 | # i) the letter, ii) the current file, iii) the current line number, 10 | # and iv) the current line offset. 11 | # 12 | # This files demonstrates how to implement such q-commands. 13 | 14 | # git add % 15 | ecmd_a() { 16 | # We can also invoke it directly here: git add $1 17 | echo '!git add %' 18 | } 19 | 20 | # Open ./ls as directory listing. 21 | ecmd_l() { 22 | # We define \l here; ql executes \l register if it is 23 | # defined, so we do not have the overhead of executing ECMD 24 | # the second time. 25 | echo 'rs \l' 26 | echo 'e ls' 27 | echo '.' 28 | # Now we can ask Neatvi to execute \l. 29 | echo '@\l' 30 | } 31 | 32 | # Make qq equivalent to -^. 33 | ecmd_q() { 34 | # Again we define \q here, while we could have simply 35 | # written: echo "e #" 36 | echo 'rs \q' 37 | echo 'e #' 38 | echo '.' 39 | echo '@\q' 40 | } 41 | 42 | # Generate and view stag-generated file outline; use gl command on its lines. 43 | ecmd_o() { 44 | if stag -a "$path" >/tmp/.tags.ls 2>/dev/null; then 45 | echo "e +1 /tmp/.tags.ls | :e" 46 | fi 47 | } 48 | 49 | # Open an email in a neatmail listing file. 50 | ecmd_m() { 51 | path="$1" 52 | lnum="$2" 53 | loc=$(sed -E -n "${lnum}s/^[A-Z]+([0-9]+(@[^ \t]+)?).*$/\\1/p" <$path) 54 | echo "ec $loc" 55 | if test -n "$loc"; then 56 | if neatmail pg -s -b path/to/mbox -i $loc >.cur.mail 2>/dev/null; then 57 | echo "e +1 .cur.mail | e" 58 | else 59 | echo "ec neatmail failed" 60 | fi 61 | fi 62 | } 63 | 64 | # Goto definition for Go; uses gopls (not very efficient without using LSP). 65 | ecmd_d() { 66 | loc=$(gopls definition $1:$2:$3) 67 | if test -n "$loc"; then 68 | echo $loc | sed -E 's/^([^:]+):([^:]+):([^:]+).*$/:e +\2 \1/' 69 | else 70 | echo "ec gopls failed" 71 | fi 72 | } 73 | 74 | # Find references for Go. Use gl command on each line. 75 | ecmd_f() { 76 | if gopls references $1:$2:$3 >.list.ls; then 77 | echo "e +1 .list.ls | :e" 78 | else 79 | echo "ec gopls failed" 80 | fi 81 | } 82 | 83 | ecmd_"$@" 2>/dev/null || echo "ec unknown command" 84 | -------------------------------------------------------------------------------- /ex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "vi.h" 9 | 10 | #define REG(s) ((s)[0] != '\\' ? (unsigned char) (s)[0] : 0x80 | (unsigned char) (s)[1]) 11 | 12 | int xrow, xoff, xtop; /* current row, column, and top row */ 13 | int xleft; /* the first visible column */ 14 | int xquit; /* exit if set */ 15 | int xvis; /* visual mode */ 16 | int xai = 1; /* autoindent option */ 17 | int xic = 1; /* ignorecase option */ 18 | int xaw; /* autowrite option */ 19 | int xwa; /* writeany option */ 20 | int xhl = 1; /* syntax highlight option */ 21 | int xhll; /* highlight current line */ 22 | int xled = 1; /* use the line editor */ 23 | int xtd = 0; /* current text direction */ 24 | int xshape = 1; /* perform letter shaping */ 25 | int xorder = 1; /* change the order of characters */ 26 | int xkmap = 0; /* the current keymap */ 27 | int xkmap_alt = 1; /* the alternate keymap */ 28 | int xlim = 256; /* do not process lines longer than this */ 29 | int xru = 1; /* show line number */ 30 | int xhist = 0; /* number of history lines */ 31 | static char xkwd[EXLEN]; /* the last searched keyword */ 32 | static char xrep[EXLEN]; /* the last replacement */ 33 | static int xkwddir; /* the last search direction */ 34 | static int xgdep; /* global command recursion depth */ 35 | static char **next; /* argument list */ 36 | static int next_pos; /* position in argument list */ 37 | 38 | static struct buf { 39 | char ft[32]; /* file type */ 40 | char *path; /* file path */ 41 | struct lbuf *lb; 42 | int row, off, top, left; 43 | short id; /* buffer number */ 44 | short td; /* text direction */ 45 | long mtime; /* modification time */ 46 | } bufs[16]; 47 | 48 | static int bufs_cnt = 0; /* number of allocated buffers */ 49 | 50 | static void bufs_free(int idx) 51 | { 52 | if (bufs[idx].lb) { 53 | free(bufs[idx].path); 54 | lbuf_free(bufs[idx].lb); 55 | memset(&bufs[idx], 0, sizeof(bufs[idx])); 56 | } 57 | } 58 | 59 | static int bufs_find(char *path) 60 | { 61 | int i; 62 | path = path[0] == '/' && path[1] == '\0' ? "" : path; 63 | for (i = 0; i < LEN(bufs); i++) 64 | if (bufs[i].path && !strcmp(bufs[i].path, path)) 65 | return i; 66 | return -1; 67 | } 68 | 69 | static int bufs_findroom(void) 70 | { 71 | int i; 72 | for (i = 0; i < LEN(bufs) - 1; i++) 73 | if (!bufs[i].lb) 74 | break; 75 | return i; 76 | } 77 | 78 | static void bufs_init(int idx, char *path) 79 | { 80 | bufs_free(idx); 81 | bufs[idx].id = ++bufs_cnt; 82 | bufs[idx].path = uc_dup(path); 83 | bufs[idx].lb = lbuf_make(); 84 | bufs[idx].row = 0; 85 | bufs[idx].off = 0; 86 | bufs[idx].top = 0; 87 | bufs[idx].left = 0; 88 | bufs[idx].td = +1; 89 | bufs[idx].mtime = -1; 90 | strcpy(bufs[idx].ft, syn_filetype(path)); 91 | } 92 | 93 | static int bufs_open(char *path) 94 | { 95 | int idx = bufs_findroom(); 96 | path = path[0] == '/' && path[1] == '\0' ? "" : path; 97 | bufs_init(idx, path); 98 | return idx; 99 | } 100 | 101 | static void bufs_save(void) 102 | { 103 | bufs[0].row = xrow; 104 | bufs[0].off = xoff; 105 | bufs[0].top = xtop; 106 | bufs[0].left = xleft; 107 | bufs[0].td = xtd; 108 | } 109 | 110 | static void bufs_load(void) 111 | { 112 | xrow = bufs[0].row; 113 | xoff = bufs[0].off; 114 | xtop = bufs[0].top; 115 | xleft = bufs[0].left; 116 | xtd = bufs[0].td; 117 | reg_put('%', bufs[0].path ? bufs[0].path : "", 0); 118 | } 119 | 120 | static void bufs_shift(void) 121 | { 122 | bufs_free(0); 123 | memmove(&bufs[0], &bufs[1], sizeof(bufs) - sizeof(bufs[0])); 124 | memset(&bufs[LEN(bufs) - 1], 0, sizeof(bufs[0])); 125 | bufs_load(); 126 | } 127 | 128 | static void bufs_switch(int idx) 129 | { 130 | struct buf tmp; 131 | bufs_save(); 132 | memcpy(&tmp, &bufs[idx], sizeof(tmp)); 133 | memmove(&bufs[1], &bufs[0], sizeof(tmp) * idx); 134 | memcpy(&bufs[0], &tmp, sizeof(tmp)); 135 | bufs_load(); 136 | } 137 | 138 | static void bufs_number(void) 139 | { 140 | int n = 0; 141 | int i; 142 | for (i = 0; i < LEN(bufs); i++) 143 | if (bufs[i].lb != NULL) 144 | bufs[i].id = ++n; 145 | bufs_cnt = n; 146 | } 147 | 148 | static long mtime(char *path) 149 | { 150 | struct stat st; 151 | if (!stat(path, &st)) 152 | return st.st_mtime; 153 | return -1; 154 | } 155 | 156 | char *ex_path(void) 157 | { 158 | return bufs[0].path; 159 | } 160 | 161 | struct lbuf *ex_lbuf(void) 162 | { 163 | return bufs[0].lb; 164 | } 165 | 166 | char *ex_filetype(void) 167 | { 168 | return bufs[0].ft; 169 | } 170 | 171 | /* replace % and # with current and alternate path names; returns a static buffer */ 172 | static char *ex_pathexpand(char *src, int spaceallowed) 173 | { 174 | static char buf[1024]; 175 | char *dst = buf; 176 | char *end = dst + sizeof(buf); 177 | while (dst + 1 < end && *src && *src != '\n' && 178 | (spaceallowed || (*src != ' ' && *src != '\t'))) { 179 | if (*src == '%' || *src == '#') { 180 | int idx = *src == '#'; 181 | if (!bufs[idx].path) { 182 | ex_show("pathname \"%\" or \"#\" is not set"); 183 | return NULL; 184 | } 185 | dst += snprintf(dst, end - dst, "%s", 186 | bufs[idx].path[0] ? bufs[idx].path : "/"); 187 | src++; 188 | } else if (dst == buf && *src == '=') { 189 | char *cur = bufs[0].path; 190 | char *dir = cur != NULL ? strrchr(cur, '/') : NULL; 191 | if (cur != NULL && dir != NULL) { 192 | int len = MIN(dir - cur, end - dst - 2); 193 | memcpy(dst, cur, len); 194 | dst += len; 195 | *dst++ = '/'; 196 | } 197 | src++; 198 | } else { 199 | if (*src == '\\' && src[1]) 200 | src++; 201 | *dst++ = *src++; 202 | } 203 | } 204 | if (dst + 1 >= end) 205 | dst = end - 1; 206 | *dst = '\0'; 207 | return buf; 208 | } 209 | 210 | /* read :e +cmd arguments */ 211 | static char *ex_plus(char *src, char *dst) 212 | { 213 | while (*src == ' ') 214 | src++; 215 | *dst = '\0'; 216 | if (*src != '+') 217 | return src; 218 | while (*src && *src != ' ') { 219 | if (src[0] == '\\' && src[1]) 220 | src++; 221 | *dst++ = *src++; 222 | } 223 | *dst = '\0'; 224 | while (*src == ' ' || *src == '\t') 225 | src++; 226 | return src; 227 | } 228 | 229 | /* read register name */ 230 | static char *ex_reg(char *src, int *reg) 231 | { 232 | while (*src == ' ') 233 | src++; 234 | *reg = REG(src); 235 | while (*src && *src != ' ' && *src != '\t') 236 | src++; 237 | while (*src == ' ' || *src == '\t') 238 | src++; 239 | return src; 240 | } 241 | 242 | /* the previous search keyword */ 243 | int ex_kwd(char **kwd, int *dir) 244 | { 245 | if (kwd) 246 | *kwd = xkwd; 247 | if (dir) 248 | *dir = xkwddir; 249 | return xkwddir == 0; 250 | } 251 | 252 | /* set the previous search keyword */ 253 | void ex_kwdset(char *kwd, int dir) 254 | { 255 | if (kwd) 256 | snprintf(xkwd, sizeof(xkwd), "%s", kwd); 257 | xkwddir = dir; 258 | } 259 | 260 | static int ex_search(char **pat) 261 | { 262 | char *pat_re; 263 | struct rstr *re; 264 | int dir, row; 265 | int delim = **pat; 266 | char *kw = re_read(pat); 267 | if (kw != NULL && *kw) 268 | ex_kwdset(kw, delim == '/' ? 1 : -1); 269 | free(kw); 270 | if (ex_kwd(&pat_re, &dir)) 271 | return -1; 272 | re = rstr_make(pat_re, xic ? RE_ICASE : 0); 273 | if (!re) 274 | return -1; 275 | row = xrow + dir; 276 | while (row >= 0 && row < lbuf_len(xb)) { 277 | if (rstr_find(re, lbuf_get(xb, row), 0, NULL, 0) >= 0) 278 | break; 279 | row += dir; 280 | } 281 | rstr_free(re); 282 | return row >= 0 && row < lbuf_len(xb) ? row : -1; 283 | } 284 | 285 | static int ex_lineno(char **num) 286 | { 287 | int n = xrow; 288 | switch ((unsigned char) **num) { 289 | case '.': 290 | ++*num; 291 | break; 292 | case '$': 293 | n = lbuf_len(xb) - 1; 294 | ++*num; 295 | break; 296 | case '\'': 297 | if (lbuf_jump(xb, (unsigned char) *++(*num), &n, NULL)) 298 | return -1; 299 | ++*num; 300 | break; 301 | case '/': 302 | case '?': 303 | n = ex_search(num); 304 | break; 305 | default: 306 | if (isdigit((unsigned char) **num)) { 307 | n = atoi(*num) - 1; 308 | while (isdigit((unsigned char) **num)) 309 | ++*num; 310 | } 311 | } 312 | while (**num == '-' || **num == '+') { 313 | n += atoi((*num)++); 314 | while (isdigit((unsigned char) **num)) 315 | (*num)++; 316 | } 317 | return n; 318 | } 319 | 320 | /* parse ex command addresses */ 321 | static int ex_region(char *loc, int *beg, int *end) 322 | { 323 | int naddr = 0; 324 | if (!strcmp("%", loc)) { 325 | *beg = 0; 326 | *end = MAX(0, lbuf_len(xb)); 327 | return 0; 328 | } 329 | if (!*loc) { 330 | *beg = xrow; 331 | *end = xrow == lbuf_len(xb) ? xrow : xrow + 1; 332 | return 0; 333 | } 334 | while (*loc) { 335 | int end0 = *end; 336 | *end = ex_lineno(&loc) + 1; 337 | *beg = naddr++ ? end0 - 1 : *end - 1; 338 | if (!naddr++) 339 | *beg = *end - 1; 340 | while (*loc && *loc != ';' && *loc != ',') 341 | loc++; 342 | if (!*loc) 343 | break; 344 | if (*loc == ';') 345 | xrow = *end - 1; 346 | loc++; 347 | } 348 | if (*beg < 0 && *end == 0) 349 | *beg = 0; 350 | if (*beg < 0 || *beg >= lbuf_len(xb)) 351 | return 1; 352 | if (*end < *beg || *end > lbuf_len(xb)) 353 | return 1; 354 | return 0; 355 | } 356 | 357 | static char *lbuf_save(struct lbuf *lb, int beg, int end, char *path, int force, long ts) 358 | { 359 | int fd; 360 | if (end < 0) 361 | end = lbuf_len(lb); 362 | if (!force && mtime > 0 && mtime(path) > ts) { 363 | return "write failed: file changed"; 364 | } else if (!force && ts <= 0 && mtime(path) >= 0) { 365 | return "write failed: file exists"; 366 | } else if ((fd = open(path, O_WRONLY | O_CREAT, conf_mode())) < 0) { 367 | return "write failed: cannot create file"; 368 | } else if (lbuf_wr(lb, fd, beg, end) != 0 || close(fd) != 0) { 369 | close(fd); 370 | return "write failed"; 371 | } 372 | return NULL; 373 | } 374 | 375 | static int bufs_modified(int idx, char *msg) 376 | { 377 | struct buf *b = &bufs[idx]; 378 | if (!b->lb || !lbuf_modified(b->lb)) 379 | return 0; 380 | if (xaw && b->path[0]) 381 | return lbuf_save(b->lb, 0, -1, b->path, 0, b->mtime) != NULL; 382 | if (msg) 383 | ex_show(msg); 384 | return 1; 385 | } 386 | 387 | static int ec_buffer(char *loc, char *cmd, char *arg, char *txt) 388 | { 389 | char *aliases = "%#^"; 390 | char ln[128]; 391 | int i; 392 | if (!arg[0]) { 393 | /* print buffer list */ 394 | for (i = 0; i < LEN(bufs) && bufs[i].lb; i++) { 395 | char c = i < strlen(aliases) ? aliases[i] : ' '; 396 | char m = lbuf_modified(bufs[i].lb) ? '*' : ' '; 397 | snprintf(ln, LEN(ln), "%2i %c %s %c", 398 | (int) bufs[i].id, c, bufs[i].path, m); 399 | ex_print(ln); 400 | } 401 | } else if (arg[0] == '!') { 402 | /* delete buffer */ 403 | bufs_shift(); 404 | if (bufs[0].lb == NULL) 405 | bufs_init(0, ""); 406 | } else if (arg[0] == '~') { 407 | /* reassign buffer ids */ 408 | bufs_number(); 409 | } else { 410 | int id = arg[0] ? atoi(arg) : 0; 411 | int idx = -1; 412 | /* switch to the given buffer */ 413 | if (isdigit((unsigned char) arg[0])) { /* buffer id given */ 414 | for (idx = 0; idx < LEN(bufs); idx++) 415 | if (bufs[idx].lb && id == bufs[idx].id) 416 | break; 417 | } else if (arg[0] == '-') { /* previous buffer */ 418 | for (i = 0; i < LEN(bufs); i++) 419 | if (bufs[i].lb && bufs[i].id < bufs[0].id) 420 | if (idx < 0 || bufs[i].id > bufs[idx].id) 421 | idx = i; 422 | } else if (arg[0] == '+') { /* next buffer */ 423 | for (i = 0; i < LEN(bufs); i++) 424 | if (bufs[i].lb && bufs[i].id > bufs[0].id) 425 | if (idx < 0 || bufs[i].id < bufs[idx].id) 426 | idx = i; 427 | } else { /* buffer alias given */ 428 | char *r = strchr(aliases, (unsigned char) arg[0]); 429 | idx = r ? r - aliases : -1; 430 | } 431 | if (idx >= 0 && idx < LEN(bufs) && bufs[idx].lb) { 432 | if (!xwa && strchr(cmd, '!') == NULL) 433 | if (bufs_modified(0, "buffer modified")) 434 | return 1; 435 | bufs_switch(idx); 436 | } else { 437 | ex_show("no such buffer"); 438 | return 1; 439 | } 440 | } 441 | return 0; 442 | } 443 | 444 | int ex_list(char **ls, int size) 445 | { 446 | int i; 447 | for (i = 0; i < LEN(bufs) && bufs[i].lb && i < size; i++) 448 | ls[i] = bufs[i].path; 449 | return i; 450 | } 451 | 452 | static int ec_edit(char *loc, char *cmd, char *arg, char *txt) 453 | { 454 | char pls[EXLEN]; 455 | char msg[128]; 456 | char *path; 457 | int fd; 458 | if (!strchr(cmd, '!')) 459 | if (xb && !xwa && bufs_modified(0, "buffer modified")) 460 | return 1; 461 | arg = ex_plus(arg, pls); 462 | if (!(path = ex_pathexpand(arg, 0))) 463 | return 1; 464 | /* ew: switch buffer without changing # */ 465 | if (path[0] && cmd[0] == 'e' && cmd[1] == 'w' && bufs_find(path) > 1) 466 | bufs_switch(1); 467 | /* check if the buffer is already available */ 468 | if (path[0] && bufs_find(path) >= 0) { 469 | bufs_switch(bufs_find(path)); 470 | if (pls[0] == '+') 471 | return ex_command(pls + 1); 472 | return 0; 473 | } 474 | if (path[0] || !bufs[0].path) 475 | bufs_switch(bufs_open(path)); 476 | fd = open(ex_path(), O_RDONLY); 477 | if (fd >= 0) { 478 | int rd = lbuf_rd(xb, fd, 0, lbuf_len(xb)); 479 | close(fd); 480 | snprintf(msg, sizeof(msg), "\"%s\" [=%d] [r]", 481 | ex_path(), lbuf_len(xb)); 482 | if (rd) 483 | ex_show("read failed"); 484 | else 485 | ex_show(msg); 486 | } 487 | lbuf_saved(xb, path[0] != '\0'); 488 | bufs[0].mtime = mtime(ex_path()); 489 | xrow = MAX(0, MIN(xrow, lbuf_len(xb) - 1)); 490 | xoff = 0; 491 | xtop = MAX(0, MIN(xtop, lbuf_len(xb) - 1)); 492 | if (pls[0] == '+') 493 | return ex_command(pls + 1); 494 | return 0; 495 | } 496 | 497 | static int ex_next(char *cmd, int dis) 498 | { 499 | char arg[EXLEN]; 500 | int old = next_pos; 501 | int idx = next != NULL && next[old] != NULL ? next_pos + dis : -1; 502 | char *path = idx >= 0 && next[idx] != NULL ? next[idx] : NULL; 503 | char *s = arg; 504 | char *r = path != NULL ? path : ""; 505 | if (dis && path == NULL) { 506 | ex_show("no more files"); 507 | return 1; 508 | } 509 | while (*r && s + 2 < arg + sizeof(arg)) { 510 | if (*r == ' ' || *r == '%' || *r == '#' || *r == '=') 511 | *s++ = '\\'; 512 | *s++ = *r++; 513 | } 514 | *s = '\0'; 515 | if (ec_edit("", cmd, arg, NULL)) 516 | return 1; 517 | next_pos = idx; 518 | return 0; 519 | } 520 | 521 | static int ec_next(char *loc, char *cmd, char *arg, char *txt) 522 | { 523 | return ex_next(cmd, +1); 524 | } 525 | 526 | static int ec_prev(char *loc, char *cmd, char *arg, char *txt) 527 | { 528 | return ex_next(cmd, -1); 529 | } 530 | 531 | static int ec_read(char *loc, char *cmd, char *arg, char *txt) 532 | { 533 | char msg[128]; 534 | int beg, end, pos; 535 | char *path; 536 | char *obuf; 537 | int n = lbuf_len(xb); 538 | path = arg[0] ? ex_pathexpand(arg, 1) : ex_path(); 539 | if (ex_region(loc, &beg, &end) || path == NULL) 540 | return 1; 541 | pos = lbuf_len(xb) ? end : 0; 542 | if (path[0] == '!') { 543 | if (!path[1]) 544 | return 1; 545 | obuf = cmd_pipe(path + 1, NULL, 1); 546 | if (obuf) 547 | lbuf_edit(xb, obuf, pos, pos); 548 | free(obuf); 549 | } else { 550 | int fd = open(path, O_RDONLY); 551 | if (fd < 0) { 552 | ex_show("read failed"); 553 | return 1; 554 | } 555 | if (lbuf_rd(xb, fd, pos, pos)) { 556 | ex_show("read failed"); 557 | close(fd); 558 | return 1; 559 | } 560 | close(fd); 561 | } 562 | xrow = end + lbuf_len(xb) - n - 1; 563 | snprintf(msg, sizeof(msg), "\"%s\" [=%d] [r]", 564 | path, lbuf_len(xb) - n); 565 | ex_show(msg); 566 | return 0; 567 | } 568 | 569 | static int ec_write(char *loc, char *cmd, char *arg, char *txt) 570 | { 571 | char msg[128]; 572 | char *path; 573 | char *ibuf; 574 | int beg, end; 575 | path = arg[0] ? ex_pathexpand(arg, 1) : ex_path(); 576 | if (cmd[0] == 'x' && !lbuf_modified(xb)) 577 | return 0; 578 | if (ex_region(loc, &beg, &end) || path == NULL) 579 | return 1; 580 | if (!loc[0]) { 581 | beg = 0; 582 | end = lbuf_len(xb); 583 | } 584 | if (path[0] == '!') { 585 | if (!path[1]) 586 | return 1; 587 | ibuf = lbuf_cp(xb, beg, end); 588 | ex_print(NULL); 589 | cmd_pipe(path + 1, ibuf, 0); 590 | free(ibuf); 591 | } else { 592 | long ts = !strcmp(ex_path(), path) ? bufs[0].mtime : 0; 593 | char *err = lbuf_save(xb, beg, end, path, !!strchr(cmd, '!'), ts); 594 | if (err != NULL) { 595 | ex_show(err); 596 | return 1; 597 | } 598 | } 599 | snprintf(msg, sizeof(msg), "\"%s\" [=%d] [w]", path, end - beg); 600 | ex_show(msg); 601 | if (!ex_path()[0]) { 602 | free(bufs[0].path); 603 | bufs[0].path = uc_dup(path); 604 | reg_put('%', path, 0); 605 | } 606 | if (!strcmp(ex_path(), path)) 607 | lbuf_saved(xb, 0); 608 | if (!strcmp(ex_path(), path)) 609 | bufs[0].mtime = mtime(path); 610 | return 0; 611 | } 612 | 613 | static int ec_quit(char *loc, char *cmd, char *arg, char *txt) 614 | { 615 | int i; 616 | if (cmd[0] == 'w' || cmd[0] == 'x') 617 | if (ec_write("", cmd, arg, NULL)) 618 | return 1; 619 | for (i = 0; i < LEN(bufs); i++) { 620 | if (bufs[i].lb) { 621 | if (!strchr(cmd, 'a') && !strchr(cmd, '!')) { 622 | if (bufs_modified(i, "buffer modified")) { 623 | bufs_switch(i); 624 | return 0; 625 | } 626 | } 627 | if (strchr(cmd, 'a')) { 628 | struct buf *b = &bufs[i]; 629 | char *err = lbuf_save(b->lb, 0, -1, b->path, 630 | !!strchr(cmd, '!'), b->mtime); 631 | if (err) { 632 | bufs_switch(i); 633 | ex_show(err); 634 | return 0; 635 | } 636 | } 637 | } 638 | } 639 | xquit = 1; 640 | return 0; 641 | } 642 | 643 | static int ec_insert(char *loc, char *cmd, char *arg, char *txt) 644 | { 645 | int beg, end; 646 | int n; 647 | if (ex_region(loc, &beg, &end) && (beg != 0 || end != 0)) 648 | return 1; 649 | if (cmd[0] == 'a') 650 | if (beg + 1 <= lbuf_len(xb)) 651 | beg++; 652 | if (cmd[0] != 'c') 653 | end = beg; 654 | n = lbuf_len(xb); 655 | lbuf_edit(xb, txt, beg, end); 656 | xrow = MIN(lbuf_len(xb) - 1, end + lbuf_len(xb) - n - 1); 657 | return 0; 658 | } 659 | 660 | static int ec_print(char *loc, char *cmd, char *arg, char *txt) 661 | { 662 | int beg, end; 663 | int i; 664 | if (!cmd[0] && !loc[0]) 665 | if (xrow >= lbuf_len(xb)) 666 | return 1; 667 | if (ex_region(loc, &beg, &end)) 668 | return 1; 669 | for (i = beg; i < end; i++) 670 | ex_print(lbuf_get(xb, i)); 671 | xrow = MAX(beg, end - 1); 672 | xoff = 0; 673 | return 0; 674 | } 675 | 676 | static int ec_null(char *loc, char *cmd, char *arg, char *txt) 677 | { 678 | int beg, end; 679 | if (!xvis) { 680 | xrow = xrow + 1 < lbuf_len(xb) ? xrow + 1 : xrow; 681 | return ec_print(loc, cmd, arg, txt); 682 | } 683 | if (ex_region(loc, &beg, &end)) 684 | return 1; 685 | xrow = MAX(beg, end - 1); 686 | xoff = 0; 687 | return 0; 688 | } 689 | 690 | static void ex_yank(int reg, int beg, int end) 691 | { 692 | char *buf = lbuf_cp(xb, beg, end); 693 | reg_put(reg, buf, 1); 694 | free(buf); 695 | } 696 | 697 | static int ec_rs(char *loc, char *cmd, char *arg, char *txt) 698 | { 699 | reg_put(REG(arg), txt, 1); 700 | return 0; 701 | } 702 | 703 | static int ec_delete(char *loc, char *cmd, char *arg, char *txt) 704 | { 705 | int beg, end; 706 | if (ex_region(loc, &beg, &end) || !lbuf_len(xb)) 707 | return 1; 708 | ex_yank(REG(arg), beg, end); 709 | lbuf_edit(xb, NULL, beg, end); 710 | xrow = beg; 711 | return 0; 712 | } 713 | 714 | static int ec_yank(char *loc, char *cmd, char *arg, char *txt) 715 | { 716 | int beg, end; 717 | if (ex_region(loc, &beg, &end) || !lbuf_len(xb)) 718 | return 1; 719 | ex_yank(REG(arg), beg, end); 720 | return 0; 721 | } 722 | 723 | static int ec_put(char *loc, char *cmd, char *arg, char *txt) 724 | { 725 | int beg, end; 726 | int lnmode; 727 | char *buf; 728 | int n = lbuf_len(xb); 729 | buf = reg_get(REG(arg), &lnmode); 730 | if (!buf || ex_region(loc, &beg, &end)) 731 | return 1; 732 | lbuf_edit(xb, buf, end, end); 733 | xrow = MIN(lbuf_len(xb) - 1, end + lbuf_len(xb) - n - 1); 734 | return 0; 735 | } 736 | 737 | static int ec_lnum(char *loc, char *cmd, char *arg, char *txt) 738 | { 739 | char msg[128]; 740 | int beg, end; 741 | if (ex_region(loc, &beg, &end)) 742 | return 1; 743 | sprintf(msg, "%d\n", end); 744 | ex_print(msg); 745 | return 0; 746 | } 747 | 748 | static int ec_undo(char *loc, char *cmd, char *arg, char *txt) 749 | { 750 | return lbuf_undo(xb); 751 | } 752 | 753 | static int ec_redo(char *loc, char *cmd, char *arg, char *txt) 754 | { 755 | return lbuf_redo(xb); 756 | } 757 | 758 | static int ec_mark(char *loc, char *cmd, char *arg, char *txt) 759 | { 760 | int beg, end; 761 | if (ex_region(loc, &beg, &end)) 762 | return 1; 763 | lbuf_mark(xb, (unsigned char) arg[0], end - 1, 0); 764 | return 0; 765 | } 766 | 767 | static void replace(struct sbuf *dst, char *rep, char *ln, int *offs) 768 | { 769 | while (rep[0]) { 770 | if (rep[0] == '\\' && rep[1]) { 771 | if (rep[1] >= '0' && rep[1] <= '9') { 772 | int grp = (rep[1] - '0') * 2; 773 | int len = offs[grp + 1] - offs[grp]; 774 | sbuf_mem(dst, ln + offs[grp], len); 775 | } else { 776 | sbuf_chr(dst, (unsigned char) rep[1]); 777 | } 778 | rep++; 779 | } else { 780 | sbuf_chr(dst, (unsigned char) rep[0]); 781 | } 782 | rep++; 783 | } 784 | } 785 | 786 | static int ec_substitute(char *loc, char *cmd, char *arg, char *txt) 787 | { 788 | struct rstr *re; 789 | int offs[32]; 790 | int beg, end; 791 | char *pat = NULL, *rep = NULL; 792 | char *s = arg; 793 | int i; 794 | if (ex_region(loc, &beg, &end)) 795 | return 1; 796 | pat = re_read(&s); 797 | if (pat && pat[0]) 798 | ex_kwdset(pat, +1); 799 | if (pat && *s) { 800 | s--; 801 | rep = re_read(&s); 802 | } 803 | if (pat || rep) 804 | snprintf(xrep, sizeof(xrep), "%s", rep ? rep : ""); 805 | free(pat); 806 | free(rep); 807 | if (ex_kwd(&pat, NULL)) 808 | return 1; 809 | re = rstr_make(pat, xic ? RE_ICASE : 0); 810 | if (!re) 811 | return 1; 812 | for (i = beg; i < end; i++) { 813 | char *ln = lbuf_get(xb, i); 814 | struct sbuf *r = NULL; 815 | while (rstr_find(re, ln, LEN(offs) / 2, offs, 0) >= 0) { 816 | if (!r) 817 | r = sbuf_make(); 818 | sbuf_mem(r, ln, offs[0]); 819 | replace(r, xrep, ln, offs); 820 | ln += offs[1]; 821 | if (offs[1] <= 0) /* zero-length match */ 822 | sbuf_chr(r, (unsigned char) *ln++); 823 | if (!*ln || *ln == '\n' || !strchr(s, 'g')) 824 | break; 825 | } 826 | if (r) { 827 | sbuf_str(r, ln); 828 | lbuf_edit(xb, sbuf_buf(r), i, i + 1); 829 | sbuf_free(r); 830 | } 831 | } 832 | rstr_free(re); 833 | return 0; 834 | } 835 | 836 | static int ec_exec(char *loc, char *cmd, char *arg, char *txt) 837 | { 838 | int beg, end; 839 | char *text; 840 | char *rep; 841 | char *ecmd; 842 | if (!xwa && bufs_modified(0, "buffer modified")) 843 | return 1; 844 | if (!(ecmd = ex_pathexpand(arg, 1))) 845 | return 1; 846 | if (!loc[0]) { 847 | ex_print(NULL); 848 | return cmd_exec(ecmd); 849 | } 850 | if (ex_region(loc, &beg, &end)) 851 | return 1; 852 | text = lbuf_cp(xb, beg, end); 853 | rep = cmd_pipe(ecmd, text, 1); 854 | if (rep) 855 | lbuf_edit(xb, rep, beg, end); 856 | free(text); 857 | free(rep); 858 | return 0; 859 | } 860 | 861 | static int ec_rx(char *loc, char *cmd, char *arg, char *txt) 862 | { 863 | char *rep, *ecmd; 864 | int reg = 0; 865 | arg = ex_reg(arg, ®); 866 | if (reg <= 0) 867 | return 1; 868 | if (!(ecmd = ex_pathexpand(arg, 1))) 869 | return 1; 870 | rep = cmd_pipe(ecmd, reg_get(reg, NULL), 1); 871 | reg_put(reg, rep ? rep : "", 1); 872 | free(rep); 873 | return !rep; 874 | } 875 | 876 | static int ec_rk(char *loc, char *cmd, char *arg, char *txt) 877 | { 878 | char *rep, *path; 879 | int reg = 0; 880 | arg = ex_reg(arg, ®); 881 | if (reg <= 0) 882 | return 1; 883 | if (!(path = ex_pathexpand(arg, 1))) 884 | return 1; 885 | rep = cmd_unix(path, reg_get(reg, NULL)); 886 | reg_put(reg, rep ? rep : "", 1); 887 | free(rep); 888 | return !rep; 889 | } 890 | 891 | static int ec_make(char *loc, char *cmd, char *arg, char *txt) 892 | { 893 | char make[EXLEN]; 894 | char *target; 895 | if (!xwa && bufs_modified(0, "buffer modified")) 896 | return 1; 897 | if (!(target = ex_pathexpand(arg, 0))) 898 | return 1; 899 | sprintf(make, "make %s", target); 900 | ex_print(NULL); 901 | if (cmd_exec(make)) 902 | return 1; 903 | return 0; 904 | } 905 | 906 | static int ec_ft(char *loc, char *cmd, char *arg, char *txt) 907 | { 908 | if (arg[0]) 909 | snprintf(bufs[0].ft, sizeof(bufs[0].ft), "%s", arg); 910 | else 911 | ex_print(ex_filetype()); 912 | return 0; 913 | } 914 | 915 | static int ec_cmap(char *loc, char *cmd, char *arg, char *txt) 916 | { 917 | if (arg[0]) 918 | xkmap_alt = conf_kmapfind(arg); 919 | else 920 | ex_print(conf_kmap(xkmap)[0]); 921 | if (arg[0] && !strchr(cmd, '!')) 922 | xkmap = xkmap_alt; 923 | return 0; 924 | } 925 | 926 | static int ex_exec(char *ln); 927 | 928 | static int ec_glob(char *loc, char *cmd, char *arg, char *txt) 929 | { 930 | struct rstr *re; 931 | int offs[32]; 932 | int beg, end, not; 933 | char *pat; 934 | char *s = arg; 935 | int i; 936 | if (!loc[0] && !xgdep) 937 | strcpy(loc, "%"); 938 | if (ex_region(loc, &beg, &end)) 939 | return 1; 940 | not = strchr(cmd, '!') || cmd[0] == 'v'; 941 | pat = re_read(&s); 942 | if (pat && pat[0]) 943 | ex_kwdset(pat, +1); 944 | free(pat); 945 | if (ex_kwd(&pat, NULL)) 946 | return 1; 947 | if (!(re = rstr_make(pat, xic ? RE_ICASE : 0))) 948 | return 1; 949 | xgdep++; 950 | for (i = beg + 1; i < end; i++) 951 | lbuf_globset(xb, i, xgdep); 952 | i = beg; 953 | while (i < lbuf_len(xb)) { 954 | char *ln = lbuf_get(xb, i); 955 | if ((rstr_find(re, ln, LEN(offs) / 2, offs, 0) < 0) == not) { 956 | xrow = i; 957 | if (ex_exec(s)) 958 | break; 959 | i = MIN(i, xrow); 960 | } 961 | while (i < lbuf_len(xb) && !lbuf_globget(xb, i, xgdep)) 962 | i++; 963 | } 964 | for (i = 0; i < lbuf_len(xb); i++) 965 | lbuf_globget(xb, i, xgdep); 966 | xgdep--; 967 | rstr_free(re); 968 | return 0; 969 | } 970 | 971 | #define TAGCNT 32 972 | #define TAGLEN 128 973 | 974 | static int tag_row[TAGCNT]; 975 | static int tag_off[TAGCNT]; 976 | static int tag_pos[TAGCNT]; 977 | static char tag_path[TAGCNT][TAGLEN]; 978 | static char tag_name[TAGCNT][TAGLEN]; 979 | static int tag_cnt = 0; 980 | 981 | static void ex_tagput(char *name) 982 | { 983 | if (tag_cnt < TAGCNT) { 984 | tag_row[tag_cnt] = xrow; 985 | tag_off[tag_cnt] = xoff; 986 | snprintf(tag_path[tag_cnt], TAGLEN, "%s", ex_path()); 987 | snprintf(tag_name[tag_cnt], TAGLEN, "%s", name); 988 | tag_cnt++; 989 | } 990 | } 991 | 992 | /* go to definition (dir=+1 next, dir=-1 prev, dir=0 first) */ 993 | static int tag_goto(char *cw, int dir) 994 | { 995 | char path[120], cmd[120]; 996 | char *s, *ln; 997 | int pos = dir == 0 || tag_cnt == 0 ? 0 : tag_pos[tag_cnt - 1]; 998 | if (tag_find(cw, &pos, dir, path, sizeof(path), cmd, sizeof(cmd))) { 999 | ex_show("not found"); 1000 | return 1; 1001 | } 1002 | if (dir == 0) 1003 | ex_tagput(cw); 1004 | tag_pos[tag_cnt - 1] = pos; 1005 | if (strcmp(path, ex_path()) != 0) { 1006 | if (access(path, R_OK) != 0) { 1007 | ex_show("cannot open"); 1008 | return 1; 1009 | } 1010 | if (ec_edit("", "e", path, NULL) != 0) 1011 | return 1; 1012 | } 1013 | xrow = 0; 1014 | xoff = 0; 1015 | ex_command(cmd); 1016 | ln = lbuf_get(xb, xrow); 1017 | if (ln && (s = strstr(ln, cw)) != NULL) 1018 | xoff = s - ln; 1019 | return 0; 1020 | } 1021 | 1022 | static int ec_tag(char *loc, char *cmd, char *arg, char *txt) 1023 | { 1024 | return tag_goto(arg, 0); 1025 | } 1026 | 1027 | static int ec_tfree(char *loc, char *cmd, char *arg, char *txt) 1028 | { 1029 | tag_done(); 1030 | return 0; 1031 | } 1032 | 1033 | static int ec_pop(char *loc, char *cmd, char *arg, char *txt) 1034 | { 1035 | if (tag_cnt > 0) { 1036 | tag_cnt--; 1037 | if (ex_path() == NULL || strcmp(tag_path[tag_cnt], ex_path()) != 0) 1038 | ec_edit("", "e", tag_path[tag_cnt], txt); 1039 | xrow = tag_row[tag_cnt]; 1040 | xoff = tag_off[tag_cnt]; 1041 | return 0; 1042 | } else { 1043 | ex_show("not found"); 1044 | } 1045 | return 1; 1046 | } 1047 | 1048 | static int ec_tnext(char *loc, char *cmd, char *arg, char *txt) 1049 | { 1050 | if (tag_cnt > 0) 1051 | return tag_goto(tag_name[tag_cnt - 1], +1); 1052 | return 1; 1053 | } 1054 | 1055 | static int ec_tprev(char *loc, char *cmd, char *arg, char *txt) 1056 | { 1057 | if (tag_cnt > 0) 1058 | return tag_goto(tag_name[tag_cnt - 1], -1); 1059 | return 1; 1060 | } 1061 | 1062 | static int ec_at(char *loc, char *cmd, char *arg, char *txt) 1063 | { 1064 | int beg, end; 1065 | int lnmode; 1066 | char *buf = reg_get(REG(arg), &lnmode); 1067 | if (!buf || ex_region(loc, &beg, &end)) 1068 | return 1; 1069 | xrow = beg; 1070 | if (cmd[0] == 'r' && cmd[1] == 'a') { 1071 | struct sbuf *r = sbuf_make(); 1072 | char *s = buf; 1073 | int ret; 1074 | while (*s) { 1075 | if ((unsigned char) *s == '' && s[1]) { 1076 | char *reg = reg_get((unsigned char) *++s, NULL); 1077 | sbuf_str(r, reg ? reg : ""); 1078 | s++; 1079 | } else { 1080 | if ((unsigned char) *s == '' && s[1]) 1081 | s++; 1082 | sbuf_chr(r, (unsigned char) *s++); 1083 | } 1084 | } 1085 | ret = ex_command(sbuf_buf(r)); 1086 | sbuf_free(r); 1087 | return ret; 1088 | } 1089 | return ex_command(buf); 1090 | } 1091 | 1092 | static int ec_source(char *loc, char *cmd, char *arg, char *txt) 1093 | { 1094 | char *path = arg[0] ? ex_pathexpand(arg, 1) : ex_path(); 1095 | char buf[1 << 10]; 1096 | struct sbuf *sb; 1097 | int fd = path[0] ? open(path, O_RDONLY) : -1; 1098 | long nr; 1099 | if (fd < 0) 1100 | return 1; 1101 | sb = sbuf_make(); 1102 | while ((nr = read(fd, buf, sizeof(buf))) > 0) 1103 | sbuf_mem(sb, buf, nr); 1104 | ex_command(sbuf_buf(sb)); 1105 | sbuf_free(sb); 1106 | return 0; 1107 | } 1108 | 1109 | static int ec_echo(char *loc, char *cmd, char *arg, char *txt) 1110 | { 1111 | ex_print(arg); 1112 | return 0; 1113 | } 1114 | 1115 | static struct option { 1116 | char *abbr; 1117 | char *name; 1118 | int *var; 1119 | } options[] = { 1120 | {"ai", "autoindent", &xai}, 1121 | {"aw", "autowrite", &xaw}, 1122 | {"hist", "history", &xhist}, 1123 | {"hl", "highlight", &xhl}, 1124 | {"hll", "highlightline", &xhll}, 1125 | {"ic", "ignorecase", &xic}, 1126 | {"lim", "linelimit", &xlim}, 1127 | {"order", "order", &xorder}, 1128 | {"ru", "ruler", &xru}, 1129 | {"shape", "shape", &xshape}, 1130 | {"td", "textdirection", &xtd}, 1131 | {"wa", "writeany", &xwa}, 1132 | }; 1133 | 1134 | static char *cutword(char *s, char *d) 1135 | { 1136 | while (isspace(*s)) 1137 | s++; 1138 | while (*s && !isspace(*s)) 1139 | *d++ = *s++; 1140 | while (isspace(*s)) 1141 | s++; 1142 | *d = '\0'; 1143 | return s; 1144 | } 1145 | 1146 | static int ec_set(char *loc, char *cmd, char *arg, char *txt) 1147 | { 1148 | char tok[EXLEN]; 1149 | char opt[EXLEN]; 1150 | char *s = arg; 1151 | int val = 0; 1152 | int i; 1153 | if (*s) { 1154 | s = cutword(s, tok); 1155 | if (tok[0] == 'n' && tok[1] == 'o') { 1156 | strcpy(opt, tok + 2); 1157 | val = 0; 1158 | } else { 1159 | char *r = strchr(tok, '='); 1160 | if (r) { 1161 | *r = '\0'; 1162 | strcpy(opt, tok); 1163 | val = atoi(r + 1); 1164 | } else { 1165 | strcpy(opt, tok); 1166 | val = 1; 1167 | } 1168 | } 1169 | for (i = 0; i < LEN(options); i++) { 1170 | struct option *o = &options[i]; 1171 | if (!strcmp(o->abbr, opt) || !strcmp(o->name, opt)) { 1172 | *o->var = val; 1173 | return 0; 1174 | } 1175 | } 1176 | ex_show("unknown option"); 1177 | return 1; 1178 | } 1179 | return 0; 1180 | } 1181 | 1182 | static struct excmd { 1183 | char *abbr; 1184 | char *name; 1185 | int (*ec)(char *loc, char *cmd, char *arg, char *txt); 1186 | } excmds[] = { 1187 | {"a", "append", ec_insert}, 1188 | {"b", "buffer", ec_buffer}, 1189 | {"d", "delete", ec_delete}, 1190 | {"c", "change", ec_insert}, 1191 | {"cm", "cmap", ec_cmap}, 1192 | {"cm!", "cmap!", ec_cmap}, 1193 | {"e", "edit", ec_edit}, 1194 | {"e!", "edit!", ec_edit}, 1195 | {"ec", "echo", ec_echo}, 1196 | {"ew", "ew", ec_edit}, 1197 | {"ew!", "ew!", ec_edit}, 1198 | {"ft", "filetype", ec_ft}, 1199 | {"g", "global", ec_glob}, 1200 | {"g!", "global!", ec_glob}, 1201 | {"i", "insert", ec_insert}, 1202 | {"k", "mark", ec_mark}, 1203 | {"make", "make", ec_make}, 1204 | {"n", "next", ec_next}, 1205 | {"p", "print", ec_print}, 1206 | {"po", "pop", ec_pop}, 1207 | {"pu", "put", ec_put}, 1208 | {"prev", "prev", ec_prev}, 1209 | {"q", "quit", ec_quit}, 1210 | {"q!", "quit!", ec_quit}, 1211 | {"r", "read", ec_read}, 1212 | {"redo", "redo", ec_redo}, 1213 | {"rs", "rs", ec_rs}, 1214 | {"rx", "rx", ec_rx}, 1215 | {"ra", "ra", ec_at}, 1216 | {"rk", "rk", ec_rk}, 1217 | {"se", "set", ec_set}, 1218 | {"s", "substitute", ec_substitute}, 1219 | {"so", "source", ec_source}, 1220 | {"ta", "tag", ec_tag}, 1221 | {"tn", "tnext", ec_tnext}, 1222 | {"tp", "tprev", ec_tprev}, 1223 | {"tf", "tfree", ec_tfree}, 1224 | {"u", "undo", ec_undo}, 1225 | {"v", "vglobal", ec_glob}, 1226 | {"w", "write", ec_write}, 1227 | {"w!", "write!", ec_write}, 1228 | {"wq", "wq", ec_quit}, 1229 | {"wq!", "wq!", ec_quit}, 1230 | {"x", "xit", ec_quit}, 1231 | {"x!", "xit!", ec_quit}, 1232 | {"xa", "xa", ec_quit}, 1233 | {"xa!", "xa!", ec_quit}, 1234 | {"y", "yank", ec_yank}, 1235 | {"!", "!", ec_exec}, 1236 | {"@", "@", ec_at}, 1237 | {"=", "=", ec_lnum}, 1238 | {"", "", ec_null}, 1239 | }; 1240 | 1241 | static int ex_idx(char *cmd) 1242 | { 1243 | int i; 1244 | for (i = 0; i < LEN(excmds); i++) 1245 | if (!strcmp(excmds[i].abbr, cmd) || !strcmp(excmds[i].name, cmd)) 1246 | return i; 1247 | return -1; 1248 | } 1249 | 1250 | /* read ex command addresses */ 1251 | static char *ex_loc(char *src, char *loc) 1252 | { 1253 | while (*src == ':' || *src == ' ' || *src == '\t') 1254 | src++; 1255 | while (*src && strchr(".$0123456789'/?+-,;%", (unsigned char) *src) != NULL) { 1256 | if (*src == '\'') 1257 | *loc++ = *src++; 1258 | if (*src == '/' || *src == '?') { 1259 | int d = *src; 1260 | *loc++ = *src++; 1261 | while (*src && *src != d) { 1262 | if (*src == '\\' && src[1]) 1263 | *loc++ = *src++; 1264 | *loc++ = *src++; 1265 | } 1266 | } 1267 | if (*src) 1268 | *loc++ = *src++; 1269 | } 1270 | *loc = '\0'; 1271 | return src; 1272 | } 1273 | 1274 | /* read ex command name */ 1275 | static char *ex_cmd(char *src, char *cmd) 1276 | { 1277 | char *cmd0 = cmd; 1278 | while (*src == ' ' || *src == '\t') 1279 | src++; 1280 | while (isalpha((unsigned char) *src) && cmd < cmd0 + 16) 1281 | if ((*cmd++ = *src++) == 'k' && cmd == cmd0 + 1) 1282 | break; 1283 | if (*src == '!' || *src == '=' || *src == '@') 1284 | *cmd++ = *src++; 1285 | *cmd = '\0'; 1286 | return src; 1287 | } 1288 | 1289 | /* read ex command argument for excmd command */ 1290 | static char *ex_arg(char *src, char *dst, char *excmd) 1291 | { 1292 | int c0 = excmd[0]; 1293 | int c1 = c0 ? excmd[1] : 0; 1294 | while (*src == ' ' || *src == '\t') 1295 | src++; 1296 | if (c0 == '!' || c0 == 'g' || c0 == 'v' || 1297 | ((c0 == 'r' || c0 == 'w') && !c1 && src[0] == '!')) { 1298 | while (*src && *src != '\n') { 1299 | if (*src == '\\' && src[1]) 1300 | *dst++ = *src++; 1301 | *dst++ = *src++; 1302 | } 1303 | } else if ((c0 == 's' && c1 != 'e') || c0 == '&' || c0 == '~') { 1304 | int delim = *src; 1305 | int cnt = 2; 1306 | if (delim != '\n' && delim != '|' && delim != '\\' && delim != '"') { 1307 | *dst++ = *src++; 1308 | while (*src && *src != '\n' && cnt > 0) { 1309 | if (*src == delim) 1310 | cnt--; 1311 | if (*src == '\\' && src[1]) 1312 | *dst++ = *src++; 1313 | *dst++ = *src++; 1314 | } 1315 | } 1316 | } 1317 | while (*src && *src != '\n' && *src != '|' && *src != '"') { 1318 | if (*src == '\\' && src[1]) 1319 | *dst++ = *src++; 1320 | *dst++ = *src++; 1321 | } 1322 | if (*src == '"') { 1323 | while (*src && *src != '\n') 1324 | src++; 1325 | } 1326 | if (*src == '\n' || *src == '|') 1327 | src++; 1328 | *dst = '\0'; 1329 | return src; 1330 | } 1331 | 1332 | /* read ex text input for excmd command */ 1333 | static char *ex_txt(char *src, char **dst, char *excmd) 1334 | { 1335 | int c0 = excmd[0]; 1336 | int c1 = c0 ? excmd[1] : 0; 1337 | *dst = NULL; 1338 | if (c0 == 'r' && c1 == 's' && src[0]) { 1339 | char *beg = src; 1340 | char *res; 1341 | while (src[0] && (src[0] != '\n' || src[1] != '.' || src[2] != '\n')) 1342 | src++; 1343 | res = malloc((src - beg) + 1 + 1); 1344 | memcpy(res, beg, src - beg); 1345 | res[src - beg] = '\n'; 1346 | res[src - beg + 1] = '\0'; 1347 | *dst = res; 1348 | return src[0] ? src + 3 : src; 1349 | } 1350 | if ((c0 == 'r' && c1 == 's') || (c1 == 0 && (c0 == 'i' || c0 == 'a' || c0 == 'c'))) { 1351 | struct sbuf *sb = sbuf_make(); 1352 | char *s; 1353 | while ((s = ex_read(""))) { 1354 | if (!strcmp(".", s)) { 1355 | free(s); 1356 | break; 1357 | } 1358 | sbuf_str(sb, s); 1359 | sbuf_chr(sb, '\n'); 1360 | free(s); 1361 | } 1362 | *dst = sbuf_done(sb); 1363 | return src; 1364 | } 1365 | return src; 1366 | } 1367 | 1368 | /* execute a single ex command */ 1369 | static int ex_exec(char *ln) 1370 | { 1371 | char loc[EXLEN], cmd[EXLEN], arg[EXLEN]; 1372 | int ret = 0; 1373 | if (strlen(ln) >= EXLEN) { 1374 | ex_show("command too long"); 1375 | return 1; 1376 | } 1377 | while (*ln) { 1378 | char *txt = NULL; 1379 | int idx; 1380 | ln = ex_loc(ln, loc); 1381 | ln = ex_cmd(ln, cmd); 1382 | idx = ex_idx(cmd); 1383 | ln = ex_arg(ln, arg, idx >= 0 ? excmds[idx].abbr : "unknown"); 1384 | ln = ex_txt(ln, &txt, idx >= 0 ? excmds[idx].abbr : "unknown"); 1385 | if (idx >= 0) 1386 | ret = excmds[idx].ec(loc, cmd, arg, txt); 1387 | else 1388 | ex_show("unknown command"); 1389 | free(txt); 1390 | } 1391 | return ret; 1392 | } 1393 | 1394 | /* execute a single ex command */ 1395 | int ex_command(char *ln) 1396 | { 1397 | int ret = ex_exec(ln); 1398 | lbuf_modified(xb); 1399 | return ret; 1400 | } 1401 | 1402 | /* ex main loop */ 1403 | void ex(void) 1404 | { 1405 | while (!xquit) { 1406 | char *ln = ex_read(":"); 1407 | if (ln) { 1408 | ex_command(ln); 1409 | reg_put(':', ln, 1); 1410 | } 1411 | free(ln); 1412 | } 1413 | } 1414 | 1415 | int ex_init(char **files) 1416 | { 1417 | next = files; 1418 | if (ex_next("e", 0)) 1419 | return 1; 1420 | if (getenv("EXINIT")) 1421 | ex_command(getenv("EXINIT")); 1422 | return 0; 1423 | } 1424 | 1425 | void ex_done(void) 1426 | { 1427 | int i; 1428 | for (i = 0; i < LEN(bufs); i++) 1429 | bufs_free(i); 1430 | } 1431 | -------------------------------------------------------------------------------- /kmap.h: -------------------------------------------------------------------------------- 1 | static char *kmap_en[256] = { 2 | [0] = "en", 3 | }; 4 | 5 | static char *kmap_fa[256] = { 6 | [0] = "fa", 7 | ['`'] = "‍", 8 | ['1'] = "۱", 9 | ['2'] = "۲", 10 | ['3'] = "۳", 11 | ['4'] = "۴", 12 | ['5'] = "۵", 13 | ['6'] = "۶", 14 | ['7'] = "۷", 15 | ['8'] = "۸", 16 | ['9'] = "۹", 17 | ['0'] = "۰", 18 | ['-'] = "-", 19 | ['='] = "=", 20 | ['q'] = "ض", 21 | ['w'] = "ص", 22 | ['e'] = "ث", 23 | ['r'] = "ق", 24 | ['t'] = "ف", 25 | ['y'] = "غ", 26 | ['u'] = "ع", 27 | ['i'] = "ه", 28 | ['o'] = "خ", 29 | ['p'] = "ح", 30 | ['['] = "ج", 31 | [']'] = "چ", 32 | ['a'] = "ش", 33 | ['s'] = "س", 34 | ['d'] = "ی", 35 | ['f'] = "ب", 36 | ['g'] = "ل", 37 | ['h'] = "ا", 38 | ['j'] = "ت", 39 | ['k'] = "ن", 40 | ['l'] = "م", 41 | [';'] = "ک", 42 | ['\''] = "گ", 43 | ['z'] = "ظ", 44 | ['x'] = "ط", 45 | ['c'] = "ز", 46 | ['v'] = "ر", 47 | ['b'] = "ذ", 48 | ['n'] = "د", 49 | ['m'] = "پ", 50 | [','] = "و", 51 | ['.'] = ".", 52 | ['/'] = "/", 53 | ['\\'] = "\\", 54 | ['~'] = "÷", 55 | ['!'] = "!", 56 | ['@'] = "٬", 57 | ['#'] = "٫", 58 | ['$'] = "﷼", 59 | ['%'] = "٪", 60 | ['^'] = "×", 61 | ['&'] = "،", 62 | ['*'] = "*", 63 | ['('] = "(", 64 | [')'] = ")", 65 | ['_'] = "ـ", 66 | ['+'] = "+", 67 | ['Q'] = "ْ", 68 | ['W'] = "ٌ", 69 | ['E'] = "ٍ", 70 | ['R'] = "ً", 71 | ['T'] = "ُ", 72 | ['Y'] = "ِ", 73 | ['U'] = "َ", 74 | ['I'] = "ّ", 75 | ['O'] = "[", 76 | ['P'] = "]", 77 | ['{'] = "{", 78 | ['}'] = "}", 79 | ['A'] = "ؤ", 80 | ['S'] = "ئ", 81 | ['D'] = "ي", 82 | ['F'] = "إ", 83 | ['G'] = "أ", 84 | ['H'] = "آ", 85 | ['J'] = "ة", 86 | ['K'] = "«", 87 | ['L'] = "»", 88 | [':'] = ":", 89 | ['"'] = "؛", 90 | ['Z'] = "ك", 91 | ['X'] = "ٓ", 92 | ['C'] = "ژ", 93 | ['V'] = "ٰ", 94 | ['B'] = "‌", 95 | ['N'] = "ٔ", 96 | ['M'] = "ء", 97 | ['<'] = "<", 98 | ['>'] = ">", 99 | ['?'] = "؟", 100 | ['|'] = "|", 101 | }; 102 | 103 | static char **kmaps[] = {kmap_en, kmap_fa}; 104 | 105 | static char *digraphs[][2] = { 106 | {"cq", "’"}, 107 | {"pl", "+"}, 108 | {"hy", "-"}, 109 | {"sl", "/"}, 110 | {"eq", "="}, 111 | {"dq", "\""}, 112 | {"rs", "\\"}, 113 | {"ru", "_"}, 114 | {"ul", "_"}, 115 | {"oq", "‘"}, 116 | {"or", "|"}, 117 | {"!!", "¡"}, 118 | {"r!", "¡"}, 119 | {"c|", "¢"}, 120 | {"ct", "¢"}, 121 | {"L-", "£"}, 122 | {"ps", "£"}, 123 | {"xo", "¤"}, 124 | {"cr", "¤"}, 125 | {"Y-", "¥"}, 126 | {"yn", "¥"}, 127 | {"||", "¦"}, 128 | {"so", "§"}, 129 | {"sc", "§"}, 130 | {"co", "©"}, 131 | {"a_", "ª"}, 132 | {"<<", "«"}, 133 | {"Fo", "«"}, 134 | {"-,", "¬"}, 135 | {"no", "¬"}, 136 | {"ro", "®"}, 137 | {"rg", "®"}, 138 | {"0^", "°"}, 139 | {"de", "°"}, 140 | {"+-", "±"}, 141 | {"2^", "²"}, 142 | {"3^", "³"}, 143 | {"P!", "¶"}, 144 | {"pg", "¶"}, 145 | {".^", "·"}, 146 | {"1^", "¹"}, 147 | {"o_", "º"}, 148 | {">>", "»"}, 149 | {"Fc", "»"}, 150 | {"14", "¼"}, 151 | {"12", "½"}, 152 | {"34", "¾"}, 153 | {"??", "¿"}, 154 | {"r?", "¿"}, 155 | {"A`", "À"}, 156 | {"A'", "Á"}, 157 | {"A^", "Â"}, 158 | {"A~", "Ã"}, 159 | {"A-", "Ā"}, 160 | {"A:", "Ä"}, 161 | {"A\"", "A:"}, 162 | {"A*", "Å"}, 163 | {"AE", "Æ"}, 164 | {"C,", "Ç"}, 165 | {"E`", "È"}, 166 | {"E'", "É"}, 167 | {"E^", "Ê"}, 168 | {"E-", "Ē"}, 169 | {"E:", "Ë"}, 170 | {"I`", "Ì"}, 171 | {"I'", "Í"}, 172 | {"I^", "Î"}, 173 | {"I-", "Ī"}, 174 | {"I:", "Ï"}, 175 | {"D-", "Ð"}, 176 | {"N~", "Ñ"}, 177 | {"O`", "Ò"}, 178 | {"O'", "Ó"}, 179 | {"O^", "Ô"}, 180 | {"O~", "Õ"}, 181 | {"O-", "Ō"}, 182 | {"O:", "Ö"}, 183 | {"xx", "×"}, 184 | {"mu", "×"}, 185 | {"O/", "Ø"}, 186 | {"U`", "Ù"}, 187 | {"U'", "Ú"}, 188 | {"U^", "Û"}, 189 | {"U-", "Ū"}, 190 | {"U:", "Ü"}, 191 | {"Y'", "Ý"}, 192 | {"TH", "Þ"}, 193 | {"ss", "ß"}, 194 | {"a`", "à"}, 195 | {"a'", "á"}, 196 | {"a^", "â"}, 197 | {"a-", "ā"}, 198 | {"a~", "ã"}, 199 | {"a:", "ä"}, 200 | {"a*", "å"}, 201 | {"ae", "æ"}, 202 | {"c,", "ç"}, 203 | {"e`", "è"}, 204 | {"e'", "é"}, 205 | {"e^", "ê"}, 206 | {"e:", "ë"}, 207 | {"e-", "ē"}, 208 | {"i`", "ì"}, 209 | {"i'", "í"}, 210 | {"i^", "î"}, 211 | {"i-", "ī"}, 212 | {"i:", "ï"}, 213 | {"d-", "ð"}, 214 | {"n~", "ñ"}, 215 | {"o`", "ò"}, 216 | {"o'", "ó"}, 217 | {"o^", "ô"}, 218 | {"o~", "õ"}, 219 | {"o-", "ō"}, 220 | {"o:", "ö"}, 221 | {"di", "÷"}, 222 | {"-:", "÷"}, 223 | {"o/", "ø"}, 224 | {"u`", "ù"}, 225 | {"u'", "ú"}, 226 | {"u^", "û"}, 227 | {"u-", "ū"}, 228 | {"u:", "ü"}, 229 | {"y'", "ý"}, 230 | {"y-", "ȳ"}, 231 | {"th", "þ"}, 232 | {"y:", "ÿ"}, 233 | {"C<", "Č"}, 234 | {"c<", "č"}, 235 | {"D<", "Ď"}, 236 | {"d<", "ď"}, 237 | {"e<", "ě"}, 238 | {"n<", "ň"}, 239 | {"O\"", "Ő"}, 240 | {"o\"", "ő"}, 241 | {"R<", "Ř"}, 242 | {"r<", "ř"}, 243 | {"S<", "Š"}, 244 | {"s<", "š"}, 245 | {"T<", "Ť"}, 246 | {"t<", "ť"}, 247 | {"U*", "Ů"}, 248 | {"u*", "ů"}, 249 | {"U\"", "Ű"}, 250 | {"u\"", "ű"}, 251 | {"Y:", "Ÿ"}, 252 | {"Y-", "Ȳ"}, 253 | {"Z<", "Ž"}, 254 | {"z<", "ž"}, 255 | {"fn", "ƒ"}, 256 | {",,", "¸"}, 257 | {",a", "¸"}, 258 | {"aa", "´"}, 259 | {"\\'", "´"}, 260 | {"-a", "¯"}, 261 | {"\"\"", "¨"}, 262 | {":a", "¨"}, 263 | {"^", "ˆ"}, 264 | {"^a", "ˆ"}, 265 | {"va", "ˇ"}, 266 | {"Ua", "˘"}, 267 | {".a", "˙"}, 268 | {"oa", "˚"}, 269 | {"Ca", "˛"}, 270 | {"\"a", "˝"}, 271 | {"~", "˜"}, 272 | {"*A", "Α"}, 273 | {"*B", "Β"}, 274 | {"*G", "Γ"}, 275 | {"*E", "Ε"}, 276 | {"*Z", "Ζ"}, 277 | {"*Y", "Η"}, 278 | {"*H", "Θ"}, 279 | {"*I", "Ι"}, 280 | {"*K", "Κ"}, 281 | {"*L", "Λ"}, 282 | {"*M", "Μ"}, 283 | {"*N", "Ν"}, 284 | {"*C", "Ξ"}, 285 | {"*O", "Ο"}, 286 | {"*P", "Π"}, 287 | {"*R", "Ρ"}, 288 | {"*S", "Σ"}, 289 | {"*T", "Τ"}, 290 | {"*U", "Υ"}, 291 | {"*F", "Φ"}, 292 | {"*X", "Χ"}, 293 | {"*Q", "Ψ"}, 294 | {"*W", "Ω"}, 295 | {"*a", "α"}, 296 | {"*b", "β"}, 297 | {"*g", "γ"}, 298 | {"*d", "δ"}, 299 | {"*e", "ε"}, 300 | {"*z", "ζ"}, 301 | {"*y", "η"}, 302 | {"*h", "θ"}, 303 | {"*i", "ι"}, 304 | {"*k", "κ"}, 305 | {"*l", "λ"}, 306 | {"*m", "μ"}, 307 | {"/u", "µ"}, 308 | {"*n", "ν"}, 309 | {"*c", "ξ"}, 310 | {"*o", "ο"}, 311 | {"*p", "π"}, 312 | {"*r", "ρ"}, 313 | {"ts", "ς"}, 314 | {"*s", "σ"}, 315 | {"*t", "τ"}, 316 | {"*u", "υ"}, 317 | {"*f", "φ"}, 318 | {"*x", "χ"}, 319 | {"*q", "ψ"}, 320 | {"*w", "ω"}, 321 | {"en", "–"}, 322 | {"\\-", "–"}, 323 | {"em", "—"}, 324 | {"--", "—"}, 325 | {"bq", "‚"}, 326 | {"``", "“"}, 327 | {"lq", "“"}, 328 | {"''", "”"}, 329 | {"rq", "”"}, 330 | {"dg", "†"}, 331 | {"dd", "‡"}, 332 | {"bu", "•"}, 333 | {"el", "…"}, 334 | {"%0", "‰"}, 335 | {"fm", "′"}, 336 | {"fo", "‹"}, 337 | {"fc", "›"}, 338 | {"fr", "⁄"}, 339 | {"If", "ℑ"}, 340 | {"ws", "℘"}, 341 | {"Rf", "ℜ"}, 342 | {"af", "ℵ"}, 343 | {"<-", "←"}, 344 | {"ua", "↑"}, 345 | {"->", "→"}, 346 | {"da", "↓"}, 347 | {"<>", "↔"}, 348 | {"ab", "↔"}, 349 | {"CR", "↵"}, 350 | {"fa", "∀"}, 351 | {"pd", "∂"}, 352 | {"te", "∃"}, 353 | {"es", "∅"}, 354 | {"*D", "Δ"}, 355 | {"gr", "∇"}, 356 | {"mo", "∈"}, 357 | {"!m", "∉"}, 358 | {"st", "∋"}, 359 | {"pr", "∏"}, 360 | {"su", "∑"}, 361 | {"mi", "−"}, 362 | {"-+", "∓"}, 363 | {"**", "∗"}, 364 | {"sr", "√"}, 365 | {"pt", "∝"}, 366 | {"if", "∞"}, 367 | {"an", "∠"}, 368 | {"l&", "∧"}, 369 | {"l|", "∨"}, 370 | {"ca", "∩"}, 371 | {"cu", "∪"}, 372 | {"is", "∫"}, 373 | {"tf", "∴"}, 374 | {"ap", "∼"}, 375 | {"cg", "≅"}, 376 | {"=~", "≅"}, 377 | {"~~", "≈"}, 378 | {"!=", "≠"}, 379 | {"==", "≡"}, 380 | {"<=", "≤"}, 381 | {">=", "≥"}, 382 | {"sb", "⊂"}, 383 | {"sp", "⊃"}, 384 | {"!b", "⊄"}, 385 | {"ib", "⊆"}, 386 | {"ip", "⊇"}, 387 | {"O+", "⊕"}, 388 | {"Ox", "⊗"}, 389 | {"pp", "⊥"}, 390 | {"c.", "⋅"}, 391 | {"b<", "〈"}, 392 | {"b>", "〉"}, 393 | {"lz", "◊"}, 394 | {"ci", "○"}, 395 | {"la", "⟨"}, 396 | {"ra", "⟩"}, 397 | {"co", ""}, 398 | {"rg", ""}, 399 | {"tm", ""}, 400 | {"rn", ""}, 401 | {"av", ""}, 402 | {"ah", ""}, 403 | {"RG", ""}, 404 | {"CO", ""}, 405 | {"TM", ""}, 406 | {"LT", ""}, 407 | {"br", ""}, 408 | {"LX", ""}, 409 | {"LB", ""}, 410 | {"LT", "⎛"}, 411 | {"LX", "⎜"}, 412 | {"LB", "⎝"}, 413 | {"lc", ""}, 414 | {"lx", ""}, 415 | {"lf", ""}, 416 | {"lc", "⎡"}, 417 | {"lx", "⎢"}, 418 | {"lf", "⎣"}, 419 | {"lt", ""}, 420 | {"lk", ""}, 421 | {"lb", ""}, 422 | {"lt", "⎧"}, 423 | {"lk", "⎨"}, 424 | {"lb", "⎩"}, 425 | {"bv", "⎪"}, 426 | {"RT", ""}, 427 | {"RX", ""}, 428 | {"RB", ""}, 429 | {"RT", "⎞"}, 430 | {"RX", "⎟"}, 431 | {"RB", "⎠"}, 432 | {"rc", ""}, 433 | {"rx", ""}, 434 | {"rf", ""}, 435 | {"rc", "⎤"}, 436 | {"rx", "⎥"}, 437 | {"rf", "⎦"}, 438 | {"rt", ""}, 439 | {"rk", ""}, 440 | {"rb", ""}, 441 | {"rt", "⎫"}, 442 | {"rk", "⎬"}, 443 | {"rb", "⎭"}, 444 | {"ff", "ff"}, 445 | {"fi", "fi"}, 446 | {"fl", "fl"}, 447 | {"ffi", "ffi"}, 448 | {"Fi", "ffi"}, 449 | {"ffl", "ffl"}, 450 | {"Fl", "ffl"}, 451 | }; 452 | -------------------------------------------------------------------------------- /lbuf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "vi.h" 7 | 8 | #define NMARKS_BASE ('z' - 'a' + 3) 9 | #define NMARKS 32 10 | 11 | /* line operations */ 12 | struct lopt { 13 | char *ins; /* inserted text */ 14 | char *del; /* deleted text */ 15 | int pos, n_ins, n_del; /* modification location */ 16 | int pos_off; /* cursor line offset */ 17 | int seq; /* operation number */ 18 | int *mark, *mark_off; /* saved marks */ 19 | }; 20 | 21 | /* line buffers */ 22 | struct lbuf { 23 | int mark[NMARKS]; /* mark lines */ 24 | int mark_off[NMARKS]; /* mark line offsets */ 25 | char **ln; /* buffer lines */ 26 | char *ln_glob; /* line global mark */ 27 | int ln_n; /* number of lines in ln[] */ 28 | int ln_sz; /* size of ln[] */ 29 | int useq; /* current operation sequence */ 30 | struct lopt *hist; /* buffer history */ 31 | int hist_sz; /* size of hist[] */ 32 | int hist_n; /* current history head in hist[] */ 33 | int hist_u; /* current undo head in hist[] */ 34 | int useq_zero; /* useq for lbuf_saved() */ 35 | int useq_last; /* useq before hist[] */ 36 | }; 37 | 38 | struct lbuf *lbuf_make(void) 39 | { 40 | struct lbuf *lb = malloc(sizeof(*lb)); 41 | int i; 42 | memset(lb, 0, sizeof(*lb)); 43 | for (i = 0; i < LEN(lb->mark); i++) 44 | lb->mark[i] = -1; 45 | lb->useq = 1; 46 | return lb; 47 | } 48 | 49 | static void lopt_done(struct lopt *lo) 50 | { 51 | free(lo->ins); 52 | free(lo->del); 53 | free(lo->mark); 54 | free(lo->mark_off); 55 | } 56 | 57 | static void lbuf_savemark(struct lbuf *lb, struct lopt *lo, int m) 58 | { 59 | if (lb->mark[m] >= 0) { 60 | if (!lo->mark) { 61 | lo->mark = malloc(sizeof(lb->mark)); 62 | lo->mark_off = malloc(sizeof(lb->mark_off)); 63 | memset(lo->mark, 0xff, sizeof(lb->mark)); 64 | } 65 | lo->mark[m] = lb->mark[m]; 66 | lo->mark_off[m] = lb->mark_off[m]; 67 | } 68 | } 69 | 70 | static void lbuf_loadmark(struct lbuf *lb, struct lopt *lo, int m) 71 | { 72 | if (lo->mark && lo->mark[m] >= 0) { 73 | lb->mark[m] = lo->mark[m]; 74 | lb->mark_off[m] = lo->mark_off[m]; 75 | } 76 | } 77 | 78 | static int markidx(int mark) 79 | { 80 | if (islower(mark)) 81 | return mark - 'a'; 82 | if (mark == '\'' || mark == '`') 83 | return 'z' - 'a' + 1; 84 | if (mark == '*') 85 | return 'z' - 'a' + 2; 86 | if (mark == '[') 87 | return 'z' - 'a' + 3; 88 | if (mark == ']') 89 | return 'z' - 'a' + 4; 90 | if (mark == '^') 91 | return 'z' - 'a' + 5; 92 | return -1; 93 | } 94 | 95 | static void lbuf_markcopy(struct lbuf *lb, int dst, int src) 96 | { 97 | lb->mark[markidx(dst)] = lb->mark[markidx(src)]; 98 | lb->mark_off[markidx(dst)] = lb->mark_off[markidx(src)]; 99 | } 100 | 101 | static void lbuf_savepos(struct lbuf *lb, struct lopt *lo) 102 | { 103 | if (lb->mark[markidx('^')] >= 0) 104 | lo->pos_off = lb->mark_off[markidx('^')]; 105 | lbuf_markcopy(lb, '*', '^'); 106 | } 107 | 108 | static void lbuf_loadpos(struct lbuf *lb, struct lopt *lo) 109 | { 110 | lb->mark[markidx('^')] = lo->pos; 111 | lb->mark_off[markidx('^')] = lo->pos_off; 112 | lbuf_markcopy(lb, '*', '^'); 113 | } 114 | 115 | void lbuf_free(struct lbuf *lb) 116 | { 117 | int i; 118 | for (i = 0; i < lb->ln_n; i++) 119 | free(lb->ln[i]); 120 | for (i = 0; i < lb->hist_n; i++) 121 | lopt_done(&lb->hist[i]); 122 | free(lb->hist); 123 | free(lb->ln); 124 | free(lb->ln_glob); 125 | free(lb); 126 | } 127 | 128 | static int linelength(char *s) 129 | { 130 | char *r = strchr(s, '\n'); 131 | return r ? r - s + 1 : strlen(s); 132 | } 133 | 134 | static int linecount(char *s) 135 | { 136 | int n; 137 | for (n = 0; s && *s; n++) 138 | s += linelength(s); 139 | return n; 140 | } 141 | 142 | 143 | /* low-level line replacement */ 144 | static void lbuf_replace(struct lbuf *lb, char *s, int pos, int n_del) 145 | { 146 | int n_ins = linecount(s); 147 | int i; 148 | while (lb->ln_n + n_ins - n_del >= lb->ln_sz) { 149 | int nsz = lb->ln_sz + (lb->ln_sz ? lb->ln_sz : 512); 150 | char **nln = malloc(nsz * sizeof(nln[0])); 151 | char *nln_glob = malloc(nsz * sizeof(nln_glob[0])); 152 | memcpy(nln, lb->ln, lb->ln_n * sizeof(lb->ln[0])); 153 | memcpy(nln_glob, lb->ln_glob, lb->ln_n * sizeof(lb->ln_glob[0])); 154 | free(lb->ln); 155 | free(lb->ln_glob); 156 | lb->ln = nln; 157 | lb->ln_glob = nln_glob; 158 | lb->ln_sz = nsz; 159 | } 160 | for (i = 0; i < n_del; i++) 161 | free(lb->ln[pos + i]); 162 | if (n_ins != n_del) { 163 | memmove(lb->ln + pos + n_ins, lb->ln + pos + n_del, 164 | (lb->ln_n - pos - n_del) * sizeof(lb->ln[0])); 165 | memmove(lb->ln_glob + pos + n_ins, lb->ln_glob + pos + n_del, 166 | (lb->ln_n - pos - n_del) * sizeof(lb->ln_glob[0])); 167 | } 168 | lb->ln_n += n_ins - n_del; 169 | for (i = 0; i < n_ins; i++) { 170 | int l = s ? linelength(s) : 0; 171 | int l_nonl = l - (s[l - 1] == '\n'); 172 | char *n = malloc(l_nonl + 2); 173 | memcpy(n, s, l_nonl); 174 | n[l_nonl + 0] = '\n'; 175 | n[l_nonl + 1] = '\0'; 176 | lb->ln[pos + i] = n; 177 | s += l; 178 | } 179 | for (i = n_del; i < n_ins; i++) 180 | lb->ln_glob[pos + i] = 0; 181 | for (i = 0; i < LEN(lb->mark); i++) { /* updating marks */ 182 | if (!s && lb->mark[i] >= pos && lb->mark[i] < pos + n_del) 183 | lb->mark[i] = -1; 184 | else if (lb->mark[i] >= pos + n_del) 185 | lb->mark[i] += n_ins - n_del; 186 | else if (lb->mark[i] >= pos + n_ins) 187 | lb->mark[i] = pos + n_ins - 1; 188 | } 189 | lbuf_mark(lb, '[', pos, 0); 190 | lbuf_mark(lb, ']', pos + (n_ins ? n_ins - 1 : 0), 0); 191 | } 192 | 193 | /* append undo/redo history */ 194 | static void lbuf_opt(struct lbuf *lb, char *buf, int pos, int n_del) 195 | { 196 | struct lopt *lo; 197 | int i; 198 | for (i = lb->hist_u; i < lb->hist_n; i++) 199 | lopt_done(&lb->hist[i]); 200 | lb->hist_n = lb->hist_u; 201 | if (lb->hist_n == lb->hist_sz) { 202 | int sz = lb->hist_sz + (lb->hist_sz ? lb->hist_sz : 128); 203 | struct lopt *hist = malloc(sz * sizeof(hist[0])); 204 | memcpy(hist, lb->hist, lb->hist_n * sizeof(hist[0])); 205 | free(lb->hist); 206 | lb->hist = hist; 207 | lb->hist_sz = sz; 208 | } 209 | lo = &lb->hist[lb->hist_n]; 210 | lb->hist_n++; 211 | lb->hist_u = lb->hist_n; 212 | memset(lo, 0, sizeof(*lo)); 213 | lo->pos = pos; 214 | lo->n_del = n_del; 215 | lo->del = n_del ? lbuf_cp(lb, pos, pos + n_del) : NULL; 216 | lo->n_ins = buf ? linecount(buf) : 0; 217 | lo->ins = buf ? uc_dup(buf) : NULL; 218 | lo->seq = lb->useq; 219 | lbuf_savepos(lb, lo); 220 | for (i = 0; i < NMARKS_BASE; i++) 221 | if (lb->mark[i] >= pos && lb->mark[i] < pos + n_del) 222 | lbuf_savemark(lb, lo, i); 223 | } 224 | 225 | int lbuf_rd(struct lbuf *lbuf, int fd, int beg, int end) 226 | { 227 | char buf[1 << 10]; 228 | struct sbuf *sb; 229 | long nr; 230 | sb = sbuf_make(); 231 | while ((nr = read(fd, buf, sizeof(buf))) > 0) 232 | sbuf_mem(sb, buf, nr); 233 | if (!nr) 234 | lbuf_edit(lbuf, sbuf_buf(sb), beg, end); 235 | sbuf_free(sb); 236 | return nr != 0; 237 | } 238 | 239 | static long write_fully(int fd, void *buf, long sz) 240 | { 241 | long nw = 0, nc = 0; 242 | while (nw < sz && (nc = write(fd, buf + nw, sz - nw)) >= 0) 243 | nw += nc; 244 | return nc >= 0 ? nw : -1; 245 | } 246 | 247 | int lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end) 248 | { 249 | char buf[4096]; 250 | long buf_len = 0, sz = 0; 251 | int i; 252 | for (i = beg; i < end; i++) { 253 | char *ln = lbuf->ln[i]; 254 | long nl = strlen(ln); 255 | if (buf_len > 0 && buf_len + nl > sizeof(buf)) { 256 | if (write_fully(fd, buf, buf_len) < 0) 257 | return 1; 258 | buf_len = 0; 259 | } 260 | if (nl >= sizeof(buf)) { 261 | if (write_fully(fd, ln, nl) < 0) 262 | return 1; 263 | } else { 264 | memcpy(buf + buf_len, ln, nl); 265 | buf_len += nl; 266 | } 267 | sz += nl; 268 | } 269 | if (buf_len > 0 && write_fully(fd, buf, buf_len) < 0) 270 | return 1; 271 | ftruncate(fd, sz); 272 | return 0; 273 | } 274 | 275 | /* replace lines beg through end with buf */ 276 | void lbuf_edit(struct lbuf *lb, char *buf, int beg, int end) 277 | { 278 | if (beg > lb->ln_n) 279 | beg = lb->ln_n; 280 | if (end > lb->ln_n) 281 | end = lb->ln_n; 282 | if (beg == end && !buf) 283 | return; 284 | lbuf_opt(lb, buf, beg, end - beg); 285 | lbuf_replace(lb, buf, beg, end - beg); 286 | } 287 | 288 | char *lbuf_cp(struct lbuf *lb, int beg, int end) 289 | { 290 | struct sbuf *sb; 291 | int i; 292 | sb = sbuf_make(); 293 | for (i = beg; i < end; i++) 294 | if (i < lb->ln_n) 295 | sbuf_str(sb, lb->ln[i]); 296 | return sbuf_done(sb); 297 | } 298 | 299 | char *lbuf_get(struct lbuf *lb, int pos) 300 | { 301 | return pos >= 0 && pos < lb->ln_n ? lb->ln[pos] : NULL; 302 | } 303 | 304 | int lbuf_len(struct lbuf *lb) 305 | { 306 | return lb->ln_n; 307 | } 308 | 309 | void lbuf_mark(struct lbuf *lbuf, int mark, int pos, int off) 310 | { 311 | if (markidx(mark) >= 0) { 312 | lbuf->mark[markidx(mark)] = pos; 313 | lbuf->mark_off[markidx(mark)] = off; 314 | } 315 | } 316 | 317 | int lbuf_jump(struct lbuf *lbuf, int mark, int *pos, int *off) 318 | { 319 | int mk = markidx(mark); 320 | if (mk < 0 || lbuf->mark[mk] < 0) 321 | return 1; 322 | *pos = lbuf->mark[mk]; 323 | if (off) 324 | *off = lbuf->mark_off[mk]; 325 | return 0; 326 | } 327 | 328 | int lbuf_undo(struct lbuf *lb) 329 | { 330 | int useq, i; 331 | if (!lb->hist_u) 332 | return 1; 333 | useq = lb->hist[lb->hist_u - 1].seq; 334 | while (lb->hist_u && lb->hist[lb->hist_u - 1].seq == useq) { 335 | struct lopt *lo = &lb->hist[--(lb->hist_u)]; 336 | lbuf_replace(lb, lo->del, lo->pos, lo->n_ins); 337 | lbuf_loadpos(lb, lo); 338 | for (i = 0; i < LEN(lb->mark); i++) 339 | lbuf_loadmark(lb, lo, i); 340 | } 341 | return 0; 342 | } 343 | 344 | int lbuf_redo(struct lbuf *lb) 345 | { 346 | int useq; 347 | if (lb->hist_u == lb->hist_n) 348 | return 1; 349 | useq = lb->hist[lb->hist_u].seq; 350 | while (lb->hist_u < lb->hist_n && lb->hist[lb->hist_u].seq == useq) { 351 | struct lopt *lo = &lb->hist[lb->hist_u++]; 352 | lbuf_replace(lb, lo->ins, lo->pos, lo->n_del); 353 | lbuf_loadpos(lb, lo); 354 | } 355 | return 0; 356 | } 357 | 358 | static int lbuf_seq(struct lbuf *lb) 359 | { 360 | return lb->hist_u ? lb->hist[lb->hist_u - 1].seq : lb->useq_last; 361 | } 362 | 363 | /* mark buffer as saved and, if clear, clear the undo history */ 364 | void lbuf_saved(struct lbuf *lb, int clear) 365 | { 366 | int i; 367 | if (clear) { 368 | for (i = 0; i < lb->hist_n; i++) 369 | lopt_done(&lb->hist[i]); 370 | lb->hist_n = 0; 371 | lb->hist_u = 0; 372 | lb->useq_last = lb->useq; 373 | } 374 | lb->useq_zero = lbuf_seq(lb); 375 | lbuf_modified(xb); 376 | } 377 | 378 | /* was the file modified since the last lbuf_modreset() */ 379 | int lbuf_modified(struct lbuf *lb) 380 | { 381 | lb->useq++; 382 | return lbuf_seq(lb) != lb->useq_zero; 383 | } 384 | 385 | /* mark the line for ex global command */ 386 | void lbuf_globset(struct lbuf *lb, int pos, int dep) 387 | { 388 | lb->ln_glob[pos] |= 1 << dep; 389 | } 390 | 391 | /* return and clear ex global command mark */ 392 | int lbuf_globget(struct lbuf *lb, int pos, int dep) 393 | { 394 | int o = lb->ln_glob[pos] & (1 << dep); 395 | lb->ln_glob[pos] &= ~(1 << dep); 396 | return o > 0; 397 | } 398 | -------------------------------------------------------------------------------- /led.c: -------------------------------------------------------------------------------- 1 | /* line editing and drawing */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "vi.h" 8 | 9 | static char *kmap_map(int kmap, int c) 10 | { 11 | static char cs[4]; 12 | char **keymap = conf_kmap(kmap); 13 | cs[0] = c; 14 | return keymap[c] ? keymap[c] : cs; 15 | } 16 | 17 | static int led_pos(int dir, int pos, int beg, int end) 18 | { 19 | return dir >= 0 ? pos - beg : end - pos - 1; 20 | } 21 | 22 | static int led_offdir(char **chrs, int *pos, int i) 23 | { 24 | if (pos[i] + ren_cwid(chrs[i], pos[i]) == pos[i + 1]) 25 | return +1; 26 | if (pos[i + 1] + ren_cwid(chrs[i + 1], pos[i + 1]) == pos[i]) 27 | return -1; 28 | return 0; 29 | } 30 | 31 | /* highlight text in reverse direction */ 32 | static void led_markrev(int n, char **chrs, int *pos, int *att) 33 | { 34 | int i = 0, j; 35 | int hl = conf_hlrev(); 36 | while (i + 1 < n) { 37 | int dir = led_offdir(chrs, pos, i); 38 | int beg = i; 39 | while (i + 1 < n && led_offdir(chrs, pos, i) == dir) 40 | i++; 41 | if (dir < 0) 42 | for (j = beg; j <= i; j++) 43 | att[j] = syn_merge(hl, att[j]); 44 | if (i == beg) 45 | i++; 46 | } 47 | } 48 | 49 | /* render and highlight a line */ 50 | static char *led_render(char *s0, int cbeg, int cend, char *syn) 51 | { 52 | int n; 53 | int *pos; /* pos[i]: the screen position of the i-th character */ 54 | int *off; /* off[i]: the character at screen position i */ 55 | int *att; /* att[i]: the attributes of i-th character */ 56 | char **chrs; /* chrs[i]: the i-th character in s1 */ 57 | int clast = 0; 58 | int att_old = 0; 59 | struct sbuf *out; 60 | int i, j; 61 | int ctx = dir_context(s0); 62 | int att_blank = 0; /* the attribute of blank space */ 63 | chrs = uc_chop(s0, &n); 64 | pos = ren_position(s0); 65 | off = malloc((cend - cbeg) * sizeof(off[0])); 66 | memset(off, 0xff, (cend - cbeg) * sizeof(off[0])); 67 | /* initialise off[] using pos[] */ 68 | for (i = 0; i < n; i++) { 69 | int curwid = ren_cwid(chrs[i], pos[i]); 70 | int curbeg = led_pos(ctx, pos[i], cbeg, cend); 71 | int curend = led_pos(ctx, pos[i] + curwid - 1, cbeg, cend); 72 | if (curbeg >= 0 && curbeg < (cend - cbeg) && 73 | curend >= 0 && curend < (cend - cbeg)) { 74 | for (j = 0; j < curwid; j++) 75 | off[led_pos(ctx, pos[i] + j, cbeg, cend)] = i; 76 | } 77 | } 78 | att = syn_highlight(n <= xlim ? syn : "", s0); 79 | /* find the last non-empty column */ 80 | for (i = cbeg; i < cend; i++) 81 | if (off[i - cbeg] >= 0) 82 | clast = i; 83 | /* the attribute of the last character is used for blanks */ 84 | att_blank = n > 0 ? att[n - 1] : 0; 85 | led_markrev(n, chrs, pos, att); 86 | /* generate term output */ 87 | out = sbuf_make(); 88 | sbuf_str(out, conf_lnpref()); 89 | i = cbeg; 90 | while (i < cend && i <= clast) { 91 | int o = off[i - cbeg]; 92 | int att_new = o >= 0 ? att[o] : att_blank; 93 | sbuf_str(out, term_seqattr(att_new, att_old)); 94 | att_old = att_new; 95 | if (o >= 0) { 96 | if (ren_translate(chrs[o], s0)) { 97 | sbuf_str(out, ren_translate(chrs[o], s0)); 98 | } else if (uc_isprint(chrs[o])) { 99 | sbuf_mem(out, chrs[o], uc_len(chrs[o])); 100 | } else { 101 | for (j = i; j < cend && off[j - cbeg] == o; j++) 102 | sbuf_chr(out, ' '); 103 | } 104 | while (i < cend && off[i - cbeg] == o) 105 | i++; 106 | } else { 107 | sbuf_chr(out, ' '); 108 | i++; 109 | } 110 | } 111 | if (clast < cend - 1) 112 | sbuf_str(out, term_seqkill()); 113 | sbuf_str(out, term_seqattr(0, att_old)); 114 | free(att); 115 | free(pos); 116 | free(off); 117 | free(chrs); 118 | return sbuf_done(out); 119 | } 120 | 121 | /* print a line on the screen */ 122 | void led_print(char *s, int row, int left, char *syn) 123 | { 124 | char *r = led_render(s, left, left + xcols, syn); 125 | term_pos(row, 0); 126 | term_kill(); 127 | term_str(r); 128 | free(r); 129 | } 130 | 131 | /* set xtd and return its old value */ 132 | static int td_set(int td) 133 | { 134 | int old = xtd; 135 | xtd = td; 136 | return old; 137 | } 138 | 139 | /* print a line on the screen; for ex messages */ 140 | void led_printmsg(char *s, int row, char *syn) 141 | { 142 | int td = td_set(+2); 143 | char *r = led_render(s, 0, xcols, syn); 144 | td_set(td); 145 | term_pos(row, 0); 146 | term_kill(); 147 | term_str(r); 148 | free(r); 149 | } 150 | 151 | static int led_lastchar(char *s) 152 | { 153 | char *r = *s ? strchr(s, '\0') : s; 154 | if (r != s) 155 | r = uc_beg(s, r - 1); 156 | return r - s; 157 | } 158 | 159 | static int led_lastword(char *s) 160 | { 161 | char *r = *s ? uc_beg(s, strchr(s, '\0') - 1) : s; 162 | int kind; 163 | while (r > s && uc_isspace(r)) 164 | r = uc_beg(s, r - 1); 165 | kind = r > s ? uc_kind(r) : 0; 166 | while (r > s && uc_kind(uc_beg(s, r - 1)) == kind) 167 | r = uc_beg(s, r - 1); 168 | return r - s; 169 | } 170 | 171 | static void led_printparts(char *ai, char *pref, char *main, 172 | char *post, int *left, int kmap, char *syn) 173 | { 174 | struct sbuf *ln; 175 | int off, pos; 176 | int idir = 0; 177 | ln = sbuf_make(); 178 | sbuf_str(ln, ai); 179 | sbuf_str(ln, pref); 180 | sbuf_str(ln, main); 181 | off = uc_slen(sbuf_buf(ln)); 182 | /* cursor position for inserting the next character */ 183 | if (*pref || *main || *ai) { 184 | int len = sbuf_len(ln); 185 | sbuf_str(ln, kmap_map(kmap, 'a')); 186 | sbuf_str(ln, post); 187 | idir = ren_pos(sbuf_buf(ln), off) - 188 | ren_pos(sbuf_buf(ln), off - 1) < 0 ? -1 : +1; 189 | sbuf_cut(ln, len); 190 | } 191 | term_record(); 192 | sbuf_str(ln, post); 193 | pos = ren_cursor(sbuf_buf(ln), ren_pos(sbuf_buf(ln), MAX(0, off - 1))); 194 | if (pos >= *left + xcols) 195 | *left = pos - xcols / 2; 196 | if (pos < *left) 197 | *left = pos < xcols ? 0 : pos - xcols / 2; 198 | led_print(sbuf_buf(ln), -1, *left, syn); 199 | term_pos(-1, led_pos(dir_context(sbuf_buf(ln)), pos + idir, *left, *left + xcols)); 200 | sbuf_free(ln); 201 | term_commit(); 202 | } 203 | 204 | /* continue reading the character starting with c */ 205 | static char *led_readchar(int c, int kmap) 206 | { 207 | static char buf[8]; 208 | int c1, c2; 209 | int i, n; 210 | if (c == TK_CTL('v')) { /* literal character */ 211 | buf[0] = term_read(); 212 | buf[1] = '\0'; 213 | return buf; 214 | } 215 | if (c == TK_CTL('k')) { /* digraph */ 216 | c1 = term_read(); 217 | if (TK_INT(c1)) 218 | return NULL; 219 | if (c1 == TK_CTL('k')) 220 | return ""; 221 | c2 = term_read(); 222 | if (TK_INT(c2)) 223 | return NULL; 224 | return conf_digraph(c1, c2); 225 | } 226 | if ((c & 0xc0) == 0xc0) { /* utf-8 character */ 227 | buf[0] = c; 228 | n = uc_len(buf); 229 | for (i = 1; i < n; i++) 230 | buf[i] = term_read(); 231 | buf[n] = '\0'; 232 | return buf; 233 | } 234 | return kmap_map(kmap, c); 235 | } 236 | 237 | /* read a character from the terminal */ 238 | char *led_read(int *kmap) 239 | { 240 | int c = term_read(); 241 | while (!TK_INT(c)) { 242 | switch (c) { 243 | case TK_CTL('f'): 244 | *kmap = xkmap_alt; 245 | break; 246 | case TK_CTL('e'): 247 | *kmap = 0; 248 | break; 249 | default: 250 | return led_readchar(c, *kmap); 251 | } 252 | c = term_read(); 253 | } 254 | return NULL; 255 | } 256 | 257 | static int led_match(char *out, int len, char *kwd, char *opt) 258 | { 259 | while (opt != NULL) { 260 | int i = 0; 261 | while (kwd[i] && kwd[i] == opt[i]) 262 | i++; 263 | if (kwd[i] == '\0') 264 | break; 265 | opt = strchr(opt, '\n') == NULL ? NULL : strchr(opt, '\n') + 1; 266 | } 267 | out[0] = '\0'; 268 | if (opt != NULL) { 269 | int i = 0; 270 | char *beg = opt + strlen(kwd); 271 | while (beg[i] && beg[i] != '\n' && i + 8 < len) 272 | i += uc_len(beg + i); 273 | memcpy(out, beg, i); 274 | out[i] = '\0'; 275 | return 0; 276 | } 277 | return 1; 278 | } 279 | 280 | /* read a line from the terminal */ 281 | static char *led_line(char *pref, char *post, char *ai, int ai_max, int *left, 282 | int *key, int *kmap, char *syn, char *hist, char *(*help)(char *ln)) 283 | { 284 | struct sbuf *sb; 285 | int ai_len = strlen(ai); 286 | int y, lnmode; 287 | int c = 0; 288 | char cmp[64] = ""; 289 | char *cs; 290 | sb = sbuf_make(); 291 | if (pref == NULL) 292 | pref = ""; 293 | if (post == NULL || !post[0]) 294 | post = cmp; 295 | while (1) { 296 | int c1 = c; 297 | if (hist != NULL) 298 | led_match(cmp, sizeof(cmp), sbuf_buf(sb), hist); 299 | led_printparts(ai, pref, sbuf_buf(sb), post, left, *kmap, syn); 300 | c = term_read(); 301 | switch (c) { 302 | case TK_CTL('f'): 303 | *kmap = xkmap_alt; 304 | continue; 305 | case TK_CTL('e'): 306 | *kmap = 0; 307 | continue; 308 | case TK_CTL('h'): 309 | case 127: 310 | if (sbuf_len(sb)) 311 | sbuf_cut(sb, led_lastchar(sbuf_buf(sb))); 312 | break; 313 | case TK_CTL('u'): 314 | sbuf_cut(sb, 0); 315 | break; 316 | case TK_CTL('w'): 317 | if (sbuf_len(sb)) 318 | sbuf_cut(sb, led_lastword(sbuf_buf(sb))); 319 | break; 320 | case TK_CTL('t'): 321 | if (ai_len < ai_max) { 322 | ai[ai_len++] = '\t'; 323 | ai[ai_len] = '\0'; 324 | } 325 | break; 326 | case TK_CTL('d'): 327 | /* when ai and pref are empty, remove the first space of sb */ 328 | if (ai_len == 0 && !pref[0]) { 329 | char *buf = sbuf_buf(sb); 330 | if (buf[0] == ' ' || buf[0] == '\t') { 331 | char *dup = uc_dup(buf + 1); 332 | sbuf_cut(sb, 0); 333 | sbuf_str(sb, dup); 334 | free(dup); 335 | } 336 | } 337 | if (ai_len > 0) 338 | ai[--ai_len] = '\0'; 339 | break; 340 | case TK_CTL('p'): 341 | if (reg_get(0, &lnmode)) 342 | sbuf_str(sb, reg_get(0, &lnmode)); 343 | break; 344 | case TK_CTL('r'): 345 | y = term_read(); 346 | if (y > 0 && reg_get(y, &lnmode)) 347 | sbuf_str(sb, reg_get(y, &lnmode)); 348 | break; 349 | case TK_CTL('a'): 350 | if (help != NULL && c1 != TK_CTL('a')) { 351 | char *ln = uc_cat(pref, sbuf_buf(sb)); 352 | char *ac = help(ln); 353 | if (ac != NULL) 354 | snprintf(cmp, sizeof(cmp), "%s", ac); 355 | free(ln); 356 | } else { 357 | sbuf_str(sb, cmp); 358 | cmp[0] = '\0'; 359 | } 360 | break; 361 | default: 362 | if (c == '\n' || TK_INT(c)) 363 | break; 364 | if ((cs = led_readchar(c, *kmap)) != NULL) 365 | sbuf_str(sb, cs); 366 | } 367 | if (c == '\n') 368 | led_printparts(ai, pref, sbuf_buf(sb), "", left, *kmap, syn); 369 | if (c == '\n' || TK_INT(c)) 370 | break; 371 | } 372 | *key = c; 373 | return sbuf_done(sb); 374 | } 375 | 376 | /* read an ex command */ 377 | char *led_prompt(char *pref, char *post, int *kmap, char *syn, char *hist) 378 | { 379 | int key; 380 | int td = td_set(+2); 381 | int left = 0; 382 | char *s = led_line(pref, post, "", 0, &left, &key, kmap, syn, hist, NULL); 383 | td_set(td); 384 | if (key == '\n') { 385 | struct sbuf *sb = sbuf_make(); 386 | if (pref) 387 | sbuf_str(sb, pref); 388 | sbuf_str(sb, s); 389 | if (post) 390 | sbuf_str(sb, post); 391 | free(s); 392 | return sbuf_done(sb); 393 | } 394 | free(s); 395 | return NULL; 396 | } 397 | 398 | static int linecount(char *s) 399 | { 400 | int n; 401 | for (n = 0; s; n++) 402 | if ((s = strchr(s, '\n'))) 403 | s++; 404 | return n; 405 | } 406 | 407 | /* read visual command input */ 408 | char *led_input(char *pref, char *post, int *left, int *kmap, char *syn, void (*nextline)(void), char *(*help)(char *ln)) 409 | { 410 | struct sbuf *sb = sbuf_make(); 411 | char ai[128]; 412 | int ai_max = sizeof(ai) - 1; 413 | int n = 0; 414 | int key; 415 | while (n < ai_max && (*pref == ' ' || *pref == '\t')) 416 | ai[n++] = *pref++; 417 | ai[n] = '\0'; 418 | while (1) { 419 | char *ln = led_line(pref, post, ai, ai_max, left, &key, kmap, syn, NULL, help); 420 | int ln_sp = 0; /* number of initial spaces in ln */ 421 | int lncnt = linecount(ln) - 1 + (key == '\n'); 422 | while (ln[ln_sp] && (ln[ln_sp] == ' ' || ln[ln_sp] == '\t')) 423 | ln_sp++; 424 | /* append the auto-indent only if there are other characters */ 425 | if (ln[ln_sp] || (pref && pref[0]) || 426 | (key != '\n' && post[0] && post[0] != '\n')) 427 | sbuf_str(sb, ai); 428 | if (pref) 429 | sbuf_str(sb, pref); 430 | sbuf_str(sb, ln); 431 | if (key == '\n') 432 | sbuf_chr(sb, '\n'); 433 | while (lncnt-- > 0) 434 | nextline(); 435 | if (!pref || !pref[0]) { /* updating autoindent */ 436 | int ai_len = ai_max ? strlen(ai) : 0; 437 | int ai_new = ln_sp; 438 | if (ai_len + ai_new > ai_max) 439 | ai_new = ai_max - ai_len; 440 | memcpy(ai + ai_len, ln, ai_new); 441 | ai[ai_len + ai_new] = '\0'; 442 | } 443 | if (!xai) 444 | ai[0] = '\0'; 445 | free(ln); 446 | if (key != '\n') 447 | break; 448 | pref = NULL; 449 | n = 0; 450 | while (xai && (post[n] == ' ' || post[n] == '\t')) 451 | n++; 452 | memmove(post, post + n, strlen(post) - n + 1); 453 | } 454 | sbuf_str(sb, post); 455 | if (TK_INT(key)) 456 | return sbuf_done(sb); 457 | sbuf_free(sb); 458 | return NULL; 459 | } 460 | -------------------------------------------------------------------------------- /mot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "vi.h" 4 | 5 | int lbuf_indents(struct lbuf *lb, int r) 6 | { 7 | char *ln = lbuf_get(lb, r); 8 | int o; 9 | if (!ln) 10 | return 0; 11 | for (o = 0; uc_isspace(ln); o++) 12 | ln = uc_next(ln); 13 | return o; 14 | } 15 | 16 | static int uc_nextdir(char **s, char *beg, int dir) 17 | { 18 | if (dir < 0) { 19 | if (*s == beg) 20 | return 1; 21 | *s = uc_prev(beg, *s); 22 | } else { 23 | *s = uc_next(*s); 24 | if (!(*s)[0]) 25 | return 1; 26 | } 27 | return 0; 28 | } 29 | 30 | int lbuf_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *row, int *off) 31 | { 32 | char *ln = lbuf_get(lb, *row); 33 | char *s; 34 | int dir = (cmd == 'f' || cmd == 't') ? +1 : -1; 35 | if (!ln) 36 | return 1; 37 | if (n < 0) 38 | dir = -dir; 39 | if (n < 0) 40 | n = -n; 41 | s = uc_chr(ln, *off); 42 | while (n > 0 && !uc_nextdir(&s, ln, dir)) 43 | if (uc_code(s) == uc_code(cs)) 44 | n--; 45 | if (!n && (cmd == 't' || cmd == 'T')) 46 | uc_nextdir(&s, ln, -dir); 47 | if (!n) 48 | *off = uc_off(ln, s - ln); 49 | return n != 0; 50 | } 51 | 52 | int lbuf_search(struct lbuf *lb, char *kw, int dir, int *r, int *o, int *len) 53 | { 54 | int offs[2]; 55 | int found = 0; 56 | int r0 = *r, o0 = *o; 57 | int i; 58 | struct rstr *re = rstr_make(kw, xic ? RE_ICASE : 0); 59 | if (!re) 60 | return 1; 61 | for (i = r0; !found && i >= 0 && i < lbuf_len(lb); i += dir) { 62 | char *s = lbuf_get(lb, i); 63 | int off = dir > 0 && r0 == i ? uc_chr(s, o0 + 1) - s : 0; 64 | while (rstr_find(re, s + off, 1, offs, 65 | off ? RE_NOTBOL : 0) >= 0) { 66 | if (dir < 0 && r0 == i && 67 | uc_off(s, off + offs[0]) >= o0) 68 | break; 69 | found = 1; 70 | *o = uc_off(s, off + offs[0]); 71 | *r = i; 72 | *len = uc_off(s + off + offs[0], offs[1] - offs[0]); 73 | off += offs[1] > offs[0] ? offs[1] : offs[1] + 1; 74 | if (dir > 0 || !s[off] || s[off] == '\n') 75 | break; 76 | } 77 | } 78 | rstr_free(re); 79 | return !found; 80 | } 81 | 82 | int lbuf_paragraphbeg(struct lbuf *lb, int dir, int *row, int *off) 83 | { 84 | while (*row >= 0 && *row < lbuf_len(lb) && !strcmp("\n", lbuf_get(lb, *row))) 85 | *row += dir; 86 | while (*row >= 0 && *row < lbuf_len(lb) && strcmp("\n", lbuf_get(lb, *row))) 87 | *row += dir; 88 | *row = MAX(0, MIN(*row, lbuf_len(lb) - 1)); 89 | *off = 0; 90 | return 0; 91 | } 92 | 93 | int lbuf_sectionbeg(struct lbuf *lb, int dir, char *sec, int *row, int *off) 94 | { 95 | struct rstr *re = rstr_make(sec, 0); 96 | *row += dir; 97 | while (*row >= 0 && *row < lbuf_len(lb)) { 98 | if (rstr_find(re, lbuf_get(lb, *row), 0, NULL, 0) >= 0) 99 | break; 100 | *row += dir; 101 | } 102 | rstr_free(re); 103 | *row = MAX(0, MIN(*row, lbuf_len(lb) - 1)); 104 | *off = 0; 105 | return 0; 106 | } 107 | 108 | static int lbuf_lnnext(struct lbuf *lb, int dir, int *r, int *o) 109 | { 110 | int off = *o + dir; 111 | if (off < 0 || !lbuf_get(lb, *r) || off >= uc_slen(lbuf_get(lb, *r))) 112 | return 1; 113 | *o = off; 114 | return 0; 115 | } 116 | 117 | int lbuf_eol(struct lbuf *lb, int row) 118 | { 119 | int len = lbuf_get(lb, row) ? uc_slen(lbuf_get(lb, row)) : 0; 120 | return len ? len - 1 : 0; 121 | } 122 | 123 | static int lbuf_next(struct lbuf *lb, int dir, int *r, int *o) 124 | { 125 | if (dir < 0 && *r >= lbuf_len(lb)) 126 | *r = MAX(0, lbuf_len(lb) - 1); 127 | if (lbuf_lnnext(lb, dir, r, o)) { 128 | if (!lbuf_get(lb, *r + dir)) 129 | return -1; 130 | *r += dir; 131 | *o = dir > 0 ? 0 : lbuf_eol(lb, *r); 132 | return 0; 133 | } 134 | return 0; 135 | } 136 | 137 | /* return a pointer to the character at visual position c of line r */ 138 | static char *lbuf_chr(struct lbuf *lb, int r, int c) 139 | { 140 | char *ln = lbuf_get(lb, r); 141 | return ln ? uc_chr(ln, c) : ""; 142 | } 143 | 144 | /* move to the last character of the word */ 145 | static int lbuf_wordlast(struct lbuf *lb, int kind, int dir, int *row, int *off) 146 | { 147 | if (!kind || !(uc_kind(lbuf_chr(lb, *row, *off)) & kind)) 148 | return 0; 149 | while (uc_kind(lbuf_chr(lb, *row, *off)) & kind) 150 | if (lbuf_next(lb, dir, row, off)) 151 | return 1; 152 | if (!(uc_kind(lbuf_chr(lb, *row, *off)) & kind)) 153 | lbuf_next(lb, -dir, row, off); 154 | return 0; 155 | } 156 | 157 | int lbuf_wordbeg(struct lbuf *lb, int big, int dir, int *row, int *off) 158 | { 159 | int nl; 160 | lbuf_wordlast(lb, big ? 3 : uc_kind(lbuf_chr(lb, *row, *off)), dir, row, off); 161 | nl = uc_code(lbuf_chr(lb, *row, *off)) == '\n'; 162 | if (lbuf_next(lb, dir, row, off)) 163 | return 1; 164 | while (uc_isspace(lbuf_chr(lb, *row, *off))) { 165 | nl += uc_code(lbuf_chr(lb, *row, *off)) == '\n'; 166 | if (nl == 2) 167 | return 0; 168 | if (lbuf_next(lb, dir, row, off)) 169 | return 1; 170 | } 171 | return 0; 172 | } 173 | 174 | int lbuf_wordend(struct lbuf *lb, int big, int dir, int *row, int *off) 175 | { 176 | int nl = 0; 177 | if (!uc_isspace(lbuf_chr(lb, *row, *off))) { 178 | if (lbuf_next(lb, dir, row, off)) 179 | return 1; 180 | nl = dir < 0 && uc_code(lbuf_chr(lb, *row, *off)) == '\n'; 181 | } 182 | nl += dir > 0 && uc_code(lbuf_chr(lb, *row, *off)) == '\n'; 183 | while (uc_isspace(lbuf_chr(lb, *row, *off))) { 184 | if (lbuf_next(lb, dir, row, off)) 185 | return 1; 186 | nl += uc_code(lbuf_chr(lb, *row, *off)) == '\n'; 187 | if (nl == 2) { 188 | if (dir < 0) 189 | lbuf_next(lb, -dir, row, off); 190 | return 0; 191 | } 192 | } 193 | if (lbuf_wordlast(lb, big ? 3 : uc_kind(lbuf_chr(lb, *row, *off)), dir, row, off)) 194 | return 1; 195 | return 0; 196 | } 197 | 198 | /* move to the matching character */ 199 | int lbuf_pair(struct lbuf *lb, int *row, int *off) 200 | { 201 | int r = *row, o = *off; 202 | char *pairs = "()[]{}"; 203 | int pchr; /* parenthesis character */ 204 | int pidx; /* index into pairs[] */ 205 | int dep = 1; /* parenthesis depth */ 206 | while ((pchr = (unsigned char) lbuf_chr(lb, r, o)[0]) && !strchr(pairs, pchr)) 207 | o++; 208 | if (!pchr) 209 | return 1; 210 | pidx = strchr(pairs, pchr) - pairs; 211 | while (!lbuf_next(lb, (pidx & 1) ? -1 : +1, &r, &o)) { 212 | int c = (unsigned char) lbuf_chr(lb, r, o)[0]; 213 | if (c == pairs[pidx ^ 1]) 214 | dep--; 215 | if (c == pairs[pidx]) 216 | dep++; 217 | if (!dep) { 218 | *row = r; 219 | *off = o; 220 | return 0; 221 | } 222 | } 223 | return 1; 224 | } 225 | -------------------------------------------------------------------------------- /reg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "vi.h" 6 | 7 | static char *bufs[256]; 8 | static int lnmode[256]; 9 | 10 | static char *reg_getraw(int c, int *ln) 11 | { 12 | if (ln != NULL) 13 | *ln = lnmode[c]; 14 | return bufs[c]; 15 | } 16 | 17 | char *reg_get(int c, int *lnmode) 18 | { 19 | static char ln[1024]; 20 | static char linno[16]; 21 | static char colno[16]; 22 | if (c == '"') 23 | c = 0; 24 | if (c == ';') { 25 | char *s = lbuf_get(xb, xrow); 26 | snprintf(ln, sizeof(ln), "%s", s ? s : ""); 27 | if (strchr(ln, '\n') != NULL) 28 | *strchr(ln, '\n') = '\0'; 29 | if (lnmode != NULL) 30 | *lnmode = 1; 31 | return ln; 32 | } 33 | if (c == '#') { 34 | snprintf(linno, sizeof(linno), "%d", xrow + 1); 35 | return linno; 36 | } 37 | if (c == '^') { 38 | snprintf(colno, sizeof(colno), "%d", xoff + 1); 39 | return colno; 40 | } 41 | return reg_getraw(c, lnmode); 42 | } 43 | 44 | static void reg_putraw(int c, char *s, int ln) 45 | { 46 | char *pre = isupper(c) && bufs[tolower(c)] ? bufs[tolower(c)] : ""; 47 | char *buf = malloc(strlen(pre) + strlen(s) + 1); 48 | strcpy(buf, pre); 49 | strcat(buf, s); 50 | free(bufs[tolower(c)]); 51 | bufs[tolower(c)] = buf; 52 | lnmode[tolower(c)] = ln; 53 | } 54 | 55 | void reg_put(int c, char *s, int ln) 56 | { 57 | int i, i_ln; 58 | char *i_s; 59 | if ((ln || strchr(s, '\n')) && (!c || isalpha(c))) { 60 | for (i = 8; i > 0; i--) 61 | if ((i_s = reg_get('0' + i, &i_ln))) 62 | reg_putraw('0' + i + 1, i_s, i_ln); 63 | reg_putraw('1', s, ln); 64 | } 65 | reg_putraw(c, s, ln); 66 | } 67 | 68 | void reg_done(void) 69 | { 70 | int i; 71 | for (i = 0; i < LEN(bufs); i++) 72 | free(bufs[i]); 73 | } 74 | -------------------------------------------------------------------------------- /regex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "regex.h" 6 | 7 | #define NGRPS 64 /* maximum number of groups */ 8 | #define NREPS 128 /* maximum repetitions */ 9 | #define NDEPT 256 /* re_rec() recursion depth limit */ 10 | 11 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 12 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 13 | 14 | /* regular expressions atoms */ 15 | #define RA_CHR '\0' /* character literal */ 16 | #define RA_BEG '^' /* string start */ 17 | #define RA_END '$' /* string end */ 18 | #define RA_ANY '.' /* any character */ 19 | #define RA_BRK '[' /* bracket expression */ 20 | #define RA_WBEG '<' /* word start */ 21 | #define RA_WEND '>' /* word end */ 22 | 23 | /* regular expression node types */ 24 | #define RN_ATOM '\0' /* regular expression */ 25 | #define RN_CAT 'c' /* concatenation */ 26 | #define RN_ALT '|' /* alternate expressions */ 27 | #define RN_GRP '(' /* pattern group */ 28 | 29 | /* regular expression program instructions */ 30 | #define RI_ATOM '\0' /* regular expression */ 31 | #define RI_FORK 'f' /* fork the execution */ 32 | #define RI_JUMP 'j' /* jump to the given instruction */ 33 | #define RI_MARK 'm' /* mark the current position */ 34 | #define RI_MATCH 'q' /* the pattern is matched */ 35 | 36 | /* regular expression atom */ 37 | struct ratom { 38 | int ra; /* atom type (RA_*) */ 39 | char *s; /* atom argument */ 40 | }; 41 | 42 | /* regular expression instruction */ 43 | struct rinst { 44 | struct ratom ra; /* regular expression atom (RI_ATOM) */ 45 | int ri; /* instruction type (RI_*) */ 46 | int a1, a2; /* destination of RI_FORK and RI_JUMP */ 47 | int mark; /* mark (RI_MARK) */ 48 | }; 49 | 50 | /* regular expression program */ 51 | struct regex { 52 | struct rinst *p; /* the program */ 53 | int n; /* number of instructions */ 54 | int flg; /* regcomp() flags */ 55 | }; 56 | 57 | /* regular expression matching state */ 58 | struct rstate { 59 | char *s; /* the current position in the string */ 60 | char *o; /* the beginning of the string */ 61 | int mark[NGRPS * 2]; /* marks for RI_MARK */ 62 | int pc; /* program counter */ 63 | int flg; /* flags passed to regcomp() and regexec() */ 64 | int dep; /* re_rec() depth */ 65 | }; 66 | 67 | /* regular expression tree; used for parsing */ 68 | struct rnode { 69 | struct ratom ra; /* regular expression atom (RN_ATOM) */ 70 | struct rnode *c1, *c2; /* children */ 71 | int mincnt, maxcnt; /* number of repetitions */ 72 | int grp; /* group number */ 73 | int rn; /* node type (RN_*) */ 74 | }; 75 | 76 | static struct rnode *rnode_make(int rn, struct rnode *c1, struct rnode *c2) 77 | { 78 | struct rnode *rnode = malloc(sizeof(*rnode)); 79 | memset(rnode, 0, sizeof(*rnode)); 80 | rnode->rn = rn; 81 | rnode->c1 = c1; 82 | rnode->c2 = c2; 83 | rnode->mincnt = 1; 84 | rnode->maxcnt = 1; 85 | return rnode; 86 | } 87 | 88 | static void rnode_free(struct rnode *rnode) 89 | { 90 | if (rnode->c1) 91 | rnode_free(rnode->c1); 92 | if (rnode->c2) 93 | rnode_free(rnode->c2); 94 | free(rnode->ra.s); 95 | free(rnode); 96 | } 97 | 98 | static int uc_len(char *s) 99 | { 100 | int c = (unsigned char) s[0]; 101 | if (~c & 0xc0) /* ASCII or invalid */ 102 | return c > 0; 103 | if (~c & 0x20) 104 | return 2; 105 | if (~c & 0x10) 106 | return 3; 107 | if (~c & 0x08) 108 | return 4; 109 | return 1; 110 | } 111 | 112 | static int uc_dec(char *s) 113 | { 114 | int c = (unsigned char) s[0]; 115 | if (~c & 0xc0) /* ASCII or invalid */ 116 | return c; 117 | if (~c & 0x20) 118 | return ((c & 0x1f) << 6) | (s[1] & 0x3f); 119 | if (~c & 0x10) 120 | return ((c & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f); 121 | if (~c & 0x08) 122 | return ((c & 0x07) << 18) | ((s[1] & 0x3f) << 12) | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); 123 | return c; 124 | } 125 | 126 | static void ratom_copy(struct ratom *dst, struct ratom *src) 127 | { 128 | dst->ra = src->ra; 129 | dst->s = NULL; 130 | if (src->s) { 131 | int len = strlen(src->s); 132 | dst->s = malloc(len + 1); 133 | memcpy(dst->s, src->s, len + 1); 134 | } 135 | } 136 | 137 | static int brk_len(char *s) 138 | { 139 | int n = 1; 140 | if (s[n] == '^') /* exclusion mark */ 141 | n++; 142 | if (s[n] == ']') /* handling []a] */ 143 | n++; 144 | while (s[n] && s[n] != ']') { 145 | if (s[n] == '[' && (s[n + 1] == ':' || s[n + 1] == '=')) 146 | while (s[n] && s[n] != ']') 147 | n++; 148 | if (s[n]) 149 | n++; 150 | } 151 | return s[n] == ']' ? n + 1 : n; 152 | } 153 | 154 | static void ratom_readbrk(struct ratom *ra, char **pat) 155 | { 156 | int len = brk_len(*pat); 157 | ra->ra = RA_BRK; 158 | ra->s = malloc(len + 1); 159 | memcpy(ra->s, *pat, len); 160 | ra->s[len] = '\0'; 161 | *pat += len; 162 | } 163 | 164 | static void ratom_read(struct ratom *ra, char **pat) 165 | { 166 | char *s; 167 | int len; 168 | switch ((unsigned char) **pat) { 169 | case '.': 170 | ra->ra = RA_ANY; 171 | (*pat)++; 172 | break; 173 | case '^': 174 | ra->ra = RA_BEG; 175 | (*pat)++; 176 | break; 177 | case '$': 178 | ra->ra = RA_END; 179 | (*pat)++; 180 | break; 181 | case '[': 182 | ratom_readbrk(ra, pat); 183 | break; 184 | case '\\': 185 | if ((*pat)[1] == '<' || (*pat)[1] == '>') { 186 | ra->ra = (*pat)[1] == '<' ? RA_WBEG : RA_WEND; 187 | *pat += 2; 188 | break; 189 | } 190 | (*pat)++; 191 | default: 192 | ra->ra = RA_CHR; 193 | s = *pat; 194 | while ((s == *pat) || !strchr(".^$[(|)*?+{\\", (unsigned char) s[0])) { 195 | int l = uc_len(s); 196 | if (s != *pat && s[l] != '\0' && strchr("*?+{", (unsigned char) s[l])) 197 | break; 198 | s += uc_len(s); 199 | } 200 | len = s - *pat; 201 | ra->s = malloc(len + 1); 202 | memcpy(ra->s, *pat, len); 203 | ra->s[len] = '\0'; 204 | *pat += len; 205 | } 206 | } 207 | 208 | static char *uc_beg(char *beg, char *s) 209 | { 210 | while (s > beg && (((unsigned char) *s) & 0xc0) == 0x80) 211 | s--; 212 | return s; 213 | } 214 | 215 | static int isword(char *s) 216 | { 217 | int c = (unsigned char) s[0]; 218 | return isalnum(c) || c == '_' || c > 127; 219 | } 220 | 221 | static char *brk_classes[][2] = { 222 | {":alnum:", "a-zA-Z0-9"}, 223 | {":alpha:", "a-zA-Z"}, 224 | {":blank:", " \t"}, 225 | {":digit:", "0-9"}, 226 | {":lower:", "a-z"}, 227 | {":print:", "\x20-\x7e"}, 228 | {":punct:", "][!\"#$%&'()*+,./:;<=>?@\\^_`{|}~-"}, 229 | {":space:", " \t\r\n\v\f"}, 230 | {":upper:", "A-Z"}, 231 | {":word:", "a-zA-Z0-9_"}, 232 | {":xdigit:", "a-fA-F0-9"}, 233 | }; 234 | 235 | static int brk_match(char *brk, int c, int flg) 236 | { 237 | int beg, end; 238 | int i; 239 | int not = brk[0] == '^'; 240 | char *p = not ? brk + 1 : brk; 241 | char *p0 = p; 242 | if (flg & REG_ICASE && c < 128 && isupper(c)) 243 | c = tolower(c); 244 | while (*p && (p == p0 || *p != ']')) { 245 | if (p[0] == '[' && p[1] == ':') { 246 | for (i = 0; i < LEN(brk_classes); i++) { 247 | char *cc = brk_classes[i][0]; 248 | char *cp = brk_classes[i][1]; 249 | if (!strncmp(cc, p + 1, strlen(cc))) 250 | if (!brk_match(cp, c, flg)) 251 | return not; 252 | } 253 | p += brk_len(p); 254 | continue; 255 | } 256 | beg = uc_dec(p); 257 | p += uc_len(p); 258 | end = beg; 259 | if (p[0] == '-' && p[1] && p[1] != ']') { 260 | p++; 261 | end = uc_dec(p); 262 | p += uc_len(p); 263 | } 264 | if (flg & REG_ICASE && beg < 128 && isupper(beg)) 265 | beg = tolower(beg); 266 | if (flg & REG_ICASE && end < 128 && isupper(end)) 267 | end = tolower(end); 268 | if (c >= beg && c <= end) 269 | return not; 270 | } 271 | return !not; 272 | } 273 | 274 | static int ratom_match(struct ratom *ra, struct rstate *rs) 275 | { 276 | if (ra->ra == RA_CHR && !(rs->flg & REG_ICASE)) { 277 | char *s = ra->s; 278 | char *r = rs->s; 279 | while (*s && *s == *r) 280 | s++, r++; 281 | if (*s) 282 | return 1; 283 | rs->s = r; 284 | return 0; 285 | } 286 | if (ra->ra == RA_CHR) { 287 | int pos = 0; 288 | while (ra->s[pos]) { 289 | int c1 = uc_dec(ra->s + pos); 290 | int c2 = uc_dec(rs->s + pos); 291 | if (rs->flg & REG_ICASE && c1 < 128 && isupper(c1)) 292 | c1 = tolower(c1); 293 | if (rs->flg & REG_ICASE && c2 < 128 && isupper(c2)) 294 | c2 = tolower(c2); 295 | if (c1 != c2) 296 | return 1; 297 | pos += uc_len(ra->s + pos); 298 | } 299 | rs->s += pos; 300 | return 0; 301 | } 302 | if (ra->ra == RA_ANY) { 303 | if (!rs->s[0] || (rs->s[0] == '\n' && !!(rs->flg & REG_NEWLINE))) 304 | return 1; 305 | rs->s += uc_len(rs->s); 306 | return 0; 307 | } 308 | if (ra->ra == RA_BRK) { 309 | int c = uc_dec(rs->s); 310 | if (!c || (c == '\n' && !!(rs->flg & REG_NEWLINE) && ra->s[1] == '^')) 311 | return 1; 312 | rs->s += uc_len(rs->s); 313 | return brk_match(ra->s + 1, c, rs->flg); 314 | } 315 | if (ra->ra == RA_BEG && rs->s == rs->o) 316 | return !!(rs->flg & REG_NOTBOL); 317 | if (ra->ra == RA_BEG && rs->s > rs->o && rs->s[-1] == '\n') 318 | return !(rs->flg & REG_NEWLINE); 319 | if (ra->ra == RA_END && rs->s[0] == '\0') 320 | return !!(rs->flg & REG_NOTEOL); 321 | if (ra->ra == RA_END && rs->s[0] == '\n') 322 | return !(rs->flg & REG_NEWLINE); 323 | if (ra->ra == RA_WBEG) 324 | return !((rs->s == rs->o || !isword(uc_beg(rs->o, rs->s - 1))) && 325 | isword(rs->s)); 326 | if (ra->ra == RA_WEND) 327 | return !(rs->s != rs->o && isword(uc_beg(rs->o, rs->s - 1)) && 328 | (!rs->s[0] || !isword(rs->s))); 329 | return 1; 330 | } 331 | 332 | static struct rnode *rnode_parse(char **pat); 333 | 334 | static struct rnode *rnode_grp(char **pat) 335 | { 336 | struct rnode *rnode = NULL; 337 | if ((*pat)[0] != '(') 338 | return NULL; 339 | ++*pat; 340 | if ((*pat)[0] != ')') { 341 | rnode = rnode_parse(pat); 342 | if (!rnode) 343 | return NULL; 344 | } 345 | if ((*pat)[0] != ')') { 346 | rnode_free(rnode); 347 | return NULL; 348 | } 349 | ++*pat; 350 | return rnode_make(RN_GRP, rnode, NULL); 351 | } 352 | 353 | static struct rnode *rnode_atom(char **pat) 354 | { 355 | struct rnode *rnode; 356 | if (!**pat) 357 | return NULL; 358 | if ((*pat)[0] == '|' || (*pat)[0] == ')') 359 | return NULL; 360 | if ((*pat)[0] == '(') { 361 | rnode = rnode_grp(pat); 362 | } else { 363 | rnode = rnode_make(RN_ATOM, NULL, NULL); 364 | ratom_read(&rnode->ra, pat); 365 | } 366 | if (!rnode) 367 | return NULL; 368 | if ((*pat)[0] == '*' || (*pat)[0] == '?') { 369 | rnode->mincnt = 0; 370 | rnode->maxcnt = (*pat)[0] == '*' ? -1 : 1; 371 | ++*pat; 372 | } 373 | if ((*pat)[0] == '+') { 374 | rnode->mincnt = 1; 375 | rnode->maxcnt = -1; 376 | ++*pat; 377 | } 378 | if ((*pat)[0] == '{') { 379 | rnode->mincnt = 0; 380 | rnode->maxcnt = 0; 381 | ++*pat; 382 | while (isdigit((unsigned char) **pat)) 383 | rnode->mincnt = rnode->mincnt * 10 + *(*pat)++ - '0'; 384 | if (**pat == ',') { 385 | (*pat)++; 386 | if ((*pat)[0] == '}') 387 | rnode->maxcnt = -1; 388 | while (isdigit((unsigned char) **pat)) 389 | rnode->maxcnt = rnode->maxcnt * 10 + *(*pat)++ - '0'; 390 | } else { 391 | rnode->maxcnt = rnode->mincnt; 392 | } 393 | ++*pat; 394 | if (rnode->mincnt > NREPS || rnode->maxcnt > NREPS) { 395 | rnode_free(rnode); 396 | return NULL; 397 | } 398 | } 399 | return rnode; 400 | } 401 | 402 | static struct rnode *rnode_seq(char **pat) 403 | { 404 | struct rnode *c1 = rnode_atom(pat); 405 | struct rnode *c2; 406 | if (!c1) 407 | return NULL; 408 | c2 = rnode_seq(pat); 409 | return c2 ? rnode_make(RN_CAT, c1, c2) : c1; 410 | } 411 | 412 | static struct rnode *rnode_parse(char **pat) 413 | { 414 | struct rnode *c1 = rnode_seq(pat); 415 | struct rnode *c2; 416 | if ((*pat)[0] != '|') 417 | return c1; 418 | ++*pat; 419 | c2 = rnode_parse(pat); 420 | return c2 ? rnode_make(RN_ALT, c1, c2) : c1; 421 | } 422 | 423 | static int rnode_count(struct rnode *rnode) 424 | { 425 | int n = 1; 426 | if (!rnode) 427 | return 0; 428 | if (rnode->rn == RN_CAT) 429 | n = rnode_count(rnode->c1) + rnode_count(rnode->c2); 430 | if (rnode->rn == RN_ALT) 431 | n = rnode_count(rnode->c1) + rnode_count(rnode->c2) + 2; 432 | if (rnode->rn == RN_GRP) 433 | n = rnode_count(rnode->c1) + 2; 434 | if (rnode->mincnt == 0 && rnode->maxcnt == 0) 435 | return 0; 436 | if (rnode->mincnt == 1 && rnode->maxcnt == 1) 437 | return n; 438 | if (rnode->maxcnt < 0) { 439 | n = (rnode->mincnt + 1) * n + 1; 440 | } else { 441 | n = (rnode->mincnt + rnode->maxcnt) * n + 442 | rnode->maxcnt - rnode->mincnt; 443 | } 444 | if (!rnode->mincnt) 445 | n++; 446 | return n; 447 | } 448 | 449 | static int rnode_grpnum(struct rnode *rnode, int num) 450 | { 451 | int cur = 0; 452 | if (!rnode) 453 | return 0; 454 | if (rnode->rn == RN_GRP) 455 | rnode->grp = num + cur++; 456 | cur += rnode_grpnum(rnode->c1, num + cur); 457 | cur += rnode_grpnum(rnode->c2, num + cur); 458 | return cur; 459 | } 460 | 461 | static int re_insert(struct regex *p, int ri) 462 | { 463 | p->p[p->n++].ri = ri; 464 | return p->n - 1; 465 | } 466 | 467 | static void rnode_emit(struct rnode *n, struct regex *p); 468 | 469 | static void rnode_emitnorep(struct rnode *n, struct regex *p) 470 | { 471 | int fork, done, mark; 472 | if (n->rn == RN_ALT) { 473 | fork = re_insert(p, RI_FORK); 474 | p->p[fork].a1 = p->n; 475 | rnode_emit(n->c1, p); 476 | done = re_insert(p, RI_JUMP); 477 | p->p[fork].a2 = p->n; 478 | rnode_emit(n->c2, p); 479 | p->p[done].a1 = p->n; 480 | } 481 | if (n->rn == RN_CAT) { 482 | rnode_emit(n->c1, p); 483 | rnode_emit(n->c2, p); 484 | } 485 | if (n->rn == RN_GRP) { 486 | mark = re_insert(p, RI_MARK); 487 | p->p[mark].mark = 2 * n->grp; 488 | rnode_emit(n->c1, p); 489 | mark = re_insert(p, RI_MARK); 490 | p->p[mark].mark = 2 * n->grp + 1; 491 | } 492 | if (n->rn == RN_ATOM) { 493 | int atom = re_insert(p, RI_ATOM); 494 | ratom_copy(&p->p[atom].ra, &n->ra); 495 | } 496 | } 497 | 498 | static void rnode_emit(struct rnode *n, struct regex *p) 499 | { 500 | int last; 501 | int jmpend[NREPS]; 502 | int jmpend_cnt = 0; 503 | int i; 504 | if (!n) 505 | return; 506 | if (n->mincnt == 0 && n->maxcnt == 0) 507 | return; 508 | if (n->mincnt == 1 && n->maxcnt == 1) { 509 | rnode_emitnorep(n, p); 510 | return; 511 | } 512 | if (n->mincnt == 0) { 513 | int fork = re_insert(p, RI_FORK); 514 | p->p[fork].a1 = p->n; 515 | jmpend[jmpend_cnt++] = fork; 516 | } 517 | for (i = 0; i < MAX(1, n->mincnt); i++) { 518 | last = p->n; 519 | rnode_emitnorep(n, p); 520 | } 521 | if (n->maxcnt < 0) { 522 | int fork; 523 | fork = re_insert(p, RI_FORK); 524 | p->p[fork].a1 = last; 525 | p->p[fork].a2 = p->n; 526 | } 527 | for (i = MAX(1, n->mincnt); i < n->maxcnt; i++) { 528 | int fork = re_insert(p, RI_FORK); 529 | p->p[fork].a1 = p->n; 530 | jmpend[jmpend_cnt++] = fork; 531 | rnode_emitnorep(n, p); 532 | } 533 | for (i = 0; i < jmpend_cnt; i++) 534 | p->p[jmpend[i]].a2 = p->n; 535 | } 536 | 537 | int regcomp(regex_t *preg, char *pat, int flg) 538 | { 539 | struct rnode *rnode = rnode_parse(&pat); 540 | struct regex *re; 541 | int n = rnode_count(rnode) + 3; 542 | int mark; 543 | if (!rnode) 544 | return 1; 545 | rnode_grpnum(rnode, 1); 546 | re = malloc(sizeof(*re)); 547 | memset(re, 0, sizeof(*re)); 548 | re->p = malloc(n * sizeof(re->p[0])); 549 | memset(re->p, 0, n * sizeof(re->p[0])); 550 | mark = re_insert(re, RI_MARK); 551 | re->p[mark].mark = 0; 552 | rnode_emit(rnode, re); 553 | mark = re_insert(re, RI_MARK); 554 | re->p[mark].mark = 1; 555 | mark = re_insert(re, RI_MATCH); 556 | rnode_free(rnode); 557 | re->flg = flg; 558 | *preg = re; 559 | return 0; 560 | } 561 | 562 | void regfree(regex_t *preg) 563 | { 564 | struct regex *re = *preg; 565 | int i; 566 | for (i = 0; i < re->n; i++) 567 | if (re->p[i].ri == RI_ATOM) 568 | free(re->p[i].ra.s); 569 | free(re->p); 570 | free(re); 571 | } 572 | 573 | static int re_rec(struct regex *re, struct rstate *rs) 574 | { 575 | struct rinst *ri = NULL; 576 | if (rs->dep >= NDEPT) 577 | return 1; 578 | rs->dep++; 579 | while (1) { 580 | ri = &re->p[rs->pc]; 581 | if (ri->ri == RI_ATOM) { 582 | if (ratom_match(&ri->ra, rs)) 583 | return 1; 584 | rs->pc++; 585 | continue; 586 | } 587 | if (ri->ri == RI_MARK) { 588 | if (ri->mark < NGRPS) 589 | rs->mark[ri->mark] = rs->s - rs->o; 590 | rs->pc++; 591 | continue; 592 | } 593 | if (ri->ri == RI_JUMP) { 594 | rs->pc = ri->a1; 595 | continue; 596 | } 597 | if (ri->ri == RI_FORK) { 598 | struct rstate base = *rs; 599 | rs->pc = ri->a1; 600 | if (!re_rec(re, rs)) 601 | return 0; 602 | *rs = base; 603 | rs->pc = ri->a2; 604 | continue; 605 | } 606 | break; 607 | } 608 | rs->dep--; 609 | return ri->ri != RI_MATCH; 610 | } 611 | 612 | static int re_recmatch(struct regex *re, struct rstate *rs, int nsub, regmatch_t *psub) 613 | { 614 | int i; 615 | rs->pc = 0; 616 | rs->dep = 0; 617 | for (i = 0; i < LEN(rs->mark) && i < nsub * 2; i++) 618 | rs->mark[i] = -1; 619 | if (!re_rec(re, rs)) { 620 | for (i = 0; i < nsub; i++) { 621 | psub[i].rm_so = i * 2 < LEN(rs->mark) ? rs->mark[i * 2] : -1; 622 | psub[i].rm_eo = i * 2 < LEN(rs->mark) ? rs->mark[i * 2 + 1] : -1; 623 | } 624 | return 0; 625 | } 626 | return 1; 627 | } 628 | 629 | int regexec(regex_t *preg, char *s, int nsub, regmatch_t psub[], int flg) 630 | { 631 | struct regex *re = *preg; 632 | struct rstate rs; 633 | char *o = s; 634 | memset(&rs, 0, sizeof(rs)); 635 | rs.flg = re->flg | flg; 636 | rs.o = s; 637 | while (*o) { 638 | rs.s = o = s; 639 | s += uc_len(s); 640 | if (!re_recmatch(re, &rs, flg & REG_NOSUB ? 0 : nsub, psub)) 641 | return 0; 642 | } 643 | return 1; 644 | } 645 | 646 | int regerror(int errcode, regex_t *preg, char *errbuf, int errbuf_size) 647 | { 648 | return 0; 649 | } 650 | -------------------------------------------------------------------------------- /regex.h: -------------------------------------------------------------------------------- 1 | #define REG_EXTENDED 0x01 2 | #define REG_NOSUB 0x02 3 | #define REG_ICASE 0x04 4 | #define REG_NEWLINE 0x08 5 | #define REG_NOTBOL 0x10 6 | #define REG_NOTEOL 0x20 7 | 8 | typedef struct { 9 | long rm_so; 10 | long rm_eo; 11 | } regmatch_t; 12 | 13 | typedef struct regex *regex_t; 14 | 15 | int regcomp(regex_t *preg, char *regex, int cflags); 16 | int regexec(regex_t *preg, char *str, int nmatch, regmatch_t pmatch[], int eflags); 17 | int regerror(int errcode, regex_t *preg, char *errbuf, int errbuf_size); 18 | void regfree(regex_t *preg); 19 | -------------------------------------------------------------------------------- /ren.c: -------------------------------------------------------------------------------- 1 | /* rendering strings */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "vi.h" 7 | 8 | /* specify the screen position of the characters in s; reordering version */ 9 | static int *ren_position_reorder(char *s) 10 | { 11 | int i, n; 12 | char **chrs = uc_chop(s, &n); 13 | int *off, *pos; 14 | int cpos = 0; 15 | pos = malloc((n + 1) * sizeof(pos[0])); 16 | for (i = 0; i < n; i++) 17 | pos[i] = i; 18 | if (xorder) 19 | dir_reorder(s, pos); 20 | off = malloc(n * sizeof(off[0])); 21 | for (i = 0; i < n; i++) 22 | off[pos[i]] = i; 23 | for (i = 0; i < n; i++) { 24 | pos[off[i]] = cpos; 25 | cpos += ren_cwid(chrs[off[i]], cpos); 26 | } 27 | pos[n] = cpos; 28 | free(chrs); 29 | free(off); 30 | return pos; 31 | } 32 | 33 | /* specify the screen position of the characters in s; fast version */ 34 | int *ren_position(char *s) 35 | { 36 | int cpos = 0; 37 | int *pos; 38 | int i; 39 | int n = uc_slen(s); 40 | if (n <= xlim && (xorder == 2 || (xorder == 1 && (n < strlen(s) || dir_context(s) < 0)))) 41 | return ren_position_reorder(s); 42 | pos = malloc((n + 1) * sizeof(pos[0])); 43 | for (i = 0; i < n; i++, s += uc_len(s)) { 44 | pos[i] = cpos; 45 | cpos += ren_cwid(s, cpos); 46 | } 47 | pos[i] = cpos; 48 | return pos; 49 | } 50 | 51 | int ren_wid(char *s) 52 | { 53 | int *pos = ren_position(s); 54 | int n = uc_slen(s); 55 | int ret = pos[n]; 56 | free(pos); 57 | return ret; 58 | } 59 | 60 | /* find the next character after visual position p; if cur, start from p itself */ 61 | static int pos_next(int *pos, int n, int p, int cur) 62 | { 63 | int i, ret = -1; 64 | for (i = 0; i < n; i++) 65 | if (pos[i] - !cur >= p && (ret < 0 || pos[i] < pos[ret])) 66 | ret = i; 67 | return ret >= 0 ? pos[ret] : -1; 68 | } 69 | 70 | /* find the previous character after visual position p; if cur, start from p itself */ 71 | static int pos_prev(int *pos, int n, int p, int cur) 72 | { 73 | int i, ret = -1; 74 | for (i = 0; i < n; i++) 75 | if (pos[i] + !cur <= p && (ret < 0 || pos[i] > pos[ret])) 76 | ret = i; 77 | return ret >= 0 ? pos[ret] : -1; 78 | } 79 | 80 | /* convert character offset to visual position */ 81 | int ren_pos(char *s, int off) 82 | { 83 | int n = uc_slen(s); 84 | int *pos = ren_position(s); 85 | int ret = off < n ? pos[off] : 0; 86 | free(pos); 87 | return ret; 88 | } 89 | 90 | /* convert visual position to character offset */ 91 | int ren_off(char *s, int p) 92 | { 93 | int off = -1; 94 | int n = uc_slen(s); 95 | int *pos = ren_position(s); 96 | int i; 97 | p = pos_prev(pos, n, p, 1); 98 | for (i = 0; i < n; i++) 99 | if (pos[i] == p) 100 | off = i; 101 | free(pos); 102 | return off >= 0 ? off : 0; 103 | } 104 | 105 | /* adjust cursor position */ 106 | int ren_cursor(char *s, int p) 107 | { 108 | int n, next; 109 | int *pos; 110 | if (!s) 111 | return 0; 112 | n = uc_slen(s); 113 | pos = ren_position(s); 114 | p = pos_prev(pos, n, p, 1); 115 | if (uc_code(uc_chr(s, ren_off(s, p))) == '\n') 116 | p = pos_prev(pos, n, p, 0); 117 | next = pos_next(pos, n, p, 0); 118 | p = (next >= 0 ? next : pos[n]) - 1; 119 | free(pos); 120 | return p >= 0 ? p : 0; 121 | } 122 | 123 | /* return an offset before EOL */ 124 | int ren_noeol(char *s, int o) 125 | { 126 | int n = s ? uc_slen(s) : 0; 127 | if (o >= n) 128 | o = MAX(0, n - 1); 129 | return o > 0 && uc_chr(s, o)[0] == '\n' ? o - 1 : o; 130 | } 131 | 132 | /* the position of the next character */ 133 | int ren_next(char *s, int p, int dir) 134 | { 135 | int n = uc_slen(s); 136 | int *pos = ren_position(s); 137 | p = pos_prev(pos, n, p, 1); 138 | if (dir >= 0) 139 | p = pos_next(pos, n, p, 0); 140 | else 141 | p = pos_prev(pos, n, p, 0); 142 | free(pos); 143 | return s && uc_chr(s, ren_off(s, p))[0] != '\n' ? p : -1; 144 | } 145 | 146 | static char *ren_placeholder(char *s, int *wid) 147 | { 148 | static int bits = 0xffff; /* common bits in placeholders */ 149 | char *src, *dst; 150 | int i; 151 | if (bits == 0xffff) { 152 | for (i = 0; !conf_placeholder(i, &src, &dst, wid); i++) 153 | bits &= (unsigned char) *src; 154 | } 155 | if ((((unsigned char) *s) & bits) == bits) { 156 | for (i = 0; !conf_placeholder(i, &src, &dst, wid); i++) 157 | if (src[0] == s[0] && uc_code(src) == uc_code(s)) 158 | return dst; 159 | } 160 | if (wid) 161 | *wid = 1; 162 | if (uc_isbell(s)) 163 | return "�"; 164 | return NULL; 165 | } 166 | 167 | int ren_cwid(char *s, int pos) 168 | { 169 | int wid; 170 | if (s[0] == '\t') 171 | return 8 - (pos & 7); 172 | if (ren_placeholder(s, &wid)) 173 | return wid; 174 | return uc_wid(s); 175 | } 176 | 177 | char *ren_translate(char *s, char *ln) 178 | { 179 | char *p = ren_placeholder(s, NULL); 180 | return p || !xshape ? p : uc_shape(ln, s); 181 | } 182 | -------------------------------------------------------------------------------- /rset.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "regex.h" 5 | #include "vi.h" 6 | 7 | /* regular expression set */ 8 | struct rset { 9 | regex_t regex; /* the combined regular expression */ 10 | int n; /* number of regular expressions in this set */ 11 | int *grp; /* the group assigned to each subgroup */ 12 | int *setgrpcnt; /* number of groups in each regular expression */ 13 | int grpcnt; /* group count */ 14 | }; 15 | 16 | static int re_groupcount(char *s) 17 | { 18 | int n = 0; /* number of groups */ 19 | int brk = 0; /* one if inside a bracket expression */ 20 | int brk2 = 0; /* nested bracket type: ':', '*', or '=' */ 21 | while (*s) { 22 | if (!brk) { 23 | if (s[0] == '(') 24 | n++; 25 | if (s[0] == '\\' && s[1]) { 26 | s++; 27 | } else if (s[0] == '[' && s[1] && s[2]) { 28 | s += s[1] == '^' ? 2 : 1; 29 | brk = 1; 30 | } 31 | } else { 32 | if (!brk2) { 33 | if (s[0] == ']') 34 | brk = 0; 35 | if (s[0] == '[' && (s[1] == ':' || s[1] == '*' || s[1] == '=')) { 36 | brk2 = s[1]; 37 | s++; 38 | } 39 | } else if (s[0] == brk2 && s[1] == ']') { 40 | brk2 = 0; 41 | s++; 42 | } 43 | } 44 | s++; 45 | } 46 | return n; 47 | } 48 | 49 | struct rset *rset_make(int n, char **re, int flg) 50 | { 51 | struct rset *rs = malloc(sizeof(*rs)); 52 | struct sbuf *sb = sbuf_make(); 53 | int regex_flg = REG_EXTENDED | (flg & RE_ICASE ? REG_ICASE : 0); 54 | int i; 55 | memset(rs, 0, sizeof(*rs)); 56 | rs->grp = malloc((n + 1) * sizeof(rs->grp[0])); 57 | rs->setgrpcnt = malloc((n + 1) * sizeof(rs->setgrpcnt[0])); 58 | rs->grpcnt = 2; 59 | rs->n = n; 60 | sbuf_chr(sb, '('); 61 | for (i = 0; i < n; i++) { 62 | if (!re[i]) { 63 | rs->grp[i] = -1; 64 | rs->setgrpcnt[i] = 0; 65 | continue; 66 | } 67 | if (sbuf_len(sb) > 1) 68 | sbuf_chr(sb, '|'); 69 | sbuf_chr(sb, '('); 70 | sbuf_str(sb, re[i]); 71 | sbuf_chr(sb, ')'); 72 | rs->grp[i] = rs->grpcnt; 73 | rs->setgrpcnt[i] = re_groupcount(re[i]); 74 | rs->grpcnt += 1 + rs->setgrpcnt[i]; 75 | } 76 | rs->grp[n] = rs->grpcnt; 77 | sbuf_chr(sb, ')'); 78 | if (regcomp(&rs->regex, sbuf_buf(sb), regex_flg)) { 79 | free(rs->grp); 80 | free(rs->setgrpcnt); 81 | free(rs); 82 | sbuf_free(sb); 83 | return NULL; 84 | } 85 | sbuf_free(sb); 86 | return rs; 87 | } 88 | 89 | /* return the index of the matching regular expression or -1 if none matches */ 90 | int rset_find(struct rset *rs, char *s, int n, int *grps, int flg) 91 | { 92 | regmatch_t *subs; 93 | int found, i, set = -1; 94 | int regex_flg = REG_NEWLINE; 95 | if (rs->grpcnt <= 2) 96 | return -1; 97 | if (flg & RE_NOTBOL) 98 | regex_flg |= REG_NOTBOL; 99 | if (flg & RE_NOTEOL) 100 | regex_flg |= REG_NOTEOL; 101 | subs = malloc(rs->grpcnt * sizeof(subs[0])); 102 | found = !regexec(&rs->regex, s, rs->grpcnt, subs, regex_flg); 103 | for (i = 0; found && i < rs->n; i++) 104 | if (rs->grp[i] >= 0 && subs[rs->grp[i]].rm_so >= 0) 105 | set = i; 106 | if (found && set >= 0) { 107 | for (i = 0; i < n; i++) { 108 | int grp = rs->grp[set] + i; 109 | if (i < rs->setgrpcnt[set] + 1) { 110 | grps[i * 2] = subs[grp].rm_so; 111 | grps[i * 2 + 1] = subs[grp].rm_eo; 112 | } else { 113 | grps[i * 2 + 0] = -1; 114 | grps[i * 2 + 1] = -1; 115 | } 116 | } 117 | } 118 | free(subs); 119 | return set; 120 | } 121 | 122 | void rset_free(struct rset *rs) 123 | { 124 | regfree(&rs->regex); 125 | free(rs->setgrpcnt); 126 | free(rs->grp); 127 | free(rs); 128 | } 129 | 130 | /* read a regular expression enclosed in a delimiter */ 131 | char *re_read(char **src) 132 | { 133 | struct sbuf *sbuf; 134 | char *s = *src; 135 | int delim = (unsigned char) *s++; 136 | if (!delim) 137 | return NULL; 138 | sbuf = sbuf_make(); 139 | while (*s && *s != delim) { 140 | if (s[0] == '\\' && s[1]) 141 | if (*(++s) != delim) 142 | sbuf_chr(sbuf, '\\'); 143 | sbuf_chr(sbuf, (unsigned char) *s++); 144 | } 145 | *src = *s ? s + 1 : s; 146 | return sbuf_done(sbuf); 147 | } 148 | -------------------------------------------------------------------------------- /rstr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "vi.h" 6 | 7 | struct rstr { 8 | struct rset *rs; /* only for regex patterns */ 9 | char *str; /* for simple, non-regex patterns */ 10 | int icase; /* ignore case */ 11 | int lbeg, lend; /* match line beg/end */ 12 | int wbeg, wend; /* match word beg/end */ 13 | }; 14 | 15 | /* return zero if a simple pattern is given */ 16 | static int rstr_simple(struct rstr *rs, char *re) 17 | { 18 | char *beg; 19 | char *end; 20 | rs->lbeg = re[0] == '^'; 21 | if (rs->lbeg) 22 | re++; 23 | rs->wbeg = re[0] == '\\' && re[1] == '<'; 24 | if (rs->wbeg) 25 | re += 2; 26 | beg = re; 27 | while (re[0] && !strchr("\\.*+?[]{}()$", (unsigned char) re[0])) 28 | re++; 29 | end = re; 30 | rs->wend = re[0] == '\\' && re[1] == '>'; 31 | if (rs->wend) 32 | re += 2; 33 | rs->lend = re[0] == '$'; 34 | if (rs->lend) 35 | re++; 36 | if (!re[0]) { 37 | int len = end - beg; 38 | rs->str = malloc(len + 1); 39 | memcpy(rs->str, beg, len); 40 | rs->str[len] = '\0'; 41 | return 0; 42 | } 43 | return 1; 44 | } 45 | 46 | struct rstr *rstr_make(char *re, int flg) 47 | { 48 | struct rstr *rs = malloc(sizeof(*rs)); 49 | memset(rs, 0, sizeof(*rs)); 50 | rs->icase = flg & RE_ICASE; 51 | if (rstr_simple(rs, re)) 52 | rs->rs = rset_make(1, &re, flg); 53 | if (!rs->rs && !rs->str) { 54 | free(rs); 55 | return NULL; 56 | } 57 | return rs; 58 | } 59 | 60 | static int isword(char *s) 61 | { 62 | int c = (unsigned char) s[0]; 63 | return isalnum(c) || c == '_' || c > 127; 64 | } 65 | 66 | static int match_case(char *s, char *r, int icase) 67 | { 68 | for (; *r && *s; s++, r++) { 69 | if (!icase && *s != *r) 70 | return 1; 71 | if (icase && tolower((unsigned char) *s) != tolower((unsigned char) *r)) 72 | return 1; 73 | } 74 | return *r; 75 | } 76 | 77 | /* return zero if an occurrence is found */ 78 | int rstr_find(struct rstr *rs, char *s, int n, int *grps, int flg) 79 | { 80 | int len; 81 | char *beg, *end; 82 | char *r; 83 | if (rs->rs) 84 | return rset_find(rs->rs, s, n, grps, flg); 85 | if ((rs->lbeg && (flg & RE_NOTBOL)) || (rs->lend && (flg & RE_NOTEOL))) 86 | return -1; 87 | len = strlen(rs->str); 88 | beg = s; 89 | end = s + strlen(s) - len - 1; 90 | if (end < beg) 91 | return -1; 92 | if (rs->lend) 93 | beg = end; 94 | if (rs->lbeg) 95 | end = s; 96 | for (r = beg; r <= end; r++) { 97 | if (rs->wbeg && r > s && (isword(r - 1) || !isword(r))) 98 | continue; 99 | if (rs->wend && r[len] && (!isword(r + len - 1) || isword(r + len))) 100 | continue; 101 | if (!match_case(r, rs->str, rs->icase)) { 102 | if (n >= 1) { 103 | grps[0] = r - s; 104 | grps[1] = r - s + len; 105 | } 106 | return 0; 107 | } 108 | } 109 | return -1; 110 | } 111 | 112 | void rstr_free(struct rstr *rs) 113 | { 114 | if (rs->rs) 115 | rset_free(rs->rs); 116 | free(rs->str); 117 | free(rs); 118 | } 119 | -------------------------------------------------------------------------------- /sbuf.c: -------------------------------------------------------------------------------- 1 | /* variable length string buffer */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "vi.h" 7 | 8 | #define SBUFSZ 128 9 | #define ALIGN(n, a) (((n) + (a) - 1) & ~((a) - 1)) 10 | #define NEXTSZ(o, r) ALIGN(MAX((o) * 2, (o) + (r)), SBUFSZ) 11 | 12 | struct sbuf { 13 | char *s; /* allocated buffer */ 14 | int s_n; /* length of the string stored in s[] */ 15 | int s_sz; /* size of memory allocated for s[] */ 16 | }; 17 | 18 | static void sbuf_extend(struct sbuf *sbuf, int newsz) 19 | { 20 | char *s = sbuf->s; 21 | sbuf->s_sz = newsz; 22 | sbuf->s = malloc(sbuf->s_sz); 23 | if (sbuf->s_n) 24 | memcpy(sbuf->s, s, sbuf->s_n); 25 | free(s); 26 | } 27 | 28 | struct sbuf *sbuf_make(void) 29 | { 30 | struct sbuf *sb = malloc(sizeof(*sb)); 31 | memset(sb, 0, sizeof(*sb)); 32 | return sb; 33 | } 34 | 35 | char *sbuf_buf(struct sbuf *sb) 36 | { 37 | if (!sb->s) 38 | sbuf_extend(sb, 1); 39 | sb->s[sb->s_n] = '\0'; 40 | return sb->s; 41 | } 42 | 43 | char *sbuf_done(struct sbuf *sb) 44 | { 45 | char *s = sbuf_buf(sb); 46 | free(sb); 47 | return s; 48 | } 49 | 50 | void sbuf_free(struct sbuf *sb) 51 | { 52 | free(sb->s); 53 | free(sb); 54 | } 55 | 56 | void sbuf_chr(struct sbuf *sbuf, int c) 57 | { 58 | if (sbuf->s_n + 2 >= sbuf->s_sz) 59 | sbuf_extend(sbuf, NEXTSZ(sbuf->s_sz, 1)); 60 | sbuf->s[sbuf->s_n++] = c; 61 | } 62 | 63 | void sbuf_mem(struct sbuf *sbuf, char *s, int len) 64 | { 65 | if (sbuf->s_n + len + 1 >= sbuf->s_sz) 66 | sbuf_extend(sbuf, NEXTSZ(sbuf->s_sz, len + 1)); 67 | memcpy(sbuf->s + sbuf->s_n, s, len); 68 | sbuf->s_n += len; 69 | } 70 | 71 | void sbuf_str(struct sbuf *sbuf, char *s) 72 | { 73 | sbuf_mem(sbuf, s, strlen(s)); 74 | } 75 | 76 | int sbuf_len(struct sbuf *sbuf) 77 | { 78 | return sbuf->s_n; 79 | } 80 | 81 | void sbuf_cut(struct sbuf *sb, int len) 82 | { 83 | if (sb->s_n > len) 84 | sb->s_n = len; 85 | } 86 | 87 | void sbuf_printf(struct sbuf *sbuf, char *s, ...) 88 | { 89 | char buf[256]; 90 | va_list ap; 91 | va_start(ap, s); 92 | vsnprintf(buf, sizeof(buf), s, ap); 93 | va_end(ap); 94 | sbuf_str(sbuf, buf); 95 | } 96 | -------------------------------------------------------------------------------- /stag.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "regex.h" 4 | 5 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 6 | 7 | static struct tag { 8 | char *ext; /* file extension */ 9 | int grp; /* tag group in pat */ 10 | char *pat; /* tag pattern */ 11 | char *loc; /* optional tag location; may reference groups in pat */ 12 | } tags[] = { 13 | {"\\.[hc]$", 1, "^#define +([a-zA-Z_0-9]+)\\>", "/^#define +\\1\\>.*$/"}, 14 | {"\\.[hc]$", 1, "^struct +([a-zA-Z_0-9]+) *\\{", "/^struct +\\1 *\\{/"}, 15 | {"\\.[hc]$", 1, "^[a-zA-Z_][^{;]*\\<([a-zA-Z_][a-zA-Z_0-9]*)\\([^;]+(//.*|/\\*.*)?$", "/^[a-zA-Z_][^{;]*\\<\\1\\([^;]+(\\/\\/.*|\\/\\*.*)?$/"}, 16 | {"\\.go$", 3, "^(func|var|const|type)( +\\([^()]+\\))? +([a-zA-Z_0-9]+)\\>", "/^\\1( +\\(.*\\))? +\\3\\>/"}, 17 | {"\\.sh$", 2, "^(function +)?([a-zA-Z_][a-zA-Z_0-9]*)(\\(\\))? *\\{", "/^\\1\\2(\\(\\))? *\\{$/"}, 18 | {"\\.py$", 2, "^(def|class) +([a-zA-Z_][a-zA-Z_0-9]*)\\>", "/^\\1 +\\2\\>/"}, 19 | {"\\.ex$", 2, "^[ \t]*(def|defp|defmodule)[ \t]+([a-zA-Z_0-9]+\\>[?!]?)", "/^[ \\t]*\\1[ \\t]+\\2\\>[?!]?/"}, 20 | }; 21 | 22 | static int tags_match(int idx, char *path) 23 | { 24 | regex_t re; 25 | int ret; 26 | if (regcomp(&re, tags[idx].ext, REG_EXTENDED) != 0) 27 | return 1; 28 | ret = regexec(&re, path, 0, NULL, 0); 29 | regfree(&re); 30 | return ret; 31 | } 32 | 33 | static void replace(char *dst, char *rep, char *ln, regmatch_t *subs) 34 | { 35 | while (rep[0]) { 36 | if (rep[0] == '\\' && rep[1]) { 37 | if (rep[1] >= '0' && rep[1] <= '9') { 38 | int grp = rep[1] - '0'; 39 | int beg = subs[grp].rm_so; 40 | int end = subs[grp].rm_eo; 41 | int len = end - beg; 42 | memcpy(dst, ln + beg, len); 43 | dst += len; 44 | } else { 45 | *dst++ = rep[0]; 46 | *dst++ = rep[1]; 47 | } 48 | rep++; 49 | } else { 50 | *dst++ = rep[0]; 51 | } 52 | rep++; 53 | } 54 | dst[0] = '\0'; 55 | } 56 | 57 | static int mktags(char *path, regex_t *re, int grp, char *rep, int alt) 58 | { 59 | char ln[128]; 60 | char loc[256]; 61 | char tag[120]; 62 | int lnum = 0; 63 | regmatch_t grps[32]; 64 | FILE *fp = fopen(path, "r"); 65 | if (fp == NULL) 66 | return 1; 67 | while (fgets(ln, sizeof(ln), fp) != NULL) { 68 | if (regexec(re, ln, LEN(grps), grps, REG_NEWLINE) == 0) { 69 | int len = grps[grp].rm_eo - grps[grp].rm_so; 70 | if (len + 1 > sizeof(tag)) 71 | len = sizeof(tag) - 1; 72 | memcpy(tag, ln + grps[grp].rm_so, len); 73 | tag[len] = '\0'; 74 | if (alt) { 75 | printf("%s:%04d: %s", path, lnum + 1, ln); 76 | } else { 77 | if (rep != NULL) 78 | replace(loc, rep, ln, grps); 79 | else 80 | sprintf(loc, "%d", lnum + 1); 81 | printf("%s\t%s\t%s \"%s", tag, path, loc, ln); 82 | } 83 | } 84 | lnum++; 85 | } 86 | fclose(fp); 87 | return 0; 88 | } 89 | 90 | int main(int argc, char *argv[]) 91 | { 92 | int i, j; 93 | int alt = 0; 94 | if (argc == 1) { 95 | printf("usage: %s [options] files >tags\n\n", argv[0]); 96 | printf("options:\n"); 97 | printf(" -a use file listing output format (path:lnum: line)\n"); 98 | return 0; 99 | } 100 | for (i = 1; i < argc && argv[i][0] == '-'; i++) { 101 | if (argv[i][1] == 'a') 102 | alt = 1; 103 | } 104 | for (; i < argc; i++) { 105 | for (j = 0; j < LEN(tags); j++) { 106 | regex_t re; 107 | if (tags_match(j, argv[i]) != 0) 108 | continue; 109 | if (regcomp(&re, tags[j].pat, REG_EXTENDED) != 0) { 110 | fprintf(stderr, "mktags: bad pattern %s\n", tags[j].pat); 111 | continue; 112 | } 113 | fprintf(stderr, "%s\n", argv[i]); 114 | if (mktags(argv[i], &re, tags[j].grp, tags[j].loc, alt)) 115 | fprintf(stderr, "mktags: failed to read %s\n", argv[i]); 116 | regfree(&re); 117 | } 118 | } 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /syn.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "vi.h" 5 | 6 | #define NFTS 32 7 | 8 | /* mapping filetypes to regular expression sets */ 9 | static struct ftmap { 10 | char ft[32]; 11 | struct rset *rs; 12 | } ftmap[NFTS]; 13 | 14 | static struct rset *syn_ftrs; 15 | static int syn_ctx; 16 | 17 | static struct rset *syn_find(char *ft) 18 | { 19 | int i; 20 | for (i = 0; i < LEN(ftmap); i++) 21 | if (!strcmp(ft, ftmap[i].ft)) 22 | return ftmap[i].rs; 23 | return NULL; 24 | } 25 | 26 | static struct rset *syn_make(char *name) 27 | { 28 | char *pats[256] = {NULL}; 29 | char *ft, *pat; 30 | int i; 31 | int n = 0; 32 | if (name == NULL || !name[0]) 33 | return NULL; 34 | for (i = 0; !conf_highlight(i, &ft, NULL, &pat, NULL) && i < LEN(pats); i++) 35 | if (!strcmp(ft, name)) 36 | pats[i] = pat; 37 | n = i; 38 | for (i = 0; i < LEN(ftmap); i++) { 39 | if (!ftmap[i].ft[0]) { 40 | strcpy(ftmap[i].ft, name); 41 | ftmap[i].rs = rset_make(n, pats, 0); 42 | return ftmap[i].rs; 43 | } 44 | } 45 | return NULL; 46 | } 47 | 48 | int syn_merge(int old, int new) 49 | { 50 | int fg = SYN_FGSET(new) ? SYN_FG(new) : SYN_FG(old); 51 | int bg = SYN_BGSET(new) ? SYN_BG(new) : SYN_BG(old); 52 | return ((old | new) & SYN_FLG) | (bg << 8) | fg; 53 | } 54 | 55 | void syn_context(int att) 56 | { 57 | syn_ctx = att; 58 | } 59 | 60 | int *syn_highlight(char *ft, char *s) 61 | { 62 | int subs[16 * 2]; 63 | int n = uc_slen(s); 64 | int *att = malloc(n * sizeof(att[0])); 65 | int sidx = 0; 66 | struct rset *rs = syn_find(ft); 67 | int flg = 0; 68 | int hl, j, i; 69 | memset(att, 0, n * sizeof(att[0])); 70 | if (!strcmp(ft, "___")) { 71 | for (i = 0; i < n; i++) 72 | att[i] = SYN_RV; 73 | return att; 74 | } 75 | if (rs == NULL) 76 | rs = syn_make(ft); 77 | if (!rs) 78 | return att; 79 | while ((hl = rset_find(rs, s + sidx, LEN(subs) / 2, subs, flg)) >= 0) { 80 | int grp = 0; 81 | int cend = 1; 82 | int *catt; 83 | conf_highlight(hl, NULL, &catt, NULL, &grp); 84 | for (i = 0; i < LEN(subs) / 2; i++) { 85 | if (subs[i * 2] >= 0) { 86 | int beg = uc_off(s, sidx + subs[i * 2 + 0]); 87 | int end = uc_off(s, sidx + subs[i * 2 + 1]); 88 | for (j = beg; j < end; j++) 89 | att[j] = syn_merge(att[j], catt[i]); 90 | if (i == grp) 91 | cend = MAX(cend, subs[i * 2 + 1]); 92 | } 93 | } 94 | sidx += cend; 95 | flg = RE_NOTBOL; 96 | } 97 | for (i = 0; i < n; i++) 98 | att[i] = syn_merge(att[i], syn_ctx); 99 | return att; 100 | } 101 | 102 | char *syn_filetype(char *path) 103 | { 104 | int hl = rset_find(syn_ftrs, path, 0, NULL, 0); 105 | char *ft; 106 | if (!conf_filetype(hl, &ft, NULL)) 107 | return ft; 108 | return ""; 109 | } 110 | 111 | void syn_init(void) 112 | { 113 | char *pats[128] = {NULL}; 114 | char *pat; 115 | int i; 116 | for (i = 0; !conf_filetype(i, NULL, &pat) && i < LEN(pats); i++) 117 | pats[i] = pat; 118 | syn_ftrs = rset_make(i, pats, 0); 119 | } 120 | 121 | void syn_done(void) 122 | { 123 | int i; 124 | for (i = 0; i < LEN(ftmap); i++) 125 | if (ftmap[i].rs) 126 | rset_free(ftmap[i].rs); 127 | rset_free(syn_ftrs); 128 | } 129 | -------------------------------------------------------------------------------- /tag.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "vi.h" 8 | 9 | static char *tagpath; 10 | static char *tag; 11 | static long taglen; 12 | 13 | int tag_init(void) 14 | { 15 | return 0; 16 | } 17 | 18 | static int tag_load(void) 19 | { 20 | char buf[1 << 10]; 21 | struct sbuf *sb; 22 | long nr; 23 | int fd; 24 | if (tagpath != NULL) 25 | return tag == NULL; 26 | tagpath = getenv("TAGPATH") ? getenv("TAGPATH") : "tags"; 27 | if ((fd = open(tagpath, O_RDONLY)) < 0) 28 | return 1; 29 | sb = sbuf_make(); 30 | while ((nr = read(fd, buf, sizeof(buf))) > 0) 31 | sbuf_mem(sb, buf, nr); 32 | close(fd); 33 | taglen = sbuf_len(sb); 34 | tag = sbuf_done(sb); 35 | return 0; 36 | } 37 | 38 | void tag_done(void) 39 | { 40 | free(tag); 41 | tag = NULL; 42 | tagpath = NULL; 43 | } 44 | 45 | static char *copypart(char *dst, int dstlen, char *src) 46 | { 47 | char *end = src; 48 | int len = dstlen - 1; 49 | while (*end && *end != '\t' && *end != '\n') 50 | end++; 51 | if (end - src < len) 52 | len = end - src; 53 | if (dst != NULL && dstlen > 0) { 54 | memcpy(dst, src, len); 55 | dst[len] = '\0'; 56 | } 57 | return *end ? end + 1 : end; 58 | } 59 | 60 | static char *tag_next(char *s, int dir) 61 | { 62 | if (dir >= 0 && *s) { 63 | if ((s = strchr(s + 1, '\n')) != NULL) 64 | return s + 1; 65 | } 66 | if (dir < 0 && s > tag) { 67 | s--; 68 | while (s > tag && s[-1] != '\n') 69 | s--; 70 | return s; 71 | } 72 | return NULL; 73 | } 74 | 75 | int tag_find(char *name, int *pos, int dir, char *path, int pathlen, char *cmd, int cmdlen) 76 | { 77 | char *s; 78 | int len = strlen(name); 79 | if (tag_load() != 0) 80 | return 1; 81 | if (*pos >= taglen) 82 | *pos = 0; 83 | s = dir != 0 ? tag_next(tag + *pos, dir) : tag + *pos; 84 | while (s) { 85 | if (!strncmp(name, s, len) && s[len] == '\t') { 86 | char *r = copypart(path, pathlen, s + len + 1); 87 | copypart(cmd, cmdlen, r); 88 | *pos = s - tag; 89 | return 0; 90 | } 91 | s = tag_next(s, dir); 92 | } 93 | return 1; 94 | } 95 | -------------------------------------------------------------------------------- /term.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "vi.h" 10 | 11 | static struct sbuf *term_sbuf; /* output buffer if not NULL */ 12 | static int rows, cols; /* number of terminal rows and columns */ 13 | static int win_beg, win_rows; /* active window rows */ 14 | static struct termios termios; 15 | 16 | void term_init(void) 17 | { 18 | struct winsize win; 19 | struct termios newtermios; 20 | tcgetattr(0, &termios); 21 | newtermios = termios; 22 | newtermios.c_lflag &= ~(ICANON | ISIG); 23 | newtermios.c_lflag &= ~ECHO; 24 | tcsetattr(0, TCSAFLUSH, &newtermios); 25 | if (getenv("LINES")) 26 | rows = atoi(getenv("LINES")); 27 | if (getenv("COLUMNS")) 28 | cols = atoi(getenv("COLUMNS")); 29 | if (!ioctl(0, TIOCGWINSZ, &win)) { 30 | cols = win.ws_col; 31 | rows = win.ws_row; 32 | } 33 | cols = cols ? cols : 80; 34 | rows = rows ? rows : 25; 35 | term_str("\33[m"); 36 | term_window(win_beg, win_rows > 0 ? win_rows : rows); 37 | } 38 | 39 | void term_window(int row, int cnt) 40 | { 41 | char cmd[64]; 42 | win_beg = row; 43 | win_rows = cnt; 44 | if (row == 0 && win_rows == rows) { 45 | term_str("\33[r"); 46 | } else { 47 | sprintf(cmd, "\33[%d;%dr", win_beg + 1, win_beg + win_rows); 48 | term_str(cmd); 49 | } 50 | } 51 | 52 | void term_done(void) 53 | { 54 | term_str("\33[r"); 55 | term_pos(rows - 1, 0); 56 | term_kill(); 57 | term_commit(); 58 | tcsetattr(0, 0, &termios); 59 | } 60 | 61 | void term_suspend(void) 62 | { 63 | term_done(); 64 | kill(0, SIGSTOP); 65 | term_init(); 66 | } 67 | 68 | void term_record(void) 69 | { 70 | if (!term_sbuf) 71 | term_sbuf = sbuf_make(); 72 | } 73 | 74 | void term_commit(void) 75 | { 76 | if (term_sbuf) { 77 | write(1, sbuf_buf(term_sbuf), sbuf_len(term_sbuf)); 78 | sbuf_free(term_sbuf); 79 | term_sbuf = NULL; 80 | } 81 | } 82 | 83 | static void term_out(char *s) 84 | { 85 | if (term_sbuf) 86 | sbuf_str(term_sbuf, s); 87 | else 88 | write(1, s, strlen(s)); 89 | } 90 | 91 | void term_str(char *s) 92 | { 93 | term_out(s); 94 | } 95 | 96 | void term_chr(int ch) 97 | { 98 | char s[4] = {ch}; 99 | term_out(s); 100 | } 101 | 102 | void term_kill(void) 103 | { 104 | term_out("\33[K"); 105 | } 106 | 107 | void term_room(int n) 108 | { 109 | char cmd[16]; 110 | if (n < 0) 111 | sprintf(cmd, "\33[%dM", -n); 112 | if (n > 0) 113 | sprintf(cmd, "\33[%dL", n); 114 | if (n) 115 | term_out(cmd); 116 | } 117 | 118 | void term_pos(int r, int c) 119 | { 120 | char buf[32] = "\r"; 121 | if (c < 0) 122 | c = 0; 123 | if (c >= term_cols()) 124 | c = cols - 1; 125 | if (r < 0) 126 | sprintf(buf, "\r\33[%d%c", abs(c), c > 0 ? 'C' : 'D'); 127 | else 128 | sprintf(buf, "\33[%d;%dH", win_beg + r + 1, c + 1); 129 | term_out(buf); 130 | } 131 | 132 | int term_rowx(void) 133 | { 134 | return rows; 135 | } 136 | 137 | int term_rows(void) 138 | { 139 | return win_rows; 140 | } 141 | 142 | int term_cols(void) 143 | { 144 | return cols; 145 | } 146 | 147 | static char ibuf[4096]; /* input character buffer */ 148 | static char icmd[4096]; /* read after the last term_cmd() */ 149 | static int ibuf_pos, ibuf_cnt; /* ibuf[] position and length */ 150 | static int icmd_pos; /* icmd[] position */ 151 | 152 | /* read s before reading from the terminal */ 153 | void term_push(char *s, int n) 154 | { 155 | n = MIN(n, sizeof(ibuf) - ibuf_cnt); 156 | memcpy(ibuf + ibuf_cnt, s, n); 157 | ibuf_cnt += n; 158 | } 159 | 160 | /* return a static buffer containing inputs read since the last term_cmd() */ 161 | char *term_cmd(int *n) 162 | { 163 | *n = icmd_pos; 164 | icmd_pos = 0; 165 | return icmd; 166 | } 167 | 168 | int term_read(void) 169 | { 170 | struct pollfd ufds[1]; 171 | int n, c; 172 | if (ibuf_pos >= ibuf_cnt) { 173 | ufds[0].fd = 0; 174 | ufds[0].events = POLLIN; 175 | if (poll(ufds, 1, -1) <= 0) 176 | return -1; 177 | /* read a single input character */ 178 | if ((n = read(0, ibuf, 1)) <= 0) 179 | return -1; 180 | ibuf_cnt = n; 181 | ibuf_pos = 0; 182 | } 183 | c = ibuf_pos < ibuf_cnt ? (unsigned char) ibuf[ibuf_pos++] : -1; 184 | if (icmd_pos < sizeof(icmd)) 185 | icmd[icmd_pos++] = c; 186 | return c; 187 | } 188 | 189 | /* return a static string that changes text attributes from old to att */ 190 | char *term_seqattr(int att, int old) 191 | { 192 | static char buf[128]; 193 | char *s = buf; 194 | int fg = SYN_FG(att); 195 | int bg = SYN_BG(att); 196 | if (att == old) 197 | return ""; 198 | s += sprintf(s, "\33["); 199 | if (att & SYN_BD) 200 | s += sprintf(s, ";1"); 201 | if (att & SYN_IT) 202 | s += sprintf(s, ";3"); 203 | else if (att & SYN_RV) 204 | s += sprintf(s, ";7"); 205 | if (SYN_FGSET(att)) { 206 | if ((fg & 0xff) < 8) 207 | s += sprintf(s, ";%d", 30 + (fg & 0xff)); 208 | else 209 | s += sprintf(s, ";38;5;%d", (fg & 0xff)); 210 | } 211 | if (SYN_BGSET(att)) { 212 | if ((bg & 0xff) < 8) 213 | s += sprintf(s, ";%d", 40 + (bg & 0xff)); 214 | else 215 | s += sprintf(s, ";48;5;%d", (bg & 0xff)); 216 | } 217 | s += sprintf(s, "m"); 218 | return buf; 219 | } 220 | 221 | char *term_seqkill(void) 222 | { 223 | return "\33[K"; 224 | } 225 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script executes Neatvi's tests. 3 | 4 | export EXINIT="" 5 | 6 | # testcase vi_options test.sh 7 | testcase() { 8 | rm -f /tmp/.neatvi[12] 9 | printf "$2: " 10 | sh $2 /tmp/.neatvi2 2>/tmp/.neatvi1 | ./vi $1 >/dev/null 11 | if ! cmp -s /tmp/.neatvi[12]; then 12 | printf "Failed\n" 13 | diff -u /tmp/.neatvi[12] 14 | exit 1 15 | fi 16 | printf "OK\n" 17 | } 18 | 19 | for x in test/e??.sh; do 20 | testcase "-s -e" "$x" 21 | done 22 | for x in test/v??.sh; do 23 | testcase "-v" "$x" 24 | done 25 | -------------------------------------------------------------------------------- /test/e00.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":w $1" 3 | echo ":q" 4 | -------------------------------------------------------------------------------- /test/e01.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":i" 3 | echo "abc def" 4 | echo "." 5 | echo ":w $1" 6 | echo ":q" 7 | 8 | # the expected output 9 | echo "abc def" >&2 10 | -------------------------------------------------------------------------------- /test/e02.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc def" 5 | echo "ghi jkl" 6 | echo "." 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "abc def" >&2 12 | echo "ghi jkl" >&2 13 | -------------------------------------------------------------------------------- /test/e03.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "." 6 | echo ":a" 7 | echo "def" 8 | echo "." 9 | echo ":undo" 10 | echo ":w" 11 | echo ":q" 12 | 13 | # the expected output 14 | echo "abc" >&2 15 | -------------------------------------------------------------------------------- /test/e04.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc def" 5 | echo "." 6 | echo ":a" 7 | echo "ghi jkl" 8 | echo "." 9 | echo ":undo" 10 | echo ":redo" 11 | echo ":w" 12 | echo ":q" 13 | 14 | # the expected output 15 | echo "abc def" >&2 16 | echo "ghi jkl" >&2 17 | -------------------------------------------------------------------------------- /test/e05.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "def" 6 | echo "ghi" 7 | echo "." 8 | echo ":2ka" 9 | echo ":'ad" 10 | echo ":w" 11 | echo ":q" 12 | 13 | # the expected output 14 | echo "abc" >&2 15 | echo "ghi" >&2 16 | -------------------------------------------------------------------------------- /test/e06.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "def" 6 | echo "ghi" 7 | echo "." 8 | echo ":1,2yank" 9 | echo ":1put" 10 | echo ":w" 11 | echo ":q" 12 | 13 | # the expected output 14 | echo "abc" >&2 15 | echo "abc" >&2 16 | echo "def" >&2 17 | echo "def" >&2 18 | echo "ghi" >&2 19 | -------------------------------------------------------------------------------- /test/e07.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "<< >> << >>" 5 | echo "<< >> << >>" 6 | echo "." 7 | echo ":%s/<>/right }/g" 9 | echo ":w" 10 | echo ":q" 11 | 12 | # the expected output 13 | echo "left { right } left { right }" >&2 14 | echo "left { right } left { right }" >&2 15 | -------------------------------------------------------------------------------- /test/e08.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "axy" 6 | echo "." 7 | printf ':%%s/a(..)/\\1a/g\n' 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "bca" >&2 13 | echo "xya" >&2 14 | -------------------------------------------------------------------------------- /test/e09.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo "e $1" 3 | echo "a" 4 | echo "abc" 5 | echo "def" 6 | echo "ghi" 7 | echo "." 8 | echo "%g/def/-1,/ghi/d" 9 | echo "wq" 10 | 11 | # the expected output 12 | printf "" >&2 13 | -------------------------------------------------------------------------------- /test/e0a.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo "e $1" 3 | echo "a" 4 | echo "abc" 5 | echo "def" 6 | echo "ghi" 7 | echo "." 8 | echo "%g/./d" 9 | echo "wq" 10 | 11 | # the expected output 12 | printf "" >&2 13 | -------------------------------------------------------------------------------- /test/e0b.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo "e $1" 3 | echo "a" 4 | echo "1" 5 | echo "2" 6 | echo "3" 7 | echo "4" 8 | echo "5" 9 | echo "." 10 | echo "1,4g/./+1s/$/x/" 11 | echo "wq" 12 | 13 | # the expected output 14 | echo "1" >&2 15 | echo "2x" >&2 16 | echo "3x" >&2 17 | echo "4x" >&2 18 | echo "5x" >&2 19 | -------------------------------------------------------------------------------- /test/e0c.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo "e $1" 3 | echo "a" 4 | echo "x" 5 | echo "1" 6 | echo "2" 7 | echo "3" 8 | echo "4" 9 | echo "5" 10 | echo "." 11 | echo "1d" 12 | echo "1,4g/./+1put" 13 | echo "wq" 14 | 15 | # the expected output 16 | echo "1" >&2 17 | echo "2" >&2 18 | echo "x" >&2 19 | echo "x" >&2 20 | echo "3" >&2 21 | echo "4" >&2 22 | echo "x" >&2 23 | echo "x" >&2 24 | echo "5" >&2 25 | -------------------------------------------------------------------------------- /test/e0d.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo "e $1" 3 | echo "a" 4 | echo "1" 5 | echo "2" 6 | echo "x" 7 | echo "3" 8 | echo "4" 9 | echo "x" 10 | echo "5" 11 | echo "." 12 | echo '%g/^x$/d|-2put' 13 | echo "wq" 14 | 15 | # the expected output 16 | echo "1" >&2 17 | echo "x" >&2 18 | echo "2" >&2 19 | echo "3" >&2 20 | echo "x" >&2 21 | echo "4" >&2 22 | echo "5" >&2 23 | -------------------------------------------------------------------------------- /test/e0e.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "def" 6 | echo "." 7 | printf ':%%s/(b.*)$/(\\1)/g\n' 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "a(bc)" >&2 13 | echo "def" >&2 14 | -------------------------------------------------------------------------------- /test/e0f.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "ab " 5 | echo " e " 6 | echo "." 7 | echo ':%s/ *$//g' 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "ab" >&2 13 | echo " e" >&2 14 | -------------------------------------------------------------------------------- /test/e10.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "def" 6 | echo "." 7 | echo ':%s/(abc|def)/xyz/' 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "xyz" >&2 13 | echo "xyz" >&2 14 | -------------------------------------------------------------------------------- /test/e11.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "a" 5 | echo "ab" 6 | echo "." 7 | echo ':%s/^a$/x/' 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "x" >&2 13 | echo "ab" >&2 14 | -------------------------------------------------------------------------------- /test/e12.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "a" 5 | echo "b" 6 | echo "c" 7 | echo "." 8 | echo ":w" 9 | echo ":e +2 $1" 10 | echo ':s/.*/z/' 11 | echo ":w" 12 | echo ":q" 13 | 14 | # the expected output 15 | echo "a" >&2 16 | echo "z" >&2 17 | echo "c" >&2 18 | -------------------------------------------------------------------------------- /test/e13.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "a" 5 | echo "b" 6 | echo "c" 7 | echo "g/^[bg]/d" 8 | echo "." 9 | echo ":4y a" 10 | echo ":@a" 11 | echo ":wq" 12 | 13 | # the expected output 14 | echo "a" >&2 15 | echo "c" >&2 16 | -------------------------------------------------------------------------------- /test/e14.sh: -------------------------------------------------------------------------------- 1 | # ex commands 2 | echo ":e $1" 3 | echo ":rs x" 4 | echo "a" 5 | echo "b" 6 | echo "c" 7 | echo "." 8 | echo ":put x" 9 | echo ":wq" 10 | 11 | # the expected output 12 | echo "a" >&2 13 | echo "b" >&2 14 | echo "c" >&2 15 | -------------------------------------------------------------------------------- /test/e15.sh: -------------------------------------------------------------------------------- 1 | # ex commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "2s/abc/def/" 5 | echo "abc" 6 | echo "." 7 | echo ":w" 8 | echo ":so %" 9 | echo ":wq" 10 | 11 | # the expected output 12 | echo "2s/abc/def/" >&2 13 | echo "def" >&2 14 | -------------------------------------------------------------------------------- /test/e16.sh: -------------------------------------------------------------------------------- 1 | # ex commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "abc" 5 | echo "." 6 | echo "1y x" 7 | echo ":rx x rev" 8 | echo ":put x" 9 | echo ":wq" 10 | 11 | # the expected output 12 | echo "abc" >&2 13 | echo "cba" >&2 14 | -------------------------------------------------------------------------------- /test/e17.sh: -------------------------------------------------------------------------------- 1 | # ex commands 2 | echo ":e $1" 3 | echo ":rs a" 4 | echo "rs b" 5 | echo "%" 6 | echo "." 7 | echo "." 8 | echo ":ra a" 9 | echo ":put b" 10 | echo ":wq" 11 | 12 | # the expected output 13 | echo "$1" >&2 14 | -------------------------------------------------------------------------------- /test/v00.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # vi commands 4 | echo ":e $1" 5 | printf "iabc" 6 | echo ":w" 7 | echo ":q" 8 | 9 | # the expected output 10 | echo "abc" >&2 11 | -------------------------------------------------------------------------------- /test/v01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # vi commands 4 | echo ":e $1" 5 | printf "iabc" 6 | printf "hhA def" 7 | printf "Ighi " 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "ghi abc def" >&2 13 | -------------------------------------------------------------------------------- /test/v02.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # vi commands 4 | echo ":e $1" 5 | printf "iabc def" 6 | printf "oghi jkl" 7 | printf "kOmno pqr" 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "mno pqr" >&2 13 | echo "abc def" >&2 14 | echo "ghi jkl" >&2 15 | -------------------------------------------------------------------------------- /test/v03.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def" 4 | printf "0dB" 5 | printf "d0" 6 | echo ":w" 7 | echo ":q" 8 | 9 | # the expected output 10 | echo "abc def" >&2 11 | -------------------------------------------------------------------------------- /test/v04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # vi commands 4 | echo ":e $1" 5 | echo "iabc def" 6 | printf "ghi jkl" 7 | printf "1Gf cE 123" 8 | printf "2Gf cB456" 9 | echo ":w" 10 | echo ":q" 11 | 12 | # the expected output 13 | echo "abc 123" >&2 14 | echo "456 jkl" >&2 15 | -------------------------------------------------------------------------------- /test/v05.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def" 4 | printf "oghi jkl" 5 | printf '1G$d$' 6 | printf '2GdB' 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "abc ghi jkl" >&2 12 | -------------------------------------------------------------------------------- /test/v06.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def" 4 | printf "oghi jkl" 5 | printf "omno pqr" 6 | printf '1Gf d^$' 7 | printf '+dfi' 8 | printf '+f d$' 9 | echo ":w" 10 | echo ":q" 11 | 12 | # the expected output 13 | echo " def" >&2 14 | echo " jkl" >&2 15 | echo "mno" >&2 16 | -------------------------------------------------------------------------------- /test/v07.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf '1Gyy' 6 | printf 'pGp' 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "abc" >&2 12 | echo "abc" >&2 13 | echo "def" >&2 14 | echo "abc" >&2 15 | -------------------------------------------------------------------------------- /test/v08.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "dd" 6 | printf "P" 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "def" >&2 12 | echo "abc" >&2 13 | -------------------------------------------------------------------------------- /test/v09.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def" 4 | printf "oghi jkl" 5 | printf "F yeP" 6 | printf 'k$p' 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "abc def jkl" >&2 12 | echo "ghi jkl jkl" >&2 13 | -------------------------------------------------------------------------------- /test/v0a.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "o" 5 | printf "odef " 6 | printf "oghi." 7 | printf "ojkl" 8 | printf "1G5J" 9 | printf "i^" 10 | echo ":w" 11 | echo ":q" 12 | 13 | # the expected output 14 | echo "abc def ghi.^ jkl" >&2 15 | -------------------------------------------------------------------------------- /test/v0b.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "oghi" 6 | echo "?abc" 7 | printf "i1" 8 | echo "/ghi" 9 | printf "i2" 10 | echo ":w" 11 | echo ":q" 12 | 13 | # the expected output 14 | echo "1abc" >&2 15 | echo "def" >&2 16 | echo "2ghi" >&2 17 | -------------------------------------------------------------------------------- /test/v0c.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def ghi" 4 | echo "?abc" 5 | printf "i1" 6 | echo "/ghi" 7 | printf "i2" 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "1abc def 2ghi" >&2 13 | -------------------------------------------------------------------------------- /test/v0d.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def" 4 | printf "oghi jkl" 5 | printf "omno pqr" 6 | echo "1Gd/jkl/0" 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "mno pqr" >&2 12 | -------------------------------------------------------------------------------- /test/v0e.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc def" 4 | printf "oghi jkl" 5 | printf "omno pqr" 6 | printf "1Gr1" 7 | printf "2Gfh2r2" 8 | printf "3Gfp4r3" 9 | echo ":w" 10 | echo ":q" 11 | 12 | # the expected output 13 | echo "1bc def" >&2 14 | echo "g22 jkl" >&2 15 | echo "mno pqr" >&2 16 | -------------------------------------------------------------------------------- /test/v0f.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "1Gfxr1" 6 | printf "dfx" 7 | echo "d/xyz/" 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "1bc" >&2 13 | echo "def" >&2 14 | -------------------------------------------------------------------------------- /test/v10.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "oghi" 6 | printf '"add' 7 | printf 'dd' 8 | printf '"bdd' 9 | printf 'P"ap"bp' 10 | echo ":4d" 11 | echo ":w" 12 | echo ":q" 13 | 14 | # the expected output 15 | echo "def" >&2 16 | echo "ghi" >&2 17 | echo "abc" >&2 18 | -------------------------------------------------------------------------------- /test/v11.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "oghi" 6 | echo "!krev" 7 | echo "" 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "abc" >&2 13 | echo "fed" >&2 14 | echo "ihg" >&2 15 | -------------------------------------------------------------------------------- /test/v12.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "ia" 4 | printf "oab" 5 | printf "oabc" 6 | printf "0kklllji1" 7 | printf "kllji2" 8 | printf '$jx' 9 | echo ":w" 10 | echo ":q" 11 | 12 | # the expected output 13 | echo "a" >&2 14 | echo "21ab" >&2 15 | echo "ab" >&2 16 | -------------------------------------------------------------------------------- /test/v13.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":set ic" 4 | printf "iABC" 5 | printf "oDEF" 6 | printf "oGHI" 7 | echo '1G/def' 8 | printf "i1" 9 | echo ":set noic" 10 | echo '/ghi' 11 | printf "i2" 12 | echo ":w" 13 | echo ":q" 14 | 15 | # the expected output 16 | echo "ABC" >&2 17 | echo "21DEF" >&2 18 | echo "GHI" >&2 19 | -------------------------------------------------------------------------------- /test/v14.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "oghi" 6 | printf "1GA123" 7 | echo 'j.j.' 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "abc123" >&2 13 | echo "def123" >&2 14 | echo "ghi123" >&2 15 | -------------------------------------------------------------------------------- /test/v15.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc" 4 | printf "odef" 5 | printf "oabc" 6 | echo ":%s/abc/ghi/" 7 | echo ":w" 8 | echo ":q" 9 | 10 | # the expected output 11 | echo "ghi" >&2 12 | echo "def" >&2 13 | echo "ghi" >&2 14 | -------------------------------------------------------------------------------- /test/v16.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | printf "iabc abc abc" 4 | printf "oabc abc abc" 5 | printf "oabc abc abc" 6 | echo ":2,3s/abc/111/" 7 | echo ":%s/abc/222/g" 8 | echo ":w" 9 | echo ":q" 10 | 11 | # the expected output 12 | echo "222 222 222" >&2 13 | echo "111 222 222" >&2 14 | echo "111 222 222" >&2 15 | -------------------------------------------------------------------------------- /test/v17.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | echo "/^/" 7 | echo "NxnP/$/" 8 | echo "xNp" 9 | echo ":w" 10 | echo ":q" 11 | 12 | # the expected output 13 | echo "abc" >&2 14 | echo "efi" >&2 15 | echo "dgh" >&2 16 | -------------------------------------------------------------------------------- /test/v18.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "o" 5 | echo "Adef" 6 | echo ":wq" 7 | 8 | # the expected output 9 | echo "abc" >&2 10 | echo "def" >&2 11 | -------------------------------------------------------------------------------- /test/v19.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":set ai" 4 | echo "iabc" 5 | echo "" 6 | echo "def" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo " abc" >&2 11 | echo "" >&2 12 | echo " def" >&2 13 | -------------------------------------------------------------------------------- /test/v1a.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | printf "1G''x" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo "abc" >&2 11 | echo "def" >&2 12 | echo "hi" >&2 13 | -------------------------------------------------------------------------------- /test/v1b.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | echo ":%g/def/d" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo "abc" >&2 11 | echo "ghi" >&2 12 | -------------------------------------------------------------------------------- /test/v1c.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | echo ":%g/^[dg]/s/h/x/" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo "abc" >&2 11 | echo "def" >&2 12 | echo "gxi" >&2 13 | -------------------------------------------------------------------------------- /test/v1d.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | echo ":%g!/def/d" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo "def" >&2 11 | -------------------------------------------------------------------------------- /test/v1e.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | printf "1G" 7 | echo ":/ghi/d" 8 | echo ":wq" 9 | 10 | # the expected output 11 | echo "abc" >&2 12 | echo "def" >&2 13 | -------------------------------------------------------------------------------- /test/v1f.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iabc" 4 | echo "odef" 5 | echo "oghi" 6 | echo ":g/[ah]/d" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo "def" >&2 11 | -------------------------------------------------------------------------------- /test/v20.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "iaa" 4 | echo "oab" 5 | echo "obb" 6 | echo ":g/a/.g/b/d" 7 | echo ":wq" 8 | 9 | # the expected output 10 | echo "aa" >&2 11 | echo "bb" >&2 12 | -------------------------------------------------------------------------------- /test/v21.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo "i" 4 | echo "oa" 5 | echo ":%s/$/x/g" 6 | echo ":wq" 7 | 8 | # the expected output 9 | echo "x" >&2 10 | echo "ax" >&2 11 | -------------------------------------------------------------------------------- /test/v22.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "a" 5 | echo "" 6 | echo "b" 7 | echo "" 8 | echo "." 9 | echo "1G>G" 10 | echo ":wq" 11 | 12 | # the expected output 13 | echo " a" >&2 14 | echo "" >&2 15 | echo " b" >&2 16 | echo "" >&2 17 | -------------------------------------------------------------------------------- /test/v23.sh: -------------------------------------------------------------------------------- 1 | # vi commands 2 | echo ":e $1" 3 | echo ":a" 4 | echo "c" 5 | echo "b" 6 | echo "a" 7 | echo "" 8 | echo "a" 9 | echo "." 10 | echo "1G!}sort" 11 | echo "" 12 | echo ":wq" 13 | 14 | # the expected output 15 | echo "a" >&2 16 | echo "b" >&2 17 | echo "c" >&2 18 | echo "" >&2 19 | echo "a" >&2 20 | -------------------------------------------------------------------------------- /uc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "vi.h" 6 | 7 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 8 | 9 | /* return the length of a utf-8 character */ 10 | int uc_len(char *s) 11 | { 12 | int c = (unsigned char) s[0]; 13 | if (~c & 0xc0) /* ASCII or invalid */ 14 | return c > 0; 15 | if (~c & 0x20) 16 | return 2; 17 | if (~c & 0x10) 18 | return 3; 19 | if (~c & 0x08) 20 | return 4; 21 | return 1; 22 | } 23 | 24 | /* the number of utf-8 characters in s */ 25 | int uc_slen(char *s) 26 | { 27 | int n; 28 | for (n = 0; *s; n++) 29 | s = uc_end(s) + 1; 30 | return n; 31 | } 32 | 33 | /* the unicode codepoint of the given utf-8 character */ 34 | int uc_code(char *s) 35 | { 36 | int c = (unsigned char) s[0]; 37 | if (~c & 0xc0) /* ASCII or invalid */ 38 | return c; 39 | if (~c & 0x20) 40 | return ((c & 0x1f) << 6) | (s[1] & 0x3f); 41 | if (~c & 0x10) 42 | return ((c & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f); 43 | if (~c & 0x08) 44 | return ((c & 0x07) << 18) | ((s[1] & 0x3f) << 12) | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); 45 | return c; 46 | } 47 | 48 | /* find the beginning of the character at s[i] */ 49 | char *uc_beg(char *beg, char *s) 50 | { 51 | while (s > beg && (((unsigned char) *s) & 0xc0) == 0x80) 52 | s--; 53 | return s; 54 | } 55 | 56 | /* find the end of the character at s[i] */ 57 | char *uc_end(char *s) 58 | { 59 | if (!*s || !((unsigned char) *s & 0x80)) 60 | return s; 61 | if (((unsigned char) *s & 0xc0) == 0xc0) 62 | s++; 63 | while (((unsigned char) *s & 0xc0) == 0x80) 64 | s++; 65 | return s - 1; 66 | } 67 | 68 | /* return a pointer to the character following s */ 69 | char *uc_next(char *s) 70 | { 71 | s = uc_end(s); 72 | return *s ? s + 1 : s; 73 | } 74 | 75 | /* return a pointer to the character preceding s */ 76 | char *uc_prev(char *beg, char *s) 77 | { 78 | return s == beg ? beg : uc_beg(beg, s - 1); 79 | } 80 | 81 | char *uc_lastline(char *s) 82 | { 83 | char *r = strrchr(s, '\n'); 84 | return r ? r + 1 : s; 85 | } 86 | 87 | /* allocate and return an array for the characters in s */ 88 | char **uc_chop(char *s, int *n) 89 | { 90 | char **chrs; 91 | int i; 92 | *n = uc_slen(s); 93 | chrs = malloc((*n + 1) * sizeof(chrs[0])); 94 | for (i = 0; i < *n + 1; i++) { 95 | chrs[i] = s; 96 | s = uc_next(s); 97 | } 98 | return chrs; 99 | } 100 | 101 | char *uc_chr(char *s, int off) 102 | { 103 | int i = 0; 104 | while (s && *s) { 105 | if (i++ == off) 106 | return s; 107 | s = uc_next(s); 108 | } 109 | return s && (off < 0 || i == off) ? s : ""; 110 | } 111 | 112 | /* the number of characters between s and s + off */ 113 | int uc_off(char *s, int off) 114 | { 115 | char *e = s + off; 116 | int i; 117 | for (i = 0; s < e && *s; i++) 118 | s = uc_next(s); 119 | return i; 120 | } 121 | 122 | char *uc_sub(char *s, int beg, int end) 123 | { 124 | char *sbeg = uc_chr(s, beg); 125 | char *send = uc_chr(s, end); 126 | int len = sbeg && send && sbeg <= send ? send - sbeg : 0; 127 | char *r = malloc(len + 1); 128 | memcpy(r, sbeg, len); 129 | r[len] = '\0'; 130 | return r; 131 | } 132 | 133 | char *uc_dup(char *s) 134 | { 135 | char *r = malloc(strlen(s) + 1); 136 | return r ? strcpy(r, s) : NULL; 137 | } 138 | 139 | char *uc_cat(char *s, char *r) 140 | { 141 | int slen = strlen(s); 142 | int rlen = strlen(r); 143 | char *o = malloc(slen + rlen + 1); 144 | memcpy(o, s, slen); 145 | memcpy(o + slen, r, rlen); 146 | o[slen + rlen] = '\0'; 147 | return o; 148 | } 149 | 150 | int uc_isspace(char *s) 151 | { 152 | int c = s ? (unsigned char) *s : 0; 153 | return c <= 0x7f && isspace(c); 154 | } 155 | 156 | int uc_isprint(char *s) 157 | { 158 | int c = s ? (unsigned char) *s : 0; 159 | return c > 0x7f || isprint(c); 160 | } 161 | 162 | int uc_isalpha(char *s) 163 | { 164 | int c = s ? (unsigned char) *s : 0; 165 | return c > 0x7f || isalpha(c); 166 | } 167 | 168 | int uc_isdigit(char *s) 169 | { 170 | int c = s ? (unsigned char) *s : 0; 171 | return c <= 0x7f && isdigit(c); 172 | } 173 | 174 | int uc_kind(char *c) 175 | { 176 | if (uc_isspace(c)) 177 | return 0; 178 | if (uc_isalpha(c) || uc_isdigit(c) || c[0] == '_') 179 | return 1; 180 | return 2; 181 | } 182 | 183 | #define UC_R2L(ch) (((ch) & 0xff00) == 0x0600 || \ 184 | ((ch) & 0xfffc) == 0x200c || \ 185 | ((ch) & 0xff00) == 0xfb00 || \ 186 | ((ch) & 0xff00) == 0xfc00 || \ 187 | ((ch) & 0xff00) == 0xfe00) 188 | 189 | /* sorted list of characters that can be shaped */ 190 | static struct achar { 191 | unsigned c; /* utf-8 code */ 192 | unsigned s; /* single form */ 193 | unsigned i; /* initial form */ 194 | unsigned m; /* medial form */ 195 | unsigned f; /* final form */ 196 | } achars[] = { 197 | {0x0621, 0xfe80}, /* hamza */ 198 | {0x0622, 0xfe81, 0, 0, 0xfe82}, /* alef madda */ 199 | {0x0623, 0xfe83, 0, 0, 0xfe84}, /* alef hamza above */ 200 | {0x0624, 0xfe85, 0, 0, 0xfe86}, /* waw hamza */ 201 | {0x0625, 0xfe87, 0, 0, 0xfe88}, /* alef hamza below */ 202 | {0x0626, 0xfe89, 0xfe8b, 0xfe8c, 0xfe8a}, /* yeh hamza */ 203 | {0x0627, 0xfe8d, 0, 0, 0xfe8e}, /* alef */ 204 | {0x0628, 0xfe8f, 0xfe91, 0xfe92, 0xfe90}, /* beh */ 205 | {0x0629, 0xfe93, 0, 0, 0xfe94}, /* teh marbuta */ 206 | {0x062a, 0xfe95, 0xfe97, 0xfe98, 0xfe96}, /* teh */ 207 | {0x062b, 0xfe99, 0xfe9b, 0xfe9c, 0xfe9a}, /* theh */ 208 | {0x062c, 0xfe9d, 0xfe9f, 0xfea0, 0xfe9e}, /* jeem */ 209 | {0x062d, 0xfea1, 0xfea3, 0xfea4, 0xfea2}, /* hah */ 210 | {0x062e, 0xfea5, 0xfea7, 0xfea8, 0xfea6}, /* khah */ 211 | {0x062f, 0xfea9, 0, 0, 0xfeaa}, /* dal */ 212 | {0x0630, 0xfeab, 0, 0, 0xfeac}, /* thal */ 213 | {0x0631, 0xfead, 0, 0, 0xfeae}, /* reh */ 214 | {0x0632, 0xfeaf, 0, 0, 0xfeb0}, /* zain */ 215 | {0x0633, 0xfeb1, 0xfeb3, 0xfeb4, 0xfeb2}, /* seen */ 216 | {0x0634, 0xfeb5, 0xfeb7, 0xfeb8, 0xfeb6}, /* sheen */ 217 | {0x0635, 0xfeb9, 0xfebb, 0xfebc, 0xfeba}, /* sad */ 218 | {0x0636, 0xfebd, 0xfebf, 0xfec0, 0xfebe}, /* dad */ 219 | {0x0637, 0xfec1, 0xfec3, 0xfec4, 0xfec2}, /* tah */ 220 | {0x0638, 0xfec5, 0xfec7, 0xfec8, 0xfec6}, /* zah */ 221 | {0x0639, 0xfec9, 0xfecb, 0xfecc, 0xfeca}, /* ain */ 222 | {0x063a, 0xfecd, 0xfecf, 0xfed0, 0xfece}, /* ghain */ 223 | {0x0640, 0x640, 0x640, 0x640}, /* tatweel */ 224 | {0x0641, 0xfed1, 0xfed3, 0xfed4, 0xfed2}, /* feh */ 225 | {0x0642, 0xfed5, 0xfed7, 0xfed8, 0xfed6}, /* qaf */ 226 | {0x0643, 0xfed9, 0xfedb, 0xfedc, 0xfeda}, /* kaf */ 227 | {0x0644, 0xfedd, 0xfedf, 0xfee0, 0xfede}, /* lam */ 228 | {0x0645, 0xfee1, 0xfee3, 0xfee4, 0xfee2}, /* meem */ 229 | {0x0646, 0xfee5, 0xfee7, 0xfee8, 0xfee6}, /* noon */ 230 | {0x0647, 0xfee9, 0xfeeb, 0xfeec, 0xfeea}, /* heh */ 231 | {0x0648, 0xfeed, 0, 0, 0xfeee}, /* waw */ 232 | {0x0649, 0xfeef, 0, 0, 0xfef0}, /* alef maksura */ 233 | {0x064a, 0xfef1, 0xfef3, 0xfef4, 0xfef2}, /* yeh */ 234 | {0x067e, 0xfb56, 0xfb58, 0xfb59, 0xfb57}, /* peh */ 235 | {0x0686, 0xfb7a, 0xfb7c, 0xfb7d, 0xfb7b}, /* tcheh */ 236 | {0x0698, 0xfb8a, 0, 0, 0xfb8b}, /* jeh */ 237 | {0x06a9, 0xfb8e, 0xfb90, 0xfb91, 0xfb8f}, /* fkaf */ 238 | {0x06af, 0xfb92, 0xfb94, 0xfb95, 0xfb93}, /* gaf */ 239 | {0x06cc, 0xfbfc, 0xfbfe, 0xfbff, 0xfbfd}, /* fyeh */ 240 | {0x200c}, /* ZWNJ */ 241 | {0x200d, 0, 0x200d, 0x200d}, /* ZWJ */ 242 | }; 243 | 244 | static struct achar *find_achar(int c) 245 | { 246 | int h, m, l; 247 | h = LEN(achars); 248 | l = 0; 249 | /* using binary search to find c */ 250 | while (l < h) { 251 | m = (h + l) >> 1; 252 | if (achars[m].c == c) 253 | return &achars[m]; 254 | if (c < achars[m].c) 255 | h = m; 256 | else 257 | l = m + 1; 258 | } 259 | return NULL; 260 | } 261 | 262 | static int can_join(int c1, int c2) 263 | { 264 | struct achar *a1 = find_achar(c1); 265 | struct achar *a2 = find_achar(c2); 266 | return a1 && a2 && (a1->i || a1->m) && (a2->f || a2->m); 267 | } 268 | 269 | static int uc_cshape(int cur, int prev, int next) 270 | { 271 | int c = cur; 272 | int join_prev, join_next; 273 | struct achar *ac = find_achar(c); 274 | if (!ac) /* ignore non-Arabic characters */ 275 | return c; 276 | join_prev = can_join(prev, c); 277 | join_next = can_join(c, next); 278 | if (join_prev && join_next) 279 | c = ac->m; 280 | if (join_prev && !join_next) 281 | c = ac->f; 282 | if (!join_prev && join_next) 283 | c = ac->i; 284 | if (!join_prev && !join_next) 285 | c = ac->c; /* some fonts do not have a glyph for ac->s */ 286 | return c ? c : cur; 287 | } 288 | 289 | /* 290 | * return nonzero for Arabic combining characters 291 | * 292 | * The standard Arabic diacritics: 293 | * + 0x064b: fathatan 294 | * + 0x064c: dammatan 295 | * + 0x064d: kasratan 296 | * + 0x064e: fatha 297 | * + 0x064f: damma 298 | * + 0x0650: kasra 299 | * + 0x0651: shadda 300 | * + 0x0652: sukun 301 | * + 0x0653: madda above 302 | * + 0x0654: hamza above 303 | * + 0x0655: hamza below 304 | * + 0x0670: superscript alef 305 | */ 306 | static int uc_acomb(int c) 307 | { 308 | return (c >= 0x064b && c <= 0x0655) || /* the standard diacritics */ 309 | (c >= 0xfc5e && c <= 0xfc63) || /* shadda ligatures */ 310 | c == 0x0670; /* superscript alef */ 311 | } 312 | 313 | static void uc_cput(char *d, int c) 314 | { 315 | int l = 0; 316 | if (c > 0xffff) { 317 | *d++ = 0xf0 | (c >> 18); 318 | l = 3; 319 | } else if (c > 0x7ff) { 320 | *d++ = 0xe0 | (c >> 12); 321 | l = 2; 322 | } else if (c > 0x7f) { 323 | *d++ = 0xc0 | (c >> 6); 324 | l = 1; 325 | } else { 326 | *d++ = c; 327 | } 328 | while (l--) 329 | *d++ = 0x80 | ((c >> (l * 6)) & 0x3f); 330 | *d = '\0'; 331 | } 332 | 333 | /* shape the given arabic character; returns a static buffer */ 334 | char *uc_shape(char *beg, char *s) 335 | { 336 | static char out[16]; 337 | char *r; 338 | int prev = 0; 339 | int next = 0; 340 | int curr = uc_code(s); 341 | if (!curr || !UC_R2L(curr)) 342 | return NULL; 343 | r = s; 344 | while (r > beg) { 345 | r = uc_beg(beg, r - 1); 346 | if (!uc_acomb(uc_code(r))) { 347 | prev = uc_code(r); 348 | break; 349 | } 350 | } 351 | r = s; 352 | while (*r) { 353 | r = uc_next(r); 354 | if (!uc_acomb(uc_code(r))) { 355 | next = uc_code(r); 356 | break; 357 | } 358 | } 359 | uc_cput(out, uc_cshape(curr, prev, next)); 360 | return out; 361 | } 362 | 363 | static int dwchars[][2] = { 364 | {0x1100, 0x115f}, {0x11a3, 0x11a7}, {0x11fa, 0x11ff}, {0x2329, 0x232a}, 365 | {0x2e80, 0x2e99}, {0x2e9b, 0x2ef3}, {0x2f00, 0x2fd5}, {0x2ff0, 0x2ffb}, 366 | {0x3000, 0x3029}, {0x3030, 0x303e}, {0x3041, 0x3096}, {0x309b, 0x30ff}, 367 | {0x3105, 0x312d}, {0x3131, 0x318e}, {0x3190, 0x31b7}, {0x31c0, 0x31e3}, 368 | {0x31f0, 0x321e}, {0x3220, 0x3247}, {0x3250, 0x32fe}, {0x3300, 0x4dbf}, 369 | {0x4e00, 0xa48c}, {0xa490, 0xa4c6}, {0xa960, 0xa97c}, {0xac00, 0xd7a3}, 370 | {0xd7b0, 0xd7c6}, {0xd7cb, 0xd7fb}, {0xf900, 0xfaff}, {0xfe10, 0xfe19}, 371 | {0xfe30, 0xfe52}, {0xfe54, 0xfe66}, {0xfe68, 0xfe6b}, {0xff01, 0xff60}, 372 | {0xffe0, 0xffe6}, {0x1f200, 0x1f200}, {0x1f210, 0x1f231}, {0x1f240, 0x1f248}, 373 | {0x20000,0x2ffff}, 374 | }; 375 | 376 | static int zwchars[][2] = { 377 | {0x0300, 0x036f}, {0x0483, 0x0489}, {0x0591, 0x05bd}, {0x05bf, 0x05bf}, 378 | {0x05c1, 0x05c2}, {0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a}, 379 | {0x064b, 0x065e}, {0x0670, 0x0670}, {0x06d6, 0x06dc}, {0x06de, 0x06e4}, 380 | {0x06e7, 0x06e8}, {0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a}, 381 | {0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819}, {0x081b, 0x0823}, 382 | {0x0825, 0x0827}, {0x0829, 0x082d}, {0x0900, 0x0903}, {0x093c, 0x093c}, 383 | {0x093e, 0x094e}, {0x0951, 0x0955}, {0x0962, 0x0963}, {0x0981, 0x0983}, 384 | {0x09bc, 0x09bc}, {0x09be, 0x09c4}, {0x09c7, 0x09c8}, {0x09cb, 0x09cd}, 385 | {0x09d7, 0x09d7}, {0x09e2, 0x09e3}, {0x0a01, 0x0a03}, {0x0a3c, 0x0a3c}, 386 | {0x0a3e, 0x0a42}, {0x0a47, 0x0a48}, {0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, 387 | {0x0a70, 0x0a71}, {0x0a75, 0x0a75}, {0x0a81, 0x0a83}, {0x0abc, 0x0abc}, 388 | {0x0abe, 0x0ac5}, {0x0ac7, 0x0ac9}, {0x0acb, 0x0acd}, {0x0ae2, 0x0ae3}, 389 | {0x0b01, 0x0b03}, {0x0b3c, 0x0b3c}, {0x0b3e, 0x0b44}, {0x0b47, 0x0b48}, 390 | {0x0b4b, 0x0b4d}, {0x0b56, 0x0b57}, {0x0b62, 0x0b63}, {0x0b82, 0x0b82}, 391 | {0x0bbe, 0x0bc2}, {0x0bc6, 0x0bc8}, {0x0bca, 0x0bcd}, {0x0bd7, 0x0bd7}, 392 | {0x0c01, 0x0c03}, {0x0c3e, 0x0c44}, {0x0c46, 0x0c48}, {0x0c4a, 0x0c4d}, 393 | {0x0c55, 0x0c56}, {0x0c62, 0x0c63}, {0x0c82, 0x0c83}, {0x0cbc, 0x0cbc}, 394 | {0x0cbe, 0x0cc4}, {0x0cc6, 0x0cc8}, {0x0cca, 0x0ccd}, {0x0cd5, 0x0cd6}, 395 | {0x0ce2, 0x0ce3}, {0x0d02, 0x0d03}, {0x0d3e, 0x0d44}, {0x0d46, 0x0d48}, 396 | {0x0d4a, 0x0d4d}, {0x0d57, 0x0d57}, {0x0d62, 0x0d63}, {0x0d82, 0x0d83}, 397 | {0x0dca, 0x0dca}, {0x0dcf, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0dd8, 0x0ddf}, 398 | {0x0df2, 0x0df3}, {0x0e31, 0x0e31}, {0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, 399 | {0x0eb1, 0x0eb1}, {0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd}, 400 | {0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37}, {0x0f39, 0x0f39}, 401 | {0x0f3e, 0x0f3f}, {0x0f71, 0x0f84}, {0x0f86, 0x0f87}, {0x0f90, 0x0f97}, 402 | {0x0f99, 0x0fbc}, {0x0fc6, 0x0fc6}, {0x102b, 0x103e}, {0x1056, 0x1059}, 403 | {0x105e, 0x1060}, {0x1062, 0x1064}, {0x1067, 0x106d}, {0x1071, 0x1074}, 404 | {0x1082, 0x108d}, {0x108f, 0x108f}, {0x109a, 0x109d}, {0x135f, 0x135f}, 405 | {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, 406 | {0x17b6, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180d}, {0x18a9, 0x18a9}, 407 | {0x1920, 0x192b}, {0x1930, 0x193b}, {0x19b0, 0x19c0}, {0x19c8, 0x19c9}, 408 | {0x1a17, 0x1a1b}, {0x1a55, 0x1a5e}, {0x1a60, 0x1a7c}, {0x1a7f, 0x1a7f}, 409 | {0x1b00, 0x1b04}, {0x1b34, 0x1b44}, {0x1b6b, 0x1b73}, {0x1b80, 0x1b82}, 410 | {0x1ba1, 0x1baa}, {0x1c24, 0x1c37}, {0x1cd0, 0x1cd2}, {0x1cd4, 0x1ce8}, 411 | {0x1ced, 0x1ced}, {0x1cf2, 0x1cf2}, {0x1dc0, 0x1de6}, {0x1dfd, 0x1dff}, 412 | {0x200b, 0x200f}, 413 | {0x20d0, 0x20f0}, {0x2cef, 0x2cf1}, {0x2de0, 0x2dff}, {0x302a, 0x302f}, 414 | {0x3099, 0x309a}, {0xa66f, 0xa672}, {0xa67c, 0xa67d}, {0xa6f0, 0xa6f1}, 415 | {0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b}, {0xa823, 0xa827}, 416 | {0xa880, 0xa881}, {0xa8b4, 0xa8c4}, {0xa8e0, 0xa8f1}, {0xa926, 0xa92d}, 417 | {0xa947, 0xa953}, {0xa980, 0xa983}, {0xa9b3, 0xa9c0}, {0xaa29, 0xaa36}, 418 | {0xaa43, 0xaa43}, {0xaa4c, 0xaa4d}, {0xaa7b, 0xaa7b}, {0xaab0, 0xaab0}, 419 | {0xaab2, 0xaab4}, {0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1}, 420 | {0xabe3, 0xabea}, {0xabec, 0xabed}, {0xfb1e, 0xfb1e}, {0xfe00, 0xfe0f}, 421 | {0xfe20, 0xfe26}, {0x101fd, 0x101fd}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06}, 422 | {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, {0x11080, 0x11082}, 423 | {0x110b0, 0x110ba}, {0x1d165, 0x1d169}, {0x1d16d, 0x1d172}, {0x1d17b, 0x1d182}, 424 | {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, {0x1d242, 0x1d244}, {0xe0100, 0xe01ef} 425 | }; 426 | 427 | static int bchars[][2] = { 428 | {0x00000, 0x0001f}, {0x00080, 0x0009f}, {0x00300, 0x0036f}, 429 | {0x00379, 0x00379}, {0x00380, 0x00383}, {0x0038d, 0x0038d}, 430 | {0x00483, 0x00489}, {0x00527, 0x00530}, {0x00558, 0x00558}, 431 | {0x00588, 0x00588}, {0x0058c, 0x005bd}, {0x005c1, 0x005c2}, 432 | {0x005c5, 0x005c5}, {0x005c8, 0x005cf}, {0x005ec, 0x005ef}, 433 | {0x005f6, 0x00605}, {0x00611, 0x0061a}, {0x0061d, 0x0061d}, 434 | {0x0064b, 0x0065f}, {0x006d6, 0x006e4}, {0x006e8, 0x006e8}, 435 | {0x006eb, 0x006ed}, {0x0070f, 0x0070f}, {0x00730, 0x0074c}, 436 | {0x007a7, 0x007b0}, {0x007b3, 0x007bf}, {0x007ec, 0x007f3}, 437 | {0x007fc, 0x007ff}, {0x00817, 0x00819}, {0x0081c, 0x00823}, 438 | {0x00826, 0x00827}, {0x0082a, 0x0082f}, {0x00840, 0x00903}, 439 | {0x0093b, 0x0093c}, {0x0093f, 0x0094f}, {0x00952, 0x00957}, 440 | {0x00963, 0x00963}, {0x00974, 0x00978}, {0x00981, 0x00984}, 441 | {0x0098e, 0x0098e}, {0x00992, 0x00992}, {0x009b1, 0x009b1}, 442 | {0x009b4, 0x009b5}, {0x009bb, 0x009bc}, {0x009bf, 0x009cd}, 443 | {0x009d0, 0x009db}, {0x009e2, 0x009e5}, {0x009fd, 0x00a04}, 444 | {0x00a0c, 0x00a0e}, {0x00a12, 0x00a12}, {0x00a31, 0x00a31}, 445 | {0x00a37, 0x00a37}, {0x00a3b, 0x00a58}, {0x00a5f, 0x00a65}, 446 | {0x00a71, 0x00a71}, {0x00a76, 0x00a84}, {0x00a92, 0x00a92}, 447 | {0x00ab1, 0x00ab1}, {0x00aba, 0x00abc}, {0x00abf, 0x00acf}, 448 | {0x00ad2, 0x00adf}, {0x00ae3, 0x00ae5}, {0x00af2, 0x00b04}, 449 | {0x00b0e, 0x00b0e}, {0x00b12, 0x00b12}, {0x00b31, 0x00b31}, 450 | {0x00b3a, 0x00b3c}, {0x00b3f, 0x00b5b}, {0x00b62, 0x00b65}, 451 | {0x00b73, 0x00b82}, {0x00b8b, 0x00b8d}, {0x00b96, 0x00b98}, 452 | {0x00b9d, 0x00b9d}, {0x00ba1, 0x00ba2}, {0x00ba6, 0x00ba7}, 453 | {0x00bac, 0x00bad}, {0x00bbb, 0x00bcf}, {0x00bd2, 0x00be5}, 454 | {0x00bfc, 0x00c04}, {0x00c11, 0x00c11}, {0x00c34, 0x00c34}, 455 | {0x00c3b, 0x00c3c}, {0x00c3f, 0x00c57}, {0x00c5b, 0x00c5f}, 456 | {0x00c63, 0x00c65}, {0x00c71, 0x00c77}, {0x00c81, 0x00c84}, 457 | {0x00c91, 0x00c91}, {0x00cb4, 0x00cb4}, {0x00cbb, 0x00cbc}, 458 | {0x00cbf, 0x00cdd}, {0x00ce2, 0x00ce5}, {0x00cf3, 0x00d04}, 459 | {0x00d11, 0x00d11}, {0x00d3a, 0x00d3c}, {0x00d3f, 0x00d5f}, 460 | {0x00d63, 0x00d65}, {0x00d77, 0x00d78}, {0x00d81, 0x00d84}, 461 | {0x00d98, 0x00d99}, {0x00dbc, 0x00dbc}, {0x00dbf, 0x00dbf}, 462 | {0x00dc8, 0x00df3}, {0x00df6, 0x00e00}, {0x00e34, 0x00e3e}, 463 | {0x00e48, 0x00e4e}, {0x00e5d, 0x00e80}, {0x00e85, 0x00e86}, 464 | {0x00e8b, 0x00e8c}, {0x00e8f, 0x00e93}, {0x00ea0, 0x00ea0}, 465 | {0x00ea6, 0x00ea6}, {0x00ea9, 0x00ea9}, {0x00eb1, 0x00eb1}, 466 | {0x00eb5, 0x00ebc}, {0x00ebf, 0x00ebf}, {0x00ec7, 0x00ecf}, 467 | {0x00edb, 0x00edb}, {0x00edf, 0x00eff}, {0x00f19, 0x00f19}, 468 | {0x00f37, 0x00f37}, {0x00f3e, 0x00f3f}, {0x00f6d, 0x00f84}, 469 | {0x00f87, 0x00f87}, {0x00f8d, 0x00fbd}, {0x00fcd, 0x00fcd}, 470 | {0x00fda, 0x00fff}, {0x0102c, 0x0103e}, {0x01057, 0x01059}, 471 | {0x0105f, 0x01060}, {0x01063, 0x01064}, {0x01068, 0x0106d}, 472 | {0x01072, 0x01074}, {0x01083, 0x0108d}, {0x0109a, 0x0109d}, 473 | {0x010c7, 0x010cf}, {0x010fe, 0x010ff}, {0x0124e, 0x0124f}, 474 | {0x01259, 0x01259}, {0x0125f, 0x0125f}, {0x0128e, 0x0128f}, 475 | {0x012b6, 0x012b7}, {0x012c1, 0x012c1}, {0x012c7, 0x012c7}, 476 | {0x01311, 0x01311}, {0x01317, 0x01317}, {0x0135c, 0x0135f}, 477 | {0x0137e, 0x0137f}, {0x0139b, 0x0139f}, {0x013f6, 0x013ff}, 478 | {0x0169e, 0x0169f}, {0x016f2, 0x016ff}, {0x01712, 0x0171f}, 479 | {0x01733, 0x01734}, {0x01738, 0x0173f}, {0x01753, 0x0175f}, 480 | {0x01771, 0x0177f}, {0x017b5, 0x017d3}, {0x017de, 0x017df}, 481 | {0x017eb, 0x017ef}, {0x017fb, 0x017ff}, {0x0180c, 0x0180d}, 482 | {0x0181a, 0x0181f}, {0x01879, 0x0187f}, {0x018ab, 0x018af}, 483 | {0x018f7, 0x018ff}, {0x0191e, 0x0193f}, {0x01942, 0x01943}, 484 | {0x0196f, 0x0196f}, {0x01976, 0x0197f}, {0x019ad, 0x019c0}, 485 | {0x019c9, 0x019cf}, {0x019dc, 0x019dd}, {0x01a18, 0x01a1d}, 486 | {0x01a56, 0x01a7f}, {0x01a8b, 0x01a8f}, {0x01a9b, 0x01a9f}, 487 | {0x01aaf, 0x01b04}, {0x01b35, 0x01b44}, {0x01b4d, 0x01b4f}, 488 | {0x01b6c, 0x01b73}, {0x01b7e, 0x01b82}, {0x01ba2, 0x01bad}, 489 | {0x01bbb, 0x01bff}, {0x01c25, 0x01c3a}, {0x01c4b, 0x01c4c}, 490 | {0x01c81, 0x01cd2}, {0x01cd5, 0x01ce8}, {0x01cf2, 0x01cff}, 491 | {0x01dc1, 0x01dff}, {0x01f17, 0x01f17}, {0x01f1f, 0x01f1f}, 492 | {0x01f47, 0x01f47}, {0x01f4f, 0x01f4f}, {0x01f5a, 0x01f5a}, 493 | {0x01f5e, 0x01f5e}, {0x01f7f, 0x01f7f}, {0x01fc5, 0x01fc5}, 494 | {0x01fd5, 0x01fd5}, {0x01ff0, 0x01ff1}, {0x01fff, 0x01fff}, 495 | {0x0200c, 0x0200f}, {0x02029, 0x0202e}, {0x02061, 0x0206f}, 496 | {0x02073, 0x02073}, {0x02095, 0x0209f}, {0x020ba, 0x020ff}, 497 | {0x0218b, 0x0218f}, {0x023ea, 0x023ff}, {0x02428, 0x0243f}, 498 | {0x0244c, 0x0245f}, {0x026e2, 0x026e2}, {0x026e5, 0x026e7}, 499 | {0x02705, 0x02705}, {0x0270b, 0x0270b}, {0x0274c, 0x0274c}, 500 | {0x02753, 0x02755}, {0x02760, 0x02760}, {0x02796, 0x02797}, 501 | {0x027bf, 0x027bf}, {0x027cd, 0x027cf}, {0x02b4e, 0x02b4f}, 502 | {0x02b5b, 0x02bff}, {0x02c5f, 0x02c5f}, {0x02cf0, 0x02cf8}, 503 | {0x02d27, 0x02d2f}, {0x02d67, 0x02d6e}, {0x02d71, 0x02d7f}, 504 | {0x02d98, 0x02d9f}, {0x02daf, 0x02daf}, {0x02dbf, 0x02dbf}, 505 | {0x02dcf, 0x02dcf}, {0x02ddf, 0x02dff}, {0x02e33, 0x02e7f}, 506 | {0x02ef4, 0x02eff}, {0x02fd7, 0x02fef}, {0x02ffd, 0x02fff}, 507 | {0x0302b, 0x0302f}, {0x03097, 0x0309a}, {0x03101, 0x03104}, 508 | {0x0312f, 0x03130}, {0x031b8, 0x031bf}, {0x031e5, 0x031ef}, 509 | {0x032ff, 0x032ff}, {0x04db7, 0x04dbf}, {0x09fcd, 0x09fff}, 510 | {0x0a48e, 0x0a48f}, {0x0a4c8, 0x0a4cf}, {0x0a62d, 0x0a63f}, 511 | {0x0a661, 0x0a661}, {0x0a670, 0x0a672}, {0x0a675, 0x0a67d}, 512 | {0x0a699, 0x0a69f}, {0x0a6f1, 0x0a6f1}, {0x0a6f9, 0x0a6ff}, 513 | {0x0a78e, 0x0a7fa}, {0x0a806, 0x0a806}, {0x0a823, 0x0a827}, 514 | {0x0a82d, 0x0a82f}, {0x0a83b, 0x0a83f}, {0x0a879, 0x0a881}, 515 | {0x0a8b5, 0x0a8cd}, {0x0a8db, 0x0a8f1}, {0x0a8fd, 0x0a8ff}, 516 | {0x0a927, 0x0a92d}, {0x0a948, 0x0a95e}, {0x0a97e, 0x0a983}, 517 | {0x0a9b4, 0x0a9c0}, {0x0a9da, 0x0a9dd}, {0x0a9e1, 0x0a9ff}, 518 | {0x0aa2a, 0x0aa3f}, {0x0aa4c, 0x0aa4f}, {0x0aa5b, 0x0aa5b}, 519 | {0x0aa7c, 0x0aa7f}, {0x0aab2, 0x0aab4}, {0x0aab8, 0x0aab8}, 520 | {0x0aabf, 0x0aabf}, {0x0aac3, 0x0aada}, {0x0aae1, 0x0abbf}, 521 | {0x0abe4, 0x0abea}, {0x0abed, 0x0abef}, {0x0abfb, 0x0abff}, 522 | {0x0d7a5, 0x0d7af}, {0x0d7c8, 0x0d7ca}, {0x0d7fd, 0x0f8ff}, 523 | {0x0fa2f, 0x0fa2f}, {0x0fa6f, 0x0fa6f}, {0x0fadb, 0x0faff}, 524 | {0x0fb08, 0x0fb12}, {0x0fb19, 0x0fb1c}, {0x0fb37, 0x0fb37}, 525 | {0x0fb3f, 0x0fb3f}, {0x0fb45, 0x0fb45}, {0x0fbb3, 0x0fbd2}, 526 | {0x0fd41, 0x0fd4f}, {0x0fd91, 0x0fd91}, {0x0fdc9, 0x0fdef}, 527 | {0x0fdff, 0x0fe0f}, {0x0fe1b, 0x0fe2f}, {0x0fe67, 0x0fe67}, 528 | {0x0fe6d, 0x0fe6f}, {0x0fefd, 0x0ff00}, {0x0ffc0, 0x0ffc1}, 529 | {0x0ffc9, 0x0ffc9}, {0x0ffd1, 0x0ffd1}, {0x0ffd9, 0x0ffd9}, 530 | {0x0ffde, 0x0ffdf}, {0x0ffef, 0x0fffb}, {0x0ffff, 0x0ffff}, 531 | {0x10027, 0x10027}, {0x1003e, 0x1003e}, {0x1004f, 0x1004f}, 532 | {0x1005f, 0x1007f}, {0x100fc, 0x100ff}, {0x10104, 0x10106}, 533 | {0x10135, 0x10136}, {0x1018c, 0x1018f}, {0x1019d, 0x101cf}, 534 | {0x101fe, 0x1027f}, {0x1029e, 0x1029f}, {0x102d2, 0x102ff}, 535 | {0x10324, 0x1032f}, {0x1034c, 0x1037f}, {0x103c4, 0x103c7}, 536 | {0x103d7, 0x103ff}, {0x1049f, 0x1049f}, {0x104ab, 0x107ff}, 537 | {0x10807, 0x10807}, {0x10836, 0x10836}, {0x1083a, 0x1083b}, 538 | {0x1083e, 0x1083e}, {0x10860, 0x108ff}, {0x1091d, 0x1091e}, 539 | {0x1093b, 0x1093e}, {0x10941, 0x109ff}, {0x10a02, 0x10a0f}, 540 | {0x10a18, 0x10a18}, {0x10a35, 0x10a3f}, {0x10a49, 0x10a4f}, 541 | {0x10a5a, 0x10a5f}, {0x10a81, 0x10aff}, {0x10b37, 0x10b38}, 542 | {0x10b57, 0x10b57}, {0x10b74, 0x10b77}, {0x10b81, 0x10bff}, 543 | {0x10c4a, 0x10e5f}, {0x10e80, 0x11082}, {0x110b1, 0x110ba}, 544 | {0x110c2, 0x11fff}, {0x12370, 0x123ff}, {0x12464, 0x1246f}, 545 | {0x12475, 0x12fff}, {0x13430, 0x1cfff}, {0x1d0f7, 0x1d0ff}, 546 | {0x1d128, 0x1d128}, {0x1d166, 0x1d169}, {0x1d16e, 0x1d182}, 547 | {0x1d186, 0x1d18b}, {0x1d1ab, 0x1d1ad}, {0x1d1df, 0x1d1ff}, 548 | {0x1d243, 0x1d244}, {0x1d247, 0x1d2ff}, {0x1d358, 0x1d35f}, 549 | {0x1d373, 0x1d3ff}, {0x1d49d, 0x1d49d}, {0x1d4a1, 0x1d4a1}, 550 | {0x1d4a4, 0x1d4a4}, {0x1d4a8, 0x1d4a8}, {0x1d4ba, 0x1d4ba}, 551 | {0x1d4c4, 0x1d4c4}, {0x1d50b, 0x1d50c}, {0x1d51d, 0x1d51d}, 552 | {0x1d53f, 0x1d53f}, {0x1d547, 0x1d549}, {0x1d6a6, 0x1d6a7}, 553 | {0x1d7cd, 0x1d7cd}, {0x1d801, 0x1efff}, {0x1f02d, 0x1f02f}, 554 | {0x1f095, 0x1f0ff}, {0x1f10c, 0x1f10f}, {0x1f130, 0x1f130}, 555 | {0x1f133, 0x1f13c}, {0x1f140, 0x1f141}, {0x1f144, 0x1f145}, 556 | {0x1f148, 0x1f149}, {0x1f150, 0x1f156}, {0x1f159, 0x1f15e}, 557 | {0x1f161, 0x1f178}, {0x1f17d, 0x1f17e}, {0x1f181, 0x1f189}, 558 | {0x1f18f, 0x1f18f}, {0x1f192, 0x1f1ff}, {0x1f202, 0x1f20f}, 559 | {0x1f233, 0x1f23f}, {0x1f24a, 0x1ffff}, {0x2a6d8, 0x2a6ff}, 560 | {0x2b736, 0x2f7ff}, {0x2fa1f, 0x10ffff}, 561 | }; 562 | 563 | static int find(int c, int tab[][2], int n) 564 | { 565 | int l = 0; 566 | int h = n - 1; 567 | int m; 568 | if (c < tab[0][0]) 569 | return 0; 570 | while (l <= h) { 571 | m = (h + l) / 2; 572 | if (c >= tab[m][0] && c <= tab[m][1]) 573 | return 1; 574 | if (c < tab[m][0]) 575 | h = m - 1; 576 | else 577 | l = m + 1; 578 | } 579 | return 0; 580 | } 581 | 582 | /* double-width characters */ 583 | static int uc_isdw(int c) 584 | { 585 | return c >= 0x1100 && find(c, dwchars, LEN(dwchars)); 586 | } 587 | 588 | /* zero-width and combining characters */ 589 | static int uc_iszw(int c) 590 | { 591 | return c >= 0x0300 && find(c, zwchars, LEN(zwchars)); 592 | } 593 | 594 | int uc_wid(char *s) 595 | { 596 | int c = uc_code(s); 597 | if (uc_iszw(c)) 598 | return 0; 599 | return uc_isdw(c) ? 2 : 1; 600 | } 601 | 602 | /* nonprintable characters */ 603 | int uc_isbell(char *s) 604 | { 605 | int c = (unsigned char) *s; 606 | if (c == ' ' || c == '\t' || c == '\n' || (c >= 0x20 && c < 0x7f)) 607 | return 0; 608 | c = uc_code(s); 609 | return uc_iszw(c) || find(c, bchars, LEN(bchars)); 610 | } 611 | 612 | /* combining characters */ 613 | int uc_iscomb(char *s) 614 | { 615 | int c = (unsigned char) *s; 616 | if (c == ' ' || c == '\t' || c == '\n' || (c <= 0x7f && isprint(c))) 617 | return 0; 618 | return uc_acomb(uc_code(s)); 619 | } 620 | -------------------------------------------------------------------------------- /vi.h: -------------------------------------------------------------------------------- 1 | /* neatvi main header */ 2 | 3 | /* helper macros */ 4 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 5 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 6 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 7 | 8 | /* line buffer, managing a number of lines */ 9 | struct lbuf *lbuf_make(void); 10 | void lbuf_free(struct lbuf *lbuf); 11 | int lbuf_rd(struct lbuf *lbuf, int fd, int beg, int end); 12 | int lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end); 13 | void lbuf_edit(struct lbuf *lbuf, char *s, int beg, int end); 14 | char *lbuf_cp(struct lbuf *lbuf, int beg, int end); 15 | char *lbuf_get(struct lbuf *lbuf, int pos); 16 | int lbuf_len(struct lbuf *lbuf); 17 | void lbuf_mark(struct lbuf *lbuf, int mark, int pos, int off); 18 | int lbuf_jump(struct lbuf *lbuf, int mark, int *pos, int *off); 19 | int lbuf_undo(struct lbuf *lbuf); 20 | int lbuf_redo(struct lbuf *lbuf); 21 | int lbuf_modified(struct lbuf *lb); 22 | void lbuf_saved(struct lbuf *lb, int clear); 23 | int lbuf_indents(struct lbuf *lb, int r); 24 | int lbuf_eol(struct lbuf *lb, int r); 25 | void lbuf_globset(struct lbuf *lb, int pos, int dep); 26 | int lbuf_globget(struct lbuf *lb, int pos, int dep); 27 | /* motions */ 28 | int lbuf_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *r, int *o); 29 | int lbuf_search(struct lbuf *lb, char *kw, int dir, int *r, int *o, int *len); 30 | int lbuf_paragraphbeg(struct lbuf *lb, int dir, int *row, int *off); 31 | int lbuf_sectionbeg(struct lbuf *lb, int dir, char *sec, int *row, int *off); 32 | int lbuf_wordbeg(struct lbuf *lb, int big, int dir, int *row, int *off); 33 | int lbuf_wordend(struct lbuf *lb, int big, int dir, int *row, int *off); 34 | int lbuf_pair(struct lbuf *lb, int *row, int *off); 35 | 36 | /* string buffer, variable-sized string */ 37 | struct sbuf *sbuf_make(void); 38 | void sbuf_free(struct sbuf *sb); 39 | char *sbuf_done(struct sbuf *sb); 40 | char *sbuf_buf(struct sbuf *sb); 41 | void sbuf_chr(struct sbuf *sb, int c); 42 | void sbuf_str(struct sbuf *sb, char *s); 43 | void sbuf_mem(struct sbuf *sb, char *s, int len); 44 | void sbuf_printf(struct sbuf *sbuf, char *s, ...); 45 | int sbuf_len(struct sbuf *sb); 46 | void sbuf_cut(struct sbuf *s, int len); 47 | 48 | /* regular expressions */ 49 | #define RE_ICASE 1 50 | #define RE_NOTBOL 2 51 | #define RE_NOTEOL 4 52 | /* regular expression sets: searching for multiple regular expressions */ 53 | struct rset *rset_make(int n, char **pat, int flg); 54 | int rset_find(struct rset *re, char *s, int n, int *grps, int flg); 55 | void rset_free(struct rset *re); 56 | char *re_read(char **src); 57 | /* searching for a single pattern regular expression */ 58 | struct rstr *rstr_make(char *re, int flg); 59 | int rstr_find(struct rstr *rs, char *s, int n, int *grps, int flg); 60 | void rstr_free(struct rstr *rs); 61 | 62 | /* rendering lines */ 63 | int *ren_position(char *s); 64 | int ren_next(char *s, int p, int dir); 65 | int ren_eol(char *s, int dir); 66 | int ren_pos(char *s, int off); 67 | int ren_cursor(char *s, int pos); 68 | int ren_noeol(char *s, int p); 69 | int ren_off(char *s, int pos); 70 | int ren_wid(char *s); 71 | int ren_region(char *s, int c1, int c2, int *l1, int *l2, int closed); 72 | char *ren_translate(char *s, char *ln); 73 | int ren_cwid(char *s, int pos); 74 | 75 | /* text direction */ 76 | int dir_context(char *s); 77 | void dir_reorder(char *s, int *ord); 78 | void dir_init(void); 79 | void dir_done(void); 80 | 81 | /* string registers */ 82 | char *reg_get(int c, int *lnmode); 83 | void reg_put(int c, char *s, int lnmode); 84 | void reg_done(void); 85 | 86 | /* utf-8 helper functions */ 87 | int uc_len(char *s); 88 | int uc_wid(char *s); 89 | int uc_slen(char *s); 90 | int uc_code(char *s); 91 | char *uc_chr(char *s, int off); 92 | int uc_off(char *s, int off); 93 | char *uc_sub(char *s, int beg, int end); 94 | char *uc_dup(char *s); 95 | char *uc_cat(char *s, char *r); 96 | int uc_isspace(char *s); 97 | int uc_isprint(char *s); 98 | int uc_isdigit(char *s); 99 | int uc_isalpha(char *s); 100 | int uc_kind(char *c); 101 | int uc_isbell(char *c); 102 | int uc_iscomb(char *c); 103 | char **uc_chop(char *s, int *n); 104 | char *uc_next(char *s); 105 | char *uc_prev(char *beg, char *s); 106 | char *uc_beg(char *beg, char *s); 107 | char *uc_end(char *s); 108 | char *uc_shape(char *beg, char *s); 109 | char *uc_lastline(char *s); 110 | 111 | /* managing the terminal */ 112 | #define xrows (term_rows()) 113 | #define xcols (term_cols()) 114 | 115 | void term_init(void); 116 | void term_done(void); 117 | void term_suspend(void); 118 | void term_str(char *s); 119 | void term_chr(int ch); 120 | void term_pos(int r, int c); 121 | void term_clear(void); 122 | void term_kill(void); 123 | void term_room(int n); 124 | void term_window(int row, int cnt); 125 | int term_rows(void); 126 | int term_cols(void); 127 | int term_rowx(void); 128 | int term_read(void); 129 | void term_record(void); 130 | void term_commit(void); 131 | char *term_seqattr(int att, int old); 132 | char *term_seqkill(void); 133 | void term_push(char *s, int n); 134 | char *term_cmd(int *n); 135 | 136 | #define TK_CTL(x) ((x) & 037) 137 | #define TK_INT(c) ((c) < 0 || (c) == TK_ESC || (c) == TK_CTL('c')) 138 | #define TK_ESC (TK_CTL('[')) 139 | 140 | /* line-oriented input and output */ 141 | char *led_prompt(char *pref, char *post, int *kmap, char *syn, char *hist); 142 | char *led_input(char *pref, char *post, int *left, int *kmap, char *syn, void (*nextline)(void), char *(*help)(char *ln)); 143 | void led_print(char *msg, int row, int left, char *syn); 144 | void led_printmsg(char *s, int row, char *syn); 145 | char *led_read(int *kmap); 146 | 147 | /* ex commands */ 148 | void ex(void); 149 | int ex_command(char *cmd); 150 | char *ex_read(char *msg); 151 | void ex_print(char *line); 152 | void ex_show(char *msg); 153 | int ex_init(char **files); 154 | void ex_done(void); 155 | char *ex_path(void); 156 | char *ex_filetype(void); 157 | struct lbuf *ex_lbuf(void); 158 | int ex_kwd(char **kwd, int *dir); 159 | void ex_kwdset(char *kwd, int dir); 160 | int ex_list(char **ls, int size); 161 | 162 | #define EXLEN 512 /* ex line length */ 163 | #define xb ex_lbuf() 164 | 165 | /* process management */ 166 | char *cmd_pipe(char *cmd, char *ibuf, int oproc); 167 | char *cmd_unix(char *path, char *ibuf); 168 | int cmd_exec(char *cmd); 169 | 170 | /* syntax highlighting */ 171 | #define SYN_BD 0x010000 172 | #define SYN_IT 0x020000 173 | #define SYN_RV 0x040000 174 | #define SYN_FGMK(f) (0x100000 | (f)) 175 | #define SYN_BGMK(b) (0x200000 | ((b) << 8)) 176 | 177 | #define SYN_FLG 0xff0000 178 | #define SYN_FGSET(a) ((a) & 0x1000ff) 179 | #define SYN_BGSET(a) ((a) & 0x20ff00) 180 | #define SYN_FG(a) ((a) & 0xff) 181 | #define SYN_BG(a) (((a) >> 8) & 0xff) 182 | 183 | int *syn_highlight(char *ft, char *s); 184 | char *syn_filetype(char *path); 185 | void syn_context(int att); 186 | int syn_merge(int old, int new); 187 | void syn_init(void); 188 | void syn_done(void); 189 | 190 | /* configuration variables */ 191 | int conf_dirmark(int idx, char **pat, int *ctx, int *dir, int *grp); 192 | int conf_dircontext(int idx, char **pat, int *ctx); 193 | int conf_placeholder(int idx, char **s, char **d, int *wid); 194 | int conf_highlight(int idx, char **ft, int **att, char **pat, int *end); 195 | int conf_filetype(int idx, char **ft, char **pat); 196 | int conf_hlback(void); 197 | int conf_hlrev(void); 198 | int conf_hlline(void); 199 | int conf_mode(void); 200 | char **conf_kmap(int id); 201 | int conf_kmapfind(char *name); 202 | char *conf_digraph(int c1, int c2); 203 | char *conf_lnpref(void); 204 | char *conf_definition(char *ft); 205 | char *conf_section(char *ft); 206 | char *conf_ecmd(void); 207 | 208 | /* global variables */ 209 | extern int xrow; 210 | extern int xoff; 211 | extern int xtop; 212 | extern int xleft; 213 | extern int xvis; 214 | extern int xled; 215 | extern int xquit; 216 | extern int xic; 217 | extern int xai; 218 | extern int xtd; 219 | extern int xshape; 220 | extern int xorder; 221 | extern int xhl; 222 | extern int xhll; 223 | extern int xkmap; 224 | extern int xkmap_alt; 225 | extern int xlim; 226 | extern int xru; 227 | extern int xhist; 228 | 229 | /* tag file handling */ 230 | int tag_init(void); 231 | int tag_find(char *name, int *pos, int dir, char *path, int pathlen, char *cmd, int cmdlen); 232 | void tag_done(void); 233 | --------------------------------------------------------------------------------