st-vimBrowse-20191107-2b8333f.diff - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) Refs
---
st-vimBrowse-20191107-2b8333f.diff (45073B)
---
1 From de020f0c06440fd19a36e1b001ef9e0058f73369 Mon Sep 17 00:00:00 2001
2 From: Julius Huelsmann <juliusHuelsmann@gmail.com>
3 Date: Thu, 7 Nov 2019 09:08:49 +0100
4 Subject: [PATCH] [PATCH:VIM]: first version
5
6 ---
7 Makefile | 6 +-
8 config.def.h | 27 ++
9 dynamicArray.h | 90 ++++++
10 st.c | 794 +++++++++++++++++++++++++++++++++++++++++++++----
11 st.h | 31 +-
12 win.h | 2 +
13 x.c | 51 +++-
14 7 files changed, 936 insertions(+), 65 deletions(-)
15 create mode 100644 dynamicArray.h
16
17 diff --git a/Makefile b/Makefile
18 index 470ac86..7d93347 100644
19 --- a/Makefile
20 +++ b/Makefile
21 @@ -21,8 +21,8 @@ config.h:
22 .c.o:
23 $(CC) $(STCFLAGS) -c $<
24
25 -st.o: config.h st.h win.h
26 -x.o: arg.h config.h st.h win.h
27 +st.o: config.h st.h win.h dynamicArray.h
28 +x.o: arg.h config.h st.h win.h dynamicArray.h
29
30 $(OBJ): config.h config.mk
31
32 @@ -35,7 +35,7 @@ clean:
33 dist: clean
34 mkdir -p st-$(VERSION)
35 cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\
36 - config.def.h st.info st.1 arg.h st.h win.h $(SRC)\
37 + config.def.h st.info st.1 arg.h st.h win.h dynamicArray.h $(SRC)\
38 st-$(VERSION)
39 tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz
40 rm -rf st-$(VERSION)
41 diff --git a/config.def.h b/config.def.h
42 index 6ebea98..1b0e501 100644
43 --- a/config.def.h
44 +++ b/config.def.h
45 @@ -149,6 +149,12 @@ static unsigned int mousebg = 0;
46 * doesn't match the ones requested.
47 */
48 static unsigned int defaultattr = 11;
49 +/// Colors for the entities that are highlighted in normal mode.
50 +static unsigned int highlightBg = 160;
51 +static unsigned int highlightFg = 15;
52 +/// Colors for the line and column that is marked 'current' in normal mode.
53 +static unsigned int currentBg = 0;
54 +static unsigned int currentFg = 15;
55
56 /*
57 * Internal mouse shortcuts.
58 @@ -162,10 +168,12 @@ static MouseShortcut mshortcuts[] = {
59
60 /* Internal keyboard shortcuts. */
61 #define MODKEY Mod1Mask
62 +#define AltMask Mod1Mask
63 #define TERMMOD (ControlMask|ShiftMask)
64
65 static Shortcut shortcuts[] = {
66 /* mask keysym function argument */
67 + { AltMask, XK_c, normalMode, {.i = 0} },
68 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
69 { ControlMask, XK_Print, toggleprinter, {.i = 0} },
70 { ShiftMask, XK_Print, printscreen, {.i = 0} },
71 @@ -178,6 +186,8 @@ static Shortcut shortcuts[] = {
72 { TERMMOD, XK_Y, selpaste, {.i = 0} },
73 { ShiftMask, XK_Insert, selpaste, {.i = 0} },
74 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
75 + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
76 + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
77 };
78
79 /*
80 @@ -456,3 +466,20 @@ static char ascii_printable[] =
81 " !\"#$%&'()*+,-./0123456789:;<=>?"
82 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
83 "`abcdefghijklmnopqrstuvwxyz{|}~";
84 +
85 +
86 +/// word sepearors normal mode
87 +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
88 +char wordDelimLarge[] = " \t"; /// <Word sepearors normal mode (capital W)
89 +
90 +/// Shortcusts executed in normal mode (which should not already be in use)
91 +struct NormalModeShortcuts normalModeShortcuts [] = {
92 + { 'C', "?Building\n" },
93 + { 'c', "/Building\n" },
94 + { 'F', "?: error:\n" },
95 + { 'f', "/: error:\n" },
96 + { 'X', "?juli@machine\n" },
97 + { 'x', "/juli@machine\n" },
98 +};
99 +
100 +size_t const amountNormalModeShortcuts = sizeof(normalModeShortcuts) / sizeof(*normalModeShortcuts);
101 diff --git a/dynamicArray.h b/dynamicArray.h
102 new file mode 100644
103 index 0000000..c65fbef
104 --- /dev/null
105 +++ b/dynamicArray.h
106 @@ -0,0 +1,90 @@
107 +#include <stdint.h>
108 +#include <assert.h>
109 +#include <stdlib.h>
110 +#include <string.h>
111 +#include <stdbool.h>
112 +
113 +/// Struct for which this file offers functionality in order to expand the array
114 +/// and set / get its content.
115 +typedef struct DynamicArray {
116 + uint8_t itemSize;
117 + uint32_t index;
118 + uint32_t allocated;
119 + char* content;
120 +} DynamicArray;
121 +
122 +#define EXPAND_STEP 15
123 +
124 +/// Default initializers for the dynamic array.
125 +#define CHAR_ARRAY {1, 0, 0, NULL}
126 +#define WORD_ARRAY {2, 0, 0, NULL}
127 +#define DWORD_ARRAY {4, 0, 0, NULL}
128 +#define QWORD_ARRAY {8, 0, 0, NULL}
129 +/// (Wasteful) utf-8 array, that always used 4 bytes in order to display a character,
130 +/// even if the space is not required.
131 +#define UTF8_ARRAY DWORD_ARRAY
132 +
133 +
134 +inline char*
135 +gnext(DynamicArray *s) { return &s->content[s->index+=s->itemSize]; }
136 +
137 +inline char*
138 +get(DynamicArray const * s) { return &s->content[s->index]; }
139 +
140 +inline char*
141 +view(DynamicArray const * s, uint32_t i) {
142 + return s->content + i*s->itemSize;
143 +}
144 +
145 +inline char *
146 +viewEnd(DynamicArray const *s, uint32_t i) {
147 + return s->content + s->index - (i + 1) * s->itemSize;
148 +}
149 +
150 +inline void
151 +set(DynamicArray* s, char const *vals, uint8_t amount) {
152 + assert(amount <= s->itemSize);
153 + memcpy(s->content + s->index, vals, amount);
154 +}
155 +
156 +inline void
157 +snext(DynamicArray* s, char const *vals, uint8_t amount) {
158 + set(s, vals, amount);
159 + s->index+=s->itemSize;
160 +}
161 +
162 +inline void
163 +empty(DynamicArray* s) { s->index = 0; }
164 +
165 +inline bool
166 +isEmpty(DynamicArray* s) { return s->index == 0; }
167 +
168 +inline uint32_t
169 +size(DynamicArray const * s) { return s->index / s->itemSize; }
170 +
171 +inline void
172 +pop(DynamicArray* s) { s->index -= s->itemSize; }
173 +
174 +inline void checkSetNext(DynamicArray *s, char const *c, uint8_t amount) {
175 + if (s->index + s->itemSize >= s->allocated) {
176 + if ((s->content = (char *)realloc(
177 + s->content, s->allocated += EXPAND_STEP * s->itemSize)) == NULL) {
178 + exit(1);
179 + };
180 + }
181 + if (amount) { snext(s, c, amount); }
182 +}
183 +
184 +char *checkGetNext(DynamicArray *s) {
185 + if (s->index + s->itemSize >= s->allocated) {
186 + if ((s->content = (char *)realloc(
187 + s->content, s->allocated += EXPAND_STEP * s->itemSize)) == NULL) {
188 + exit(1);
189 + };
190 + }
191 + s->index+=s->itemSize;
192 + return viewEnd(s, 0);
193 +}
194 +
195 +#define append(s, c) checkSetNext((s), (char const *) (c), (s)->itemSize)
196 +#define appendPartial(s, c, i) checkSetNext((s), (char const *) (c), (i))
197 diff --git a/st.c b/st.c
198 index ede7ae6..27bfca8 100644
199 --- a/st.c
200 +++ b/st.c
201 @@ -1,8 +1,10 @@
202 /* See LICENSE for license details. */
203 +#include <assert.h>
204 #include <ctype.h>
205 #include <errno.h>
206 #include <fcntl.h>
207 #include <limits.h>
208 +#include <math.h>
209 #include <pwd.h>
210 #include <stdarg.h>
211 #include <stdio.h>
212 @@ -17,8 +19,10 @@
213 #include <unistd.h>
214 #include <wchar.h>
215
216 +
217 #include "st.h"
218 #include "win.h"
219 +#include "dynamicArray.h"
220
221 #if defined(__linux)
222 #include <pty.h>
223 @@ -35,6 +39,8 @@
224 #define ESC_ARG_SIZ 16
225 #define STR_BUF_SIZ ESC_BUF_SIZ
226 #define STR_ARG_SIZ ESC_ARG_SIZ
227 +//#define HISTSIZE 100
228 +#define HISTSIZE 2000
229
230 /* macros */
231 #define IS_SET(flag) ((term.mode & (flag)) != 0)
232 @@ -42,6 +48,9 @@
233 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
234 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
235 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
236 +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
237 + term.scr + HISTSIZE + 1) % HISTSIZE] : \
238 + term.line[(y) - term.scr])
239
240 enum term_mode {
241 MODE_WRAP = 1 << 0,
242 @@ -97,16 +106,18 @@ typedef struct {
243 int mode;
244 int type;
245 int snap;
246 - /*
247 - * Selection variables:
248 - * nb – normalized coordinates of the beginning of the selection
249 - * ne – normalized coordinates of the end of the selection
250 - * ob – original coordinates of the beginning of the selection
251 - * oe – original coordinates of the end of the selection
252 - */
253 + /// Selection variables:
254 + /// ob – original coordinates of the beginning of the selection
255 + /// oe – original coordinates of the end of the selection
256 + struct {
257 + int x, y, scroll;
258 + } ob, oe;
259 + /// Selection variables; currently displayed chunk.
260 + /// nb – normalized coordinates of the beginning of the selection
261 + /// ne – normalized coordinates of the end of the selection
262 struct {
263 int x, y;
264 - } nb, ne, ob, oe;
265 + } nb, ne;
266
267 int alt;
268 } Selection;
269 @@ -117,6 +128,9 @@ typedef struct {
270 int col; /* nb col */
271 Line *line; /* screen */
272 Line *alt; /* alternate screen */
273 + Line hist[HISTSIZE]; /* history buffer */
274 + int histi; /* history index */
275 + int scr; /* scroll back */
276 int *dirty; /* dirtyness of lines */
277 TCursor c; /* cursor */
278 int ocx; /* old cursor col */
279 @@ -152,6 +166,50 @@ typedef struct {
280 int narg; /* nb of args */
281 } STREscape;
282
283 +/// Position (x, y , and current scroll in the y dimension).
284 +typedef struct Position {
285 + uint32_t x;
286 + uint32_t y;
287 + uint32_t yScr;
288 +} Position;
289 +
290 +/// The entire normal mode state, consisting of an operation
291 +/// and a motion.
292 +struct NormalModeState {
293 + Position initialPosition;
294 + // Operation:
295 + struct OperationState {
296 + enum Operation {
297 + noop,
298 + visual,
299 + visualLine,
300 + yank
301 + } op;
302 + Position startPosition;
303 + } command;
304 + // Motions:
305 + struct MotionState {
306 + uint32_t amount;
307 + enum Search {
308 + none,
309 + forward,
310 + backward,
311 + } search;
312 + Position searchPosition;
313 + bool finished;
314 + } motion;
315 +} stateNormalMode;
316 +
317 +
318 +DynamicArray searchString = UTF8_ARRAY;
319 +DynamicArray commandHist0 = UTF8_ARRAY;
320 +DynamicArray commandHist1 = UTF8_ARRAY;
321 +DynamicArray highlights = QWORD_ARRAY;
322 +/// History command toggle
323 +bool toggle = false;
324 +#define currentCommand toggle ? &commandHist0 : &commandHist1
325 +#define lastCommand toggle ? &commandHist1 : &commandHist0
326 +
327 static void execsh(char *, char **);
328 static void stty(char **);
329 static void sigchld(int);
330 @@ -184,8 +242,8 @@ static void tnewline(int);
331 static void tputtab(int);
332 static void tputc(Rune);
333 static void treset(void);
334 -static void tscrollup(int, int);
335 -static void tscrolldown(int, int);
336 +static void tscrollup(int, int, int);
337 +static void tscrolldown(int, int, int);
338 static void tsetattr(int *, int);
339 static void tsetchar(Rune, Glyph *, int, int);
340 static void tsetdirt(int, int);
341 @@ -231,6 +289,12 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
342 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
343 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
344
345 +void applyPosition(Position const *pos) {
346 + term.c.x = pos->x;
347 + term.c.y = pos->y;
348 + term.scr = pos->yScr;
349 +}
350 +
351 ssize_t
352 xwrite(int fd, const char *s, size_t len)
353 {
354 @@ -409,17 +473,22 @@ tlinelen(int y)
355 {
356 int i = term.col;
357
358 - if (term.line[y][i - 1].mode & ATTR_WRAP)
359 + if (TLINE(y)[i - 1].mode & ATTR_WRAP)
360 return i;
361
362 - while (i > 0 && term.line[y][i - 1].u == ' ')
363 + while (i > 0 && TLINE(y)[i - 1].u == ' ')
364 --i;
365
366 return i;
367 }
368
369 void
370 -selstart(int col, int row, int snap)
371 +xselstart(int col, int row, int snap) {
372 + selstart(col, row, term.scr, snap);
373 +}
374 +
375 +void
376 +selstart(int col, int row, int scroll, int snap)
377 {
378 selclear();
379 sel.mode = SEL_EMPTY;
380 @@ -428,6 +497,7 @@ selstart(int col, int row, int snap)
381 sel.snap = snap;
382 sel.oe.x = sel.ob.x = col;
383 sel.oe.y = sel.ob.y = row;
384 + sel.oe.scroll = sel.ob.scroll = scroll;
385 selnormalize();
386
387 if (sel.snap != 0)
388 @@ -436,10 +506,13 @@ selstart(int col, int row, int snap)
389 }
390
391 void
392 -selextend(int col, int row, int type, int done)
393 -{
394 - int oldey, oldex, oldsby, oldsey, oldtype;
395 +xselextend(int col, int row, int type, int done) {
396 + selextend(col, row, term.scr, type, done);
397 +}
398
399 +void
400 +selextend(int col, int row, int scroll, int type, int done)
401 +{
402 if (sel.mode == SEL_IDLE)
403 return;
404 if (done && sel.mode == SEL_EMPTY) {
405 @@ -447,18 +520,22 @@ selextend(int col, int row, int type, int done)
406 return;
407 }
408
409 - oldey = sel.oe.y;
410 - oldex = sel.oe.x;
411 - oldsby = sel.nb.y;
412 - oldsey = sel.ne.y;
413 - oldtype = sel.type;
414 + int const oldey = sel.oe.y;
415 + int const oldex = sel.oe.x;
416 + int const oldscroll = sel.oe.scroll;
417 + int const oldsby = sel.nb.y;
418 + int const oldsey = sel.ne.y;
419 + int const oldtype = sel.type;
420
421 sel.oe.x = col;
422 sel.oe.y = row;
423 + sel.oe.scroll = scroll;
424 +
425 selnormalize();
426 sel.type = type;
427
428 - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
429 + if (oldey != sel.oe.y || oldex != sel.oe.x || oldscroll != sel.oe.scroll
430 + || oldtype != sel.type || sel.mode == SEL_EMPTY)
431 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
432
433 sel.mode = done ? SEL_IDLE : SEL_READY;
434 @@ -467,17 +544,21 @@ selextend(int col, int row, int type, int done)
435 void
436 selnormalize(void)
437 {
438 - int i;
439 -
440 - if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
441 - sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
442 - sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
443 + sel.nb.y = INTERVAL(sel.ob.y + term.scr - sel.ob.scroll, 0, term.bot);
444 + sel.ne.y = INTERVAL(sel.oe.y + term.scr - sel.oe.scroll, 0, term.bot);
445 + if (sel.type == SEL_REGULAR && sel.nb.y != sel.ne.y) {
446 + sel.nb.x = sel.nb.y < sel.ne.y ? sel.ob.x : sel.oe.x;
447 + sel.ne.x = sel.nb.y < sel.ne.y ? sel.oe.x : sel.ob.x;
448 } else {
449 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
450 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
451 }
452 - sel.nb.y = MIN(sel.ob.y, sel.oe.y);
453 - sel.ne.y = MAX(sel.ob.y, sel.oe.y);
454 +
455 + if (sel.nb.y > sel.ne.y) {
456 + int32_t const tmp = sel.nb.y;
457 + sel.nb.y = sel.ne.y;
458 + sel.ne.y = tmp;
459 + }
460
461 selsnap(&sel.nb.x, &sel.nb.y, -1);
462 selsnap(&sel.ne.x, &sel.ne.y, +1);
463 @@ -485,7 +566,7 @@ selnormalize(void)
464 /* expand selection over line breaks */
465 if (sel.type == SEL_RECTANGULAR)
466 return;
467 - i = tlinelen(sel.nb.y);
468 + int i = tlinelen(sel.nb.y);
469 if (i < sel.nb.x)
470 sel.nb.x = i;
471 if (tlinelen(sel.ne.y) <= sel.ne.x)
472 @@ -521,7 +602,7 @@ selsnap(int *x, int *y, int direction)
473 * Snap around if the word wraps around at the end or
474 * beginning of a line.
475 */
476 - prevgp = &term.line[*y][*x];
477 + prevgp = &TLINE(*y)[*x];
478 prevdelim = ISDELIM(prevgp->u);
479 for (;;) {
480 newx = *x + direction;
481 @@ -536,14 +617,14 @@ selsnap(int *x, int *y, int direction)
482 yt = *y, xt = *x;
483 else
484 yt = newy, xt = newx;
485 - if (!(term.line[yt][xt].mode & ATTR_WRAP))
486 + if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
487 break;
488 }
489
490 if (newx >= tlinelen(newy))
491 break;
492
493 - gp = &term.line[newy][newx];
494 + gp = &TLINE(newy)[newx];
495 delim = ISDELIM(gp->u);
496 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
497 || (delim && gp->u != prevgp->u)))
498 @@ -564,14 +645,14 @@ selsnap(int *x, int *y, int direction)
499 *x = (direction < 0) ? 0 : term.col - 1;
500 if (direction < 0) {
501 for (; *y > 0; *y += direction) {
502 - if (!(term.line[*y-1][term.col-1].mode
503 + if (!(TLINE(*y-1)[term.col-1].mode
504 & ATTR_WRAP)) {
505 break;
506 }
507 }
508 } else if (direction > 0) {
509 for (; *y < term.row-1; *y += direction) {
510 - if (!(term.line[*y][term.col-1].mode
511 + if (!(TLINE(*y)[term.col-1].mode
512 & ATTR_WRAP)) {
513 break;
514 }
515 @@ -591,24 +672,32 @@ getsel(void)
516 if (sel.ob.x == -1)
517 return NULL;
518
519 - bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
520 + int32_t syb = sel.ob.y - sel.ob.scroll + term.scr;
521 + int32_t sye = sel.oe.y - sel.oe.scroll + term.scr;
522 + if (syb > sye) {
523 + int32_t tmp = sye;
524 + sye = syb;
525 + syb = tmp;
526 + }
527 +
528 + bufsize = (term.col+1) * (sye - syb + 1) * UTF_SIZ;
529 ptr = str = xmalloc(bufsize);
530
531 /* append every set & selected glyph to the selection */
532 - for (y = sel.nb.y; y <= sel.ne.y; y++) {
533 + for (y = syb; y <= sye; y++) {
534 if ((linelen = tlinelen(y)) == 0) {
535 *ptr++ = '\n';
536 continue;
537 }
538
539 if (sel.type == SEL_RECTANGULAR) {
540 - gp = &term.line[y][sel.nb.x];
541 + gp = &TLINE(y)[sel.nb.x];
542 lastx = sel.ne.x;
543 } else {
544 - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
545 - lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
546 + gp = &TLINE(y)[syb == y ? sel.nb.x : 0];
547 + lastx = (sye == y) ? sel.ne.x : term.col-1;
548 }
549 - last = &term.line[y][MIN(lastx, linelen-1)];
550 + last = &TLINE(y)[MIN(lastx, linelen-1)];
551 while (last >= gp && last->u == ' ')
552 --last;
553
554 @@ -831,6 +920,9 @@ void
555 ttywrite(const char *s, size_t n, int may_echo)
556 {
557 const char *next;
558 + Arg arg = (Arg) { .i = term.scr };
559 +
560 + kscrolldown(&arg);
561
562 if (may_echo && IS_SET(MODE_ECHO))
563 twrite(s, n, 1);
564 @@ -1042,13 +1134,53 @@ tswapscreen(void)
565 }
566
567 void
568 -tscrolldown(int orig, int n)
569 +kscrolldown(const Arg* a)
570 +{
571 + int n = a->i;
572 +
573 + if (n < 0)
574 + n = term.row + n;
575 +
576 + if (n > term.scr)
577 + n = term.scr;
578 +
579 + if (term.scr > 0) {
580 + term.scr -= n;
581 + selscroll(0, -n);
582 + tfulldirt();
583 + }
584 +}
585 +
586 +void
587 +kscrollup(const Arg* a)
588 +{
589 + int n = a->i;
590 +
591 + if (n < 0)
592 + n = term.row + n;
593 +
594 + if (term.scr <= HISTSIZE-n) {
595 + term.scr += n;
596 + selscroll(0, n);
597 + tfulldirt();
598 + }
599 +}
600 +
601 +void
602 +tscrolldown(int orig, int n, int copyhist)
603 {
604 int i;
605 Line temp;
606
607 LIMIT(n, 0, term.bot-orig+1);
608
609 + if (copyhist) {
610 + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
611 + temp = term.hist[term.histi];
612 + term.hist[term.histi] = term.line[term.bot];
613 + term.line[term.bot] = temp;
614 + }
615 +
616 tsetdirt(orig, term.bot-n);
617 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
618
619 @@ -1062,13 +1194,23 @@ tscrolldown(int orig, int n)
620 }
621
622 void
623 -tscrollup(int orig, int n)
624 +tscrollup(int orig, int n, int copyhist)
625 {
626 int i;
627 Line temp;
628
629 LIMIT(n, 0, term.bot-orig+1);
630
631 + if (copyhist) {
632 + term.histi = (term.histi + 1) % HISTSIZE;
633 + temp = term.hist[term.histi];
634 + term.hist[term.histi] = term.line[orig];
635 + term.line[orig] = temp;
636 + }
637 +
638 + if (term.scr > 0 && term.scr < HISTSIZE)
639 + term.scr = MIN(term.scr + n, HISTSIZE-1);
640 +
641 tclearregion(0, orig, term.col-1, orig+n-1);
642 tsetdirt(orig+n, term.bot);
643
644 @@ -1088,6 +1230,7 @@ selscroll(int orig, int n)
645 return;
646
647 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
648 + sel.oe.scroll = sel.ob.scroll = term.scr;
649 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
650 selclear();
651 return;
652 @@ -1117,13 +1260,544 @@ tnewline(int first_col)
653 int y = term.c.y;
654
655 if (y == term.bot) {
656 - tscrollup(term.top, 1);
657 + tscrollup(term.top, 1, 1);
658 } else {
659 y++;
660 }
661 tmoveto(first_col ? 0 : term.c.x, y);
662 }
663
664 +int
665 +currentLine(int x, int y)
666 +{
667 + return (x == term.c.x || y == term.c.y);
668 +}
669 +
670 +int
671 +highlighted(int x, int y)
672 +{
673 + // Compute the legal bounds for a hit:
674 + int32_t const stringSize = size(&searchString);
675 + int32_t xMin = x - stringSize;
676 + int32_t yMin = y;
677 + while (xMin < 0 && yMin > 0) { //< I think this temds to be more efficient than
678 + xMin += term.col; // division + modulo.
679 + --yMin;
680 + }
681 + if (xMin < 0) { xMin = 0; }
682 +
683 + uint32_t highSize = size(&highlights);
684 + uint32_t *ptr = (uint32_t*) highlights.content;
685 + for (uint32_t i = 0; i < highSize; ++i) {
686 + int32_t const sx = *(ptr++);
687 + int32_t const sy = *(ptr++);
688 + if (BETWEEN(sy, yMin, y) && (sy != yMin || sx > xMin) && (sy != y || sx <= x)) {
689 + return true;
690 + }
691 + }
692 + return false;
693 +}
694 +
695 +int mod(int a, int b) {
696 + while (a < 0) {
697 + a+= b;
698 + }
699 + return a % b;
700 +}
701 +
702 +void displayString(DynamicArray const *str, Glyph *g, int yPos) {
703 + // Threshold: if there is nothing or no space to print, do not print.
704 + if (term.col == 0 || str->index == 0) {
705 + term.dirty[yPos] = 1; //< mark this line as 'dirty', because the line is not
706 + // marked dirty when scrolling due to string display.
707 + return;
708 + }
709 +
710 + uint32_t lineSize = MIN(size(str), term.col / 3);
711 + uint32_t xEnd = term.col - 1;
712 + assert(lineSize <= 1 + xEnd); //< as lineSize <= term.col/3 <= term.col - 1 + 1 = xEnd + 1
713 + uint32_t xStart = 1 + xEnd - lineSize;
714 +
715 + Line line = malloc(sizeof(Glyph) * lineSize);
716 + assert(str->index - 1 >= lineSize - 1); //< lineSize <= str->index -1 direct premise.
717 +
718 + for (uint32_t lineIdx = 0; lineIdx < lineSize; lineIdx++) {
719 + line[lineIdx] = *g;
720 + char* end = viewEnd(str, lineSize - lineIdx - 1);
721 + memcpy(&line[lineIdx].u, end, str->itemSize);
722 + }
723 + xdrawline(TLINE(yPos), 0, yPos, xStart);
724 + xdrawline(line -xStart, xStart, yPos, xEnd+1);
725 + free(line); // that sucks.
726 +}
727 +
728 +/// Print either the current command or the last comman din case the current command is empty.
729 +void printCommandString() {
730 + Glyph g = {'c', ATTR_ITALIC | ATTR_FAINT , defaultfg, defaultbg};
731 + if (term.c.y == term.row-1) { g.mode ^= ATTR_CURRENT; } //< dont highlight
732 + DynamicArray * cc = currentCommand;
733 + displayString(isEmpty(cc) ? lastCommand : cc, &g, term.row - 1);
734 + //displayString(lastCommand, &g, term.row - 2);
735 +}
736 +
737 +void printSearchString() {
738 + Glyph g = {'c', ATTR_ITALIC | ATTR_BOLD_FAINT, defaultfg, defaultbg};
739 + if (term.c.y == term.row-2) { g.mode ^= ATTR_CURRENT; } //< dont highlight
740 + displayString(&searchString, &g, term.row - 2);
741 +}
742 +
743 +/// Default state if no operation is performed.
744 +struct NormalModeState defaultNormalMode = {{0,0,0}, {noop, {0, 0, 0}}, {0, none, {0, 0, 0}, false}};
745 +
746 +void enableMode(enum Operation o) {
747 + stateNormalMode.command.op = o;
748 + stateNormalMode.command.startPosition.x = term.c.x;
749 + stateNormalMode.command.startPosition.y = term.c.y;
750 + stateNormalMode.command.startPosition.yScr = term.scr;
751 +}
752 +
753 +bool normalModeEnabled = false;
754 +
755 +void onNormalModeStart() {
756 + normalModeEnabled = true;
757 +}
758 +
759 +void onNormalModeStop() { //XXX breaks if resized
760 + normalModeEnabled = false;
761 + applyPosition(&stateNormalMode.initialPosition);
762 +}
763 +
764 +void moveLine(int8_t sign) {
765 + if (sign == -1) {
766 + if (term.c.y-- == 0) {
767 + if (++term.scr == HISTSIZE) {
768 + term.c.y = term.row - 1;
769 + term.scr = 0;
770 + } else {
771 + term.c.y = 0;
772 + }
773 + }
774 + } else {
775 + term.c.x = 0;
776 + if (++term.c.y == term.row) {
777 + if (term.scr-- == 0) {
778 + term.c.y = 0;
779 + term.scr = HISTSIZE - 1;
780 + } else {
781 + term.c.y = term.row - 1;
782 + }
783 + }
784 + }
785 +}
786 +
787 +void moveLetter(int8_t sign) {
788 + term.c.x += sign;
789 + if (!BETWEEN(term.c.x, 0, term.col-1)) {
790 + if (term.c.x < 0) {
791 + term.c.x = term.col - 1;
792 + moveLine(sign);
793 + } else {
794 + term.c.x = 0;
795 + moveLine(sign);
796 + }
797 + }
798 +}
799 +
800 +bool contains (char ksym, char const * values, uint32_t amount) {
801 + for (uint32_t i = 0; i < amount; i++) { if (ksym == values[i]) { return true; } }
802 + return false;
803 +}
804 +
805 +
806 +void terminateCommand(bool abort) {
807 + stateNormalMode.command = defaultNormalMode.command; //< clear command + motion
808 + stateNormalMode.motion = defaultNormalMode.motion;
809 + selclear(); //< clear selection if any
810 +
811 + if (!abort) { toggle = !toggle; }
812 + empty(currentCommand);
813 +
814 + printCommandString();
815 + printSearchString();
816 + //tsetdirt(0, term.row-3);
817 +}
818 +inline void exitCommand() { terminateCommand(false); }
819 +inline void abortCommand() { terminateCommand(true); }
820 +
821 +/// Go to next occurrence of string relative to the current location
822 +/// conduct search, starting at start pos
823 +bool
824 +gotoString(int8_t sign) {
825 + uint32_t findIndex = 0;
826 + uint32_t searchStringSize = size(&searchString);
827 + uint32_t const maxIteration = (HISTSIZE + term.row) * term.col + searchStringSize; //< one complete traversal.
828 + for (uint32_t cIteration = 0; findIndex < searchStringSize
829 + && cIteration ++ < maxIteration; moveLetter(sign)) {
830 + uint32_t const searchChar = *((uint32_t*)(sign == 1 ? view(&searchString, findIndex)
831 + : viewEnd(&searchString, findIndex)));
832 +
833 + uint32_t const fu = TLINE(term.c.y)[term.c.x].u;
834 +
835 + if (fu == searchChar) findIndex++;
836 + else findIndex = 0;
837 + }
838 + bool const found = findIndex == searchStringSize;
839 + if (found) { for (uint32_t i = 0; i < searchStringSize; i++) { moveLetter(-sign); } }
840 + return found;
841 +}
842 +
843 +/// Find the next occurrence of a word
844 +bool
845 +gotoNextString(int8_t sign) {
846 + moveLetter(sign);
847 + return gotoString(sign);
848 +}
849 +
850 +/// Highlight all found strings on the current screen.
851 +void
852 +highlightStringOnScreen() {
853 + if (isEmpty(&searchString)) { return; }
854 + uint32_t const searchStringSize = size(&searchString);
855 + uint32_t findIndex = 0;
856 + uint32_t xStart, yStart;
857 + for (uint32_t y = 0; y < term.row; y++) {
858 + for (uint32_t x = 0; x < term.col; x++) {
859 + if (TLINE(y)[x].u == *((uint32_t*)(view(&searchString, findIndex)))) {
860 + if (findIndex++ == 0) {
861 + xStart = x;
862 + yStart = y;
863 + }
864 + if (findIndex == searchStringSize) {
865 + // mark selected
866 + append(&highlights, &xStart);
867 + append(&highlights, &yStart);
868 +
869 + findIndex = 0;
870 + term.dirty[yStart] = 1;
871 + }
872 + } else {
873 + findIndex = 0;
874 + }
875 + }
876 + }
877 +}
878 +
879 +void gotoStringAndHighlight(int8_t sign) {
880 + bool const found = gotoString(sign); //< find the next string to the current position
881 + empty(&highlights); //< remove previous highlights
882 + if (found) { //< apply new highlights if found
883 + //if (sign == -1) { moveLetter(-1); }
884 + highlightStringOnScreen(sign);
885 + } else { //< go to the position where the search started.
886 + applyPosition(&stateNormalMode.motion.searchPosition);
887 + }
888 + tsetdirt(0, term.row-3); //< repaint everything except for the status bar, which
889 + // is painted separately.
890 +}
891 +
892 +void pressKeys(char const* nullTerminatedString) {
893 + size_t end;
894 + for (size_t i = 0, end=strlen(nullTerminatedString); i < end; ++i) {
895 + if (nullTerminatedString[i] == '\n') {
896 + kpressNormalMode(&nullTerminatedString[i], 0, false, true, false);
897 + } else {
898 + kpressNormalMode(&nullTerminatedString[i], 1, false, false, false);
899 + }
900 + }
901 +}
902 +
903 +void executeCommand(DynamicArray const *command) {
904 + size_t end;
905 + char decoded [32];
906 + for (size_t i = 0, end=size(command); i < end; ++i) {
907 + size_t len = utf8encode(*((Rune*)view(command, i)) , decoded);
908 + kpressNormalMode(decoded, len, false, false, false);
909 + }
910 + //kpressNormalMode(NULL, 0, false, true, false);
911 +}
912 +
913 +void kpressNormalMode(char const * ksym, uint32_t len, bool esc, bool enter, bool backspace) {
914 + // [ESC] or [ENTER] abort resp. finish the current operation or
915 + // the Normal Mode if no operation is currently executed.
916 + if (esc || enter) {
917 + if (stateNormalMode.command.op == noop
918 + && stateNormalMode.motion.search == none
919 + && stateNormalMode.motion.amount == 0) {
920 + terminateCommand(!enter);
921 + empty(&highlights);
922 + tfulldirt(); // < this also removes the search string and the last command.
923 + normalMode(NULL);
924 + } else {
925 + if (enter && stateNormalMode.motion.search != none && !isEmpty(&searchString)) {
926 + exitCommand(); //stateNormalMode.motion.finished = true;
927 + return;
928 + } else {
929 + abortCommand();
930 + }
931 + }
932 + return;
933 + } //< ! (esc || enter)
934 + // Search: append to search string & conduct search for best hit, starting at start pos,
935 + // highlighting all other occurrences on the current page if one is found.
936 + if (stateNormalMode.motion.search != none && !stateNormalMode.motion.finished) {
937 + int8_t const sign = stateNormalMode.motion.search == forward ? 1 : -1;
938 + // Apply start position.
939 + if (backspace) { // XXX: if a quantifier is subject to removal, it is currently only removed
940 + // from the command string.
941 + if (!isEmpty(currentCommand) && !isEmpty(&searchString)) {
942 + pop(currentCommand);
943 + pop(&searchString);
944 + } else if (isEmpty(currentCommand) || isEmpty(&searchString)) {
945 + empty(&highlights);
946 + stateNormalMode.motion = defaultNormalMode .motion; //< if typed once more than there are
947 + selclear(); // letters, the search motion is
948 + return; // terminated
949 + }
950 + applyPosition(&stateNormalMode.motion.searchPosition);
951 + } else {
952 + if (len > 0) {
953 + char* kSearch = checkGetNext(&searchString);
954 + utf8decode(ksym, (Rune*)(kSearch), len);
955 +
956 + char* kCommand = checkGetNext(currentCommand);
957 + utf8decode(ksym, (Rune*)(kCommand), len);
958 + }
959 + }
960 + if (sign == -1) { moveLetter(1); }
961 + gotoStringAndHighlight(sign); //< go to the next occurrence of the string and highlight
962 + // all occurrences currently on screen
963 +
964 + if (stateNormalMode.command.op == visual) {
965 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0);
966 + } else if (stateNormalMode.command.op == visualLine) {
967 + selextend(term.col-1, term.c.y, term.scr, sel.type, 0);
968 + }
969 + printCommandString();
970 + printSearchString();
971 + return;
972 + }
973 +
974 + if (len == 0) { return; }
975 + // V / v or y take precedence over movement commands.
976 + switch(ksym[0]) {
977 + case '.':
978 + {
979 +
980 + if (!isEmpty(currentCommand)) { toggle = !toggle; empty(currentCommand); }
981 + executeCommand(lastCommand);
982 + }
983 + return;
984 + case 'y': //< Yank mode
985 + {
986 + char* kCommand = checkGetNext(currentCommand);
987 + utf8decode(ksym, (Rune*)(kCommand), len);
988 + switch(stateNormalMode.command.op) {
989 + case noop: //< Start yank mode & set #op
990 + enableMode(yank);
991 + selstart(term.c.x, term.c.y, term.scr, 0);
992 + empty(currentCommand);
993 + break;
994 + case visualLine: //< Complete yank operation
995 + case visual:
996 + xsetsel(getsel()); //< yank
997 + xclipcopy();
998 + exitCommand(); //< reset command
999 + break;
1000 + case yank: //< Complete yank operation as in y#amount j
1001 + selstart(0, term.c.y, term.scr, 0);
1002 + uint32_t const origY = term.c.y;
1003 + for (int32_t i = 0; i < MAX(stateNormalMode.motion.amount, 1) - 1; i ++) moveLine(1);
1004 + selextend(term.col-1, term.c.y, term.scr, SEL_RECTANGULAR, 0);
1005 + xsetsel(getsel());
1006 + xclipcopy();
1007 + term.c.y = origY;
1008 + exitCommand();
1009 + }
1010 + }
1011 + printCommandString();
1012 + printSearchString();
1013 + return;
1014 + case 'v': //< Visual Mode: Toggle mode.
1015 + case 'V':
1016 + {
1017 + enum Operation mode = ksym[0] == 'v' ? visual : visualLine;
1018 + bool assign = stateNormalMode.command.op != mode;
1019 + abortCommand();
1020 + if (assign) {
1021 + enableMode(mode);
1022 + char* kCommand = checkGetNext(currentCommand);
1023 + utf8decode(ksym, (Rune*)(kCommand), len);
1024 + if (mode == visualLine) {
1025 + selstart(0, term.c.y, term.scr, 0);
1026 + selextend(term.col-1, term.c.y, term.scr, SEL_RECTANGULAR, 0);
1027 + } else {
1028 + selstart(term.c.x, term.c.y, term.scr, 0);
1029 + }
1030 + }
1031 + }
1032 + return;
1033 + }
1034 + // Perform the movement.
1035 + int32_t sign = -1; //< whehter a command goes 'forward' (1) or 'backward' (-1)
1036 + bool discard = false; //< discard input, as it does not have a meaning.
1037 + switch(ksym[0]) {
1038 + case 'j': sign = 1;
1039 + case 'k':
1040 + term.c.y += sign * MAX(stateNormalMode.motion.amount, 1);
1041 + break;
1042 + case 'H': term.c.y = 0; break; //< [numer]H ~ L[number]j is not supported.
1043 + case 'M': term.c.y = term.bot / 2; break;
1044 + case 'L': term.c.y = term.bot; break; //< [numer]L ~ L[number]k is not supported.
1045 + case 'G': //< a little different from vim, but in this use case the most useful translation.
1046 + applyPosition(&stateNormalMode.initialPosition);
1047 + case 'l': sign = 1;
1048 + case 'h':
1049 + {
1050 + int32_t const amount = term.c.x + sign * MAX(stateNormalMode.motion.amount, 1);
1051 + term.c.x = amount % term.col;
1052 + while (term.c.x < 0) { term.c.x += term.col; }
1053 + term.c.y += floor(1.0 * amount / term.col);
1054 + break;
1055 + }
1056 + case '0':
1057 + if (stateNormalMode.motion.amount == 0) { term.c.x = 0; }
1058 + else { discard = true; }
1059 + break;
1060 + case '$': term.c.x = term.col-1; break;
1061 + case 'w':
1062 + case 'W':
1063 + case 'e':
1064 + case 'E': sign = 1;
1065 + case 'B':
1066 + case 'b':
1067 + {
1068 + bool const startSpaceIsSeparator = !(ksym[0] == 'w' || ksym[0] == 'W');
1069 + bool const capital = ksym[0] <= 90; //< defines the word separators to use
1070 + char const * const wDelim = capital ? wordDelimLarge : wordDelimSmall;
1071 + uint32_t const wDelimLen = strlen(wDelim);
1072 + bool const performOffset = startSpaceIsSeparator; //< start & end with offset.
1073 + uint32_t const maxIteration = (HISTSIZE + term.row) * term.col; //< one complete traversal.
1074 +
1075 + // doesn't work exactly as in vim, but I think this version is better;
1076 + // Linebreak is counted as 'normal' separator; hence a jump can span multiple lines here.
1077 + stateNormalMode.motion.amount = MAX(stateNormalMode.motion.amount, 1);
1078 + for (; stateNormalMode.motion.amount > 0; stateNormalMode.motion.amount--) {
1079 + uint8_t state = 0;
1080 + if (performOffset) { moveLetter(sign); }
1081 + for (uint32_t cIteration = 0; cIteration ++ < maxIteration; moveLetter(sign)) {
1082 + if (startSpaceIsSeparator == contains(TLINE(term.c.y)[term.c.x].u, wDelim, wDelimLen)) {
1083 + if (state == 1) {
1084 + if (performOffset) { moveLetter(-sign); }
1085 + break;
1086 + }
1087 + } else if (state == 0) { state = 1; }
1088 + }
1089 + }
1090 + break;
1091 + }
1092 + case '/': sign = 1;
1093 + case '?':
1094 + empty(&searchString);
1095 + stateNormalMode.motion.search = sign == 1 ? forward : backward;
1096 + stateNormalMode.motion.searchPosition.x = term.c.x;
1097 + stateNormalMode.motion.searchPosition.y = term.c.y;
1098 + stateNormalMode.motion.searchPosition.yScr = term.scr;
1099 + stateNormalMode.motion.finished = false;
1100 + break;
1101 + case 'n': sign = 1;
1102 + case 'N':
1103 + toggle = !toggle;
1104 + empty(currentCommand);
1105 + if (stateNormalMode.motion.search == none) {
1106 + stateNormalMode.motion.search = forward;
1107 + stateNormalMode.motion.finished = true;
1108 + }
1109 + for (int32_t amount = MAX(stateNormalMode.motion.amount, 1); amount > 0; amount--) {
1110 + if (stateNormalMode.motion.search == backward) { sign *= -1; }
1111 + moveLetter(sign);
1112 + gotoStringAndHighlight(sign);
1113 + }
1114 + break;
1115 + case 't':
1116 + if (sel.type == SEL_REGULAR) {
1117 + sel.type = SEL_RECTANGULAR;
1118 + } else {
1119 + sel.type = SEL_REGULAR;
1120 + }
1121 + tsetdirt(sel.nb.y, sel.ne.y);
1122 + discard = true;
1123 + default:
1124 + discard = true;
1125 + }
1126 + bool const isNumber = len == 1 && BETWEEN(ksym[0], 48, 57);
1127 + if (isNumber) { //< record numbers
1128 + discard = false;
1129 + stateNormalMode.motion.amount =
1130 + MIN(SHRT_MAX, stateNormalMode.motion.amount * 10 + ksym[0] - 48);
1131 + } else if (!discard) {
1132 + stateNormalMode.motion.amount = 0;
1133 + }
1134 +
1135 + if (discard) {
1136 + for (size_t i = 0; i < amountNormalModeShortcuts; ++i) {
1137 + if (ksym[0] == normalModeShortcuts[i].key) {
1138 + pressKeys(normalModeShortcuts[i].value);
1139 + }
1140 + }
1141 + } else {
1142 + char* kCommand = checkGetNext(currentCommand);
1143 + utf8decode(ksym, (Rune*)(kCommand), len);
1144 +
1145 + int diff = 0;
1146 + if (term.c.y > 0) {
1147 + if (term.c.y > term.bot) {
1148 + diff = term.bot - term.c.y;
1149 + term.c.y = term.bot;
1150 + }
1151 + } else {
1152 + if (term.c.y < 0) {
1153 + diff = -term.c.y;
1154 + term.c.y = 0;
1155 + }
1156 + }
1157 +
1158 + int const _newScr = term.scr + diff;
1159 + term.c.y = _newScr < 0 ? 0 : (_newScr >= HISTSIZE ? term.bot : term.c.y);
1160 + term.scr = mod(_newScr, HISTSIZE);
1161 +
1162 + if (!isEmpty(&highlights)) {
1163 + empty(&highlights);
1164 + highlightStringOnScreen();
1165 + }
1166 +
1167 + tsetdirt(0, term.row-3);
1168 + printCommandString();
1169 + printSearchString();
1170 +
1171 + if (stateNormalMode.command.op == visual) {
1172 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0);
1173 + } else if (stateNormalMode.command.op == visualLine) {
1174 + selextend(term.col-1, term.c.y, term.scr, sel.type, 0);
1175 + } else {
1176 + if (!isNumber && (stateNormalMode.motion.search == none
1177 + || stateNormalMode.motion.finished)) {
1178 + toggle = !toggle;
1179 + empty(currentCommand);
1180 + }
1181 + if (stateNormalMode.command.op == yank) {
1182 + if (!isNumber && !discard) {
1183 + // copy
1184 + selextend(term.c.x, term.c.y, term.scr, sel.mode, 0);
1185 + xsetsel(getsel());
1186 + xclipcopy();
1187 + applyPosition(&stateNormalMode.command.startPosition);
1188 + exitCommand();
1189 + }
1190 + }
1191 + }
1192 + }
1193 +}
1194 +
1195 void
1196 csiparse(void)
1197 {
1198 @@ -1176,6 +1850,10 @@ tmoveto(int x, int y)
1199 term.c.state &= ~CURSOR_WRAPNEXT;
1200 term.c.x = LIMIT(x, 0, term.col-1);
1201 term.c.y = LIMIT(y, miny, maxy);
1202 + // Set the last position in order to restore after normal mode exits.
1203 + stateNormalMode.initialPosition.x = term.c.x;
1204 + stateNormalMode.initialPosition.y = term.c.y;
1205 + stateNormalMode.initialPosition.yScr = term.scr;
1206 }
1207
1208 void
1209 @@ -1282,14 +1960,14 @@ void
1210 tinsertblankline(int n)
1211 {
1212 if (BETWEEN(term.c.y, term.top, term.bot))
1213 - tscrolldown(term.c.y, n);
1214 + tscrolldown(term.c.y, n, 0);
1215 }
1216
1217 void
1218 tdeleteline(int n)
1219 {
1220 if (BETWEEN(term.c.y, term.top, term.bot))
1221 - tscrollup(term.c.y, n);
1222 + tscrollup(term.c.y, n, 0);
1223 }
1224
1225 int32_t
1226 @@ -1720,11 +2398,11 @@ csihandle(void)
1227 break;
1228 case 'S': /* SU -- Scroll <n> line up */
1229 DEFAULT(csiescseq.arg[0], 1);
1230 - tscrollup(term.top, csiescseq.arg[0]);
1231 + tscrollup(term.top, csiescseq.arg[0], 0);
1232 break;
1233 case 'T': /* SD -- Scroll <n> line down */
1234 DEFAULT(csiescseq.arg[0], 1);
1235 - tscrolldown(term.top, csiescseq.arg[0]);
1236 + tscrolldown(term.top, csiescseq.arg[0], 0);
1237 break;
1238 case 'L': /* IL -- Insert <n> blank lines */
1239 DEFAULT(csiescseq.arg[0], 1);
1240 @@ -2227,7 +2905,7 @@ eschandle(uchar ascii)
1241 return 0;
1242 case 'D': /* IND -- Linefeed */
1243 if (term.c.y == term.bot) {
1244 - tscrollup(term.top, 1);
1245 + tscrollup(term.top, 1, 1);
1246 } else {
1247 tmoveto(term.c.x, term.c.y+1);
1248 }
1249 @@ -2240,7 +2918,7 @@ eschandle(uchar ascii)
1250 break;
1251 case 'M': /* RI -- Reverse index */
1252 if (term.c.y == term.top) {
1253 - tscrolldown(term.top, 1);
1254 + tscrolldown(term.top, 1, 1);
1255 } else {
1256 tmoveto(term.c.x, term.c.y-1);
1257 }
1258 @@ -2458,7 +3136,7 @@ twrite(const char *buf, int buflen, int show_ctrl)
1259 void
1260 tresize(int col, int row)
1261 {
1262 - int i;
1263 + int i, j;
1264 int minrow = MIN(row, term.row);
1265 int mincol = MIN(col, term.col);
1266 int *bp;
1267 @@ -2495,6 +3173,14 @@ tresize(int col, int row)
1268 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
1269 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
1270
1271 + for (i = 0; i < HISTSIZE; i++) {
1272 + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
1273 + for (j = mincol; j < col; j++) {
1274 + term.hist[i][j] = term.c.attr;
1275 + term.hist[i][j].u = ' ';
1276 + }
1277 + }
1278 +
1279 /* resize each row to new width, zero-pad if needed */
1280 for (i = 0; i < minrow; i++) {
1281 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
1282 @@ -2552,7 +3238,7 @@ drawregion(int x1, int y1, int x2, int y2)
1283 continue;
1284
1285 term.dirty[y] = 0;
1286 - xdrawline(term.line[y], x1, y, x2);
1287 + xdrawline(TLINE(y), x1, y, x2);
1288 }
1289 }
1290
1291 @@ -2573,8 +3259,8 @@ draw(void)
1292 cx--;
1293
1294 drawregion(0, 0, term.col, term.row);
1295 - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
1296 - term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
1297 + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
1298 + term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]);
1299 term.ocx = cx, term.ocy = term.c.y;
1300 xfinishdraw();
1301 xximspot(term.ocx, term.ocy);
1302 diff --git a/st.h b/st.h
1303 index 4da3051..7bd8bba 100644
1304 --- a/st.h
1305 +++ b/st.h
1306 @@ -1,5 +1,6 @@
1307 /* See LICENSE for license details. */
1308
1309 +#include <stdbool.h>
1310 #include <stdint.h>
1311 #include <sys/types.h>
1312
1313 @@ -10,6 +11,8 @@
1314 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
1315 #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
1316 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
1317 +#define INTERVAL(x, a, b) (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
1318 +#define INTERVAL_DIFF(x, a, b) (x) < (a) ? (x) - (a) : (x) > (b) ? (x) - (b) : 0
1319 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
1320 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
1321 (a).bg != (b).bg)
1322 @@ -33,6 +36,8 @@ enum glyph_attribute {
1323 ATTR_WRAP = 1 << 8,
1324 ATTR_WIDE = 1 << 9,
1325 ATTR_WDUMMY = 1 << 10,
1326 + ATTR_HIGHLIGHT = 1 << 11 | ATTR_UNDERLINE,
1327 + ATTR_CURRENT = 1 << 12,
1328 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
1329 };
1330
1331 @@ -80,6 +85,14 @@ void die(const char *, ...);
1332 void redraw(void);
1333 void draw(void);
1334
1335 +int highlighted(int, int);
1336 +int currentLine(int, int);
1337 +void kscrolldown(const Arg *);
1338 +void kscrollup(const Arg *);
1339 +void kpressNormalMode(char const * ksym, uint32_t len, bool esc, bool enter, bool backspace);
1340 +void normalMode(Arg const *);
1341 +void onNormalModeStart();
1342 +void onNormalModeStop();
1343 void printscreen(const Arg *);
1344 void printsel(const Arg *);
1345 void sendbreak(const Arg *);
1346 @@ -99,8 +112,10 @@ void resettitle(void);
1347
1348 void selclear(void);
1349 void selinit(void);
1350 -void selstart(int, int, int);
1351 -void selextend(int, int, int, int);
1352 +void selstart(int, int, int, int);
1353 +void xselstart(int, int, int);
1354 +void selextend(int, int, int, int, int);
1355 +void xselextend(int, int, int, int);
1356 int selected(int, int);
1357 char *getsel(void);
1358
1359 @@ -110,6 +125,8 @@ void *xmalloc(size_t);
1360 void *xrealloc(void *, size_t);
1361 char *xstrdup(char *);
1362
1363 +
1364 +
1365 /* config.h globals */
1366 extern char *utmp;
1367 extern char *stty_args;
1368 @@ -120,3 +137,13 @@ extern char *termname;
1369 extern unsigned int tabspaces;
1370 extern unsigned int defaultfg;
1371 extern unsigned int defaultbg;
1372 +extern char wordDelimSmall[];
1373 +extern char wordDelimLarge[];
1374 +
1375 +typedef struct NormalModeShortcuts {
1376 + char key;
1377 + char *value;
1378 +} NormalModeShortcuts;
1379 +
1380 +extern NormalModeShortcuts normalModeShortcuts[];
1381 +extern size_t const amountNormalModeShortcuts;
1382 diff --git a/win.h b/win.h
1383 index a6ef1b9..1a6fefe 100644
1384 --- a/win.h
1385 +++ b/win.h
1386 @@ -19,6 +19,7 @@ enum win_mode {
1387 MODE_MOUSEMANY = 1 << 15,
1388 MODE_BRCKTPASTE = 1 << 16,
1389 MODE_NUMLOCK = 1 << 17,
1390 + MODE_NORMAL = 1 << 18,
1391 MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\
1392 |MODE_MOUSEMANY,
1393 };
1394 @@ -27,6 +28,7 @@ void xbell(void);
1395 void xclipcopy(void);
1396 void xdrawcursor(int, int, Glyph, int, int, Glyph);
1397 void xdrawline(Line, int, int, int);
1398 +void xdrawglyph(Glyph, int, int);
1399 void xfinishdraw(void);
1400 void xloadcols(void);
1401 int xsetcolorname(int, const char *);
1402 diff --git a/x.c b/x.c
1403 index 5828a3b..ccf1751 100644
1404 --- a/x.c
1405 +++ b/x.c
1406 @@ -136,7 +136,6 @@ typedef struct {
1407 static inline ushort sixd_to_16bit(int);
1408 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
1409 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
1410 -static void xdrawglyph(Glyph, int, int);
1411 static void xclear(int, int, int, int);
1412 static int xgeommasktogravity(int);
1413 static void ximopen(Display *);
1414 @@ -340,7 +339,7 @@ mousesel(XEvent *e, int done)
1415 break;
1416 }
1417 }
1418 - selextend(evcol(e), evrow(e), seltype, done);
1419 + xselextend(evcol(e), evrow(e), seltype, done);
1420 if (done)
1421 setsel(getsel(), e->xbutton.time);
1422 }
1423 @@ -444,7 +443,7 @@ bpress(XEvent *e)
1424 xsel.tclick2 = xsel.tclick1;
1425 xsel.tclick1 = now;
1426
1427 - selstart(evcol(e), evrow(e), snap);
1428 + xselstart(evcol(e), evrow(e), snap);
1429 }
1430 }
1431
1432 @@ -730,6 +729,19 @@ xloadcolor(int i, const char *name, Color *ncolor)
1433 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
1434 }
1435
1436 +void
1437 +normalMode(Arg const *_) //< the argument is just for the sake of
1438 + // adhering to the function format.
1439 +{
1440 + win.mode ^= MODE_NORMAL; //< toggle normal mode via exclusive or.
1441 + if (win.mode & MODE_NORMAL) {
1442 + onNormalModeStart();
1443 + } else {
1444 + onNormalModeStop();
1445 + }
1446 +}
1447 +
1448 +
1449 void
1450 xloadcols(void)
1451 {
1452 @@ -1296,6 +1308,14 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
1453 base.fg = defaultattr;
1454 }
1455
1456 + if (base.mode & ATTR_HIGHLIGHT) {
1457 + base.bg = highlightBg;
1458 + base.fg = highlightFg;
1459 + } else if ((base.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) {
1460 + base.bg = currentBg;
1461 + base.fg = currentFg;
1462 + }
1463 +
1464 if (IS_TRUECOL(base.fg)) {
1465 colfg.alpha = 0xffff;
1466 colfg.red = TRUERED(base.fg);
1467 @@ -1428,8 +1448,9 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
1468 Color drawcol;
1469
1470 /* remove the old cursor */
1471 - if (selected(ox, oy))
1472 - og.mode ^= ATTR_REVERSE;
1473 + if (selected(ox, oy)) og.mode ^= ATTR_REVERSE;
1474 + if (highlighted(ox, oy)) { og.mode ^= ATTR_HIGHLIGHT; }
1475 + if (currentLine(ox, oy)) { og.mode ^= ATTR_CURRENT; }
1476 xdrawglyph(og, ox, oy);
1477
1478 if (IS_SET(MODE_HIDE))
1479 @@ -1461,6 +1482,11 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
1480 drawcol = dc.col[g.bg];
1481 }
1482
1483 + if ((g.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) {
1484 + g.bg = currentBg;
1485 + g.fg = currentFg;
1486 + }
1487 +
1488 /* draw the new one */
1489 if (IS_SET(MODE_FOCUSED)) {
1490 switch (win.cursor) {
1491 @@ -1550,6 +1576,12 @@ xdrawline(Line line, int x1, int y1, int x2)
1492 continue;
1493 if (selected(x, y1))
1494 new.mode ^= ATTR_REVERSE;
1495 + if (highlighted(x, y1)) {
1496 + new.mode ^= ATTR_HIGHLIGHT;
1497 + }
1498 + if (currentLine(x, y1)) {
1499 + new.mode ^= ATTR_CURRENT;
1500 + }
1501 if (i > 0 && ATTRCMP(base, new)) {
1502 xdrawglyphfontspecs(specs, base, i, ox, y1);
1503 specs += i;
1504 @@ -1731,6 +1763,12 @@ kpress(XEvent *ev)
1505 return;
1506
1507 len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status);
1508 + if (IS_SET(MODE_NORMAL)) {
1509 + kpressNormalMode(buf, strlen(buf),
1510 + ksym == XK_Escape, ksym == XK_Return, ksym == XK_BackSpace);
1511 + return;
1512 + }
1513 +
1514 /* 1. shortcuts */
1515 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
1516 if (ksym == bp->keysym && match(bp->mod, e->state)) {
1517 @@ -1870,8 +1908,9 @@ run(void)
1518 XNextEvent(xw.dpy, &ev);
1519 if (XFilterEvent(&ev, None))
1520 continue;
1521 - if (handler[ev.type])
1522 + if (handler[ev.type]) {
1523 (handler[ev.type])(&ev);
1524 + }
1525 }
1526
1527 draw();
1528 --
1529 2.24.0
1530