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 "util.h"
42#include "shell.h"
43#include "alias.h"
44#include "builtins.h"
45#include "error.h"
46#include "eval.h"
47#include "exec.h"
48#include "main.h"
49#include "memalloc.h"
50#include "mystring.h"
51#include "options.h"
52#include "output.h"
53#include "parser.h"
54#include "var.h"
55
56#ifndef NO_HISTORY
57#include "lineedit.h"
58
59#include <sys/param.h>
60#include <sys/stat.h>
61
62#include <dirent.h>
63#include <errno.h>
64#include <fcntl.h>
65#include <limits.h>
66#include <paths.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(const char *fs_dir, const char *user_prefix,
240 char **comps, int comp_idx, int comp_count, int is_cmd, struct redlineCompletions *lc)
241{
242 DIR *dir;
243 struct dirent *de;
244 struct stat st;
245 char next_fs[4096];
246 char next_user[4096];
247 size_t len;
248
249 if (comp_idx == comp_count) {
250 /* reached the end of components, check if the path exists */
251 if (stat(fs_dir, &st) == 0) {
252 if (is_cmd && !S_ISDIR(st.st_mode) && access(fs_dir, X_OK) != 0) {
253 return;
254 }
255 snprintf(next_user, sizeof(next_user), "%s", user_prefix);
256 if (S_ISDIR(st.st_mode)) {
257 /* if it is a directory and doesnt end with slash, add slash */
258 len = strlen(next_user);
259 if (len > 0 && next_user[len - 1] != '/') {
260 strlcat(next_user, "/", sizeof(next_user));
261 }
262 } else {
263 /* file, add space */
264 strlcat(next_user, " ", sizeof(next_user));
265 }
266 redlineAddCompletion(lc, next_user);
267 }
268 return;
269 }
270
271 if (strcmp(comps[comp_idx], ".") == 0 || strcmp(comps[comp_idx], "..") == 0) {
272 /* construct filesystem path */
273 if (strcmp(fs_dir, "/") == 0) {
274 snprintf(next_fs, sizeof(next_fs), "/%s", comps[comp_idx]);
275 } else if (strcmp(fs_dir, ".") == 0) {
276 snprintf(next_fs, sizeof(next_fs), "./%s", comps[comp_idx]);
277 } else {
278 snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, comps[comp_idx]);
279 }
280
281 /* construct user visible path */
282 if (strcmp(user_prefix, "/") == 0) {
283 snprintf(next_user, sizeof(next_user), "/%s", comps[comp_idx]);
284 } else if (strcmp(user_prefix, "~/") == 0) {
285 snprintf(next_user, sizeof(next_user), "~/%s", comps[comp_idx]);
286 } else if (user_prefix[0] == '\0') {
287 snprintf(next_user, sizeof(next_user), "%s", comps[comp_idx]);
288 } else {
289 len = strlen(user_prefix);
290 if (user_prefix[len - 1] == '/') {
291 snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, comps[comp_idx]);
292 } else {
293 snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, comps[comp_idx]);
294 }
295 }
296
297 find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
298 return;
299 }
300
301 dir = opendir(fs_dir);
302 if (!dir)
303 return;
304
305 while ((de = readdir(dir)) != NULL) {
306 if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
307 continue;
308
309 if (strncmp(de->d_name, comps[comp_idx], strlen(comps[comp_idx])) == 0) {
310 char *escaped_name = escape_filename(de->d_name);
311 /* construct filesystem path */
312 if (strcmp(fs_dir, "/") == 0) {
313 snprintf(next_fs, sizeof(next_fs), "/%s", de->d_name);
314 } else if (strcmp(fs_dir, ".") == 0) {
315 snprintf(next_fs, sizeof(next_fs), "./%s", de->d_name);
316 } else {
317 snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, de->d_name);
318 }
319
320 /* middle components must be directories */
321 if (comp_idx < comp_count - 1) {
322 if (stat(next_fs, &st) != 0 || !S_ISDIR(st.st_mode)) {
323 free(escaped_name);
324 continue;
325 }
326 }
327
328 /* construct user-visible path */
329 if (strcmp(user_prefix, "/") == 0) {
330 snprintf(next_user, sizeof(next_user), "/%s", escaped_name);
331 } else if (strcmp(user_prefix, "~/") == 0) {
332 snprintf(next_user, sizeof(next_user), "~/%s", escaped_name);
333 } else if (user_prefix[0] == '\0') {
334 snprintf(next_user, sizeof(next_user), "%s", escaped_name);
335 } else {
336 len = strlen(user_prefix);
337 if (user_prefix[len - 1] == '/') {
338 snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, escaped_name);
339 } else {
340 snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, escaped_name);
341 }
342 }
343
344 find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
345 free(escaped_name);
346 }
347 }
348 closedir(dir);
349}
350
351/* complete matching files in the filesystem */
352static void
353complete_files(const char *word, int is_cmd, struct redlineCompletions *lc)
354{
355 char *path_to_split;
356 const char *fs_dir;
357 const char *user_prefix;
358 const char *home;
359 char *path_copy;
360 char *p;
361 char *comps[128];
362 int comp_count;
363 char *unescaped_word;
364
365 path_to_split = NULL;
366 fs_dir = ".";
367 user_prefix = "";
368 home = getenv("HOME");
369 comp_count = 0;
370 unescaped_word = unescape_filename(word);
371
372 if (unescaped_word[0] == '~') {
373 if (unescaped_word[1] == '/' || unescaped_word[1] == '\0') {
374 fs_dir = home ? home : "/";
375 user_prefix = "~/";
376 path_to_split = (unescaped_word[1] == '\0') ? "" : (char *)(unescaped_word + 2);
377 } else {
378 /* ~username is not supported for abbreviation, fallback to home */
379 fs_dir = home ? home : "/";
380 user_prefix = "~/";
381 path_to_split = (char *)(unescaped_word + 1);
382 }
383 } else if (unescaped_word[0] == '/') {
384 fs_dir = "/";
385 user_prefix = "/";
386 path_to_split = (char *)(unescaped_word + 1);
387 } else {
388 fs_dir = ".";
389 user_prefix = "";
390 path_to_split = (char *)unescaped_word;
391 }
392
393 path_copy = estrdup(path_to_split);
394 p = path_copy;
395 if (*p != '\0') {
396 comps[comp_count++] = p;
397 while (*p != '\0') {
398 if (*p == '/') {
399 *p = '\0';
400 p++;
401 while (*p == '/')
402 p++;
403 if (*p == '\0') {
404 comps[comp_count++] = p;
405 break;
406 }
407 comps[comp_count++] = p;
408 } else {
409 p++;
410 }
411 }
412 } else {
413 /* empty path_to_split (e.g. exactly ~ or exactly / or empty word) */
414 comps[comp_count++] = p;
415 }
416
417 find_completions_recurse(fs_dir, user_prefix, comps, 0, comp_count, is_cmd, lc);
418 free(path_copy);
419 free(unescaped_word);
420}
421
422/* complete matching executable commands and builtins */
423static void
424complete_commands(const char *word, struct redlineCompletions *lc)
425{
426 char *free_path = NULL, *path;
427 const char *dirname;
428 struct cmdentry e;
429 const struct alias *ap = NULL;
430 const unsigned char *bp = builtincmd;
431 const void *a = NULL;
432 DIR *dir;
433 struct dirent *entry;
434 int dfd;
435 struct stat statb;
436 char completed[512];
437
438 while ((ap = iteralias(ap)) != NULL) {
439 if (strncmp(ap->name, word, strlen(word)) == 0) {
440 snprintf(completed, sizeof(completed), "%s ", ap->name);
441 redlineAddCompletion(lc, completed);
442 }
443 }
444
445 while (bp && *bp != 0) {
446 if (strncmp((const char *)(bp + 2), word, strlen(word)) == 0) {
447 snprintf(completed, sizeof(completed), "%.*s ", (int)bp[0], bp + 2);
448 redlineAddCompletion(lc, completed);
449 }
450 bp += 2 + bp[0];
451 }
452
453 while ((a = itercmd(a, &e)) != NULL) {
454 if (e.cmdtype == CMDFUNCTION && strncmp(e.cmdname, word, strlen(word)) == 0) {
455 snprintf(completed, sizeof(completed), "%s ", e.cmdname);
456 redlineAddCompletion(lc, completed);
457 }
458 }
459
460 path = pathval();
461 if (path) {
462 free_path = path = estrdup(path);
463 while ((dirname = strsep(&path, ":")) != NULL) {
464 dir = opendir(dirname[0] == '\0' ? "." : dirname);
465 if (dir == NULL)
466 continue;
467 dfd = dirfd(dir);
468 if (dfd == -1) {
469 closedir(dir);
470 continue;
471 }
472 while ((entry = readdir(dir)) != NULL) {
473 if (strncmp(entry->d_name, word, strlen(word)) != 0)
474 continue;
475 if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
476 if (fstatat(dfd, entry->d_name, &statb, 0) == -1)
477 continue;
478 if (!S_ISREG(statb.st_mode))
479 continue;
480 } else if (entry->d_type != DT_REG) {
481 continue;
482 }
483 snprintf(completed, sizeof(completed), "%s ", entry->d_name);
484 redlineAddCompletion(lc, completed);
485 }
486 closedir(dir);
487 }
488 free(free_path);
489 }
490}
491
492/* main completion callback called by redline library */
493static void
494sh_complete_callback(const char *buf, struct redlineCompletions *lc)
495{
496 const char *word;
497 int start;
498 int is_cmd;
499 int p;
500 struct redlineCompletions temp_lc;
501 char line_prefix[4096];
502 char full_completion[4096];
503 size_t i;
504
505 start = strlen(buf);
506 while (start > 0) {
507 char c = buf[start - 1];
508 if (c == ' ' || c == '\t' || c == '\n' || c == '"' || c == '\'' || c == '`' ||
509 c == '@' || c == '$' || c == '>' || c == '<' || c == '=' || c == ';' ||
510 c == '|' || c == '&' || c == '{' || c == '(') {
511 if (start > 1 && buf[start - 2] == '\\') {
512 start -= 2;
513 continue;
514 }
515 break;
516 } else if (c == '\\') {
517 break;
518 }
519 start--;
520 }
521 word = buf + start;
522
523 is_cmd = 0;
524 if (start == 0) {
525 is_cmd = 1;
526 } else {
527 p = start;
528 while (p > 0 && (buf[p - 1] == ' ' || buf[p - 1] == '\t'))
529 p--;
530 if (p == 0 || strchr(";&|({`\n", buf[p - 1]) != NULL)
531 is_cmd = 1;
532 }
533
534 if (start >= (int)sizeof(line_prefix))
535 return;
536 snprintf(line_prefix, sizeof(line_prefix), "%.*s", start, buf);
537
538 temp_lc.len = 0;
539 temp_lc.cvec = NULL;
540
541 if (word[0] == '$') {
542 complete_variables(word, &temp_lc);
543 } else if (word[0] == '~' && strchr(word, '/') == NULL) {
544 complete_tildes(word, &temp_lc);
545 } else if (is_cmd && strchr(word, '/') == NULL && word[0] != '~' && word[0] != '.') {
546 complete_commands(word, &temp_lc);
547 } else {
548 complete_files(word, is_cmd, &temp_lc);
549 }
550
551 for (i = 0; i < temp_lc.len; i++) {
552 snprintf(full_completion, sizeof(full_completion), "%s%s", line_prefix, temp_lc.cvec[i]);
553 redlineAddCompletion(lc, full_completion);
554 }
555
556 for (i = 0; i < temp_lc.len; i++) {
557 free(temp_lc.cvec[i]);
558 }
559 free(temp_lc.cvec);
560}
561
562void
563histedit(void)
564{
565 sh_history_enabled = (iflag && (Eflag || Vflag));
566 if (sh_history_enabled) {
567 redlineSetCompletionCallback(sh_complete_callback);
568 redlineSetMultiLine(1);
569 }
570}
571
572void
573sethistsize(const char *hs)
574{
575 int histsize;
576
577 if (hs == NULL || !is_number(hs))
578 histsize = 128;
579 else
580 histsize = atoi(hs);
581 redlineHistorySetMaxLen(histsize);
582}
583
584void
585setterm(const char *term __unused)
586{
587}
588
589int
590histcmd(int argc, char **argv __unused)
591{
592 const char *editor = NULL;
593 int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
594 int i;
595 const char *firststr, *laststr;
596 int first, last;
597 char *pat = NULL, *repl = NULL;
598 static int active = 0;
599 struct jmploc jmploc;
600 struct jmploc *savehandler;
601 char editfilestr[PATH_MAX];
602 char *volatile editfile;
603 FILE *efp = NULL;
604 int dir;
605
606 if (redlineHistoryLen() == 0)
607 error("history not active");
608
609 if (argc == 1)
610 error("missing history argument");
611
612 while (not_fcnumber(*argptr))
613 do {
614 switch (nextopt("e:lnrs")) {
615 case 'e':
616 editor = shoptarg;
617 break;
618 case 'l':
619 lflg = 1;
620 break;
621 case 'n':
622 nflg = 1;
623 break;
624 case 'r':
625 rflg = 1;
626 break;
627 case 's':
628 sflg = 1;
629 break;
630 case '\0':
631 goto operands;
632 }
633 } while (nextopt_optptr != NULL);
634operands:
635 savehandler = handler;
636 if (lflg == 0 || editor || sflg) {
637 lflg = 0;
638 editfile = NULL;
639 if (setjmp(jmploc.loc)) {
640 active = 0;
641 if (editfile)
642 unlink(editfile);
643 handler = savehandler;
644 longjmp(handler->loc, 1);
645 }
646 handler = &jmploc;
647 if (++active > MAXHISTLOOPS) {
648 active = 0;
649 displayhist = 0;
650 error("called recursively too many times");
651 }
652 if (sflg == 0) {
653 if (editor == NULL &&
654 (editor = bltinlookup("FCEDIT", 1)) == NULL &&
655 (editor = bltinlookup("EDITOR", 1)) == NULL)
656 editor = DEFEDITOR;
657 if (editor[0] == '-' && editor[1] == '\0') {
658 sflg = 1;
659 editor = NULL;
660 }
661 }
662 }
663
664 if (lflg == 0 && *argptr != NULL &&
665 ((repl = strchr(*argptr, '=')) != NULL)) {
666 pat = *argptr;
667 *repl++ = '\0';
668 argptr++;
669 }
670
671 if (*argptr == NULL) {
672 firststr = lflg ? "-16" : "-1";
673 laststr = "-1";
674 } else if (argptr[1] == NULL) {
675 firststr = argptr[0];
676 laststr = lflg ? "-1" : argptr[0];
677 } else if (argptr[2] == NULL) {
678 firststr = argptr[0];
679 laststr = argptr[1];
680 } else {
681 error("too many arguments");
682 }
683
684 first = str_to_event(firststr, 0);
685 last = str_to_event(laststr, 1);
686
687 if (rflg) {
688 i = last;
689 last = first;
690 first = i;
691 }
692
693 if (editor) {
694 int fd;
695 INTOFF;
696 sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
697 if ((fd = mkstemp(editfilestr)) < 0)
698 error("can't create temporary file %s", editfile);
699 editfile = editfilestr;
700 if ((efp = fdopen(fd, "w")) == NULL) {
701 close(fd);
702 error("Out of space");
703 }
704 }
705
706 dir = (first <= last) ? 1 : -1;
707 for (i = first; ; i += dir) {
708 if (i < 1 || i > redlineHistoryLen())
709 continue;
710 const char *hstr = redlineHistoryGet(i - 1);
711 if (lflg) {
712 if (!nflg)
713 out1fmt("%5d ", i);
714 out1fmt("%s\n", hstr);
715 } else {
716 const char *s = pat ? fc_replace(hstr, pat, repl) : hstr;
717 if (sflg) {
718 if (displayhist) {
719 out2fmt_flush("%s\n", s);
720 }
721 evalstring(s, 0);
722 if (displayhist) {
723 redlineHistoryAdd(s);
724 }
725 } else {
726 fprintf(efp, "%s\n", s);
727 }
728 }
729 if (i == last)
730 break;
731 }
732
733 if (editor) {
734 char *editcmd;
735
736 fclose(efp);
737 INTON;
738 editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
739 sprintf(editcmd, "%s %s", editor, editfile);
740 evalstring(editcmd, 0);
741 readcmdfile(editfile, 0);
742 unlink(editfile);
743 }
744
745 if (lflg == 0 && active > 0)
746 --active;
747 if (displayhist)
748 displayhist = 0;
749 handler = savehandler;
750 return 0;
751}
752
753static char *
754fc_replace(const char *s, char *p, char *r)
755{
756 char *dest;
757 int plen = strlen(p);
758
759 STARTSTACKSTR(dest);
760 while (*s) {
761 if (*s == *p && strncmp(s, p, plen) == 0) {
762 STPUTS(r, dest);
763 s += plen;
764 *p = '\0';
765 } else
766 STPUTC(*s++, dest);
767 }
768 STPUTC('\0', dest);
769 dest = grabstackstr(dest);
770
771 return (dest);
772}
773
774static int
775not_fcnumber(const char *s)
776{
777 if (s == NULL)
778 return (0);
779 if (*s == '-')
780 s++;
781 return (!is_number(s));
782}
783
784static int
785str_to_event(const char *str, int last_fallback)
786{
787 int relative = 0;
788 int i;
789 const char *s = str;
790
791 if (s == NULL) {
792 return last_fallback ? redlineHistoryLen() : (redlineHistoryLen() > 16 ? redlineHistoryLen() - 15 : 1);
793 }
794
795 switch (*s) {
796 case '-':
797 relative = 1;
798 s++;
799 break;
800 case '+':
801 s++;
802 break;
803 }
804
805 if (is_number(s)) {
806 i = atoi(s);
807 if (relative) {
808 return redlineHistoryLen() - i;
809 }
810 return i;
811 }
812
813 for (i = redlineHistoryLen() - 1; i >= 0; i--) {
814 if (strncmp(redlineHistoryGet(i), str, strlen(str)) == 0) {
815 return i + 1;
816 }
817 }
818 error("history pattern not found: %s", str);
819 return 0;
820}
821
822int
823bindcmd(int argc __unused, char **argv __unused)
824{
825 error("not compiled with line editing support");
826 return (0);
827}
828
829#else
830
831int
832histcmd(int argc __unused, char **argv __unused)
833{
834 error("not compiled with history support");
835 return (0);
836}
837
838int
839bindcmd(int argc __unused, char **argv __unused)
840{
841 error("not compiled with line editing support");
842 return (0);
843}
844#endif