=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/less/cmdbuf.c,v retrieving revision 1.1.1.2 retrieving revision 1.1.1.3 diff -u -r1.1.1.2 -r1.1.1.3 --- src/usr.bin/less/cmdbuf.c 2003/04/13 18:21:21 1.1.1.2 +++ src/usr.bin/less/cmdbuf.c 2011/09/16 17:47:01 1.1.1.3 @@ -1,5 +1,5 @@ /* - * Copyright (C) 1984-2002 Mark Nudelman + * Copyright (C) 1984-2011 Mark Nudelman * * You may distribute under the terms of either the GNU General Public * License or the Less License, as specified in the README file. @@ -16,8 +16,13 @@ #include "less.h" #include "cmd.h" +#include "charset.h" +#if HAVE_STAT +#include +#endif extern int sc_width; +extern int utf_mode; static char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */ static int cmd_col; /* Current column of the cursor */ @@ -48,6 +53,12 @@ #endif #if CMD_HISTORY + +/* History file */ +#define HISTFILE_FIRST_LINE ".less-history-file:" +#define HISTFILE_SEARCH_SECTION ".search" +#define HISTFILE_SHELL_SECTION ".shell" + /* * A mlist structure represents a command history. */ @@ -57,22 +68,23 @@ struct mlist *prev; struct mlist *curr_mp; char *string; + int modified; }; /* * These are the various command histories that exist. */ struct mlist mlist_search = - { &mlist_search, &mlist_search, &mlist_search, NULL }; + { &mlist_search, &mlist_search, &mlist_search, NULL, 0 }; public void * constant ml_search = (void *) &mlist_search; struct mlist mlist_examine = - { &mlist_examine, &mlist_examine, &mlist_examine, NULL }; + { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 }; public void * constant ml_examine = (void *) &mlist_examine; #if SHELL_ESCAPE || PIPEC struct mlist mlist_shell = - { &mlist_shell, &mlist_shell, &mlist_shell, NULL }; + { &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 }; public void * constant ml_shell = (void *) &mlist_shell; #endif @@ -93,7 +105,11 @@ static struct mlist *curr_mlist = NULL; static int curr_cmdflags; +static char cmd_mbc_buf[MAX_UTF_CHAR_LEN]; +static int cmd_mbc_buf_len; +static int cmd_mbc_buf_index; + /* * Reset command buffer (to empty). */ @@ -105,16 +121,17 @@ cmd_col = 0; cmd_offset = 0; literal = 0; + cmd_mbc_buf_len = 0; } /* - * Clear command line on display. + * Clear command line. */ public void clear_cmd() { - clear_bot(); cmd_col = prompt_col = 0; + cmd_mbc_buf_len = 0; } /* @@ -124,9 +141,28 @@ cmd_putstr(s) char *s; { - putstr(s); - cmd_col += strlen(s); - prompt_col += strlen(s); + LWCHAR prev_ch = 0; + LWCHAR ch; + char *endline = s + strlen(s); + while (*s != '\0') + { + char *ns = s; + ch = step_char(&ns, +1, endline); + while (s < ns) + putchr(*s++); + if (!utf_mode) + { + cmd_col++; + prompt_col++; + } else if (!is_composing_char(ch) && + !is_combining_char(prev_ch, ch)) + { + int width = is_wide_char(ch) ? 2 : 1; + cmd_col += width; + prompt_col += width; + } + prev_ch = ch; + } } /* @@ -135,10 +171,116 @@ public int len_cmdbuf() { - return (strlen(cmdbuf)); + char *s = cmdbuf; + char *endline = s + strlen(s); + int len = 0; + + while (*s != '\0') + { + step_char(&s, +1, endline); + len++; + } + return (len); } /* + * Common part of cmd_step_right() and cmd_step_left(). + */ + static char * +cmd_step_common(p, ch, len, pwidth, bswidth) + char *p; + LWCHAR ch; + int len; + int *pwidth; + int *bswidth; +{ + char *pr; + + if (len == 1) + { + pr = prchar((int) ch); + if (pwidth != NULL || bswidth != NULL) + { + int len = strlen(pr); + if (pwidth != NULL) + *pwidth = len; + if (bswidth != NULL) + *bswidth = len; + } + } else + { + pr = prutfchar(ch); + if (pwidth != NULL || bswidth != NULL) + { + if (is_composing_char(ch)) + { + if (pwidth != NULL) + *pwidth = 0; + if (bswidth != NULL) + *bswidth = 0; + } else if (is_ubin_char(ch)) + { + int len = strlen(pr); + if (pwidth != NULL) + *pwidth = len; + if (bswidth != NULL) + *bswidth = len; + } else + { + LWCHAR prev_ch = step_char(&p, -1, cmdbuf); + if (is_combining_char(prev_ch, ch)) + { + if (pwidth != NULL) + *pwidth = 0; + if (bswidth != NULL) + *bswidth = 0; + } else + { + if (pwidth != NULL) + *pwidth = is_wide_char(ch) + ? 2 + : 1; + if (bswidth != NULL) + *bswidth = 1; + } + } + } + } + + return (pr); +} + +/* + * Step a pointer one character right in the command buffer. + */ + static char * +cmd_step_right(pp, pwidth, bswidth) + char **pp; + int *pwidth; + int *bswidth; +{ + char *p = *pp; + LWCHAR ch = step_char(pp, +1, p + strlen(p)); + + return cmd_step_common(p, ch, *pp - p, pwidth, bswidth); +} + +/* + * Step a pointer one character left in the command buffer. + */ + static char * +cmd_step_left(pp, pwidth, bswidth) + char **pp; + int *pwidth; + int *bswidth; +{ + char *p = *pp; + LWCHAR ch = step_char(pp, -1, cmdbuf); + + return cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth); +} + +/* * Repaint the line from cp onwards. * Then position the cursor just after the char old_cp (a pointer into cmdbuf). */ @@ -146,20 +288,31 @@ cmd_repaint(old_cp) char *old_cp; { - char *p; - /* * Repaint the line from the current position. */ clear_eol(); - for ( ; *cp != '\0'; cp++) + while (*cp != '\0') { - p = prchar(*cp); - if (cmd_col + (int)strlen(p) >= sc_width) + char *np = cp; + int width; + char *pr = cmd_step_right(&np, &width, NULL); + if (cmd_col + width >= sc_width) break; - putstr(p); - cmd_col += strlen(p); + cp = np; + putstr(pr); + cmd_col += width; } + while (*cp != '\0') + { + char *np = cp; + int width; + char *pr = cmd_step_right(&np, &width, NULL); + if (width > 0) + break; + cp = np; + putstr(pr); + } /* * Back up the cursor to the correct position. @@ -177,8 +330,12 @@ { while (cmd_col > prompt_col) { - putbs(); - cmd_col--; + int width, bswidth; + + cmd_step_left(&cp, &width, &bswidth); + while (bswidth-- > 0) + putbs(); + cmd_col -= width; } cp = &cmdbuf[cmd_offset]; @@ -201,7 +358,20 @@ s = cmdbuf + cmd_offset; cols = 0; while (cols < (sc_width - prompt_col) / 2 && *s != '\0') - cols += strlen(prchar(*s++)); + { + int width; + cmd_step_right(&s, &width, NULL); + cols += width; + } + while (*s != '\0') + { + int width; + char *ns = s; + cmd_step_right(&ns, &width, NULL); + if (width > 0) + break; + s = ns; + } cmd_offset = s - cmdbuf; save_cp = cp; @@ -216,7 +386,6 @@ cmd_rshift() { char *s; - char *p; char *save_cp; int cols; @@ -229,8 +398,9 @@ cols = 0; while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf) { - p = prchar(*--s); - cols += strlen(p); + int width; + cmd_step_left(&s, &width, NULL); + cols += width; } cmd_offset = s - cmdbuf; @@ -245,23 +415,32 @@ static int cmd_right() { - char *p; + char *pr; + char *ncp; + int width; if (*cp == '\0') { - /* - * Already at the end of the line. - */ + /* Already at the end of the line. */ return (CC_OK); } - p = prchar(*cp); - if (cmd_col + (int)strlen(p) >= sc_width) + ncp = cp; + pr = cmd_step_right(&ncp, &width, NULL); + if (cmd_col + width >= sc_width) cmd_lshift(); - else if (cmd_col + (int)strlen(p) == sc_width - 1 && cp[1] != '\0') + else if (cmd_col + width == sc_width - 1 && cp[1] != '\0') cmd_lshift(); - cp++; - putstr(p); - cmd_col += strlen(p); + cp = ncp; + cmd_col += width; + putstr(pr); + while (*cp != '\0') + { + pr = cmd_step_right(&ncp, &width, NULL); + if (width > 0) + break; + putstr(pr); + cp = ncp; + } return (CC_OK); } @@ -271,19 +450,26 @@ static int cmd_left() { - char *p; + char *ncp; + int width, bswidth; if (cp <= cmdbuf) { /* Already at the beginning of the line */ return (CC_OK); } - p = prchar(cp[-1]); - if (cmd_col < prompt_col + (int)strlen(p)) + ncp = cp; + while (ncp > cmdbuf) + { + cmd_step_left(&ncp, &width, &bswidth); + if (width > 0) + break; + } + if (cmd_col < prompt_col + width) cmd_rshift(); - cp--; - cmd_col -= strlen(p); - while (*p++ != '\0') + cp = ncp; + cmd_col -= width; + while (bswidth-- > 0) putbs(); return (CC_OK); } @@ -292,27 +478,30 @@ * Insert a char into the command buffer, at the current position. */ static int -cmd_ichar(c) - int c; +cmd_ichar(cs, clen) + char *cs; + int clen; { char *s; - if (strlen(cmdbuf) >= sizeof(cmdbuf)-2) + if (strlen(cmdbuf) + clen >= sizeof(cmdbuf)-1) { - /* - * No room in the command buffer for another char. - */ + /* No room in the command buffer for another char. */ bell(); return (CC_ERROR); } /* - * Insert the character into the buffer. + * Make room for the new character (shift the tail of the buffer right). */ for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--) - s[1] = s[0]; - *cp = c; + s[clen] = s[0]; /* + * Insert the character into the buffer. + */ + for (s = cp; s < cp + clen; s++) + *s = *cs++; + /* * Reprint the tail of the line from the inserted char. */ cmd_repaint(cp); @@ -328,6 +517,7 @@ cmd_erase() { register char *s; + int clen; if (cp == cmdbuf) { @@ -340,12 +530,20 @@ /* * Move cursor left (to the char being erased). */ + s = cp; cmd_left(); + clen = s - cp; + /* * Remove the char from the buffer (shift the buffer left). */ - for (s = cp; *s != '\0'; s++) - s[0] = s[1]; + for (s = cp; ; s++) + { + s[0] = s[clen]; + if (s[0] == '\0') + break; + } + /* * Repaint the buffer after the erased char. */ @@ -368,9 +566,7 @@ { if (*cp == '\0') { - /* - * At end of string; there is no char under the cursor. - */ + /* At end of string; there is no char under the cursor. */ return (CC_OK); } /* @@ -441,9 +637,7 @@ { if (cmdbuf[0] == '\0') { - /* - * Buffer is already empty; abort the current command. - */ + /* Buffer is already empty; abort the current command. */ return (CC_QUIT); } cmd_offset = 0; @@ -468,8 +662,14 @@ void *mlist; int cmdflags; { +#if CMD_HISTORY curr_mlist = (struct mlist *) mlist; curr_cmdflags = cmdflags; + + /* Make sure the next up-arrow moves to the last string in the mlist. */ + if (curr_mlist != NULL) + curr_mlist->curr_mp = curr_mlist; +#endif } #if CMD_HISTORY @@ -505,12 +705,9 @@ s = curr_mlist->curr_mp->string; if (s == NULL) s = ""; - for (cp = cmdbuf; *s != '\0'; s++) - { - *cp = *s; + strcpy(cmdbuf, s); + for (cp = cmdbuf; *cp != '\0'; ) cmd_right(); - } - *cp = '\0'; return (CC_OK); } #endif @@ -531,18 +728,14 @@ */ if (strlen(cmd) == 0) return; + /* - * Don't save if a duplicate of a command which is already - * in the history. - * But select the one already in the history to be current. + * Save the command unless it's a duplicate of the + * last command in the history. */ - for (ml = mlist->next; ml != mlist; ml = ml->next) + ml = mlist->prev; + if (ml == mlist || strcmp(ml->string, cmd) != 0) { - if (strcmp(ml->string, cmd) == 0) - break; - } - if (ml == mlist) - { /* * Did not find command in history. * Save the command and put it at the end of the history list. @@ -576,6 +769,7 @@ if (curr_mlist == NULL) return; cmd_addhist(curr_mlist, cmdbuf); + curr_mlist->modified = 1; #endif } @@ -663,6 +857,10 @@ case EC_LINEKILL: not_in_completion(); return (cmd_kill()); + case EC_ABORT: + not_in_completion(); + (void) cmd_kill(); + return (CC_QUIT); case EC_W_BACKSPACE: not_in_completion(); return (cmd_werase()); @@ -705,10 +903,13 @@ { char *s; int action; + char *endline = str + strlen(str); - for (s = str; *s != '\0'; s++) + for (s = str; *s != '\0'; ) { - action = cmd_ichar(*s); + char *os = s; + step_char(&s, +1, endline); + action = cmd_ichar(os, s - os); if (action != CC_OK) { bell(); @@ -995,20 +1196,70 @@ int c; { int action; + int len; + if (!utf_mode) + { + cmd_mbc_buf[0] = c; + len = 1; + } else + { + /* Perform strict validation in all possible cases. */ + if (cmd_mbc_buf_len == 0) + { + retry: + cmd_mbc_buf_index = 1; + *cmd_mbc_buf = c; + if (IS_ASCII_OCTET(c)) + cmd_mbc_buf_len = 1; + else if (IS_UTF8_LEAD(c)) + { + cmd_mbc_buf_len = utf_len(c); + return (CC_OK); + } else + { + /* UTF8_INVALID or stray UTF8_TRAIL */ + bell(); + return (CC_ERROR); + } + } else if (IS_UTF8_TRAIL(c)) + { + cmd_mbc_buf[cmd_mbc_buf_index++] = c; + if (cmd_mbc_buf_index < cmd_mbc_buf_len) + return (CC_OK); + if (!is_utf8_well_formed(cmd_mbc_buf)) + { + /* complete, but not well formed (non-shortest form), sequence */ + cmd_mbc_buf_len = 0; + bell(); + return (CC_ERROR); + } + } else + { + /* Flush incomplete (truncated) sequence. */ + cmd_mbc_buf_len = 0; + bell(); + /* Handle new char. */ + goto retry; + } + + len = cmd_mbc_buf_len; + cmd_mbc_buf_len = 0; + } + if (literal) { /* * Insert the char, even if it is a line-editing char. */ literal = 0; - return (cmd_ichar(c)); + return (cmd_ichar(cmd_mbc_buf, len)); } /* - * See if it is a special line-editing character. + * See if it is a line-editing character. */ - if (in_mca()) + if (in_mca() && len == 1) { action = cmd_edit(c); switch (action) @@ -1024,20 +1275,28 @@ /* * Insert the char into the command buffer. */ - return (cmd_ichar(c)); + return (cmd_ichar(cmd_mbc_buf, len)); } /* * Return the number currently in the command buffer. */ public LINENUM -cmd_int() +cmd_int(frac) + long *frac; { - register char *p; + char *p; LINENUM n = 0; + int err; - for (p = cmdbuf; *p != '\0'; p++) - n = (10 * n) + (*p - '0'); + for (p = cmdbuf; *p >= '0' && *p <= '9'; p++) + n = (n * 10) + (*p - '0'); + *frac = 0; + if (*p++ == '.') + { + *frac = getfraction(&p, NULL, &err); + /* {{ do something if err is set? }} */ + } return (n); } @@ -1048,4 +1307,197 @@ get_cmdbuf() { return (cmdbuf); +} + +#if CMD_HISTORY +/* + * Return the last (most recent) string in the current command history. + */ + public char * +cmd_lastpattern() +{ + if (curr_mlist == NULL) + return (NULL); + return (curr_mlist->curr_mp->prev->string); +} +#endif + +#if CMD_HISTORY +/* + * Get the name of the history file. + */ + static char * +histfile_name() +{ + char *home; + char *name; + int len; + + /* See if filename is explicitly specified by $LESSHISTFILE. */ + name = lgetenv("LESSHISTFILE"); + if (name != NULL && *name != '\0') + { + if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0) + /* $LESSHISTFILE == "-" means don't use a history file. */ + return (NULL); + return (save(name)); + } + + /* Otherwise, file is in $HOME. */ + home = lgetenv("HOME"); + if (home == NULL || *home == '\0') + { +#if OS2 + home = lgetenv("INIT"); + if (home == NULL || *home == '\0') +#endif + return (NULL); + } + len = strlen(home) + strlen(LESSHISTFILE) + 2; + name = (char *) ecalloc(len, sizeof(char)); + SNPRINTF2(name, len, "%s/%s", home, LESSHISTFILE); + return (name); +} +#endif /* CMD_HISTORY */ + +/* + * Initialize history from a .lesshist file. + */ + public void +init_cmdhist() +{ +#if CMD_HISTORY + struct mlist *ml = NULL; + char line[CMDBUF_SIZE]; + char *filename; + FILE *f; + char *p; + + filename = histfile_name(); + if (filename == NULL) + return; + f = fopen(filename, "r"); + free(filename); + if (f == NULL) + return; + if (fgets(line, sizeof(line), f) == NULL || + strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0) + { + fclose(f); + return; + } + while (fgets(line, sizeof(line), f) != NULL) + { + for (p = line; *p != '\0'; p++) + { + if (*p == '\n' || *p == '\r') + { + *p = '\0'; + break; + } + } + if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0) + ml = &mlist_search; + else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0) + { +#if SHELL_ESCAPE || PIPEC + ml = &mlist_shell; +#else + ml = NULL; +#endif + } else if (*line == '"') + { + if (ml != NULL) + cmd_addhist(ml, line+1); + } + } + fclose(f); +#endif /* CMD_HISTORY */ +} + +/* + * + */ +#if CMD_HISTORY + static void +save_mlist(ml, f) + struct mlist *ml; + FILE *f; +{ + int histsize = 0; + int n; + char *s; + + s = lgetenv("LESSHISTSIZE"); + if (s != NULL) + histsize = atoi(s); + if (histsize == 0) + histsize = 100; + + ml = ml->prev; + for (n = 0; n < histsize; n++) + { + if (ml->string == NULL) + break; + ml = ml->prev; + } + for (ml = ml->next; ml->string != NULL; ml = ml->next) + fprintf(f, "\"%s\n", ml->string); +} +#endif /* CMD_HISTORY */ + +/* + * + */ + public void +save_cmdhist() +{ +#if CMD_HISTORY + char *filename; + FILE *f; + int modified = 0; + + filename = histfile_name(); + if (filename == NULL) + return; + if (mlist_search.modified) + modified = 1; +#if SHELL_ESCAPE || PIPEC + if (mlist_shell.modified) + modified = 1; +#endif + if (!modified) + return; + f = fopen(filename, "w"); + free(filename); + if (f == NULL) + return; +#if HAVE_FCHMOD +{ + /* Make history file readable only by owner. */ + int do_chmod = 1; +#if HAVE_STAT + struct stat statbuf; + int r = fstat(fileno(f), &statbuf); + if (r < 0 || !S_ISREG(statbuf.st_mode)) + /* Don't chmod if not a regular file. */ + do_chmod = 0; +#endif + if (do_chmod) + fchmod(fileno(f), 0600); +} +#endif + + fprintf(f, "%s\n", HISTFILE_FIRST_LINE); + + fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION); + save_mlist(&mlist_search, f); + +#if SHELL_ESCAPE || PIPEC + fprintf(f, "%s\n", HISTFILE_SHELL_SECTION); + save_mlist(&mlist_shell, f); +#endif + + fclose(f); +#endif /* CMD_HISTORY */ }