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