mot: word motions stop at empty lines
[neatvi.git] / lbuf.c
blob7a9f3b358c03a51b203b7b7b428ba7349c43aa20
1 #include <ctype.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include "vi.h"
8 #define NMARKS_BASE ('z' - 'a' + 2)
9 #define NMARKS 32
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 */
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 int ln_n; /* number of lines in ln[] */
27 int ln_sz; /* size of ln[] */
28 int useq; /* current operation sequence */
29 struct lopt *hist; /* buffer history */
30 int hist_sz; /* size of hist[] */
31 int hist_n; /* current history head in hist[] */
32 int hist_u; /* current undo head in hist[] */
33 int useq_zero; /* useq for lbuf_saved() */
34 int useq_last; /* useq before hist[] */
37 struct lbuf *lbuf_make(void)
39 struct lbuf *lb = malloc(sizeof(*lb));
40 int i;
41 memset(lb, 0, sizeof(*lb));
42 for (i = 0; i < LEN(lb->mark); i++)
43 lb->mark[i] = -1;
44 lb->useq = 1;
45 return lb;
48 static void lopt_done(struct lopt *lo)
50 free(lo->ins);
51 free(lo->del);
52 free(lo->mark);
53 free(lo->mark_off);
56 static void lbuf_savemark(struct lbuf *lb, struct lopt *lo, int m)
58 if (lb->mark[m] >= 0) {
59 if (!lo->mark) {
60 lo->mark = malloc(sizeof(lb->mark));
61 lo->mark_off = malloc(sizeof(lb->mark_off));
62 memset(lo->mark, 0xff, sizeof(lb->mark));
64 lo->mark[m] = lb->mark[m];
65 lo->mark_off[m] = lb->mark_off[m];
69 static void lbuf_loadmark(struct lbuf *lb, struct lopt *lo, int m)
71 if (lo->mark && lo->mark[m] >= 0) {
72 lb->mark[m] = lo->mark[m];
73 lb->mark_off[m] = lo->mark_off[m];
77 static int markidx(int mark)
79 if (islower(mark))
80 return mark - 'a';
81 if (mark == '\'' || mark == '`')
82 return 'z' - 'a' + 1;
83 if (mark == '*')
84 return 'z' - 'a' + 2;
85 if (mark == '[')
86 return 'z' - 'a' + 3;
87 if (mark == ']')
88 return 'z' - 'a' + 4;
89 return -1;
92 static void lbuf_savepos(struct lbuf *lb, struct lopt *lo)
94 if (lb->mark[markidx('*')] >= 0)
95 lo->pos_off = lb->mark_off[markidx('*')];
98 static void lbuf_loadpos(struct lbuf *lb, struct lopt *lo)
100 lb->mark[markidx('*')] = lo->pos;
101 lb->mark_off[markidx('*')] = lo->pos_off;
104 void lbuf_free(struct lbuf *lb)
106 int i;
107 for (i = 0; i < lb->ln_n; i++)
108 free(lb->ln[i]);
109 for (i = 0; i < lb->hist_n; i++)
110 lopt_done(&lb->hist[i]);
111 free(lb->hist);
112 free(lb->ln);
113 free(lb);
116 /* insert a line at pos */
117 static void lbuf_insertline(struct lbuf *lb, int pos, char *s)
119 if (lb->ln_n == lb->ln_sz) {
120 int nsz = lb->ln_sz + 512;
121 char **nln = malloc(nsz * sizeof(nln[0]));
122 memcpy(nln, lb->ln, lb->ln_n * sizeof(lb->ln[0]));
123 free(lb->ln);
124 lb->ln = nln;
125 lb->ln_sz = nsz;
127 memmove(lb->ln + pos + 1, lb->ln + pos,
128 (lb->ln_n - pos) * sizeof(lb->ln[0]));
129 lb->ln_n++;
130 lb->ln[pos] = s;
133 /* low-level replacement */
134 static void lbuf_replace(struct lbuf *lb, char *s, int pos, int n_del)
136 char *r;
137 int n_ins = 0;
138 int i;
139 for (i = 0; i < n_del; i++)
140 free(lb->ln[pos + i]);
141 memmove(lb->ln + pos, lb->ln + pos + n_del,
142 (lb->ln_n - pos - n_del) * sizeof(lb->ln[0]));
143 lb->ln_n -= n_del;
144 while (s && (r = strchr(s, '\n'))) {
145 char *n = malloc(r - s + 2);
146 memcpy(n, s, r - s + 1);
147 n[r - s + 1] = '\0';
148 lbuf_insertline(lb, pos + n_ins++, n);
149 s = r + 1;
151 for (i = 0; i < LEN(lb->mark); i++) { /* updating marks */
152 if (!s && lb->mark[i] >= pos && lb->mark[i] < pos + n_del)
153 lb->mark[i] = -1;
154 else if (lb->mark[i] >= pos + n_del)
155 lb->mark[i] += n_ins - n_del;
156 else if (lb->mark[i] >= pos + n_ins)
157 lb->mark[i] = pos + n_ins - 1;
159 lbuf_mark(lb, '[', pos, 0);
160 lbuf_mark(lb, ']', pos + (n_ins ? n_ins - 1 : 0), 0);
163 static int uc_newlines(char *s)
165 int n;
166 for (n = 0; (s = strchr(s, '\n')); n++)
167 s++;
168 return n;
171 /* append undo/redo history */
172 static void lbuf_opt(struct lbuf *lb, char *buf, int pos, int n_del)
174 struct lopt *lo;
175 int i;
176 for (i = lb->hist_u; i < lb->hist_n; i++)
177 lopt_done(&lb->hist[i]);
178 lb->hist_n = lb->hist_u;
179 if (lb->hist_n == lb->hist_sz) {
180 int sz = lb->hist_sz + 128;
181 struct lopt *hist = malloc(sz * sizeof(hist[0]));
182 memcpy(hist, lb->hist, lb->hist_n * sizeof(hist[0]));
183 free(lb->hist);
184 lb->hist = hist;
185 lb->hist_sz = sz;
187 lo = &lb->hist[lb->hist_n];
188 lb->hist_n++;
189 lb->hist_u = lb->hist_n;
190 memset(lo, 0, sizeof(*lo));
191 lo->pos = pos;
192 lo->n_del = n_del;
193 lo->del = n_del ? lbuf_cp(lb, pos, pos + n_del) : NULL;
194 lo->n_ins = buf ? uc_newlines(buf) : 0;
195 lo->ins = buf ? uc_dup(buf) : NULL;
196 lo->seq = lb->useq;
197 lbuf_savepos(lb, lo);
198 for (i = 0; i < NMARKS_BASE; i++)
199 if (lb->mark[i] >= pos && lb->mark[i] < pos + n_del)
200 lbuf_savemark(lb, lo, i);
203 int lbuf_rd(struct lbuf *lbuf, int fd, int beg, int end)
205 char buf[1 << 10];
206 struct sbuf *sb;
207 long nr;
208 sb = sbuf_make();
209 while ((nr = read(fd, buf, sizeof(buf))) > 0)
210 sbuf_mem(sb, buf, nr);
211 if (!nr)
212 lbuf_edit(lbuf, sbuf_buf(sb), beg, end);
213 sbuf_free(sb);
214 return nr != 0;
217 int lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end)
219 int i;
220 for (i = beg; i < end; i++) {
221 char *ln = lbuf->ln[i];
222 long nw = 0;
223 long nl = strlen(ln);
224 while (nw < nl) {
225 long nc = write(fd, ln + nw, nl - nw);
226 if (nc < 0)
227 return 1;
228 nw += nc;
231 return 0;
234 /* replace lines beg through end with buf */
235 void lbuf_edit(struct lbuf *lb, char *buf, int beg, int end)
237 if (beg > lb->ln_n)
238 beg = lb->ln_n;
239 if (end > lb->ln_n)
240 end = lb->ln_n;
241 if (beg == end && !buf)
242 return;
243 lbuf_opt(lb, buf, beg, end - beg);
244 lbuf_replace(lb, buf, beg, end - beg);
247 char *lbuf_cp(struct lbuf *lb, int beg, int end)
249 struct sbuf *sb;
250 int i;
251 sb = sbuf_make();
252 for (i = beg; i < end; i++)
253 if (i < lb->ln_n)
254 sbuf_str(sb, lb->ln[i]);
255 return sbuf_done(sb);
258 char *lbuf_get(struct lbuf *lb, int pos)
260 return pos >= 0 && pos < lb->ln_n ? lb->ln[pos] : NULL;
263 int lbuf_len(struct lbuf *lb)
265 return lb->ln_n;
268 void lbuf_mark(struct lbuf *lbuf, int mark, int pos, int off)
270 if (markidx(mark) >= 0) {
271 lbuf->mark[markidx(mark)] = pos;
272 lbuf->mark_off[markidx(mark)] = off;
276 int lbuf_jump(struct lbuf *lbuf, int mark, int *pos, int *off)
278 int mk = markidx(mark);
279 if (mk < 0 || lbuf->mark[mk] < 0)
280 return 1;
281 *pos = lbuf->mark[mk];
282 if (off)
283 *off = lbuf->mark_off[mk];
284 return 0;
287 int lbuf_undo(struct lbuf *lb)
289 int useq, i;
290 if (!lb->hist_u)
291 return 1;
292 useq = lb->hist[lb->hist_u - 1].seq;
293 while (lb->hist_u && lb->hist[lb->hist_u - 1].seq == useq) {
294 struct lopt *lo = &lb->hist[--(lb->hist_u)];
295 lbuf_replace(lb, lo->del, lo->pos, lo->n_ins);
296 lbuf_loadpos(lb, lo);
297 for (i = 0; i < LEN(lb->mark); i++)
298 lbuf_loadmark(lb, lo, i);
300 return 0;
303 int lbuf_redo(struct lbuf *lb)
305 int useq;
306 if (lb->hist_u == lb->hist_n)
307 return 1;
308 useq = lb->hist[lb->hist_u].seq;
309 while (lb->hist_u < lb->hist_n && lb->hist[lb->hist_u].seq == useq) {
310 struct lopt *lo = &lb->hist[lb->hist_u++];
311 lbuf_replace(lb, lo->ins, lo->pos, lo->n_del);
312 lbuf_loadpos(lb, lo);
314 return 0;
317 static int lbuf_seq(struct lbuf *lb)
319 return lb->hist_u ? lb->hist[lb->hist_u - 1].seq : lb->useq_last;
322 /* mark buffer as saved and, if clear, clear the undo history */
323 void lbuf_saved(struct lbuf *lb, int clear)
325 int i;
326 if (clear) {
327 for (i = 0; i < lb->hist_n; i++)
328 lopt_done(&lb->hist[i]);
329 lb->hist_n = 0;
330 lb->hist_u = 0;
331 lb->useq_last = lb->useq;
333 lb->useq_zero = lbuf_seq(lb);
334 lbuf_modified(xb);
337 /* was the file modified since the last lbuf_modreset() */
338 int lbuf_modified(struct lbuf *lb)
340 lb->useq++;
341 return lbuf_seq(lb) != lb->useq_zero;