Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/decode.c
39476 views
1
/*
2
* Copyright (C) 1984-2025 Mark Nudelman
3
*
4
* You may distribute under the terms of either the GNU General Public
5
* License or the Less License, as specified in the README file.
6
*
7
* For more information, see the README file.
8
*/
9
10
11
/*
12
* Routines to decode user commands.
13
*
14
* This is all table driven.
15
* A command table is a sequence of command descriptors.
16
* Each command descriptor is a sequence of bytes with the following format:
17
* <c1><c2>...<cN><0><action>
18
* The characters c1,c2,...,cN are the command string; that is,
19
* the characters which the user must type.
20
* It is terminated by a null <0> byte.
21
* The byte after the null byte is the action code associated
22
* with the command string.
23
* If an action byte is OR-ed with A_EXTRA, this indicates
24
* that the option byte is followed by an extra string.
25
*
26
* There may be many command tables.
27
* The first (default) table is built-in.
28
* Other tables are read in from "lesskey" files.
29
* All the tables are linked together and are searched in order.
30
*/
31
32
#include "less.h"
33
#include "cmd.h"
34
#include "lesskey.h"
35
36
extern int erase_char, erase2_char, kill_char;
37
extern int mousecap;
38
extern int sc_height;
39
40
static constant lbool allow_drag = TRUE;
41
42
#if USERFILE
43
/* "content" is lesskey source, never binary. */
44
static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar);
45
static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar);
46
#endif /* USERFILE */
47
48
#define SK(k) \
49
SK_SPECIAL_KEY, (k), 6, 1, 1, 1
50
/*
51
* Command table is ordered roughly according to expected
52
* frequency of use, so the common commands are near the beginning.
53
*/
54
55
static unsigned char cmdtable[] =
56
{
57
'\r',0, A_F_LINE,
58
'\n',0, A_F_LINE,
59
'e',0, A_F_LINE,
60
'j',0, A_F_LINE,
61
SK(SK_DOWN_ARROW),0, A_F_LINE,
62
CONTROL('E'),0, A_F_LINE,
63
CONTROL('N'),0, A_F_LINE,
64
'k',0, A_B_LINE,
65
'y',0, A_B_LINE,
66
CONTROL('Y'),0, A_B_LINE,
67
SK(SK_CONTROL_K),0, A_B_LINE,
68
CONTROL('P'),0, A_B_LINE,
69
SK(SK_UP_ARROW),0, A_B_LINE,
70
'J',0, A_FF_LINE,
71
'K',0, A_BF_LINE,
72
'Y',0, A_BF_LINE,
73
'd',0, A_F_SCROLL,
74
CONTROL('D'),0, A_F_SCROLL,
75
'u',0, A_B_SCROLL,
76
CONTROL('U'),0, A_B_SCROLL,
77
ESC,'[','M',0, A_X11MOUSE_IN,
78
ESC,'[','<',0, A_X116MOUSE_IN,
79
' ',0, A_F_SCREEN,
80
'f',0, A_F_SCREEN,
81
CONTROL('F'),0, A_F_SCREEN,
82
CONTROL('V'),0, A_F_SCREEN,
83
SK(SK_PAGE_DOWN),0, A_F_SCREEN,
84
'b',0, A_B_SCREEN,
85
CONTROL('B'),0, A_B_SCREEN,
86
ESC,'v',0, A_B_SCREEN,
87
SK(SK_PAGE_UP),0, A_B_SCREEN,
88
'z',0, A_F_WINDOW,
89
'w',0, A_B_WINDOW,
90
ESC,' ',0, A_FF_SCREEN,
91
ESC,'b',0, A_BF_SCREEN,
92
ESC,'j',0, A_F_NEWLINE,
93
ESC,'k',0, A_B_NEWLINE,
94
'F',0, A_F_FOREVER,
95
ESC,'F',0, A_F_UNTIL_HILITE,
96
'R',0, A_FREPAINT,
97
'r',0, A_REPAINT,
98
CONTROL('R'),0, A_REPAINT,
99
CONTROL('L'),0, A_REPAINT,
100
ESC,'u',0, A_UNDO_SEARCH,
101
ESC,'U',0, A_CLR_SEARCH,
102
'g',0, A_GOLINE,
103
SK(SK_HOME),0, A_GOLINE,
104
'<',0, A_GOLINE,
105
ESC,'<',0, A_GOLINE,
106
'p',0, A_PERCENT,
107
'%',0, A_PERCENT,
108
ESC,'(',0, A_LSHIFT,
109
ESC,')',0, A_RSHIFT,
110
ESC,'{',0, A_LLSHIFT,
111
ESC,'}',0, A_RRSHIFT,
112
SK(SK_RIGHT_ARROW),0, A_RSHIFT,
113
SK(SK_LEFT_ARROW),0, A_LSHIFT,
114
SK(SK_CTL_RIGHT_ARROW),0, A_RRSHIFT,
115
SK(SK_CTL_LEFT_ARROW),0, A_LLSHIFT,
116
'{',0, A_F_BRACKET|A_EXTRA, '{','}',0,
117
'}',0, A_B_BRACKET|A_EXTRA, '{','}',0,
118
'(',0, A_F_BRACKET|A_EXTRA, '(',')',0,
119
')',0, A_B_BRACKET|A_EXTRA, '(',')',0,
120
'[',0, A_F_BRACKET|A_EXTRA, '[',']',0,
121
']',0, A_B_BRACKET|A_EXTRA, '[',']',0,
122
ESC,CONTROL('F'),0, A_F_BRACKET,
123
ESC,CONTROL('B'),0, A_B_BRACKET,
124
'G',0, A_GOEND,
125
ESC,'G',0, A_GOEND_BUF,
126
ESC,'>',0, A_GOEND,
127
'>',0, A_GOEND,
128
SK(SK_END),0, A_GOEND,
129
'P',0, A_GOPOS,
130
131
'0',0, A_DIGIT,
132
'1',0, A_DIGIT,
133
'2',0, A_DIGIT,
134
'3',0, A_DIGIT,
135
'4',0, A_DIGIT,
136
'5',0, A_DIGIT,
137
'6',0, A_DIGIT,
138
'7',0, A_DIGIT,
139
'8',0, A_DIGIT,
140
'9',0, A_DIGIT,
141
'.',0, A_DIGIT,
142
143
'=',0, A_STAT,
144
CONTROL('G'),0, A_STAT,
145
':','f',0, A_STAT,
146
'/',0, A_F_SEARCH,
147
'?',0, A_B_SEARCH,
148
ESC,'/',0, A_F_SEARCH|A_EXTRA, '*',0,
149
ESC,'?',0, A_B_SEARCH|A_EXTRA, '*',0,
150
'n',0, A_AGAIN_SEARCH,
151
ESC,'n',0, A_T_AGAIN_SEARCH,
152
'N',0, A_REVERSE_SEARCH,
153
ESC,'N',0, A_T_REVERSE_SEARCH,
154
'&',0, A_FILTER,
155
'm',0, A_SETMARK,
156
'M',0, A_SETMARKBOT,
157
ESC,'m',0, A_CLRMARK,
158
'\'',0, A_GOMARK,
159
CONTROL('X'),CONTROL('X'),0, A_GOMARK,
160
'E',0, A_EXAMINE,
161
':','e',0, A_EXAMINE,
162
CONTROL('X'),CONTROL('V'),0, A_EXAMINE,
163
':','n',0, A_NEXT_FILE,
164
':','p',0, A_PREV_FILE,
165
CONTROL('O'),CONTROL('N'),0, A_OSC8_F_SEARCH,
166
CONTROL('O'),'n',0, A_OSC8_F_SEARCH,
167
CONTROL('O'),CONTROL('P'),0, A_OSC8_B_SEARCH,
168
CONTROL('O'),'p',0, A_OSC8_B_SEARCH,
169
CONTROL('O'),CONTROL('O'),0, A_OSC8_OPEN,
170
CONTROL('O'),'o',0, A_OSC8_OPEN,
171
CONTROL('O'),CONTROL('L'),0, A_OSC8_JUMP,
172
CONTROL('O'),'l',0, A_OSC8_JUMP,
173
't',0, A_NEXT_TAG,
174
'T',0, A_PREV_TAG,
175
':','x',0, A_INDEX_FILE,
176
':','d',0, A_REMOVE_FILE,
177
'-',0, A_OPT_TOGGLE,
178
':','t',0, A_OPT_TOGGLE|A_EXTRA, 't',0,
179
's',0, A_OPT_TOGGLE|A_EXTRA, 'o',0,
180
'_',0, A_DISP_OPTION,
181
'|',0, A_PIPE,
182
'v',0, A_VISUAL,
183
'!',0, A_SHELL,
184
'#',0, A_PSHELL,
185
'+',0, A_FIRSTCMD,
186
ESC,'[','2','0','0','~',0, A_START_PASTE,
187
ESC,'[','2','0','1','~',0, A_END_PASTE,
188
189
'H',0, A_HELP,
190
'h',0, A_HELP,
191
SK(SK_F1),0, A_HELP,
192
'V',0, A_VERSION,
193
'q',0, A_QUIT,
194
'Q',0, A_QUIT,
195
':','q',0, A_QUIT,
196
':','Q',0, A_QUIT,
197
'Z','Z',0, A_QUIT
198
};
199
200
static unsigned char edittable[] =
201
{
202
'\t',0, EC_F_COMPLETE, /* TAB */
203
'\17',0, EC_B_COMPLETE, /* BACKTAB */
204
SK(SK_BACKTAB),0, EC_B_COMPLETE, /* BACKTAB */
205
ESC,'\t',0, EC_B_COMPLETE, /* ESC TAB */
206
CONTROL('L'),0, EC_EXPAND, /* CTRL-L */
207
CONTROL('V'),0, EC_LITERAL, /* BACKSLASH */
208
CONTROL('A'),0, EC_LITERAL, /* BACKSLASH */
209
ESC,'l',0, EC_RIGHT, /* ESC l */
210
SK(SK_RIGHT_ARROW),0, EC_RIGHT, /* RIGHTARROW */
211
ESC,'h',0, EC_LEFT, /* ESC h */
212
SK(SK_LEFT_ARROW),0, EC_LEFT, /* LEFTARROW */
213
ESC,'b',0, EC_W_LEFT, /* ESC b */
214
ESC,SK(SK_LEFT_ARROW),0, EC_W_LEFT, /* ESC LEFTARROW */
215
SK(SK_CTL_LEFT_ARROW),0, EC_W_LEFT, /* CTRL-LEFTARROW */
216
ESC,'w',0, EC_W_RIGHT, /* ESC w */
217
ESC,SK(SK_RIGHT_ARROW),0, EC_W_RIGHT, /* ESC RIGHTARROW */
218
SK(SK_CTL_RIGHT_ARROW),0, EC_W_RIGHT, /* CTRL-RIGHTARROW */
219
ESC,'i',0, EC_INSERT, /* ESC i */
220
SK(SK_INSERT),0, EC_INSERT, /* INSERT */
221
ESC,'x',0, EC_DELETE, /* ESC x */
222
SK(SK_DELETE),0, EC_DELETE, /* DELETE */
223
ESC,'X',0, EC_W_DELETE, /* ESC X */
224
ESC,SK(SK_DELETE),0, EC_W_DELETE, /* ESC DELETE */
225
SK(SK_CTL_DELETE),0, EC_W_DELETE, /* CTRL-DELETE */
226
SK(SK_CTL_BACKSPACE),0, EC_W_BACKSPACE, /* CTRL-BACKSPACE */
227
ESC,SK(SK_BACKSPACE),0, EC_W_BACKSPACE, /* ESC BACKSPACE */
228
ESC,'0',0, EC_HOME, /* ESC 0 */
229
SK(SK_HOME),0, EC_HOME, /* HOME */
230
ESC,'$',0, EC_END, /* ESC $ */
231
SK(SK_END),0, EC_END, /* END */
232
ESC,'k',0, EC_UP, /* ESC k */
233
SK(SK_UP_ARROW),0, EC_UP, /* UPARROW */
234
ESC,'j',0, EC_DOWN, /* ESC j */
235
SK(SK_DOWN_ARROW),0, EC_DOWN, /* DOWNARROW */
236
CONTROL('G'),0, EC_ABORT, /* CTRL-G */
237
ESC,'[','M',0, EC_X11MOUSE, /* X11 mouse report */
238
ESC,'[','<',0, EC_X116MOUSE, /* X11 1006 mouse report */
239
ESC,'[','2','0','0','~',0, A_START_PASTE, /* open paste bracket */
240
ESC,'[','2','0','1','~',0, A_END_PASTE, /* close paste bracket */
241
};
242
243
static unsigned char dflt_vartable[] =
244
{
245
'L','E','S','S','_','O','S','C','8','_','m','a','n', 0, EV_OK|A_EXTRA,
246
/* echo '%o' | sed -e "s,^man\:\\([^(]*\\)( *\\([^)]*\\)\.*,-man '\\2' '\\1'," -e"t X" -e"s,\.*,-echo Invalid man link," -e"\: X" */
247
'e','c','h','o',' ','\'','%','o','\'',' ','|',' ','s','e','d',' ','-','e',' ','"','s',',','^','m','a','n','\\',':','\\','\\','(','[','^','(',']','*','\\','\\',')','(',' ','*','\\','\\','(','[','^',')',']','*','\\','\\',')','\\','.','*',',','-','m','a','n',' ','\'','\\','\\','2','\'',' ','\'','\\','\\','1','\'',',','"',' ','-','e','"','t',' ','X','"',' ','-','e','"','s',',','\\','.','*',',','-','e','c','h','o',' ','I','n','v','a','l','i','d',' ','m','a','n',' ','l','i','n','k',',','"',' ','-','e','"','\\',':',' ','X','"',
248
0,
249
250
'L','E','S','S','_','O','S','C','8','_','f','i','l','e', 0, EV_OK|A_EXTRA,
251
/* eval `echo '%o' | sed -e "s,^file://\\([^/]*\\)\\(.*\\),_H=\\1;_P=\\2;_E=0," -e"t X" -e"s,.*,_E=1," -e": X"`; if [ "$_E" = 1 ]; then echo -echo Invalid file link; elif [ -z "$_H" -o "$_H" = localhost -o "$_H" = $HOSTNAME ]; then echo ":e $_P"; else echo -echo Cannot open remote file on "$_H"; fi */
252
'e','v','a','l',' ','`','e','c','h','o',' ','\'','%','o','\'',' ','|',' ','s','e','d',' ','-','e',' ','"','s',',','^','f','i','l','e','\\',':','/','/','\\','\\','(','[','^','/',']','*','\\','\\',')','\\','\\','(','\\','.','*','\\','\\',')',',','_','H','=','\\','\\','1',';','_','P','=','\\','\\','2',';','_','E','=','0',',','"',' ','-','e','"','t',' ','X','"',' ','-','e','"','s',',','\\','.','*',',','_','E','=','1',',','"',' ','-','e','"','\\',':',' ','X','"','`',';',' ','i','f',' ','[',' ','"','$','_','E','"',' ','=',' ','1',' ',']',';',' ','t','h','e','n',' ','e','c','h','o',' ','-','e','c','h','o',' ','I','n','v','a','l','i','d',' ','f','i','l','e',' ','l','i','n','k',';',' ','e','l','i','f',' ','[',' ','-','z',' ','"','$','_','H','"',' ','-','o',' ','"','$','_','H','"',' ','=',' ','l','o','c','a','l','h','o','s','t',' ','-','o',' ','"','$','_','H','"',' ','=',' ','$','H','O','S','T','N','A','M','E',' ',']',';',' ','t','h','e','n',' ','e','c','h','o',' ','"','\\',':','e',' ','$','_','P','"',';',' ','e','l','s','e',' ','e','c','h','o',' ','-','e','c','h','o',' ','C','a','n','n','o','t',' ','o','p','e','n',' ','r','e','m','o','t','e',' ','f','i','l','e',' ','o','n',' ','"','$','_','H','"',';',' ','f','i',
253
0,
254
};
255
256
/*
257
* Structure to support a list of command tables.
258
*/
259
struct tablelist
260
{
261
struct tablelist *t_next;
262
unsigned char *t_start;
263
unsigned char *t_end;
264
};
265
266
/*
267
* List of command tables and list of line-edit tables.
268
*/
269
static struct tablelist *list_fcmd_tables = NULL;
270
static struct tablelist *list_ecmd_tables = NULL;
271
static struct tablelist *list_var_tables = NULL;
272
static struct tablelist *list_sysvar_tables = NULL;
273
274
275
/*
276
* Expand special key abbreviations in a command table.
277
*/
278
static void expand_special_keys(unsigned char *table, size_t len)
279
{
280
unsigned char *fm;
281
unsigned char *to;
282
int a;
283
constant char *repl;
284
size_t klen;
285
286
for (fm = table; fm < table + len; )
287
{
288
/*
289
* Rewrite each command in the table with any
290
* special key abbreviations expanded.
291
*/
292
for (to = fm; *fm != '\0'; )
293
{
294
if (*fm != SK_SPECIAL_KEY)
295
{
296
*to++ = *fm++;
297
continue;
298
}
299
/*
300
* After SK_SPECIAL_KEY, next byte is the type
301
* of special key (one of the SK_* constants),
302
* and the byte after that is the number of bytes,
303
* N, reserved by the abbreviation (including the
304
* SK_SPECIAL_KEY and key type bytes).
305
* Replace all N bytes with the actual bytes
306
* output by the special key on this terminal.
307
*/
308
repl = special_key_str(fm[1]);
309
klen = fm[2] & 0377;
310
fm += klen;
311
if (repl == NULL || strlen(repl) > klen)
312
repl = "\377";
313
while (*repl != '\0')
314
*to++ = (unsigned char) *repl++; /*{{type-issue}}*/
315
}
316
*to++ = '\0';
317
/*
318
* Fill any unused bytes between end of command and
319
* the action byte with A_SKIP.
320
*/
321
while (to <= fm)
322
*to++ = A_SKIP;
323
fm++;
324
a = *fm++ & 0377;
325
if (a & A_EXTRA)
326
{
327
while (*fm++ != '\0')
328
continue;
329
}
330
}
331
}
332
333
/*
334
* Expand special key abbreviations in a list of command tables.
335
*/
336
static void expand_cmd_table(struct tablelist *tlist)
337
{
338
struct tablelist *t;
339
for (t = tlist; t != NULL; t = t->t_next)
340
{
341
expand_special_keys(t->t_start, ptr_diff(t->t_end, t->t_start));
342
}
343
}
344
345
/*
346
* Expand special key abbreviations in all command tables.
347
*/
348
public void expand_cmd_tables(void)
349
{
350
expand_cmd_table(list_fcmd_tables);
351
expand_cmd_table(list_ecmd_tables);
352
expand_cmd_table(list_var_tables);
353
expand_cmd_table(list_sysvar_tables);
354
}
355
356
/*
357
* Initialize the command lists.
358
*/
359
public void init_cmds(void)
360
{
361
/*
362
* Add the default command tables.
363
*/
364
add_fcmd_table(cmdtable, sizeof(cmdtable));
365
add_ecmd_table(edittable, sizeof(edittable));
366
add_sysvar_table(dflt_vartable, sizeof(dflt_vartable));
367
#if USERFILE
368
#ifdef BINDIR /* For backwards compatibility */
369
/* Try to add tables in the OLD system lesskey file. */
370
add_hometable(lesskey, NULL, BINDIR "/.sysless", TRUE);
371
#endif
372
/*
373
* Try to load lesskey source file or binary file.
374
* If the source file succeeds, don't load binary file.
375
* The binary file is likely to have been generated from
376
* a (possibly out of date) copy of the src file,
377
* so loading it is at best redundant.
378
*/
379
/*
380
* Try to add tables in system lesskey src file.
381
*/
382
#if HAVE_LESSKEYSRC
383
if (add_hometable(lesskey_src, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS, TRUE) != 0)
384
#endif
385
{
386
/*
387
* Try to add the tables in the system lesskey binary file.
388
*/
389
add_hometable(lesskey, "LESSKEY_SYSTEM", LESSKEYFILE_SYS, TRUE);
390
}
391
/*
392
* Try to add tables in the lesskey src file "$HOME/.lesskey".
393
*/
394
#if HAVE_LESSKEYSRC
395
if (add_hometable(lesskey_src, "LESSKEYIN", DEF_LESSKEYINFILE, FALSE) != 0)
396
#endif
397
{
398
/*
399
* Try to add the tables in the standard lesskey binary file "$HOME/.less".
400
*/
401
add_hometable(lesskey, "LESSKEY", LESSKEYFILE, FALSE);
402
}
403
404
add_content_table(lesskey_content, "LESSKEY_CONTENT_SYSTEM", TRUE);
405
add_content_table(lesskey_content, "LESSKEY_CONTENT", FALSE);
406
#endif /* USERFILE */
407
}
408
409
/*
410
* Add a command table.
411
*/
412
static int add_cmd_table(struct tablelist **tlist, unsigned char *buf, size_t len)
413
{
414
struct tablelist *t;
415
416
if (len == 0)
417
return (0);
418
/*
419
* Allocate a tablelist structure, initialize it,
420
* and link it into the list of tables.
421
*/
422
if ((t = (struct tablelist *)
423
calloc(1, sizeof(struct tablelist))) == NULL)
424
{
425
return (-1);
426
}
427
t->t_start = buf;
428
t->t_end = buf + len;
429
t->t_next = NULL;
430
if (*tlist == NULL)
431
*tlist = t;
432
else
433
{
434
struct tablelist *e;
435
for (e = *tlist; e->t_next != NULL; e = e->t_next)
436
continue;
437
e->t_next = t;
438
}
439
return (0);
440
}
441
442
/*
443
* Remove the last command table in a list.
444
*/
445
static void pop_cmd_table(struct tablelist **tlist)
446
{
447
struct tablelist *t;
448
if (*tlist == NULL)
449
return;
450
if ((*tlist)->t_next == NULL)
451
{
452
t = *tlist;
453
*tlist = NULL;
454
} else
455
{
456
struct tablelist *e;
457
for (e = *tlist; e->t_next->t_next != NULL; e = e->t_next)
458
continue;
459
t = e->t_next;
460
e->t_next = NULL;
461
}
462
free(t);
463
}
464
465
/*
466
* Add a command table.
467
*/
468
public void add_fcmd_table(unsigned char *buf, size_t len)
469
{
470
if (add_cmd_table(&list_fcmd_tables, buf, len) < 0)
471
error("Warning: some commands disabled", NULL_PARG);
472
}
473
474
/*
475
* Add an editing command table.
476
*/
477
public void add_ecmd_table(unsigned char *buf, size_t len)
478
{
479
if (add_cmd_table(&list_ecmd_tables, buf, len) < 0)
480
error("Warning: some edit commands disabled", NULL_PARG);
481
}
482
483
/*
484
* Add an environment variable table.
485
*/
486
static void add_var_table(struct tablelist **tlist, unsigned char *buf, size_t len)
487
{
488
struct xbuffer xbuf;
489
490
xbuf_init(&xbuf);
491
expand_evars((char*)buf, len, &xbuf); /*{{unsigned-issue}}*/
492
/* {{ We leak the table in buf. expand_evars scribbled in it so it's useless anyway. }} */
493
if (add_cmd_table(tlist, xbuf.data, xbuf.end) < 0)
494
error("Warning: environment variables from lesskey file unavailable", NULL_PARG);
495
}
496
497
public void add_uvar_table(unsigned char *buf, size_t len)
498
{
499
add_var_table(&list_var_tables, buf, len);
500
}
501
502
public void add_sysvar_table(unsigned char *buf, size_t len)
503
{
504
add_var_table(&list_sysvar_tables, buf, len);
505
}
506
507
/*
508
* Return action for a mouse wheel down event.
509
*/
510
static int mouse_wheel_down(void)
511
{
512
return ((mousecap == OPT_ONPLUS) ? A_B_MOUSE : A_F_MOUSE);
513
}
514
515
/*
516
* Return action for a mouse wheel up event.
517
*/
518
static int mouse_wheel_up(void)
519
{
520
return ((mousecap == OPT_ONPLUS) ? A_F_MOUSE : A_B_MOUSE);
521
}
522
523
/*
524
* Return action for the left mouse button trigger.
525
*/
526
static int mouse_button_left(int x, int y, lbool down, lbool drag)
527
{
528
static int last_drag_y = -1;
529
static int last_click_y = -1;
530
531
if (down && !drag)
532
{
533
last_drag_y = last_click_y = y;
534
}
535
if (allow_drag && drag && last_drag_y >= 0)
536
{
537
/* Drag text up/down */
538
if (y > last_drag_y)
539
{
540
cmd_exec();
541
backward(y - last_drag_y, FALSE, FALSE, FALSE);
542
last_drag_y = y;
543
} else if (y < last_drag_y)
544
{
545
cmd_exec();
546
forward(last_drag_y - y, FALSE, FALSE, FALSE);
547
last_drag_y = y;
548
}
549
} else if (!down)
550
{
551
#if OSC8_LINK
552
if (osc8_click(y, x))
553
return (A_NOACTION);
554
#else
555
(void) x;
556
#endif /* OSC8_LINK */
557
if (y < sc_height-1 && y == last_click_y)
558
{
559
setmark('#', y);
560
screen_trashed();
561
}
562
}
563
return (A_NOACTION);
564
}
565
566
/*
567
* Return action for the right mouse button trigger.
568
*/
569
static int mouse_button_right(int x, int y, lbool down, lbool drag)
570
{
571
(void) x; (void) drag;
572
/*
573
* {{ unlike mouse_button_left, we could return an action,
574
* but keep it near mouse_button_left for readability. }}
575
*/
576
if (!down && y < sc_height-1)
577
{
578
gomark('#');
579
screen_trashed();
580
}
581
return (A_NOACTION);
582
}
583
584
/*
585
* Read a decimal integer. Return the integer and set *pterm to the terminating char.
586
*/
587
static int getcc_int(char *pterm)
588
{
589
int num = 0;
590
int digits = 0;
591
for (;;)
592
{
593
char ch = getcc();
594
if (ch < '0' || ch > '9')
595
{
596
if (pterm != NULL) *pterm = ch;
597
if (digits == 0)
598
return (-1);
599
return (num);
600
}
601
if (ckd_mul(&num, num, 10) || ckd_add(&num, num, ch - '0'))
602
return -1;
603
++digits;
604
}
605
}
606
607
static int x11mouse_button(int btn, int x, int y, lbool down, lbool drag)
608
{
609
switch (btn) {
610
case X11MOUSE_BUTTON1:
611
return mouse_button_left(x, y, down, drag);
612
/* is BUTTON2 the rightmost with 2-buttons mouse? */
613
case X11MOUSE_BUTTON2:
614
case X11MOUSE_BUTTON3:
615
return mouse_button_right(x, y, down, drag);
616
}
617
return (A_NOACTION);
618
}
619
620
/*
621
* Read suffix of mouse input and return the action to take.
622
* The prefix ("\e[M") has already been read.
623
*/
624
static int x11mouse_action(lbool skip)
625
{
626
static int prev_b = X11MOUSE_BUTTON_REL;
627
int x, y;
628
int b = getcc() - X11MOUSE_OFFSET;
629
lbool drag = ((b & X11MOUSE_DRAG) != 0);
630
b &= ~X11MOUSE_DRAG;
631
x = getcc() - X11MOUSE_OFFSET-1;
632
y = getcc() - X11MOUSE_OFFSET-1;
633
if (skip)
634
return (A_NOACTION);
635
switch (b) {
636
case X11MOUSE_WHEEL_DOWN:
637
return mouse_wheel_down();
638
case X11MOUSE_WHEEL_UP:
639
return mouse_wheel_up();
640
case X11MOUSE_BUTTON1:
641
case X11MOUSE_BUTTON2:
642
case X11MOUSE_BUTTON3:
643
prev_b = b;
644
return x11mouse_button(b, x, y, TRUE, drag);
645
case X11MOUSE_BUTTON_REL: /* button up */
646
return x11mouse_button(prev_b, x, y, FALSE, drag);
647
}
648
return (A_NOACTION);
649
}
650
651
/*
652
* Read suffix of mouse input and return the action to take.
653
* The prefix ("\e[<") has already been read.
654
*/
655
static int x116mouse_action(lbool skip)
656
{
657
char ch;
658
int x, y;
659
int b = getcc_int(&ch);
660
lbool drag = ((b & X11MOUSE_DRAG) != 0);
661
b &= ~X11MOUSE_DRAG;
662
if (b < 0 || ch != ';') return (A_NOACTION);
663
x = getcc_int(&ch) - 1;
664
if (x < 0 || ch != ';') return (A_NOACTION);
665
y = getcc_int(&ch) - 1;
666
if (y < 0) return (A_NOACTION);
667
if (skip)
668
return (A_NOACTION);
669
switch (b) {
670
case X11MOUSE_WHEEL_DOWN:
671
return mouse_wheel_down();
672
case X11MOUSE_WHEEL_UP:
673
return mouse_wheel_up();
674
case X11MOUSE_BUTTON1:
675
case X11MOUSE_BUTTON2:
676
case X11MOUSE_BUTTON3: {
677
lbool down = (ch == 'M');
678
lbool up = (ch == 'm');
679
if (up || down)
680
return x11mouse_button(b, x, y, down, drag);
681
break; }
682
}
683
return (A_NOACTION);
684
}
685
686
/*
687
* Return the largest N such that the first N chars of goal
688
* are equal to the last N chars of str.
689
*/
690
static size_t cmd_match(constant char *goal, constant char *str)
691
{
692
size_t slen = strlen(str);
693
size_t len;
694
for (len = slen; len > 0; len--)
695
if (strncmp(str + slen - len, goal, len) == 0)
696
break;
697
return len;
698
}
699
700
/*
701
* Return pointer to next command table entry.
702
* Also return the action and the extra string from the entry.
703
*/
704
static constant unsigned char * cmd_next_entry(constant unsigned char *entry, mutable int *action, mutable constant unsigned char **extra, mutable size_t *cmdlen)
705
{
706
int a;
707
constant unsigned char *oentry = entry;
708
while (*entry != '\0') /* skip cmd */
709
++entry;
710
if (cmdlen != NULL)
711
*cmdlen = ptr_diff(entry, oentry);
712
do
713
a = *++entry; /* get action */
714
while (a == A_SKIP);
715
++entry; /* skip action */
716
if (extra != NULL)
717
*extra = (a & A_EXTRA) ? entry : NULL;
718
if (a & A_EXTRA)
719
{
720
while (*entry++ != '\0') /* skip extra string */
721
continue;
722
a &= ~A_EXTRA;
723
}
724
if (action != NULL)
725
*action = a;
726
return entry;
727
}
728
729
/*
730
* Search a single command table for the command string in cmd.
731
*/
732
static int cmd_search(constant char *cmd, constant unsigned char *table, constant unsigned char *endtable, constant unsigned char **extra, size_t *mlen)
733
{
734
int action = A_INVALID;
735
size_t match_len = 0;
736
if (extra != NULL)
737
*extra = NULL;
738
while (table < endtable)
739
{
740
int taction;
741
const unsigned char *textra;
742
size_t cmdlen;
743
size_t match = cmd_match((constant char *) table, cmd);
744
table = cmd_next_entry(table, &taction, &textra, &cmdlen);
745
if (taction == A_END_LIST)
746
return (-action);
747
if (match >= match_len)
748
{
749
if (match == cmdlen) /* (last chars of) cmd matches this table entry */
750
{
751
action = taction;
752
*extra = textra;
753
} else if (match > 0 && action == A_INVALID) /* cmd is a prefix of this table entry */
754
{
755
action = A_PREFIX;
756
}
757
match_len = match;
758
}
759
}
760
if (mlen != NULL)
761
*mlen = match_len;
762
return (action);
763
}
764
765
/*
766
* Decode a command character and return the associated action.
767
* The "extra" string, if any, is returned in sp.
768
*/
769
static int cmd_decode(struct tablelist *tlist, constant char *cmd, constant char **sp)
770
{
771
struct tablelist *t;
772
int action = A_INVALID;
773
size_t match_len = 0;
774
775
/*
776
* Search for the cmd thru all the command tables.
777
* If we find it more than once, take the last one.
778
*/
779
*sp = NULL;
780
for (t = tlist; t != NULL; t = t->t_next)
781
{
782
constant unsigned char *tsp;
783
size_t mlen;
784
int taction = cmd_search(cmd, t->t_start, t->t_end, &tsp, &mlen);
785
if (mlen >= match_len)
786
{
787
match_len = mlen;
788
if (taction == A_UINVALID)
789
taction = A_INVALID;
790
if (taction != A_INVALID)
791
{
792
*sp = (constant char *) tsp;
793
if (taction < 0)
794
{
795
action = -taction;
796
break;
797
}
798
action = taction;
799
}
800
}
801
}
802
if (action == A_X11MOUSE_IN)
803
action = x11mouse_action(FALSE);
804
else if (action == A_X116MOUSE_IN)
805
action = x116mouse_action(FALSE);
806
return (action);
807
}
808
809
/*
810
* Decode a command from the cmdtables list.
811
*/
812
public int fcmd_decode(constant char *cmd, constant char **sp)
813
{
814
return (cmd_decode(list_fcmd_tables, cmd, sp));
815
}
816
817
/*
818
* Decode a command from the edittables list.
819
*/
820
public int ecmd_decode(constant char *cmd, constant char **sp)
821
{
822
return (cmd_decode(list_ecmd_tables, cmd, sp));
823
}
824
825
826
/*
827
* Get the value of an environment variable.
828
* Looks first in the lesskey file, then in the real environment.
829
*/
830
public constant char * lgetenv(constant char *var)
831
{
832
int a;
833
constant char *s;
834
835
a = cmd_decode(list_var_tables, var, &s);
836
if (a == EV_OK)
837
return (s);
838
s = getenv(var);
839
if (s != NULL && *s != '\0')
840
return (s);
841
a = cmd_decode(list_sysvar_tables, var, &s);
842
if (a == EV_OK)
843
return (s);
844
return (NULL);
845
}
846
847
/*
848
* Like lgetenv, but also uses a buffer partially filled with an env table.
849
*/
850
public constant char * lgetenv_ext(constant char *var, unsigned char *env_buf, size_t env_buf_len)
851
{
852
constant char *r;
853
size_t e;
854
size_t env_end = 0;
855
856
for (e = 0;;)
857
{
858
for (; e < env_buf_len; e++)
859
if (env_buf[e] == '\0')
860
break;
861
if (e >= env_buf_len) break;
862
if (env_buf[++e] & A_EXTRA)
863
{
864
for (e = e+1; e < env_buf_len; e++)
865
if (env_buf[e] == '\0')
866
break;
867
}
868
e++;
869
if (e >= env_buf_len) break;
870
env_end = e;
871
}
872
/* Temporarily add env_buf to var_tables, do the lookup, then remove it. */
873
add_uvar_table(env_buf, env_end);
874
r = lgetenv(var);
875
pop_cmd_table(&list_var_tables);
876
return r;
877
}
878
879
/*
880
* Is a string null or empty?
881
*/
882
public lbool isnullenv(constant char *s)
883
{
884
return (s == NULL || *s == '\0');
885
}
886
887
#if USERFILE
888
/*
889
* Get an "integer" from a lesskey file.
890
* Integers are stored in a funny format:
891
* two bytes, low order first, in radix KRADIX.
892
*/
893
static size_t gint(unsigned char **sp)
894
{
895
size_t n;
896
897
n = *(*sp)++;
898
n += *(*sp)++ * KRADIX;
899
return (n);
900
}
901
902
/*
903
* Process an old (pre-v241) lesskey file.
904
*/
905
static int old_lesskey(unsigned char *buf, size_t len)
906
{
907
/*
908
* Old-style lesskey file.
909
* The file must end with either
910
* ...,cmd,0,action
911
* or ...,cmd,0,action|A_EXTRA,string,0
912
* So the last byte or the second to last byte must be zero.
913
*/
914
if (buf[len-1] != '\0' && buf[len-2] != '\0')
915
return (-1);
916
add_fcmd_table(buf, len);
917
return (0);
918
}
919
920
/*
921
* Process a new (post-v241) lesskey file.
922
*/
923
static int new_lesskey(unsigned char *buf, size_t len, lbool sysvar)
924
{
925
unsigned char *p;
926
unsigned char *end;
927
int c;
928
size_t n;
929
930
/*
931
* New-style lesskey file.
932
* Extract the pieces.
933
*/
934
if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
935
buf[len-2] != C1_END_LESSKEY_MAGIC ||
936
buf[len-1] != C2_END_LESSKEY_MAGIC)
937
return (-1);
938
p = buf + 4;
939
end = buf + len;
940
for (;;)
941
{
942
c = *p++;
943
switch (c)
944
{
945
case CMD_SECTION:
946
n = gint(&p);
947
if (p+n >= end)
948
return (-1);
949
add_fcmd_table(p, n);
950
p += n;
951
break;
952
case EDIT_SECTION:
953
n = gint(&p);
954
if (p+n >= end)
955
return (-1);
956
add_ecmd_table(p, n);
957
p += n;
958
break;
959
case VAR_SECTION:
960
n = gint(&p);
961
if (p+n >= end)
962
return (-1);
963
if (sysvar)
964
add_sysvar_table(p, n);
965
else
966
add_uvar_table(p, n);
967
p += n;
968
break;
969
case END_SECTION:
970
return (0);
971
default:
972
/*
973
* Unrecognized section type.
974
*/
975
return (-1);
976
}
977
}
978
}
979
980
/*
981
* Set up a user command table, based on a "lesskey" file.
982
*/
983
public int lesskey(constant char *filename, lbool sysvar)
984
{
985
unsigned char *buf;
986
POSITION len;
987
ssize_t n;
988
int f;
989
990
if (!secure_allow(SF_LESSKEY))
991
return (1);
992
/*
993
* Try to open the lesskey file.
994
*/
995
f = open(filename, OPEN_READ);
996
if (f < 0)
997
return (1);
998
999
/*
1000
* Read the file into a buffer.
1001
* We first figure out the size of the file and allocate space for it.
1002
* {{ Minimal error checking is done here.
1003
* A garbage .less file will produce strange results.
1004
* To avoid a large amount of error checking code here, we
1005
* rely on the lesskey program to generate a good .less file. }}
1006
*/
1007
len = filesize(f);
1008
if (len == NULL_POSITION || len < 3)
1009
{
1010
/*
1011
* Bad file (valid file must have at least 3 chars).
1012
*/
1013
close(f);
1014
return (-1);
1015
}
1016
if ((buf = (unsigned char *) calloc((size_t)len, sizeof(char))) == NULL)
1017
{
1018
close(f);
1019
return (-1);
1020
}
1021
if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
1022
{
1023
free(buf);
1024
close(f);
1025
return (-1);
1026
}
1027
n = read(f, buf, (size_t) len);
1028
close(f);
1029
if (n != len)
1030
{
1031
free(buf);
1032
return (-1);
1033
}
1034
1035
/*
1036
* Figure out if this is an old-style (before version 241)
1037
* or new-style lesskey file format.
1038
*/
1039
if (len < 4 ||
1040
buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
1041
buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
1042
return (old_lesskey(buf, (size_t) len));
1043
return (new_lesskey(buf, (size_t) len, sysvar));
1044
}
1045
1046
#if HAVE_LESSKEYSRC
1047
static int lesskey_text(constant char *filename, lbool sysvar, lbool content)
1048
{
1049
int r;
1050
static struct lesskey_tables tables;
1051
1052
if (!secure_allow(SF_LESSKEY))
1053
return (1);
1054
r = content ? parse_lesskey_content(filename, &tables) : parse_lesskey(filename, &tables);
1055
if (r != 0)
1056
return (r);
1057
add_fcmd_table(tables.cmdtable.buf.data, tables.cmdtable.buf.end);
1058
add_ecmd_table(tables.edittable.buf.data, tables.edittable.buf.end);
1059
if (sysvar)
1060
add_sysvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1061
else
1062
add_uvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1063
return (0);
1064
}
1065
1066
public int lesskey_src(constant char *filename, lbool sysvar)
1067
{
1068
return lesskey_text(filename, sysvar, FALSE);
1069
}
1070
1071
public int lesskey_content(constant char *content, lbool sysvar)
1072
{
1073
return lesskey_text(content, sysvar, TRUE);
1074
}
1075
1076
void lesskey_parse_error(char *s)
1077
{
1078
PARG parg;
1079
parg.p_string = s;
1080
error("%s", &parg);
1081
}
1082
#endif /* HAVE_LESSKEYSRC */
1083
1084
/*
1085
* Add a lesskey file.
1086
*/
1087
static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar)
1088
{
1089
char *filename = NULL;
1090
constant char *efilename;
1091
int r;
1092
1093
if (envname != NULL && (efilename = lgetenv(envname)) != NULL)
1094
filename = save(efilename);
1095
else if (sysvar) /* def_filename is full path */
1096
filename = save(def_filename);
1097
else /* def_filename is just basename */
1098
{
1099
/* Remove first char (normally a dot) unless stored in $HOME. */
1100
constant char *xdg = lgetenv("XDG_CONFIG_HOME");
1101
if (!isnullenv(xdg))
1102
filename = dirfile(xdg, &def_filename[1], 1);
1103
if (filename == NULL)
1104
{
1105
constant char *home = lgetenv("HOME");
1106
if (!isnullenv(home))
1107
{
1108
char *cfg_dir = dirfile(home, ".config", 0);
1109
filename = dirfile(cfg_dir, &def_filename[1], 1);
1110
free(cfg_dir);
1111
}
1112
}
1113
if (filename == NULL)
1114
filename = homefile(def_filename);
1115
}
1116
if (filename == NULL)
1117
return -1;
1118
r = (*call_lesskey)(filename, sysvar);
1119
free(filename);
1120
return (r);
1121
}
1122
1123
/*
1124
* Add the content of a lesskey source file.
1125
*/
1126
static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar)
1127
{
1128
constant char *content;
1129
1130
(void) call_lesskey; /* not used */
1131
content = lgetenv(envname);
1132
if (isnullenv(content))
1133
return;
1134
lesskey_content(content, sysvar);
1135
}
1136
#endif /* USERFILE */
1137
1138
/*
1139
* See if a char is a special line-editing command.
1140
*/
1141
public int editchar(char c, int flags)
1142
{
1143
int action;
1144
int nch;
1145
constant char *s;
1146
char usercmd[MAX_CMDLEN+1];
1147
1148
/*
1149
* An editing character could actually be a sequence of characters;
1150
* for example, an escape sequence sent by pressing the uparrow key.
1151
* To match the editing string, we use the command decoder
1152
* but give it the edit-commands command table
1153
* This table is constructed to match the user's keyboard.
1154
*/
1155
if (c == erase_char || c == erase2_char)
1156
return (EC_BACKSPACE);
1157
if (c == kill_char)
1158
{
1159
#if MSDOS_COMPILER==WIN32C
1160
if (!win32_kbhit())
1161
#endif
1162
return (EC_LINEKILL);
1163
}
1164
1165
/*
1166
* Collect characters in a buffer.
1167
* Start with the one we have, and get more if we need them.
1168
*/
1169
nch = 0;
1170
do {
1171
if (nch > 0)
1172
c = getcc();
1173
usercmd[nch] = c;
1174
usercmd[nch+1] = '\0';
1175
nch++;
1176
action = ecmd_decode(usercmd, &s);
1177
} while (action == A_PREFIX && nch < MAX_CMDLEN);
1178
1179
if (action == EC_X11MOUSE)
1180
return (x11mouse_action(TRUE));
1181
if (action == EC_X116MOUSE)
1182
return (x116mouse_action(TRUE));
1183
1184
if (flags & ECF_NORIGHTLEFT)
1185
{
1186
switch (action)
1187
{
1188
case EC_RIGHT:
1189
case EC_LEFT:
1190
action = A_INVALID;
1191
break;
1192
}
1193
}
1194
#if CMD_HISTORY
1195
if (flags & ECF_NOHISTORY)
1196
{
1197
/*
1198
* The caller says there is no history list.
1199
* Reject any history-manipulation action.
1200
*/
1201
switch (action)
1202
{
1203
case EC_UP:
1204
case EC_DOWN:
1205
action = A_INVALID;
1206
break;
1207
}
1208
}
1209
#endif
1210
if (flags & ECF_NOCOMPLETE)
1211
{
1212
/*
1213
* The caller says we don't want any filename completion cmds.
1214
* Reject them.
1215
*/
1216
switch (action)
1217
{
1218
case EC_F_COMPLETE:
1219
case EC_B_COMPLETE:
1220
case EC_EXPAND:
1221
action = A_INVALID;
1222
break;
1223
}
1224
}
1225
if ((flags & ECF_PEEK) || action == A_INVALID)
1226
{
1227
/*
1228
* We're just peeking, or we didn't understand the command.
1229
* Unget all the characters we read in the loop above.
1230
* This does NOT include the original character that was
1231
* passed in as a parameter.
1232
*/
1233
while (nch > 1)
1234
{
1235
ungetcc(usercmd[--nch]);
1236
}
1237
} else
1238
{
1239
if (s != NULL)
1240
ungetsc(s);
1241
}
1242
return action;
1243
}
1244
1245
1246