1 /*
2 * Copyright (C) 1984-2026 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 size_t cmd_mbc_buf_len;
117 static size_t cmd_mbc_buf_index;
118
119
120 /*
121 * Reset command buffer (to empty).
122 */
cmd_reset(void)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 */
clear_cmd(void)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 */
cmd_putstr(constant char * s)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 */
len_cmdbuf(void)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 */
cmdbuf_empty(void)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 */
cmd_step_common(char * p,LWCHAR ch,size_t len,int * pwidth,int * bswidth)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 */
cmd_step_right(char ** pp,int * pwidth,int * bswidth)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 */
cmd_step_left(char ** pp,int * pwidth,int * bswidth)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 */
cmd_home(void)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 */
cmd_repaint(constant char * old_cp)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 */
cmd_repaint_curr(void)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 */
cmd_lshift(void)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) ptr_diff(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 */
cmd_rshift(void)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) ptr_diff(s, cmdbuf);
391 save_cp = cp;
392 cmd_home();
393 cmd_repaint(save_cp);
394 }
395
396 /*
397 * Move cursor right one character.
398 */
cmd_right(void)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 */
cmd_left(void)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 */
cmd_erase(void)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) ptr_diff(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 */
cmd_delete(void)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 */
cmd_ichar(constant char * cs,size_t clen)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 */
cmd_werase(void)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 */
cmd_wdelete(void)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 */
cmd_kill(void)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 */
set_mlist(void * mlist,int cmdflags)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 */
cmd_updown(int action)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 */
save_updown_match(void)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
restore_updown_match(ssize_t udm)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 */
ml_link(struct mlist * mlist,struct mlist * ml)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 */
ml_unlink(struct mlist * ml)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 */
cmd_addhist(struct mlist * mlist,constant char * cmd,lbool modified)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 */
cmd_accept(void)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 */
cmd_edit(char c,lbool stay_in_completion)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 */
cmd_istr(constant char * str)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 */
set_tk_original(constant char * word)994 static void set_tk_original(constant char *word)
995 {
996 size_t len = ptr_diff(cp, word);
997 if (tk_original != NULL)
998 free(tk_original);
999 tk_original = (char *) ecalloc(len+1, sizeof(char));
1000 strncpy(tk_original, word, len);
1001 tk_original[len] = '\0';
1002 }
1003
1004 #if TAB_COMPLETE_FILENAME
1005 /*
1006 * Find the beginning and end of the "current" word.
1007 * This is the word which the cursor (cp) is inside or at the end of.
1008 * Return pointer to the beginning of the word and put the
1009 * cursor at the end of the word.
1010 */
delimit_word(void)1011 static char * delimit_word(void)
1012 {
1013 char *word;
1014 #if SPACES_IN_FILENAMES
1015 char *p;
1016 int delim_quoted = FALSE;
1017 int meta_quoted = FALSE;
1018 constant char *esc = get_meta_escape();
1019 size_t esclen = strlen(esc);
1020 #endif
1021
1022 /*
1023 * Move cursor to end of word.
1024 */
1025 if (*cp != ' ' && *cp != '\0')
1026 {
1027 /*
1028 * Cursor is on a nonspace.
1029 * Move cursor right to the next space.
1030 */
1031 while (*cp != ' ' && *cp != '\0')
1032 cmd_right();
1033 } else if (cp > cmdbuf && cp[-1] != ' ')
1034 {
1035 /*
1036 * Cursor is on a space, and char to the left is a nonspace.
1037 * We're already at the end of the word.
1038 */
1039 ;
1040 #if 0
1041 } else
1042 {
1043 /*
1044 * Cursor is on a space and char to the left is a space.
1045 * Huh? There's no word here.
1046 */
1047 return (NULL);
1048 #endif
1049 }
1050 /*
1051 * Find the beginning of the word which the cursor is in.
1052 */
1053 if (cp == cmdbuf)
1054 return (NULL);
1055 #if SPACES_IN_FILENAMES
1056 /*
1057 * If we have an unbalanced quote (that is, an open quote
1058 * without a corresponding close quote), we return everything
1059 * from the open quote, including spaces.
1060 */
1061 for (word = cmdbuf; word < cp; word++)
1062 if (*word != ' ')
1063 break;
1064 if (word >= cp)
1065 return (cp);
1066 for (p = cmdbuf; p < cp; p++)
1067 {
1068 if (meta_quoted)
1069 {
1070 meta_quoted = FALSE;
1071 } else if (esclen > 0 && p + esclen < cp &&
1072 strncmp(p, esc, esclen) == 0)
1073 {
1074 meta_quoted = TRUE;
1075 p += esclen - 1;
1076 } else if (delim_quoted)
1077 {
1078 if (*p == closequote)
1079 delim_quoted = FALSE;
1080 } else /* (!delim_quoted) */
1081 {
1082 if (*p == openquote)
1083 delim_quoted = TRUE;
1084 else if (*p == ' ')
1085 word = p+1;
1086 }
1087 }
1088 #endif
1089 return (word);
1090 }
1091
1092 /*
1093 * Set things up to enter file completion mode.
1094 * Expand the word under the cursor into a list of filenames
1095 * which start with that word, and set tk_text to that list.
1096 */
init_file_compl(void)1097 static void init_file_compl(void)
1098 {
1099 char *word;
1100 char c;
1101
1102 /*
1103 * Find the original (uncompleted) word in the command buffer.
1104 */
1105 word = delimit_word();
1106 if (word == NULL)
1107 return;
1108 /*
1109 * Set the insertion point to the point in the command buffer
1110 * where the original (uncompleted) word now sits.
1111 */
1112 tk_ipoint = word;
1113 set_tk_original(word);
1114 /*
1115 * Get the expanded filename.
1116 * This may result in a single filename, or
1117 * a blank-separated list of filenames.
1118 */
1119 c = *cp;
1120 *cp = '\0';
1121 if (*word != openquote)
1122 {
1123 tk_text = fcomplete(word);
1124 } else
1125 {
1126 #if MSDOS_COMPILER
1127 char *qword = NULL;
1128 #else
1129 char *qword = shell_quote(word+1);
1130 #endif
1131 if (qword == NULL)
1132 tk_text = fcomplete(word+1);
1133 else
1134 {
1135 tk_text = fcomplete(qword);
1136 free(qword);
1137 }
1138 }
1139 *cp = c;
1140 }
1141 #endif /* TAB_COMPLETE_FILENAME */
1142
1143 /*
1144 * Set things up to enter option completion mode.
1145 */
init_opt_compl(void)1146 static void init_opt_compl(void)
1147 {
1148 tk_ipoint = cmdbuf;
1149 set_tk_original(cmdbuf);
1150 tk_text = findopts_name(cmdbuf);
1151 }
1152
1153 /*
1154 * Return the next word in the current completion list.
1155 */
next_compl(int action,constant char * prev)1156 static constant char * next_compl(int action, constant char *prev)
1157 {
1158 switch (action)
1159 {
1160 case EC_F_COMPLETE:
1161 return (forw_textlist(&tk_tlist, prev));
1162 case EC_B_COMPLETE:
1163 return (back_textlist(&tk_tlist, prev));
1164 }
1165 /* Cannot happen */
1166 return ("?");
1167 }
1168
1169 /*
1170 * Complete the filename before (or under) the cursor.
1171 * cmd_complete may be called multiple times. The global in_completion
1172 * remembers whether this call is the first time (create the list),
1173 * or a subsequent time (step thru the list).
1174 */
cmd_complete(int action)1175 static int cmd_complete(int action)
1176 {
1177 constant char *s;
1178
1179 if (!in_completion || action == EC_EXPAND)
1180 {
1181 /*
1182 * Expand the word under the cursor and
1183 * use the first word in the expansion
1184 * (or the entire expansion if we're doing EC_EXPAND).
1185 */
1186 if (tk_text != NULL)
1187 {
1188 free(tk_text);
1189 tk_text = NULL;
1190 }
1191 if (curr_cmdflags & CF_OPTION)
1192 init_opt_compl();
1193 else
1194 #if TAB_COMPLETE_FILENAME
1195 init_file_compl();
1196 #else
1197 quit(QUIT_ERROR); /* cannot happen */
1198 #endif /* TAB_COMPLETE_FILENAME */
1199 if (tk_text == NULL)
1200 {
1201 lbell();
1202 return (CC_OK);
1203 }
1204 if (action == EC_EXPAND)
1205 {
1206 /*
1207 * Use the whole list.
1208 */
1209 tk_trial = tk_text;
1210 } else
1211 {
1212 /*
1213 * Use the first filename in the list.
1214 */
1215 in_completion = TRUE;
1216 init_textlist(&tk_tlist, tk_text);
1217 tk_trial = next_compl(action, (char*)NULL);
1218 }
1219 } else
1220 {
1221 /*
1222 * We already have a completion list.
1223 * Use the next/previous filename from the list.
1224 */
1225 tk_trial = next_compl(action, tk_trial);
1226 }
1227
1228 /*
1229 * Remove the original word, or the previous trial completion.
1230 */
1231 while (cp > tk_ipoint)
1232 (void) cmd_erase();
1233
1234 if (tk_trial == NULL)
1235 {
1236 /*
1237 * There are no more trial completions.
1238 * Insert the original (uncompleted) filename.
1239 */
1240 in_completion = FALSE;
1241 if (cmd_istr(tk_original) != CC_OK)
1242 goto fail;
1243 } else
1244 {
1245 /*
1246 * Insert trial completion.
1247 */
1248 if (cmd_istr(tk_trial) != CC_OK)
1249 goto fail;
1250 /*
1251 * If it is a directory, append a slash.
1252 */
1253 if (is_dir(tk_trial))
1254 {
1255 if (cp > cmdbuf && cp[-1] == closequote)
1256 (void) cmd_erase();
1257 s = lgetenv("LESSSEPARATOR");
1258 if (s == NULL)
1259 s = PATHNAME_SEP;
1260 if (cmd_istr(s) != CC_OK)
1261 goto fail;
1262 }
1263 }
1264
1265 return (CC_OK);
1266
1267 fail:
1268 in_completion = FALSE;
1269 lbell();
1270 return (CC_OK);
1271 }
1272
1273 /*
1274 * Build a UTF-8 char in cmd_mbc_buf.
1275 * Returns:
1276 * CC_OK Char has been stored but we don't have a complete UTF-8 sequence yet.
1277 * CC_ERROR This is an invalid UTF-8 sequence.
1278 * CC_PASS There is a complete UTF-8 sequence in cmd_mbc_buf.
1279 * The length of the complete sequence is returned in *plen.
1280 */
cmd_uchar(char c,size_t * plen)1281 static int cmd_uchar(char c, size_t *plen)
1282 {
1283 if (!utf_mode)
1284 {
1285 cmd_mbc_buf[0] = c;
1286 *plen = 1;
1287 } else
1288 {
1289 /* Perform strict validation in all possible cases. */
1290 if (cmd_mbc_buf_len == 0)
1291 {
1292 retry:
1293 cmd_mbc_buf_index = 1;
1294 *cmd_mbc_buf = c;
1295 if (IS_ASCII_OCTET(c))
1296 cmd_mbc_buf_len = 1;
1297 #if MSDOS_COMPILER || OS2
1298 else if (c == '\340' && IS_ASCII_OCTET(peekcc()))
1299 {
1300 /* Assume a special key. */
1301 cmd_mbc_buf_len = 1;
1302 }
1303 #endif
1304 else if (IS_UTF8_LEAD(c))
1305 {
1306 cmd_mbc_buf_len = utf_len(c);
1307 return (CC_OK);
1308 } else
1309 {
1310 /* UTF8_INVALID or stray UTF8_TRAIL */
1311 lbell();
1312 return (CC_ERROR);
1313 }
1314 } else if (IS_UTF8_TRAIL(c))
1315 {
1316 cmd_mbc_buf[cmd_mbc_buf_index++] = c;
1317 if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1318 return (CC_OK);
1319 if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index))
1320 {
1321 /* complete, but not well formed (non-shortest form), sequence */
1322 cmd_mbc_buf_len = 0;
1323 lbell();
1324 return (CC_ERROR);
1325 }
1326 } else
1327 {
1328 /* Flush incomplete (truncated) sequence. */
1329 cmd_mbc_buf_len = 0;
1330 lbell();
1331 /* Handle new char. */
1332 goto retry;
1333 }
1334
1335 *plen = cmd_mbc_buf_len;
1336 cmd_mbc_buf_len = 0;
1337 }
1338 return (CC_PASS);
1339 }
1340
1341 /*
1342 * Process a single character of a multi-character command, such as
1343 * a number, or the pattern of a search command.
1344 * Returns:
1345 * CC_OK The char was accepted.
1346 * CC_QUIT The char requests the command to be aborted.
1347 * CC_ERROR The char could not be accepted due to an error.
1348 */
cmd_char2(char c,lbool stay_in_completion)1349 static int cmd_char2(char c, lbool stay_in_completion)
1350 {
1351 size_t len;
1352 int action = cmd_uchar(c, &len);
1353 if (action != CC_PASS)
1354 return (action);
1355
1356 if (literal)
1357 {
1358 /*
1359 * Insert the char, even if it is a line-editing char.
1360 */
1361 literal = FALSE;
1362 return (cmd_ichar(cmd_mbc_buf, len));
1363 }
1364
1365 /*
1366 * See if it is a line-editing character.
1367 */
1368 if (in_mca() && len == 1)
1369 {
1370 action = cmd_edit(c, stay_in_completion);
1371 switch (action)
1372 {
1373 case CC_OK:
1374 case CC_QUIT:
1375 return (action);
1376 case CC_PASS:
1377 break;
1378 }
1379 }
1380
1381 /*
1382 * Insert the char into the command buffer.
1383 */
1384 return (cmd_ichar(cmd_mbc_buf, len));
1385 }
1386
cmd_char(char c)1387 public int cmd_char(char c)
1388 {
1389 return cmd_char2(c, FALSE);
1390 }
1391
1392 /*
1393 * Copy an ASCII string to the command buffer.
1394 */
cmd_setstring(constant char * s,lbool uc)1395 public int cmd_setstring(constant char *s, lbool uc)
1396 {
1397 while (*s != '\0')
1398 {
1399 int action;
1400 char c = *s++;
1401 if (uc && ASCII_IS_LOWER(c))
1402 c = ASCII_TO_UPPER(c);
1403 action = cmd_char2(c, TRUE);
1404 if (action != CC_OK)
1405 return (action);
1406 }
1407 cmd_repaint_curr();
1408 return (CC_OK);
1409 }
1410
1411 /*
1412 * Return the number currently in the command buffer.
1413 */
cmd_int(mutable long * frac)1414 public LINENUM cmd_int(mutable long *frac)
1415 {
1416 constant char *p;
1417 LINENUM n = 0;
1418
1419 for (p = cmdbuf; *p >= '0' && *p <= '9'; p++)
1420 {
1421 if (ckd_mul(&n, n, 10) || ckd_add(&n, n, *p - '0'))
1422 {
1423 error(LM(Integer_is_too_big), NULL_PARG);
1424 return (0);
1425 }
1426 }
1427 if (frac != NULL)
1428 {
1429 *frac = 0;
1430 if (*p++ == '.')
1431 {
1432 /* {{ Just ignore error in fractional part. }} */
1433 (void) getfraction(&p, frac);
1434 }
1435 }
1436 return (n);
1437 }
1438
1439 /*
1440 * Return a pointer to the command buffer.
1441 */
get_cmdbuf(void)1442 public constant char * get_cmdbuf(void)
1443 {
1444 if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1445 /* Don't return buffer containing an incomplete multibyte char. */
1446 return (NULL);
1447 return (cmdbuf);
1448 }
1449
1450 #if CMD_HISTORY
1451 /*
1452 * Return the last (most recent) string in the current command history.
1453 */
cmd_lastpattern(void)1454 public constant char * cmd_lastpattern(void)
1455 {
1456 if (curr_mlist == NULL)
1457 return (NULL);
1458 return (curr_mlist->curr_mp->prev->string);
1459 }
1460 #endif
1461
1462 #if CMD_HISTORY
1463 /*
1464 */
mlist_size(struct mlist * ml)1465 static int mlist_size(struct mlist *ml)
1466 {
1467 int size = 0;
1468 for (ml = ml->next; ml->string != NULL; ml = ml->next)
1469 ++size;
1470 return size;
1471 }
1472
1473 /*
1474 * Get the name of the history file.
1475 */
histfile_find(lbool must_exist)1476 static char * histfile_find(lbool must_exist)
1477 {
1478 constant char *home = lgetenv("HOME");
1479 char *name;
1480
1481 /* Try in $XDG_STATE_HOME, then in $HOME/.local/state, then in $XDG_DATA_HOME, then in $HOME. */
1482 #if OS2
1483 if (isnullenv(home))
1484 home = lgetenv("INIT");
1485 #endif
1486 name = dirfile(lgetenv("XDG_STATE_HOME"), &LESSHISTFILE[1], must_exist);
1487 if (name == NULL)
1488 {
1489 char *dir = dirfile(home, ".local/state", TRUE);
1490 if (dir != NULL)
1491 {
1492 name = dirfile(dir, &LESSHISTFILE[1], must_exist);
1493 free(dir);
1494 }
1495 }
1496 if (name == NULL)
1497 name = dirfile(lgetenv("XDG_DATA_HOME"), &LESSHISTFILE[1], must_exist);
1498 if (name == NULL)
1499 name = dirfile(home, LESSHISTFILE, must_exist);
1500 return (name);
1501 }
1502
histfile_name(lbool must_exist)1503 static char * histfile_name(lbool must_exist)
1504 {
1505 constant char *name;
1506 char *wname;
1507
1508 /* See if filename is explicitly specified by $LESSHISTFILE. */
1509 name = lgetenv("LESSHISTFILE");
1510 if (!isnullenv(name))
1511 {
1512 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1513 /* $LESSHISTFILE == "-" means don't use a history file. */
1514 return (NULL);
1515 return (save(name));
1516 }
1517
1518 /* See if history file is disabled in the build. */
1519 if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0)
1520 return (NULL);
1521
1522 wname = NULL;
1523 if (!must_exist)
1524 {
1525 /* If we're writing the file and the file already exists, use it. */
1526 wname = histfile_find(TRUE);
1527 }
1528 if (wname == NULL)
1529 wname = histfile_find(must_exist);
1530 return (wname);
1531 }
1532
1533 /*
1534 * Read a .lesshst file and call a callback for each line in the file.
1535 */
read_cmdhist2(void (* action)(void *,struct mlist *,constant char *),void * uparam,int skip_search,int skip_shell)1536 static void read_cmdhist2(void (*action)(void*,struct mlist*,constant char*), void *uparam, int skip_search, int skip_shell)
1537 {
1538 struct mlist *ml = NULL;
1539 char line[CMDBUF_SIZE];
1540 char *filename;
1541 FILE *f;
1542 int *skip = NULL;
1543
1544 filename = histfile_name(TRUE);
1545 if (filename == NULL)
1546 return;
1547 f = fopen(filename, "r");
1548 free(filename);
1549 if (f == NULL)
1550 return;
1551 if (fgets(line, sizeof(line), f) == NULL ||
1552 strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0)
1553 {
1554 fclose(f);
1555 return;
1556 }
1557 while (fgets(line, sizeof(line), f) != NULL)
1558 {
1559 char *p;
1560 for (p = line; *p != '\0'; p++)
1561 {
1562 if (*p == '\n' || *p == '\r')
1563 {
1564 *p = '\0';
1565 break;
1566 }
1567 }
1568 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1569 {
1570 ml = &mlist_search;
1571 skip = &skip_search;
1572 } else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0)
1573 {
1574 #if SHELL_ESCAPE || PIPEC
1575 ml = &mlist_shell;
1576 skip = &skip_shell;
1577 #else
1578 ml = NULL;
1579 skip = NULL;
1580 (void) skip_shell;
1581 #endif
1582 } else if (strcmp(line, HISTFILE_MARK_SECTION) == 0)
1583 {
1584 ml = NULL;
1585 } else if (*line == '"')
1586 {
1587 if (ml != NULL)
1588 {
1589 if (skip != NULL && *skip > 0)
1590 --(*skip);
1591 else
1592 (*action)(uparam, ml, line+1);
1593 }
1594 } else if (*line == 'm')
1595 {
1596 (*action)(uparam, NULL, line);
1597 }
1598 }
1599 fclose(f);
1600 }
1601
read_cmdhist(void (* action)(void *,struct mlist *,constant char *),void * uparam,lbool skip_search,lbool skip_shell)1602 static void read_cmdhist(void (*action)(void*,struct mlist*,constant char*), void *uparam, lbool skip_search, lbool skip_shell)
1603 {
1604 if (!secure_allow(SF_HISTORY))
1605 return;
1606 read_cmdhist2(action, uparam, skip_search, skip_shell);
1607 (*action)(uparam, NULL, NULL); /* signal end of file */
1608 }
1609
addhist_init(void * uparam,struct mlist * ml,constant char * string)1610 static void addhist_init(void *uparam, struct mlist *ml, constant char *string)
1611 {
1612 (void) uparam;
1613 if (ml != NULL)
1614 cmd_addhist(ml, string, FALSE);
1615 else if (string != NULL)
1616 restore_mark(string);
1617 }
1618 #endif /* CMD_HISTORY */
1619
1620 /*
1621 * Initialize history from a .lesshist file.
1622 */
init_cmdhist(void)1623 public void init_cmdhist(void)
1624 {
1625 #if CMD_HISTORY
1626 read_cmdhist(&addhist_init, NULL, 0, 0);
1627 #endif /* CMD_HISTORY */
1628 }
1629
1630 /*
1631 * Write the header for a section of the history file.
1632 */
1633 #if CMD_HISTORY
write_mlist_header(struct mlist * ml,FILE * f)1634 static void write_mlist_header(struct mlist *ml, FILE *f)
1635 {
1636 if (ml == &mlist_search)
1637 fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1638 #if SHELL_ESCAPE || PIPEC
1639 else if (ml == &mlist_shell)
1640 fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1641 #endif
1642 }
1643
1644 /*
1645 * Write all modified entries in an mlist to the history file.
1646 */
write_mlist(struct mlist * ml,FILE * f)1647 static void write_mlist(struct mlist *ml, FILE *f)
1648 {
1649 for (ml = ml->next; ml->string != NULL; ml = ml->next)
1650 {
1651 if (!ml->modified)
1652 continue;
1653 fprintf(f, "\"%s\n", ml->string);
1654 ml->modified = FALSE;
1655 }
1656 ml->modified = FALSE; /* entire mlist is now unmodified */
1657 }
1658
1659 /*
1660 * Make a temp name in the same directory as filename.
1661 */
make_tempname(constant char * filename)1662 static char * make_tempname(constant char *filename)
1663 {
1664 char lastch;
1665 char *tempname = ecalloc(1, strlen(filename)+1);
1666 strcpy(tempname, filename);
1667 lastch = tempname[strlen(tempname)-1];
1668 tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q';
1669 return tempname;
1670 }
1671
1672 struct save_ctx
1673 {
1674 struct mlist *mlist;
1675 FILE *fout;
1676 };
1677
1678 /*
1679 * Copy entries from the saved history file to a new file.
1680 * At the end of each mlist, append any new entries
1681 * created during this session.
1682 */
copy_hist(void * uparam,struct mlist * ml,constant char * string)1683 static void copy_hist(void *uparam, struct mlist *ml, constant char *string)
1684 {
1685 struct save_ctx *ctx = (struct save_ctx *) uparam;
1686
1687 if (ml != NULL && ml != ctx->mlist) {
1688 /* We're changing mlists. */
1689 if (ctx->mlist)
1690 /* Append any new entries to the end of the current mlist. */
1691 write_mlist(ctx->mlist, ctx->fout);
1692 /* Write the header for the new mlist. */
1693 ctx->mlist = ml;
1694 write_mlist_header(ctx->mlist, ctx->fout);
1695 }
1696
1697 if (string == NULL) /* End of file */
1698 {
1699 /* Write any sections that were not in the original file. */
1700 if (mlist_search.modified)
1701 {
1702 write_mlist_header(&mlist_search, ctx->fout);
1703 write_mlist(&mlist_search, ctx->fout);
1704 }
1705 #if SHELL_ESCAPE || PIPEC
1706 if (mlist_shell.modified)
1707 {
1708 write_mlist_header(&mlist_shell, ctx->fout);
1709 write_mlist(&mlist_shell, ctx->fout);
1710 }
1711 #endif
1712 } else if (ml != NULL)
1713 {
1714 /* Copy mlist entry. */
1715 fprintf(ctx->fout, "\"%s\n", string);
1716 }
1717 /* Skip marks */
1718 }
1719 #endif /* CMD_HISTORY */
1720
1721 /*
1722 * Make a file readable only by its owner.
1723 */
make_file_private(FILE * f)1724 static void make_file_private(FILE *f)
1725 {
1726 #if HAVE_FCHMOD
1727 lbool do_chmod = TRUE;
1728 #if HAVE_STAT
1729 less_stat_t statbuf;
1730 int r = less_fstat(fileno(f), &statbuf);
1731 if (r < 0 || !S_ISREG(statbuf.st_mode))
1732 /* Don't chmod if not a regular file. */
1733 do_chmod = FALSE;
1734 #endif
1735 if (do_chmod)
1736 fchmod(fileno(f), 0600);
1737 #endif
1738 }
1739
1740 /*
1741 * Does the history file need to be updated?
1742 */
1743 #if CMD_HISTORY
histfile_modified(void)1744 static lbool histfile_modified(void)
1745 {
1746 if (mlist_search.modified)
1747 return TRUE;
1748 #if SHELL_ESCAPE || PIPEC
1749 if (mlist_shell.modified)
1750 return TRUE;
1751 #endif
1752 if (marks_modified)
1753 return TRUE;
1754 return FALSE;
1755 }
1756 #endif
1757
1758 /*
1759 * Update the .lesshst file.
1760 */
save_cmdhist(void)1761 public void save_cmdhist(void)
1762 {
1763 #if CMD_HISTORY
1764 char *histname;
1765 char *tempname;
1766 int skip_search;
1767 int skip_shell;
1768 struct save_ctx ctx;
1769 constant char *s;
1770 FILE *fout;
1771 int histsize = 0;
1772
1773 if (!secure_allow(SF_HISTORY) || !histfile_modified())
1774 return;
1775 histname = histfile_name(0);
1776 if (histname == NULL)
1777 return;
1778 tempname = make_tempname(histname);
1779 fout = fopen(tempname, "w");
1780 if (fout != NULL)
1781 {
1782 make_file_private(fout);
1783 s = lgetenv("LESSHISTSIZE");
1784 if (s != NULL)
1785 histsize = atoi(s);
1786 if (histsize <= 0)
1787 histsize = 100;
1788 skip_search = mlist_size(&mlist_search) - histsize;
1789 #if SHELL_ESCAPE || PIPEC
1790 skip_shell = mlist_size(&mlist_shell) - histsize;
1791 #else
1792 skip_shell = 0; /* not actually used */
1793 #endif
1794 fprintf(fout, "%s\n", HISTFILE_FIRST_LINE);
1795 ctx.fout = fout;
1796 ctx.mlist = NULL;
1797 read_cmdhist(©_hist, &ctx, skip_search, skip_shell);
1798 save_marks(fout, HISTFILE_MARK_SECTION);
1799 fclose(fout);
1800 #if MSDOS_COMPILER==WIN32C
1801 /*
1802 * Windows rename doesn't remove an existing file,
1803 * making it useless for atomic operations. Sigh.
1804 */
1805 remove(histname);
1806 #endif
1807 rename(tempname, histname);
1808 }
1809 free(tempname);
1810 free(histname);
1811 #endif /* CMD_HISTORY */
1812 }
1813