1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Kenneth Almquist.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#if !FEATURE_SH_HISTEDIT
36#ifndef NO_HISTORY
37#define NO_HISTORY
38#endif
39#endif
40
41#include "../../../shared/paths.h"
42#include "alias.h"
43#include "builtins.h"
44#include "error.h"
45#include "eval.h"
46#include "exec.h"
47#include "main.h"
48#include "memalloc.h"
49#include "mystring.h"
50#include "options.h"
51#include "output.h"
52#include "parser.h"
53#include "shell.h"
54#include "util.h"
55#include "var.h"
56
57#ifndef NO_HISTORY
58#include "lineedit.h"
59
60#include <sys/param.h>
61#include <sys/stat.h>
62
63#include <dirent.h>
64#include <errno.h>
65#include <fcntl.h>
66#include <limits.h>
67#include <pwd.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#include <unistd.h>
72
73#define MAXHISTLOOPS 4
74#define DEFEDITOR "ed"
75#define VTABSIZE 39
76
77extern struct var *vartab[VTABSIZE];
78
79int sh_history_enabled = 0;
80int displayhist = 0;
81static int savehist = 0;
82
83static char *fc_replace(const char *, char *, char *);
84static int not_fcnumber(const char *);
85static int str_to_event(const char *, int);
86
87static char *
88escape_filename(const char *filename)
89{
90 size_t len;
91 size_t i;
92 size_t j;
93 char *escaped;
94 const char *special;
95
96 len = 0;
97 special = " \t\n\"'\\$&|;<>()*?[]!{}";
98 for (i = 0; filename[i] != '\0'; i++) {
99 if (strchr(special, filename[i]) != NULL)
100 len += 2;
101 else
102 len += 1;
103 }
104
105 escaped = malloc(len + 1);
106 j = 0;
107 for (i = 0; filename[i] != '\0'; i++) {
108 if (strchr(special, filename[i]) != NULL) {
109 escaped[j++] = '\\';
110 escaped[j++] = filename[i];
111 } else {
112 escaped[j++] = filename[i];
113 }
114 }
115 escaped[j] = '\0';
116 return escaped;
117}
118
119static char *
120unescape_filename(const char *filename)
121{
122 size_t len;
123 char *unescaped;
124 size_t i;
125 size_t j;
126
127 len = strlen(filename);
128 unescaped = malloc(len + 1);
129 i = 0;
130 j = 0;
131 while (i < len) {
132 if (filename[i] == '\\' && i + 1 < len) {
133 unescaped[j++] = filename[i + 1];
134 i += 2;
135 } else {
136 unescaped[j++] = filename[i];
137 i++;
138 }
139 }
140 unescaped[j] = '\0';
141 return unescaped;
142}
143
144static void
145complete_tildes(const char *word, struct redlineCompletions *lc)
146{
147 struct passwd *pw;
148 char completed[512];
149 const char *user_prefix;
150 size_t prefix_len;
151
152 user_prefix = word + 1;
153 prefix_len = strlen(user_prefix);
154
155 setpwent();
156 while ((pw = getpwent()) != NULL) {
157 if (strncmp(pw->pw_name, user_prefix, prefix_len) == 0) {
158 snprintf(completed, sizeof(completed), "~%s/", pw->pw_name);
159 redlineAddCompletion(lc, completed);
160 }
161 }
162 endpwent();
163}
164
165static void
166complete_variables(const char *word, struct redlineCompletions *lc)
167{
168 struct var **vpp;
169 struct var *vp;
170 char name[256];
171 char completed[512];
172 const char *var_prefix;
173 size_t prefix_len;
174 char *eq;
175 size_t name_len;
176
177 var_prefix = word + 1;
178 prefix_len = strlen(var_prefix);
179
180 for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) {
181 for (vp = *vpp; vp; vp = vp->next) {
182 if (!(vp->flags & VUNSET)) {
183 eq = strchr(vp->text, '=');
184 if (eq) {
185 name_len = eq - vp->text;
186 if (name_len < sizeof(name)) {
187 memcpy(name, vp->text, name_len);
188 name[name_len] = '\0';
189 if (strncmp(name, var_prefix, prefix_len) == 0) {
190 snprintf(completed, sizeof(completed), "$%s ", name);
191 redlineAddCompletion(lc, completed);
192 }
193 }
194 }
195 }
196 }
197 }
198}
199
200static const char *
201get_histfile(void)
202{
203 const char *histfile;
204
205 if (!strcmp(histsizeval(), "0"))
206 return (NULL);
207 histfile = expandstr("${HISTFILE-${HOME-}/.sh_history}");
208
209 if (histfile[0] == '\0')
210 return (NULL);
211 return (histfile);
212}
213
214void
215histsave(void)
216{
217 const char *histfile;
218
219 if (!savehist || (histfile = get_histfile()) == NULL)
220 return;
221 INTOFF;
222 redlineHistorySave(histfile);
223 INTON;
224}
225
226void
227histload(void)
228{
229 const char *histfile;
230
231 if ((histfile = get_histfile()) == NULL)
232 return;
233 errno = 0;
234 if (redlineHistoryLoad(histfile) != -1 || errno == ENOENT)
235 savehist = 1;
236}
237
238static void
239find_completions_recurse(
240 const char *fs_dir,
241 const char *user_prefix,
242 char **comps,
243 int comp_idx,
244 int comp_count,
245 int is_cmd,
246 struct redlineCompletions *lc
247)
248{
249 DIR *dir;
250 struct dirent *de;
251 struct stat st;
252 char next_fs[4096];
253 char next_user[4096];
254 size_t len;
255
256 if (comp_idx == comp_count) {
257 /* reached the end of components, check if the path exists */
258 if (stat(fs_dir, &st) == 0) {
259 if (is_cmd && !S_ISDIR(st.st_mode) && access(fs_dir, X_OK) != 0) {
260 return;
261 }
262 snprintf(next_user, sizeof(next_user), "%s", user_prefix);
263 if (S_ISDIR(st.st_mode)) {
264 /* if it is a directory and doesnt end with
265 * slash, add slash */
266 len = strlen(next_user);
267 if (len > 0 && next_user[len - 1] != '/') {
268 strlcat(next_user, "/", sizeof(next_user));
269 }
270 } else {
271 /* file, add space */
272 strlcat(next_user, " ", sizeof(next_user));
273 }
274 redlineAddCompletion(lc, next_user);
275 }
276 return;
277 }
278
279 if (strcmp(comps[comp_idx], ".") == 0 || strcmp(comps[comp_idx], "..") == 0) {
280 /* construct filesystem path */
281 if (strcmp(fs_dir, "/") == 0) {
282 snprintf(next_fs, sizeof(next_fs), "/%s", comps[comp_idx]);
283 } else if (strcmp(fs_dir, ".") == 0) {
284 snprintf(next_fs, sizeof(next_fs), "./%s", comps[comp_idx]);
285 } else {
286 snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, comps[comp_idx]);
287 }
288
289 /* construct user visible path */
290 if (strcmp(user_prefix, "/") == 0) {
291 snprintf(next_user, sizeof(next_user), "/%s", comps[comp_idx]);
292 } else if (strcmp(user_prefix, "~/") == 0) {
293 snprintf(next_user, sizeof(next_user), "~/%s", comps[comp_idx]);
294 } else if (user_prefix[0] == '\0') {
295 snprintf(next_user, sizeof(next_user), "%s", comps[comp_idx]);
296 } else {
297 len = strlen(user_prefix);
298 if (user_prefix[len - 1] == '/') {
299 snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, comps[comp_idx]);
300 } else {
301 snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, comps[comp_idx]);
302 }
303 }
304
305 find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
306 return;
307 }
308
309 dir = opendir(fs_dir);
310 if (!dir)
311 return;
312
313 while ((de = readdir(dir)) != NULL) {
314 if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
315 continue;
316
317 if (strncmp(de->d_name, comps[comp_idx], strlen(comps[comp_idx])) == 0) {
318 char *escaped_name = escape_filename(de->d_name);
319 /* construct filesystem path */
320 if (strcmp(fs_dir, "/") == 0) {
321 snprintf(next_fs, sizeof(next_fs), "/%s", de->d_name);
322 } else if (strcmp(fs_dir, ".") == 0) {
323 snprintf(next_fs, sizeof(next_fs), "./%s", de->d_name);
324 } else {
325 snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, de->d_name);
326 }
327
328 /* middle components must be directories */
329 if (comp_idx < comp_count - 1) {
330 if (stat(next_fs, &st) != 0 || !S_ISDIR(st.st_mode)) {
331 free(escaped_name);
332 continue;
333 }
334 }
335
336 /* construct user-visible path */
337 if (strcmp(user_prefix, "/") == 0) {
338 snprintf(next_user, sizeof(next_user), "/%s", escaped_name);
339 } else if (strcmp(user_prefix, "~/") == 0) {
340 snprintf(next_user, sizeof(next_user), "~/%s", escaped_name);
341 } else if (user_prefix[0] == '\0') {
342 snprintf(next_user, sizeof(next_user), "%s", escaped_name);
343 } else {
344 len = strlen(user_prefix);
345 if (user_prefix[len - 1] == '/') {
346 snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, escaped_name);
347 } else {
348 snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, escaped_name);
349 }
350 }
351
352 find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
353 free(escaped_name);
354 }
355 }
356 closedir(dir);
357}
358
359/* complete matching files in the filesystem */
360static void
361complete_files(const char *word, int is_cmd, struct redlineCompletions *lc)
362{
363 char *path_to_split;
364 const char *fs_dir;
365 const char *user_prefix;
366 const char *home;
367 char *path_copy;
368 char *p;
369 char *comps[128];
370 int comp_count;
371 char *unescaped_word;
372
373 path_to_split = NULL;
374 fs_dir = ".";
375 user_prefix = "";
376 home = getenv("HOME");
377 comp_count = 0;
378 unescaped_word = unescape_filename(word);
379
380 if (unescaped_word[0] == '~') {
381 if (unescaped_word[1] == '/' || unescaped_word[1] == '\0') {
382 fs_dir = home ? home : "/";
383 user_prefix = "~/";
384 path_to_split = (unescaped_word[1] == '\0') ? "" : (char *)(unescaped_word + 2);
385 } else {
386 /* ~username is not supported for abbreviation, fallback
387 * to home */
388 fs_dir = home ? home : "/";
389 user_prefix = "~/";
390 path_to_split = (char *)(unescaped_word + 1);
391 }
392 } else if (unescaped_word[0] == '/') {
393 fs_dir = "/";
394 user_prefix = "/";
395 path_to_split = (char *)(unescaped_word + 1);
396 } else {
397 fs_dir = ".";
398 user_prefix = "";
399 path_to_split = (char *)unescaped_word;
400 }
401
402 path_copy = estrdup(path_to_split);
403 p = path_copy;
404 if (*p != '\0') {
405 comps[comp_count++] = p;
406 while (*p != '\0') {
407 if (*p == '/') {
408 *p = '\0';
409 p++;
410 while (*p == '/')
411 p++;
412 if (*p == '\0') {
413 comps[comp_count++] = p;
414 break;
415 }
416 comps[comp_count++] = p;
417 } else {
418 p++;
419 }
420 }
421 } else {
422 /* empty path_to_split (e.g. exactly ~ or exactly / or empty
423 * word) */
424 comps[comp_count++] = p;
425 }
426
427 find_completions_recurse(fs_dir, user_prefix, comps, 0, comp_count, is_cmd, lc);
428 free(path_copy);
429 free(unescaped_word);
430}
431
432/* complete matching executable commands and builtins */
433static void
434complete_commands(const char *word, struct redlineCompletions *lc)
435{
436 char *free_path = NULL, *path;
437 const char *dirname;
438 struct cmdentry e;
439 const struct alias *ap = NULL;
440 const unsigned char *bp = builtincmd;
441 const void *a = NULL;
442 DIR *dir;
443 struct dirent *entry;
444 int dfd;
445 struct stat statb;
446 char completed[512];
447
448 while ((ap = iteralias(ap)) != NULL) {
449 if (strncmp(ap->name, word, strlen(word)) == 0) {
450 snprintf(completed, sizeof(completed), "%s ", ap->name);
451 redlineAddCompletion(lc, completed);
452 }
453 }
454
455 while (bp && *bp != 0) {
456 if (strncmp((const char *)(bp + 2), word, strlen(word)) == 0) {
457 snprintf(completed, sizeof(completed), "%.*s ", (int)bp[0], bp + 2);
458 redlineAddCompletion(lc, completed);
459 }
460 bp += 2 + bp[0];
461 }
462
463 while ((a = itercmd(a, &e)) != NULL) {
464 if (e.cmdtype == CMDFUNCTION && strncmp(e.cmdname, word, strlen(word)) == 0) {
465 snprintf(completed, sizeof(completed), "%s ", e.cmdname);
466 redlineAddCompletion(lc, completed);
467 }
468 }
469
470 path = pathval();
471 if (path) {
472 free_path = path = estrdup(path);
473 while ((dirname = strsep(&path, ":")) != NULL) {
474 dir = opendir(dirname[0] == '\0' ? "." : dirname);
475 if (dir == NULL)
476 continue;
477 dfd = dirfd(dir);
478 if (dfd == -1) {
479 closedir(dir);
480 continue;
481 }
482 while ((entry = readdir(dir)) != NULL) {
483 if (strncmp(entry->d_name, word, strlen(word)) != 0)
484 continue;
485 if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
486 if (fstatat(dfd, entry->d_name, &statb, 0) == -1)
487 continue;
488 if (!S_ISREG(statb.st_mode))
489 continue;
490 } else if (entry->d_type != DT_REG) {
491 continue;
492 }
493 snprintf(completed, sizeof(completed), "%s ", entry->d_name);
494 redlineAddCompletion(lc, completed);
495 }
496 closedir(dir);
497 }
498 free(free_path);
499 }
500}
501
502/* main completion callback called by redline library */
503static void
504sh_complete_callback(const char *buf, struct redlineCompletions *lc)
505{
506 const char *word;
507 int start;
508 int is_cmd;
509 int p;
510 struct redlineCompletions temp_lc;
511 char line_prefix[4096];
512 char full_completion[4096];
513 size_t i;
514
515 start = strlen(buf);
516 while (start > 0) {
517 char c = buf[start - 1];
518 if (c == ' ' || c == '\t' || c == '\n' || c == '"' || c == '\'' || c == '`' || c == '@'
519 || c == '$' || c == '>' || c == '<' || c == '=' || c == ';' || c == '|' || c == '&'
520 || c == '{' || c == '(') {
521 if (start > 1 && buf[start - 2] == '\\') {
522 start -= 2;
523 continue;
524 }
525 break;
526 } else if (c == '\\') {
527 break;
528 }
529 start--;
530 }
531 word = buf + start;
532
533 is_cmd = 0;
534 if (start == 0) {
535 is_cmd = 1;
536 } else {
537 p = start;
538 while (p > 0 && (buf[p - 1] == ' ' || buf[p - 1] == '\t'))
539 p--;
540 if (p == 0 || strchr(";&|({`\n", buf[p - 1]) != NULL)
541 is_cmd = 1;
542 }
543
544 if (start >= (int)sizeof(line_prefix))
545 return;
546 snprintf(line_prefix, sizeof(line_prefix), "%.*s", start, buf);
547
548 temp_lc.len = 0;
549 temp_lc.cvec = NULL;
550
551 if (word[0] == '$') {
552 complete_variables(word, &temp_lc);
553 } else if (word[0] == '~' && strchr(word, '/') == NULL) {
554 complete_tildes(word, &temp_lc);
555 } else if (is_cmd && strchr(word, '/') == NULL && word[0] != '~' && word[0] != '.') {
556 complete_commands(word, &temp_lc);
557 } else {
558 complete_files(word, is_cmd, &temp_lc);
559 }
560
561 for (i = 0; i < temp_lc.len; i++) {
562 snprintf(full_completion, sizeof(full_completion), "%s%s", line_prefix, temp_lc.cvec[i]);
563 redlineAddCompletion(lc, full_completion);
564 }
565
566 for (i = 0; i < temp_lc.len; i++) {
567 free(temp_lc.cvec[i]);
568 }
569 free(temp_lc.cvec);
570}
571
572void
573histedit(void)
574{
575 sh_history_enabled = (iflag && (Eflag || Vflag));
576 if (sh_history_enabled) {
577 redlineSetCompletionCallback(sh_complete_callback);
578 redlineSetMultiLine(1);
579 }
580}
581
582void
583sethistsize(const char *hs)
584{
585 int histsize;
586
587 if (hs == NULL || !is_number(hs))
588 histsize = 128;
589 else
590 histsize = atoi(hs);
591 redlineHistorySetMaxLen(histsize);
592}
593
594void
595setterm(const char *term __unused)
596{
597}
598
599int
600histcmd(int argc, char **argv __unused)
601{
602 const char *editor = NULL;
603 int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
604 int i;
605 const char *firststr, *laststr;
606 int first, last;
607 char *pat = NULL, *repl = NULL;
608 static int active = 0;
609 struct jmploc jmploc;
610 struct jmploc *savehandler;
611 char editfilestr[PATH_MAX];
612 char *volatile editfile;
613 FILE *efp = NULL;
614 int dir;
615
616 if (redlineHistoryLen() == 0)
617 error("history not active");
618
619 if (argc == 1)
620 error("missing history argument");
621
622 while (not_fcnumber(*argptr))
623 do {
624 switch (nextopt("e:lnrs")) {
625 case 'e':
626 editor = shoptarg;
627 break;
628 case 'l':
629 lflg = 1;
630 break;
631 case 'n':
632 nflg = 1;
633 break;
634 case 'r':
635 rflg = 1;
636 break;
637 case 's':
638 sflg = 1;
639 break;
640 case '\0':
641 goto operands;
642 }
643 } while (nextopt_optptr != NULL);
644operands:
645 savehandler = handler;
646 if (lflg == 0 || editor || sflg) {
647 lflg = 0;
648 editfile = NULL;
649 if (setjmp(jmploc.loc)) {
650 active = 0;
651 if (editfile)
652 unlink(editfile);
653 handler = savehandler;
654 longjmp(handler->loc, 1);
655 }
656 handler = &jmploc;
657 if (++active > MAXHISTLOOPS) {
658 active = 0;
659 displayhist = 0;
660 error("called recursively too many times");
661 }
662 if (sflg == 0) {
663 if (editor == NULL && (editor = bltinlookup("FCEDIT", 1)) == NULL
664 && (editor = bltinlookup("EDITOR", 1)) == NULL)
665 editor = DEFEDITOR;
666 if (editor[0] == '-' && editor[1] == '\0') {
667 sflg = 1;
668 editor = NULL;
669 }
670 }
671 }
672
673 if (lflg == 0 && *argptr != NULL && ((repl = strchr(*argptr, '=')) != NULL)) {
674 pat = *argptr;
675 *repl++ = '\0';
676 argptr++;
677 }
678
679 if (*argptr == NULL) {
680 firststr = lflg ? "-16" : "-1";
681 laststr = "-1";
682 } else if (argptr[1] == NULL) {
683 firststr = argptr[0];
684 laststr = lflg ? "-1" : argptr[0];
685 } else if (argptr[2] == NULL) {
686 firststr = argptr[0];
687 laststr = argptr[1];
688 } else {
689 error("too many arguments");
690 }
691
692 first = str_to_event(firststr, 0);
693 last = str_to_event(laststr, 1);
694
695 if (rflg) {
696 i = last;
697 last = first;
698 first = i;
699 }
700
701 if (editor) {
702 int fd;
703 INTOFF;
704 sprintf(editfilestr, "%s/_shXXXXXX", ARUU_PATH_TMP);
705 if ((fd = mkstemp(editfilestr)) < 0)
706 error("can't create temporary file %s", editfile);
707 editfile = editfilestr;
708 if ((efp = fdopen(fd, "w")) == NULL) {
709 close(fd);
710 error("Out of space");
711 }
712 }
713
714 dir = (first <= last) ? 1 : -1;
715 for (i = first;; i += dir) {
716 if (i < 1 || i > redlineHistoryLen())
717 continue;
718 const char *hstr = redlineHistoryGet(i - 1);
719 if (lflg) {
720 if (!nflg)
721 out1fmt("%5d ", i);
722 out1fmt("%s\n", hstr);
723 } else {
724 const char *s = pat ? fc_replace(hstr, pat, repl) : hstr;
725 if (sflg) {
726 if (displayhist) {
727 out2fmt_flush("%s\n", s);
728 }
729 evalstring(s, 0);
730 if (displayhist) {
731 redlineHistoryAdd(s);
732 }
733 } else {
734 fprintf(efp, "%s\n", s);
735 }
736 }
737 if (i == last)
738 break;
739 }
740
741 if (editor) {
742 char *editcmd;
743
744 fclose(efp);
745 INTON;
746 editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
747 sprintf(editcmd, "%s %s", editor, editfile);
748 evalstring(editcmd, 0);
749 readcmdfile(editfile, 0);
750 unlink(editfile);
751 }
752
753 if (lflg == 0 && active > 0)
754 --active;
755 if (displayhist)
756 displayhist = 0;
757 handler = savehandler;
758 return 0;
759}
760
761static char *
762fc_replace(const char *s, char *p, char *r)
763{
764 char *dest;
765 int plen = strlen(p);
766
767 STARTSTACKSTR(dest);
768 while (*s) {
769 if (*s == *p && strncmp(s, p, plen) == 0) {
770 STPUTS(r, dest);
771 s += plen;
772 *p = '\0';
773 } else
774 STPUTC(*s++, dest);
775 }
776 STPUTC('\0', dest);
777 dest = grabstackstr(dest);
778
779 return (dest);
780}
781
782static int
783not_fcnumber(const char *s)
784{
785 if (s == NULL)
786 return (0);
787 if (*s == '-')
788 s++;
789 return (!is_number(s));
790}
791
792static int
793str_to_event(const char *str, int last_fallback)
794{
795 int relative = 0;
796 int i;
797 const char *s = str;
798
799 if (s == NULL) {
800 return last_fallback ? redlineHistoryLen()
801 : (redlineHistoryLen() > 16 ? redlineHistoryLen() - 15 : 1);
802 }
803
804 switch (*s) {
805 case '-':
806 relative = 1;
807 s++;
808 break;
809 case '+':
810 s++;
811 break;
812 }
813
814 if (is_number(s)) {
815 i = atoi(s);
816 if (relative) {
817 return redlineHistoryLen() - i;
818 }
819 return i;
820 }
821
822 for (i = redlineHistoryLen() - 1; i >= 0; i--) {
823 if (strncmp(redlineHistoryGet(i), str, strlen(str)) == 0) {
824 return i + 1;
825 }
826 }
827 error("history pattern not found: %s", str);
828 return 0;
829}
830
831int
832bindcmd(int argc __unused, char **argv __unused)
833{
834 error("not compiled with line editing support");
835 return (0);
836}
837
838#else
839
840int
841histcmd(int argc __unused, char **argv __unused)
842{
843 error("not compiled with history support");
844 return (0);
845}
846
847int
848bindcmd(int argc __unused, char **argv __unused)
849{
850 error("not compiled with line editing support");
851 return (0);
852}
853#endif