Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/cmdbuf.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
* Functions which manipulate the command buffer.
13
* Used only by command() and related functions.
14
*/
15
16
#include "less.h"
17
#include "cmd.h"
18
#include "charset.h"
19
#if HAVE_STAT
20
#include <sys/stat.h>
21
#endif
22
23
extern int sc_width;
24
extern int utf_mode;
25
extern int no_hist_dups;
26
extern int marks_modified;
27
extern int no_paste;
28
public lbool pasting = FALSE;
29
30
static char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */
31
static int cmd_col; /* Current column of the cursor */
32
static int prompt_col; /* Column of cursor just after prompt */
33
static char *cp; /* Pointer into cmdbuf */
34
static int cmd_offset; /* Index into cmdbuf of first displayed char */
35
static lbool literal; /* Next input char should not be interpreted */
36
static size_t updown_match; /* Prefix length in up/down movement */
37
static lbool have_updown_match = FALSE;
38
39
static int cmd_complete(int action);
40
/*
41
* These variables are statics used by cmd_complete.
42
*/
43
static lbool in_completion = FALSE;
44
static char *tk_text;
45
static char *tk_original;
46
static constant char *tk_ipoint;
47
static constant char *tk_trial = NULL;
48
static struct textlist tk_tlist;
49
50
static int cmd_left();
51
static int cmd_right();
52
53
#if SPACES_IN_FILENAMES
54
public char openquote = '"';
55
public char closequote = '"';
56
#endif
57
58
#if CMD_HISTORY
59
60
/* History file */
61
#define HISTFILE_FIRST_LINE ".less-history-file:"
62
#define HISTFILE_SEARCH_SECTION ".search"
63
#define HISTFILE_SHELL_SECTION ".shell"
64
#define HISTFILE_MARK_SECTION ".mark"
65
66
/*
67
* A mlist structure represents a command history.
68
*/
69
struct mlist
70
{
71
struct mlist *next;
72
struct mlist *prev;
73
struct mlist *curr_mp;
74
char *string;
75
lbool modified;
76
};
77
78
/*
79
* These are the various command histories that exist.
80
*/
81
struct mlist mlist_search =
82
{ &mlist_search, &mlist_search, &mlist_search, NULL, 0 };
83
public void *ml_search = (void *) &mlist_search;
84
85
struct mlist mlist_examine =
86
{ &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
87
public void *ml_examine = (void *) &mlist_examine;
88
89
#if SHELL_ESCAPE || PIPEC
90
struct mlist mlist_shell =
91
{ &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 };
92
public void *ml_shell = (void *) &mlist_shell;
93
#endif
94
95
#else /* CMD_HISTORY */
96
97
/* If CMD_HISTORY is off, these are just flags. */
98
public void *ml_search = (void *)1;
99
public void *ml_examine = (void *)2;
100
#if SHELL_ESCAPE || PIPEC
101
public void *ml_shell = (void *)3;
102
#endif
103
104
#endif /* CMD_HISTORY */
105
106
/*
107
* History for the current command.
108
*/
109
static struct mlist *curr_mlist = NULL;
110
static int curr_cmdflags;
111
112
static char cmd_mbc_buf[MAX_UTF_CHAR_LEN];
113
static int cmd_mbc_buf_len;
114
static int cmd_mbc_buf_index;
115
116
117
/*
118
* Reset command buffer (to empty).
119
*/
120
public void cmd_reset(void)
121
{
122
cp = cmdbuf;
123
*cp = '\0';
124
cmd_col = 0;
125
cmd_offset = 0;
126
literal = FALSE;
127
cmd_mbc_buf_len = 0;
128
have_updown_match = FALSE;
129
}
130
131
/*
132
* Clear command line.
133
*/
134
public void clear_cmd(void)
135
{
136
cmd_col = prompt_col = 0;
137
cmd_mbc_buf_len = 0;
138
have_updown_match = FALSE;
139
}
140
141
/*
142
* Display a string, usually as a prompt for input into the command buffer.
143
*/
144
public void cmd_putstr(constant char *s)
145
{
146
LWCHAR prev_ch = 0;
147
LWCHAR ch;
148
constant char *endline = s + strlen(s);
149
while (*s != '\0')
150
{
151
constant char *os = s;
152
int width;
153
ch = step_charc(&s, +1, endline);
154
while (os < s)
155
putchr(*os++);
156
if (!utf_mode)
157
width = 1;
158
else if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
159
width = 0;
160
else
161
width = is_wide_char(ch) ? 2 : 1;
162
cmd_col += width;
163
prompt_col += width;
164
prev_ch = ch;
165
}
166
}
167
168
/*
169
* How many characters are in the command buffer?
170
*/
171
public int len_cmdbuf(void)
172
{
173
constant char *s = cmdbuf;
174
constant char *endline = s + strlen(s);
175
int len = 0;
176
177
while (*s != '\0')
178
{
179
step_charc(&s, +1, endline);
180
len++;
181
}
182
return (len);
183
}
184
185
/*
186
* Is the command buffer empty?
187
* It is considered nonempty if there is any text in it,
188
* or if a multibyte command is being entered but not yet complete.
189
*/
190
public lbool cmdbuf_empty(void)
191
{
192
return cp == cmdbuf && cmd_mbc_buf_len == 0;
193
}
194
195
/*
196
* Common part of cmd_step_right() and cmd_step_left().
197
* {{ Returning pwidth and bswidth separately is a historical artifact
198
* since they're always the same. Maybe clean this up someday. }}
199
*/
200
static constant char * cmd_step_common(char *p, LWCHAR ch, size_t len, int *pwidth, int *bswidth)
201
{
202
constant char *pr;
203
int width;
204
205
if (len == 1)
206
{
207
pr = prchar(ch);
208
width = (int) strlen(pr);
209
} else
210
{
211
pr = prutfchar(ch);
212
if (is_composing_char(ch))
213
width = 0;
214
else if (is_ubin_char(ch))
215
width = (int) strlen(pr);
216
else
217
{
218
LWCHAR prev_ch = step_char(&p, -1, cmdbuf);
219
if (is_combining_char(prev_ch, ch))
220
width = 0;
221
else
222
width = is_wide_char(ch) ? 2 : 1;
223
}
224
}
225
if (pwidth != NULL)
226
*pwidth = width;
227
if (bswidth != NULL)
228
*bswidth = width;
229
return (pr);
230
}
231
232
/*
233
* Step a pointer one character right in the command buffer.
234
*/
235
static constant char * cmd_step_right(char **pp, int *pwidth, int *bswidth)
236
{
237
char *p = *pp;
238
LWCHAR ch = step_char(pp, +1, p + strlen(p));
239
240
return cmd_step_common(p, ch, ptr_diff(*pp, p), pwidth, bswidth);
241
}
242
243
/*
244
* Step a pointer one character left in the command buffer.
245
*/
246
static constant char * cmd_step_left(char **pp, int *pwidth, int *bswidth)
247
{
248
char *p = *pp;
249
LWCHAR ch = step_char(pp, -1, cmdbuf);
250
251
return cmd_step_common(*pp, ch, ptr_diff(p, *pp), pwidth, bswidth);
252
}
253
254
/*
255
* Put the cursor at "home" (just after the prompt),
256
* and set cp to the corresponding char in cmdbuf.
257
*/
258
static void cmd_home(void)
259
{
260
while (cmd_col > prompt_col)
261
{
262
int width, bswidth;
263
264
cmd_step_left(&cp, &width, &bswidth);
265
while (bswidth-- > 0)
266
putbs();
267
cmd_col -= width;
268
}
269
270
cp = &cmdbuf[cmd_offset];
271
}
272
273
/*
274
* Repaint the line from cp onwards.
275
* Then position the cursor just after the char old_cp (a pointer into cmdbuf).
276
*/
277
public void cmd_repaint(constant char *old_cp)
278
{
279
/*
280
* Repaint the line from the current position.
281
*/
282
if (old_cp == NULL)
283
{
284
old_cp = cp;
285
cmd_home();
286
}
287
clear_eol();
288
while (*cp != '\0')
289
{
290
char *np = cp;
291
int width;
292
constant char *pr = cmd_step_right(&np, &width, NULL);
293
if (cmd_col + width >= sc_width)
294
break;
295
cp = np;
296
putstr(pr);
297
cmd_col += width;
298
}
299
while (*cp != '\0')
300
{
301
char *np = cp;
302
int width;
303
constant char *pr = cmd_step_right(&np, &width, NULL);
304
if (width > 0)
305
break;
306
cp = np;
307
putstr(pr);
308
}
309
310
/*
311
* Back up the cursor to the correct position.
312
*/
313
while (cp > old_cp)
314
cmd_left();
315
}
316
317
/*
318
* Repaint the entire line, without moving the cursor.
319
*/
320
static void cmd_repaint_curr(void)
321
{
322
char *save_cp = cp;
323
cmd_home();
324
cmd_repaint(save_cp);
325
}
326
327
/*
328
* Shift the cmdbuf display left a half-screen.
329
*/
330
static void cmd_lshift(void)
331
{
332
char *s;
333
char *save_cp;
334
int cols;
335
336
/*
337
* Start at the first displayed char, count how far to the
338
* right we'd have to move to reach the center of the screen.
339
*/
340
s = cmdbuf + cmd_offset;
341
cols = 0;
342
while (cols < (sc_width - prompt_col) / 2 && *s != '\0')
343
{
344
int width;
345
cmd_step_right(&s, &width, NULL);
346
cols += width;
347
}
348
while (*s != '\0')
349
{
350
int width;
351
char *ns = s;
352
cmd_step_right(&ns, &width, NULL);
353
if (width > 0)
354
break;
355
s = ns;
356
}
357
358
cmd_offset = (int) (s - cmdbuf);
359
save_cp = cp;
360
cmd_home();
361
cmd_repaint(save_cp);
362
}
363
364
/*
365
* Shift the cmdbuf display right a half-screen.
366
*/
367
static void cmd_rshift(void)
368
{
369
char *s;
370
char *save_cp;
371
int cols;
372
373
/*
374
* Start at the first displayed char, count how far to the
375
* left we'd have to move to traverse a half-screen width
376
* of displayed characters.
377
*/
378
s = cmdbuf + cmd_offset;
379
cols = 0;
380
while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf)
381
{
382
int width;
383
cmd_step_left(&s, &width, NULL);
384
cols += width;
385
}
386
387
cmd_offset = (int) (s - cmdbuf);
388
save_cp = cp;
389
cmd_home();
390
cmd_repaint(save_cp);
391
}
392
393
/*
394
* Move cursor right one character.
395
*/
396
static int cmd_right(void)
397
{
398
constant char *pr;
399
char *ncp;
400
int width;
401
402
if (*cp == '\0')
403
{
404
/* Already at the end of the line. */
405
return (CC_OK);
406
}
407
ncp = cp;
408
pr = cmd_step_right(&ncp, &width, NULL);
409
if (cmd_col + width >= sc_width)
410
cmd_lshift();
411
else if (cmd_col + width == sc_width - 1 && cp[1] != '\0')
412
cmd_lshift();
413
cp = ncp;
414
cmd_col += width;
415
putstr(pr);
416
while (*cp != '\0')
417
{
418
pr = cmd_step_right(&ncp, &width, NULL);
419
if (width > 0)
420
break;
421
putstr(pr);
422
cp = ncp;
423
}
424
return (CC_OK);
425
}
426
427
/*
428
* Move cursor left one character.
429
*/
430
static int cmd_left(void)
431
{
432
char *ncp;
433
int width = 0;
434
int bswidth = 0;
435
436
if (cp <= cmdbuf)
437
{
438
/* Already at the beginning of the line */
439
return (CC_OK);
440
}
441
ncp = cp;
442
while (ncp > cmdbuf)
443
{
444
cmd_step_left(&ncp, &width, &bswidth);
445
if (width > 0)
446
break;
447
}
448
if (cmd_col < prompt_col + width)
449
cmd_rshift();
450
cp = ncp;
451
cmd_col -= width;
452
while (bswidth-- > 0)
453
putbs();
454
return (CC_OK);
455
}
456
457
/*
458
* Insert a char into the command buffer, at the current position.
459
*/
460
static int cmd_ichar(constant char *cs, size_t clen)
461
{
462
char *s;
463
464
if (strlen(cmdbuf) + clen >= sizeof(cmdbuf)-1)
465
{
466
/* No room in the command buffer for another char. */
467
bell();
468
return (CC_ERROR);
469
}
470
471
/*
472
* Make room for the new character (shift the tail of the buffer right).
473
*/
474
for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--)
475
s[clen] = s[0];
476
/*
477
* Insert the character into the buffer.
478
*/
479
for (s = cp; s < cp + clen; s++)
480
*s = *cs++;
481
/*
482
* Reprint the tail of the line from the inserted char.
483
*/
484
have_updown_match = FALSE;
485
cmd_repaint(cp);
486
cmd_right();
487
return (CC_OK);
488
}
489
490
/*
491
* Backspace in the command buffer.
492
* Delete the char to the left of the cursor.
493
*/
494
static int cmd_erase(void)
495
{
496
char *s;
497
int clen;
498
499
if (cp == cmdbuf)
500
{
501
/*
502
* Backspace past beginning of the buffer:
503
* this usually means abort the command.
504
*/
505
return (CC_QUIT);
506
}
507
/*
508
* Move cursor left (to the char being erased).
509
*/
510
s = cp;
511
cmd_left();
512
clen = (int) (s - cp);
513
514
/*
515
* Remove the char from the buffer (shift the buffer left).
516
*/
517
for (s = cp; ; s++)
518
{
519
s[0] = s[clen];
520
if (s[0] == '\0')
521
break;
522
}
523
524
/*
525
* Repaint the buffer after the erased char.
526
*/
527
have_updown_match = FALSE;
528
cmd_repaint(cp);
529
530
/*
531
* We say that erasing the entire command string causes us
532
* to abort the current command, if CF_QUIT_ON_ERASE is set.
533
*/
534
if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0')
535
return (CC_QUIT);
536
return (CC_OK);
537
}
538
539
/*
540
* Delete the char under the cursor.
541
*/
542
static int cmd_delete(void)
543
{
544
if (*cp == '\0')
545
{
546
/* At end of string; there is no char under the cursor. */
547
return (CC_OK);
548
}
549
/*
550
* Move right, then use cmd_erase.
551
*/
552
cmd_right();
553
cmd_erase();
554
return (CC_OK);
555
}
556
557
/*
558
* Delete the "word" to the left of the cursor.
559
*/
560
static int cmd_werase(void)
561
{
562
if (cp > cmdbuf && cp[-1] == ' ')
563
{
564
/*
565
* If the char left of cursor is a space,
566
* erase all the spaces left of cursor (to the first non-space).
567
*/
568
while (cp > cmdbuf && cp[-1] == ' ')
569
(void) cmd_erase();
570
} else
571
{
572
/*
573
* If the char left of cursor is not a space,
574
* erase all the nonspaces left of cursor (the whole "word").
575
*/
576
while (cp > cmdbuf && cp[-1] != ' ')
577
(void) cmd_erase();
578
}
579
return (CC_OK);
580
}
581
582
/*
583
* Delete the "word" under the cursor.
584
*/
585
static int cmd_wdelete(void)
586
{
587
if (*cp == ' ')
588
{
589
/*
590
* If the char under the cursor is a space,
591
* delete it and all the spaces right of cursor.
592
*/
593
while (*cp == ' ')
594
(void) cmd_delete();
595
} else
596
{
597
/*
598
* If the char under the cursor is not a space,
599
* delete it and all nonspaces right of cursor (the whole word).
600
*/
601
while (*cp != ' ' && *cp != '\0')
602
(void) cmd_delete();
603
}
604
return (CC_OK);
605
}
606
607
/*
608
* Delete all chars in the command buffer.
609
*/
610
static int cmd_kill(void)
611
{
612
if (cmdbuf[0] == '\0')
613
{
614
/* Buffer is already empty; abort the current command. */
615
return (CC_QUIT);
616
}
617
cmd_offset = 0;
618
cmd_home();
619
*cp = '\0';
620
have_updown_match = FALSE;
621
cmd_repaint(cp);
622
623
/*
624
* We say that erasing the entire command string causes us
625
* to abort the current command, if CF_QUIT_ON_ERASE is set.
626
*/
627
if (curr_cmdflags & CF_QUIT_ON_ERASE)
628
return (CC_QUIT);
629
return (CC_OK);
630
}
631
632
/*
633
* Select an mlist structure to be the current command history.
634
*/
635
public void set_mlist(void *mlist, int cmdflags)
636
{
637
#if CMD_HISTORY
638
curr_mlist = (struct mlist *) mlist;
639
curr_cmdflags = cmdflags;
640
641
/* Make sure the next up-arrow moves to the last string in the mlist. */
642
if (curr_mlist != NULL)
643
curr_mlist->curr_mp = curr_mlist;
644
#endif
645
}
646
647
#if CMD_HISTORY
648
/*
649
* Move up or down in the currently selected command history list.
650
* Only consider entries whose first updown_match chars are equal to
651
* cmdbuf's corresponding chars.
652
*/
653
static int cmd_updown(int action)
654
{
655
constant char *s;
656
struct mlist *ml;
657
658
if (curr_mlist == NULL)
659
{
660
/*
661
* The current command has no history list.
662
*/
663
bell();
664
return (CC_OK);
665
}
666
667
if (!have_updown_match)
668
{
669
updown_match = ptr_diff(cp, cmdbuf);
670
have_updown_match = TRUE;
671
}
672
673
/*
674
* Find the next history entry which matches.
675
*/
676
for (ml = curr_mlist->curr_mp;;)
677
{
678
ml = (action == EC_UP) ? ml->prev : ml->next;
679
if (ml == curr_mlist)
680
{
681
/*
682
* We reached the end (or beginning) of the list.
683
*/
684
break;
685
}
686
if (strncmp(cmdbuf, ml->string, updown_match) == 0)
687
{
688
/*
689
* This entry matches; stop here.
690
* Copy the entry into cmdbuf and echo it on the screen.
691
*/
692
curr_mlist->curr_mp = ml;
693
s = ml->string;
694
if (s == NULL)
695
s = "";
696
cmd_offset = 0;
697
cmd_home();
698
clear_eol();
699
strcpy(cmdbuf, s);
700
for (cp = cmdbuf; *cp != '\0'; )
701
cmd_right();
702
return (CC_OK);
703
}
704
}
705
/*
706
* We didn't find a history entry that matches.
707
*/
708
bell();
709
return (CC_OK);
710
}
711
712
/*
713
* Yet another lesson in the evils of global variables.
714
*/
715
public ssize_t save_updown_match(void)
716
{
717
if (!have_updown_match)
718
return (ssize_t)(-1);
719
return (ssize_t) updown_match;
720
}
721
722
public void restore_updown_match(ssize_t udm)
723
{
724
updown_match = udm;
725
have_updown_match = (udm != (ssize_t)(-1));
726
}
727
#endif /* CMD_HISTORY */
728
729
/*
730
*
731
*/
732
static void ml_link(struct mlist *mlist, struct mlist *ml)
733
{
734
ml->next = mlist;
735
ml->prev = mlist->prev;
736
mlist->prev->next = ml;
737
mlist->prev = ml;
738
}
739
740
/*
741
*
742
*/
743
static void ml_unlink(struct mlist *ml)
744
{
745
ml->prev->next = ml->next;
746
ml->next->prev = ml->prev;
747
}
748
749
/*
750
* Add a string to an mlist.
751
*/
752
public void cmd_addhist(struct mlist *mlist, constant char *cmd, lbool modified)
753
{
754
#if CMD_HISTORY
755
struct mlist *ml;
756
757
/*
758
* Don't save a trivial command.
759
*/
760
if (strlen(cmd) == 0)
761
return;
762
763
if (no_hist_dups)
764
{
765
struct mlist *next = NULL;
766
for (ml = mlist->next; ml->string != NULL; ml = next)
767
{
768
next = ml->next;
769
if (strcmp(ml->string, cmd) == 0)
770
{
771
ml_unlink(ml);
772
free(ml->string);
773
free(ml);
774
}
775
}
776
}
777
778
/*
779
* Save the command unless it's a duplicate of the
780
* last command in the history.
781
*/
782
ml = mlist->prev;
783
if (ml == mlist || strcmp(ml->string, cmd) != 0)
784
{
785
/*
786
* Did not find command in history.
787
* Save the command and put it at the end of the history list.
788
*/
789
ml = (struct mlist *) ecalloc(1, sizeof(struct mlist));
790
ml->string = save(cmd);
791
ml->modified = modified;
792
ml_link(mlist, ml);
793
}
794
/*
795
* Point to the cmd just after the just-accepted command.
796
* Thus, an UPARROW will always retrieve the previous command.
797
*/
798
mlist->curr_mp = ml->next;
799
#endif
800
}
801
802
/*
803
* Accept the command in the command buffer.
804
* Add it to the currently selected history list.
805
*/
806
public void cmd_accept(void)
807
{
808
#if CMD_HISTORY
809
/*
810
* Nothing to do if there is no currently selected history list.
811
*/
812
if (curr_mlist == NULL || curr_mlist == ml_examine)
813
return;
814
cmd_addhist(curr_mlist, cmdbuf, TRUE);
815
curr_mlist->modified = TRUE;
816
#endif
817
}
818
819
/*
820
* Try to perform a line-edit function on the command buffer,
821
* using a specified char as a line-editing command.
822
* Returns:
823
* CC_PASS The char does not invoke a line edit function.
824
* CC_OK Line edit function done.
825
* CC_QUIT The char requests the current command to be aborted.
826
*/
827
static int cmd_edit(char c, lbool stay_in_completion)
828
{
829
int action;
830
int flags;
831
832
#define not_in_completion() do { if (!stay_in_completion) in_completion = FALSE; } while(0)
833
834
/*
835
* See if the char is indeed a line-editing command.
836
*/
837
flags = 0;
838
#if CMD_HISTORY
839
if (curr_mlist == NULL)
840
/*
841
* No current history; don't accept history manipulation cmds.
842
*/
843
flags |= ECF_NOHISTORY;
844
#endif
845
846
/*
847
* Don't accept completion cmds in contexts
848
* such as search pattern, digits, etc.
849
*/
850
if ((curr_mlist == NULL && (curr_cmdflags & CF_OPTION))
851
#if TAB_COMPLETE_FILENAME
852
|| curr_mlist == ml_examine || curr_mlist == ml_shell
853
#endif
854
)
855
; /* allow completion */
856
else
857
flags |= ECF_NOCOMPLETE;
858
859
action = editchar(c, flags);
860
if (is_ignoring_input(action))
861
return (CC_OK);
862
863
switch (action)
864
{
865
case A_NOACTION:
866
return (CC_OK);
867
case EC_START_PASTE:
868
if (no_paste)
869
pasting = TRUE;
870
return (CC_OK);
871
case EC_END_PASTE:
872
stop_ignoring_input();
873
return (CC_OK);
874
case EC_RIGHT:
875
not_in_completion();
876
return (cmd_right());
877
case EC_LEFT:
878
not_in_completion();
879
return (cmd_left());
880
case EC_W_RIGHT:
881
not_in_completion();
882
while (*cp != '\0' && *cp != ' ')
883
cmd_right();
884
while (*cp == ' ')
885
cmd_right();
886
return (CC_OK);
887
case EC_W_LEFT:
888
not_in_completion();
889
while (cp > cmdbuf && cp[-1] == ' ')
890
cmd_left();
891
while (cp > cmdbuf && cp[-1] != ' ')
892
cmd_left();
893
return (CC_OK);
894
case EC_HOME:
895
not_in_completion();
896
cmd_offset = 0;
897
cmd_home();
898
cmd_repaint(cp);
899
return (CC_OK);
900
case EC_END:
901
not_in_completion();
902
while (*cp != '\0')
903
cmd_right();
904
return (CC_OK);
905
case EC_INSERT:
906
not_in_completion();
907
return (CC_OK);
908
case EC_BACKSPACE:
909
not_in_completion();
910
return (cmd_erase());
911
case EC_LINEKILL:
912
not_in_completion();
913
return (cmd_kill());
914
case EC_ABORT:
915
not_in_completion();
916
(void) cmd_kill();
917
return (CC_QUIT);
918
case EC_W_BACKSPACE:
919
not_in_completion();
920
return (cmd_werase());
921
case EC_DELETE:
922
not_in_completion();
923
return (cmd_delete());
924
case EC_W_DELETE:
925
not_in_completion();
926
return (cmd_wdelete());
927
case EC_LITERAL:
928
literal = TRUE;
929
return (CC_OK);
930
#if CMD_HISTORY
931
case EC_UP:
932
case EC_DOWN:
933
not_in_completion();
934
return (cmd_updown(action));
935
#endif
936
case EC_F_COMPLETE:
937
case EC_B_COMPLETE:
938
case EC_EXPAND:
939
return (cmd_complete(action));
940
default:
941
not_in_completion();
942
return (CC_PASS);
943
}
944
}
945
946
/*
947
* Insert a string into the command buffer, at the current position.
948
*/
949
static int cmd_istr(constant char *str)
950
{
951
constant char *endline = str + strlen(str);
952
constant char *s;
953
int action;
954
955
for (s = str; *s != '\0'; )
956
{
957
constant char *os = s;
958
step_charc(&s, +1, endline);
959
action = cmd_ichar(os, ptr_diff(s, os));
960
if (action != CC_OK)
961
return (action);
962
}
963
return (CC_OK);
964
}
965
966
/*
967
* Set tk_original to word.
968
*/
969
static void set_tk_original(constant char *word)
970
{
971
if (tk_original != NULL)
972
free(tk_original);
973
tk_original = (char *) ecalloc(ptr_diff(cp,word)+1, sizeof(char));
974
strncpy(tk_original, word, ptr_diff(cp,word));
975
}
976
977
#if TAB_COMPLETE_FILENAME
978
/*
979
* Find the beginning and end of the "current" word.
980
* This is the word which the cursor (cp) is inside or at the end of.
981
* Return pointer to the beginning of the word and put the
982
* cursor at the end of the word.
983
*/
984
static char * delimit_word(void)
985
{
986
char *word;
987
#if SPACES_IN_FILENAMES
988
char *p;
989
int delim_quoted = FALSE;
990
int meta_quoted = FALSE;
991
constant char *esc = get_meta_escape();
992
size_t esclen = strlen(esc);
993
#endif
994
995
/*
996
* Move cursor to end of word.
997
*/
998
if (*cp != ' ' && *cp != '\0')
999
{
1000
/*
1001
* Cursor is on a nonspace.
1002
* Move cursor right to the next space.
1003
*/
1004
while (*cp != ' ' && *cp != '\0')
1005
cmd_right();
1006
} else if (cp > cmdbuf && cp[-1] != ' ')
1007
{
1008
/*
1009
* Cursor is on a space, and char to the left is a nonspace.
1010
* We're already at the end of the word.
1011
*/
1012
;
1013
#if 0
1014
} else
1015
{
1016
/*
1017
* Cursor is on a space and char to the left is a space.
1018
* Huh? There's no word here.
1019
*/
1020
return (NULL);
1021
#endif
1022
}
1023
/*
1024
* Find the beginning of the word which the cursor is in.
1025
*/
1026
if (cp == cmdbuf)
1027
return (NULL);
1028
#if SPACES_IN_FILENAMES
1029
/*
1030
* If we have an unbalanced quote (that is, an open quote
1031
* without a corresponding close quote), we return everything
1032
* from the open quote, including spaces.
1033
*/
1034
for (word = cmdbuf; word < cp; word++)
1035
if (*word != ' ')
1036
break;
1037
if (word >= cp)
1038
return (cp);
1039
for (p = cmdbuf; p < cp; p++)
1040
{
1041
if (meta_quoted)
1042
{
1043
meta_quoted = FALSE;
1044
} else if (esclen > 0 && p + esclen < cp &&
1045
strncmp(p, esc, esclen) == 0)
1046
{
1047
meta_quoted = TRUE;
1048
p += esclen - 1;
1049
} else if (delim_quoted)
1050
{
1051
if (*p == closequote)
1052
delim_quoted = FALSE;
1053
} else /* (!delim_quoted) */
1054
{
1055
if (*p == openquote)
1056
delim_quoted = TRUE;
1057
else if (*p == ' ')
1058
word = p+1;
1059
}
1060
}
1061
#endif
1062
return (word);
1063
}
1064
1065
/*
1066
* Set things up to enter file completion mode.
1067
* Expand the word under the cursor into a list of filenames
1068
* which start with that word, and set tk_text to that list.
1069
*/
1070
static void init_file_compl(void)
1071
{
1072
char *word;
1073
char c;
1074
1075
/*
1076
* Find the original (uncompleted) word in the command buffer.
1077
*/
1078
word = delimit_word();
1079
if (word == NULL)
1080
return;
1081
/*
1082
* Set the insertion point to the point in the command buffer
1083
* where the original (uncompleted) word now sits.
1084
*/
1085
tk_ipoint = word;
1086
set_tk_original(word);
1087
/*
1088
* Get the expanded filename.
1089
* This may result in a single filename, or
1090
* a blank-separated list of filenames.
1091
*/
1092
c = *cp;
1093
*cp = '\0';
1094
if (*word != openquote)
1095
{
1096
tk_text = fcomplete(word);
1097
} else
1098
{
1099
#if MSDOS_COMPILER
1100
char *qword = NULL;
1101
#else
1102
char *qword = shell_quote(word+1);
1103
#endif
1104
if (qword == NULL)
1105
tk_text = fcomplete(word+1);
1106
else
1107
{
1108
tk_text = fcomplete(qword);
1109
free(qword);
1110
}
1111
}
1112
*cp = c;
1113
}
1114
#endif /* TAB_COMPLETE_FILENAME */
1115
1116
/*
1117
* Set things up to enter option completion mode.
1118
*/
1119
static void init_opt_compl(void)
1120
{
1121
tk_ipoint = cmdbuf;
1122
set_tk_original(cmdbuf);
1123
tk_text = findopts_name(cmdbuf);
1124
}
1125
1126
/*
1127
* Return the next word in the current completion list.
1128
*/
1129
static constant char * next_compl(int action, constant char *prev)
1130
{
1131
switch (action)
1132
{
1133
case EC_F_COMPLETE:
1134
return (forw_textlist(&tk_tlist, prev));
1135
case EC_B_COMPLETE:
1136
return (back_textlist(&tk_tlist, prev));
1137
}
1138
/* Cannot happen */
1139
return ("?");
1140
}
1141
1142
/*
1143
* Complete the filename before (or under) the cursor.
1144
* cmd_complete may be called multiple times. The global in_completion
1145
* remembers whether this call is the first time (create the list),
1146
* or a subsequent time (step thru the list).
1147
*/
1148
static int cmd_complete(int action)
1149
{
1150
constant char *s;
1151
1152
if (!in_completion || action == EC_EXPAND)
1153
{
1154
/*
1155
* Expand the word under the cursor and
1156
* use the first word in the expansion
1157
* (or the entire expansion if we're doing EC_EXPAND).
1158
*/
1159
if (tk_text != NULL)
1160
{
1161
free(tk_text);
1162
tk_text = NULL;
1163
}
1164
if (curr_cmdflags & CF_OPTION)
1165
init_opt_compl();
1166
else
1167
#if TAB_COMPLETE_FILENAME
1168
init_file_compl();
1169
#else
1170
quit(QUIT_ERROR); /* cannot happen */
1171
#endif /* TAB_COMPLETE_FILENAME */
1172
if (tk_text == NULL)
1173
{
1174
bell();
1175
return (CC_OK);
1176
}
1177
if (action == EC_EXPAND)
1178
{
1179
/*
1180
* Use the whole list.
1181
*/
1182
tk_trial = tk_text;
1183
} else
1184
{
1185
/*
1186
* Use the first filename in the list.
1187
*/
1188
in_completion = TRUE;
1189
init_textlist(&tk_tlist, tk_text);
1190
tk_trial = next_compl(action, (char*)NULL);
1191
}
1192
} else
1193
{
1194
/*
1195
* We already have a completion list.
1196
* Use the next/previous filename from the list.
1197
*/
1198
tk_trial = next_compl(action, tk_trial);
1199
}
1200
1201
/*
1202
* Remove the original word, or the previous trial completion.
1203
*/
1204
while (cp > tk_ipoint)
1205
(void) cmd_erase();
1206
1207
if (tk_trial == NULL)
1208
{
1209
/*
1210
* There are no more trial completions.
1211
* Insert the original (uncompleted) filename.
1212
*/
1213
in_completion = FALSE;
1214
if (cmd_istr(tk_original) != CC_OK)
1215
goto fail;
1216
} else
1217
{
1218
/*
1219
* Insert trial completion.
1220
*/
1221
if (cmd_istr(tk_trial) != CC_OK)
1222
goto fail;
1223
/*
1224
* If it is a directory, append a slash.
1225
*/
1226
if (is_dir(tk_trial))
1227
{
1228
if (cp > cmdbuf && cp[-1] == closequote)
1229
(void) cmd_erase();
1230
s = lgetenv("LESSSEPARATOR");
1231
if (s == NULL)
1232
s = PATHNAME_SEP;
1233
if (cmd_istr(s) != CC_OK)
1234
goto fail;
1235
}
1236
}
1237
1238
return (CC_OK);
1239
1240
fail:
1241
in_completion = FALSE;
1242
bell();
1243
return (CC_OK);
1244
}
1245
1246
/*
1247
* Build a UTF-8 char in cmd_mbc_buf.
1248
* Returns:
1249
* CC_OK Char has been stored but we don't have a complete UTF-8 sequence yet.
1250
* CC_ERROR This is an invalid UTF-8 sequence.
1251
* CC_PASS There is a complete UTF-8 sequence in cmd_mbc_buf.
1252
* The length of the complete sequence is returned in *plen.
1253
*/
1254
static int cmd_uchar(char c, size_t *plen)
1255
{
1256
if (!utf_mode)
1257
{
1258
cmd_mbc_buf[0] = c;
1259
*plen = 1;
1260
} else
1261
{
1262
/* Perform strict validation in all possible cases. */
1263
if (cmd_mbc_buf_len == 0)
1264
{
1265
retry:
1266
cmd_mbc_buf_index = 1;
1267
*cmd_mbc_buf = c;
1268
if (IS_ASCII_OCTET(c))
1269
cmd_mbc_buf_len = 1;
1270
#if MSDOS_COMPILER || OS2
1271
else if (c == '\340' && IS_ASCII_OCTET(peekcc()))
1272
{
1273
/* Assume a special key. */
1274
cmd_mbc_buf_len = 1;
1275
}
1276
#endif
1277
else if (IS_UTF8_LEAD(c))
1278
{
1279
cmd_mbc_buf_len = utf_len(c);
1280
return (CC_OK);
1281
} else
1282
{
1283
/* UTF8_INVALID or stray UTF8_TRAIL */
1284
bell();
1285
return (CC_ERROR);
1286
}
1287
} else if (IS_UTF8_TRAIL(c))
1288
{
1289
cmd_mbc_buf[cmd_mbc_buf_index++] = c;
1290
if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1291
return (CC_OK);
1292
if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index))
1293
{
1294
/* complete, but not well formed (non-shortest form), sequence */
1295
cmd_mbc_buf_len = 0;
1296
bell();
1297
return (CC_ERROR);
1298
}
1299
} else
1300
{
1301
/* Flush incomplete (truncated) sequence. */
1302
cmd_mbc_buf_len = 0;
1303
bell();
1304
/* Handle new char. */
1305
goto retry;
1306
}
1307
1308
*plen = (size_t) cmd_mbc_buf_len; /*{{type-issue}}*/
1309
cmd_mbc_buf_len = 0;
1310
}
1311
return (CC_PASS);
1312
}
1313
1314
/*
1315
* Process a single character of a multi-character command, such as
1316
* a number, or the pattern of a search command.
1317
* Returns:
1318
* CC_OK The char was accepted.
1319
* CC_QUIT The char requests the command to be aborted.
1320
* CC_ERROR The char could not be accepted due to an error.
1321
*/
1322
static int cmd_char2(char c, lbool stay_in_completion)
1323
{
1324
size_t len;
1325
int action = cmd_uchar(c, &len);
1326
if (action != CC_PASS)
1327
return (action);
1328
1329
if (literal)
1330
{
1331
/*
1332
* Insert the char, even if it is a line-editing char.
1333
*/
1334
literal = FALSE;
1335
return (cmd_ichar(cmd_mbc_buf, len));
1336
}
1337
1338
/*
1339
* See if it is a line-editing character.
1340
*/
1341
if (in_mca() && len == 1)
1342
{
1343
action = cmd_edit(c, stay_in_completion);
1344
switch (action)
1345
{
1346
case CC_OK:
1347
case CC_QUIT:
1348
return (action);
1349
case CC_PASS:
1350
break;
1351
}
1352
}
1353
1354
/*
1355
* Insert the char into the command buffer.
1356
*/
1357
return (cmd_ichar(cmd_mbc_buf, len));
1358
}
1359
1360
public int cmd_char(char c)
1361
{
1362
return cmd_char2(c, FALSE);
1363
}
1364
1365
/*
1366
* Copy an ASCII string to the command buffer.
1367
*/
1368
public int cmd_setstring(constant char *s, lbool uc)
1369
{
1370
while (*s != '\0')
1371
{
1372
int action;
1373
char c = *s++;
1374
if (uc && ASCII_IS_LOWER(c))
1375
c = ASCII_TO_UPPER(c);
1376
action = cmd_char2(c, TRUE);
1377
if (action != CC_OK)
1378
return (action);
1379
}
1380
cmd_repaint_curr();
1381
return (CC_OK);
1382
}
1383
1384
/*
1385
* Return the number currently in the command buffer.
1386
*/
1387
public LINENUM cmd_int(mutable long *frac)
1388
{
1389
constant char *p;
1390
LINENUM n = 0;
1391
lbool err;
1392
1393
for (p = cmdbuf; *p >= '0' && *p <= '9'; p++)
1394
{
1395
if (ckd_mul(&n, n, 10) || ckd_add(&n, n, *p - '0'))
1396
{
1397
error("Integer is too big", NULL_PARG);
1398
return (0);
1399
}
1400
}
1401
*frac = 0;
1402
if (*p++ == '.')
1403
{
1404
*frac = getfraction(&p, NULL, &err);
1405
/* {{ do something if err is set? }} */
1406
}
1407
return (n);
1408
}
1409
1410
/*
1411
* Return a pointer to the command buffer.
1412
*/
1413
public constant char * get_cmdbuf(void)
1414
{
1415
if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1416
/* Don't return buffer containing an incomplete multibyte char. */
1417
return (NULL);
1418
return (cmdbuf);
1419
}
1420
1421
#if CMD_HISTORY
1422
/*
1423
* Return the last (most recent) string in the current command history.
1424
*/
1425
public constant char * cmd_lastpattern(void)
1426
{
1427
if (curr_mlist == NULL)
1428
return (NULL);
1429
return (curr_mlist->curr_mp->prev->string);
1430
}
1431
#endif
1432
1433
#if CMD_HISTORY
1434
/*
1435
*/
1436
static int mlist_size(struct mlist *ml)
1437
{
1438
int size = 0;
1439
for (ml = ml->next; ml->string != NULL; ml = ml->next)
1440
++size;
1441
return size;
1442
}
1443
1444
/*
1445
* Get the name of the history file.
1446
*/
1447
static char * histfile_find(lbool must_exist)
1448
{
1449
constant char *home = lgetenv("HOME");
1450
char *name = NULL;
1451
1452
/* Try in $XDG_STATE_HOME, then in $HOME/.local/state, then in $XDG_DATA_HOME, then in $HOME. */
1453
#if OS2
1454
if (isnullenv(home))
1455
home = lgetenv("INIT");
1456
#endif
1457
name = dirfile(lgetenv("XDG_STATE_HOME"), &LESSHISTFILE[1], must_exist);
1458
if (name == NULL)
1459
{
1460
char *dir = dirfile(home, ".local/state", 1);
1461
if (dir != NULL)
1462
{
1463
name = dirfile(dir, &LESSHISTFILE[1], must_exist);
1464
free(dir);
1465
}
1466
}
1467
if (name == NULL)
1468
name = dirfile(lgetenv("XDG_DATA_HOME"), &LESSHISTFILE[1], must_exist);
1469
if (name == NULL)
1470
name = dirfile(home, LESSHISTFILE, must_exist);
1471
return (name);
1472
}
1473
1474
static char * histfile_name(lbool must_exist)
1475
{
1476
constant char *name;
1477
char *wname;
1478
1479
/* See if filename is explicitly specified by $LESSHISTFILE. */
1480
name = lgetenv("LESSHISTFILE");
1481
if (!isnullenv(name))
1482
{
1483
if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1484
/* $LESSHISTFILE == "-" means don't use a history file. */
1485
return (NULL);
1486
return (save(name));
1487
}
1488
1489
/* See if history file is disabled in the build. */
1490
if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0)
1491
return (NULL);
1492
1493
wname = NULL;
1494
if (!must_exist)
1495
{
1496
/* If we're writing the file and the file already exists, use it. */
1497
wname = histfile_find(TRUE);
1498
}
1499
if (wname == NULL)
1500
wname = histfile_find(must_exist);
1501
return (wname);
1502
}
1503
1504
/*
1505
* Read a .lesshst file and call a callback for each line in the file.
1506
*/
1507
static void read_cmdhist2(void (*action)(void*,struct mlist*,constant char*), void *uparam, int skip_search, int skip_shell)
1508
{
1509
struct mlist *ml = NULL;
1510
char line[CMDBUF_SIZE];
1511
char *filename;
1512
FILE *f;
1513
int *skip = NULL;
1514
1515
filename = histfile_name(TRUE);
1516
if (filename == NULL)
1517
return;
1518
f = fopen(filename, "r");
1519
free(filename);
1520
if (f == NULL)
1521
return;
1522
if (fgets(line, sizeof(line), f) == NULL ||
1523
strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0)
1524
{
1525
fclose(f);
1526
return;
1527
}
1528
while (fgets(line, sizeof(line), f) != NULL)
1529
{
1530
char *p;
1531
for (p = line; *p != '\0'; p++)
1532
{
1533
if (*p == '\n' || *p == '\r')
1534
{
1535
*p = '\0';
1536
break;
1537
}
1538
}
1539
if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1540
{
1541
ml = &mlist_search;
1542
skip = &skip_search;
1543
} else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0)
1544
{
1545
#if SHELL_ESCAPE || PIPEC
1546
ml = &mlist_shell;
1547
skip = &skip_shell;
1548
#else
1549
ml = NULL;
1550
skip = NULL;
1551
(void) skip_shell;
1552
#endif
1553
} else if (strcmp(line, HISTFILE_MARK_SECTION) == 0)
1554
{
1555
ml = NULL;
1556
} else if (*line == '"')
1557
{
1558
if (ml != NULL)
1559
{
1560
if (skip != NULL && *skip > 0)
1561
--(*skip);
1562
else
1563
(*action)(uparam, ml, line+1);
1564
}
1565
} else if (*line == 'm')
1566
{
1567
(*action)(uparam, NULL, line);
1568
}
1569
}
1570
fclose(f);
1571
}
1572
1573
static void read_cmdhist(void (*action)(void*,struct mlist*,constant char*), void *uparam, lbool skip_search, lbool skip_shell)
1574
{
1575
if (!secure_allow(SF_HISTORY))
1576
return;
1577
read_cmdhist2(action, uparam, skip_search, skip_shell);
1578
(*action)(uparam, NULL, NULL); /* signal end of file */
1579
}
1580
1581
static void addhist_init(void *uparam, struct mlist *ml, constant char *string)
1582
{
1583
(void) uparam;
1584
if (ml != NULL)
1585
cmd_addhist(ml, string, 0);
1586
else if (string != NULL)
1587
restore_mark(string);
1588
}
1589
#endif /* CMD_HISTORY */
1590
1591
/*
1592
* Initialize history from a .lesshist file.
1593
*/
1594
public void init_cmdhist(void)
1595
{
1596
#if CMD_HISTORY
1597
read_cmdhist(&addhist_init, NULL, 0, 0);
1598
#endif /* CMD_HISTORY */
1599
}
1600
1601
/*
1602
* Write the header for a section of the history file.
1603
*/
1604
#if CMD_HISTORY
1605
static void write_mlist_header(struct mlist *ml, FILE *f)
1606
{
1607
if (ml == &mlist_search)
1608
fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1609
#if SHELL_ESCAPE || PIPEC
1610
else if (ml == &mlist_shell)
1611
fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1612
#endif
1613
}
1614
1615
/*
1616
* Write all modified entries in an mlist to the history file.
1617
*/
1618
static void write_mlist(struct mlist *ml, FILE *f)
1619
{
1620
for (ml = ml->next; ml->string != NULL; ml = ml->next)
1621
{
1622
if (!ml->modified)
1623
continue;
1624
fprintf(f, "\"%s\n", ml->string);
1625
ml->modified = FALSE;
1626
}
1627
ml->modified = FALSE; /* entire mlist is now unmodified */
1628
}
1629
1630
/*
1631
* Make a temp name in the same directory as filename.
1632
*/
1633
static char * make_tempname(constant char *filename)
1634
{
1635
char lastch;
1636
char *tempname = ecalloc(1, strlen(filename)+1);
1637
strcpy(tempname, filename);
1638
lastch = tempname[strlen(tempname)-1];
1639
tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q';
1640
return tempname;
1641
}
1642
1643
struct save_ctx
1644
{
1645
struct mlist *mlist;
1646
FILE *fout;
1647
};
1648
1649
/*
1650
* Copy entries from the saved history file to a new file.
1651
* At the end of each mlist, append any new entries
1652
* created during this session.
1653
*/
1654
static void copy_hist(void *uparam, struct mlist *ml, constant char *string)
1655
{
1656
struct save_ctx *ctx = (struct save_ctx *) uparam;
1657
1658
if (ml != NULL && ml != ctx->mlist) {
1659
/* We're changing mlists. */
1660
if (ctx->mlist)
1661
/* Append any new entries to the end of the current mlist. */
1662
write_mlist(ctx->mlist, ctx->fout);
1663
/* Write the header for the new mlist. */
1664
ctx->mlist = ml;
1665
write_mlist_header(ctx->mlist, ctx->fout);
1666
}
1667
1668
if (string == NULL) /* End of file */
1669
{
1670
/* Write any sections that were not in the original file. */
1671
if (mlist_search.modified)
1672
{
1673
write_mlist_header(&mlist_search, ctx->fout);
1674
write_mlist(&mlist_search, ctx->fout);
1675
}
1676
#if SHELL_ESCAPE || PIPEC
1677
if (mlist_shell.modified)
1678
{
1679
write_mlist_header(&mlist_shell, ctx->fout);
1680
write_mlist(&mlist_shell, ctx->fout);
1681
}
1682
#endif
1683
} else if (ml != NULL)
1684
{
1685
/* Copy mlist entry. */
1686
fprintf(ctx->fout, "\"%s\n", string);
1687
}
1688
/* Skip marks */
1689
}
1690
#endif /* CMD_HISTORY */
1691
1692
/*
1693
* Make a file readable only by its owner.
1694
*/
1695
static void make_file_private(FILE *f)
1696
{
1697
#if HAVE_FCHMOD
1698
lbool do_chmod = TRUE;
1699
#if HAVE_STAT
1700
struct stat statbuf;
1701
int r = fstat(fileno(f), &statbuf);
1702
if (r < 0 || !S_ISREG(statbuf.st_mode))
1703
/* Don't chmod if not a regular file. */
1704
do_chmod = FALSE;
1705
#endif
1706
if (do_chmod)
1707
fchmod(fileno(f), 0600);
1708
#endif
1709
}
1710
1711
/*
1712
* Does the history file need to be updated?
1713
*/
1714
#if CMD_HISTORY
1715
static lbool histfile_modified(void)
1716
{
1717
if (mlist_search.modified)
1718
return TRUE;
1719
#if SHELL_ESCAPE || PIPEC
1720
if (mlist_shell.modified)
1721
return TRUE;
1722
#endif
1723
if (marks_modified)
1724
return TRUE;
1725
return FALSE;
1726
}
1727
#endif
1728
1729
/*
1730
* Update the .lesshst file.
1731
*/
1732
public void save_cmdhist(void)
1733
{
1734
#if CMD_HISTORY
1735
char *histname;
1736
char *tempname;
1737
int skip_search;
1738
int skip_shell;
1739
struct save_ctx ctx;
1740
constant char *s;
1741
FILE *fout = NULL;
1742
int histsize = 0;
1743
1744
if (!secure_allow(SF_HISTORY) || !histfile_modified())
1745
return;
1746
histname = histfile_name(0);
1747
if (histname == NULL)
1748
return;
1749
tempname = make_tempname(histname);
1750
fout = fopen(tempname, "w");
1751
if (fout != NULL)
1752
{
1753
make_file_private(fout);
1754
s = lgetenv("LESSHISTSIZE");
1755
if (s != NULL)
1756
histsize = atoi(s);
1757
if (histsize <= 0)
1758
histsize = 100;
1759
skip_search = mlist_size(&mlist_search) - histsize;
1760
#if SHELL_ESCAPE || PIPEC
1761
skip_shell = mlist_size(&mlist_shell) - histsize;
1762
#else
1763
skip_shell = 0; /* not actually used */
1764
#endif
1765
fprintf(fout, "%s\n", HISTFILE_FIRST_LINE);
1766
ctx.fout = fout;
1767
ctx.mlist = NULL;
1768
read_cmdhist(&copy_hist, &ctx, skip_search, skip_shell);
1769
save_marks(fout, HISTFILE_MARK_SECTION);
1770
fclose(fout);
1771
#if MSDOS_COMPILER==WIN32C
1772
/*
1773
* Windows rename doesn't remove an existing file,
1774
* making it useless for atomic operations. Sigh.
1775
*/
1776
remove(histname);
1777
#endif
1778
rename(tempname, histname);
1779
}
1780
free(tempname);
1781
free(histname);
1782
#endif /* CMD_HISTORY */
1783
}
1784
1785