git: f80a33ea416e - main - MFV: less v643.
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 13 Aug 2023 07:15:20 UTC
The branch main has been updated by delphij: URL: https://cgit.FreeBSD.org/src/commit/?id=f80a33ea416e408c490a1f1f031a6abbccbae085 commit f80a33ea416e408c490a1f1f031a6abbccbae085 Merge: 09078445fb94 448d114b2515 Author: Xin LI <delphij@FreeBSD.org> AuthorDate: 2023-08-13 07:13:31 +0000 Commit: Xin LI <delphij@FreeBSD.org> CommitDate: 2023-08-13 07:13:31 +0000 MFV: less v643. MFC after: 2 weeks contrib/less/NEWS | 38 +++++++++++++++++++++++++++++++++ contrib/less/ch.c | 2 ++ contrib/less/command.c | 5 +++-- contrib/less/edit.c | 2 +- contrib/less/forwback.c | 8 +++---- contrib/less/funcs.h | 1 + contrib/less/help.c | 2 +- contrib/less/less.h | 9 ++++++++ contrib/less/less.nro | 15 ++++++++++---- contrib/less/lessecho.nro | 2 +- contrib/less/lesskey.nro | 15 ++++++++++---- contrib/less/optfunc.c | 8 +++---- contrib/less/os.c | 44 +++++++++++++++++++++++++++++++-------- contrib/less/screen.c | 53 +++++++++++++++++++++++++++++++++++++---------- contrib/less/search.c | 2 ++ contrib/less/version.c | 14 ++++++++++++- contrib/less/xbuf.c | 8 +++---- usr.bin/less/defines.h | 18 ++++++++-------- 18 files changed, 191 insertions(+), 55 deletions(-) diff --cc contrib/less/command.c index 88c0cb49ee77,000000000000..c1003d55fada mode 100644,000000..100644 --- a/contrib/less/command.c +++ b/contrib/less/command.c @@@ -1,2095 -1,0 +1,2096 @@@ +/* $FreeBSD$ */ +/* + * Copyright (C) 1984-2023 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. + * + * For more information, see the README file. + */ + + +/* + * User-level command processor. + */ + +#include "less.h" +#if MSDOS_COMPILER==WIN32C +#include <windows.h> +#endif +#include "position.h" +#include "option.h" +#include "cmd.h" + +extern int erase_char, erase2_char, kill_char; +extern int sigs; +extern int quit_if_one_screen; +extern int one_screen; +extern int squished; +extern int sc_width; +extern int sc_height; +extern char *kent; +extern int swindow; +extern int jump_sline; +extern int quitting; +extern int wscroll; +extern int top_scroll; +extern int ignore_eoi; +extern int secure; +extern int hshift; +extern int bs_mode; +extern int proc_backspace; +extern int show_attn; +extern int less_is_more; +extern int status_col; +extern POSITION highest_hilite; +extern POSITION start_attnpos; +extern POSITION end_attnpos; +extern char *every_first_cmd; +extern char version[]; +extern struct scrpos initial_scrpos; +extern IFILE curr_ifile; +extern void *ml_search; +extern void *ml_examine; +extern int wheel_lines; +extern int header_lines; +extern int def_search_type; +extern int updown_match; +#if SHELL_ESCAPE || PIPEC +extern void *ml_shell; +#endif +#if EDITOR +extern char *editor; +extern char *editproto; +#endif +extern int screen_trashed; /* The screen has been overwritten */ +extern int shift_count; +extern int oldbot; +extern int forw_prompt; +extern int incr_search; +extern int full_screen; +#if MSDOS_COMPILER==WIN32C +extern int utf_mode; +#endif + +#if SHELL_ESCAPE +static char *shellcmd = NULL; /* For holding last shell command for "!!" */ +#endif +static int mca; /* The multicharacter command (action) */ +static int search_type; /* The previous type of search */ +static int last_search_type; /* Type of last executed search */ +static LINENUM number; /* The number typed by the user */ +static long fraction; /* The fractional part of the number */ +static struct loption *curropt; +static int opt_lower; +static int optflag; +static int optgetname; +static POSITION bottompos; +static int save_hshift; +static int save_bs_mode; +static int save_proc_backspace; +#if PIPEC +static char pipec; +#endif + +/* Stack of ungotten chars (via ungetcc) */ +struct ungot { + struct ungot *ug_next; + LWCHAR ug_char; +}; +static struct ungot* ungot = NULL; + +static void multi_search (char *pattern, int n, int silent); + +/* + * Move the cursor to start of prompt line before executing a command. + * This looks nicer if the command takes a long time before + * updating the screen. + */ +static void cmd_exec(void) +{ + clear_attn(); + clear_bot(); + flush(); +} + +/* + * Indicate we are reading a multi-character command. + */ +static void set_mca(int action) +{ + mca = action; + clear_bot(); + clear_cmd(); +} + +/* + * Indicate we are not reading a multi-character command. + */ +static void clear_mca(void) +{ + if (mca == 0) + return; + mca = 0; +} + +/* + * Set up the display to start a new multi-character command. + */ +static void start_mca(int action, constant char *prompt, void *mlist, int cmdflags) +{ + set_mca(action); + cmd_putstr(prompt); + set_mlist(mlist, cmdflags); +} + +public int in_mca(void) +{ + return (mca != 0 && mca != A_PREFIX); +} + +/* + * Set up the display to start a new search command. + */ +static void mca_search1(void) +{ + int i; + +#if HILITE_SEARCH + if (search_type & SRCH_FILTER) + set_mca(A_FILTER); + else +#endif + if (search_type & SRCH_FORW) + set_mca(A_F_SEARCH); + else + set_mca(A_B_SEARCH); + + if (search_type & SRCH_NO_MATCH) + cmd_putstr("Non-match "); + if (search_type & SRCH_FIRST_FILE) + cmd_putstr("First-file "); + if (search_type & SRCH_PAST_EOF) + cmd_putstr("EOF-ignore "); + if (search_type & SRCH_NO_MOVE) + cmd_putstr("Keep-pos "); + if (search_type & SRCH_NO_REGEX) + cmd_putstr("Regex-off "); + if (search_type & SRCH_WRAP) + cmd_putstr("Wrap "); + for (i = 1; i <= NUM_SEARCH_COLORS; i++) + { + if (search_type & SRCH_SUBSEARCH(i)) + { - char buf[8]; ++ char buf[INT_STRLEN_BOUND(int)+8]; + SNPRINTF1(buf, sizeof(buf), "Sub-%d ", i); + cmd_putstr(buf); + } + } + +#if HILITE_SEARCH + if (search_type & SRCH_FILTER) + cmd_putstr("&/"); + else +#endif + if (search_type & SRCH_FORW) + cmd_putstr("/"); + else + cmd_putstr("?"); + forw_prompt = 0; +} + +static void mca_search(void) +{ + mca_search1(); + set_mlist(ml_search, 0); +} + +/* + * Set up the display to start a new toggle-option command. + */ +static void mca_opt_toggle(void) +{ + int no_prompt; + int flag; + char *dash; + + no_prompt = (optflag & OPT_NO_PROMPT); + flag = (optflag & ~OPT_NO_PROMPT); + dash = (flag == OPT_NO_TOGGLE) ? "_" : "-"; + + set_mca(A_OPT_TOGGLE); + cmd_putstr(dash); + if (optgetname) + cmd_putstr(dash); + if (no_prompt) + cmd_putstr("(P)"); + switch (flag) + { + case OPT_UNSET: + cmd_putstr("+"); + break; + case OPT_SET: + cmd_putstr("!"); + break; + } + forw_prompt = 0; + set_mlist(NULL, 0); +} + +/* + * Execute a multicharacter command. + */ +static void exec_mca(void) +{ + char *cbuf; + + cmd_exec(); + cbuf = get_cmdbuf(); + if (cbuf == NULL) + return; + + switch (mca) + { + case A_F_SEARCH: + case A_B_SEARCH: + multi_search(cbuf, (int) number, 0); + break; +#if HILITE_SEARCH + case A_FILTER: + search_type ^= SRCH_NO_MATCH; + set_filter_pattern(cbuf, search_type); + break; +#endif + case A_FIRSTCMD: + /* + * Skip leading spaces or + signs in the string. + */ + while (*cbuf == '+' || *cbuf == ' ') + cbuf++; + if (every_first_cmd != NULL) + free(every_first_cmd); + if (*cbuf == '\0') + every_first_cmd = NULL; + else + every_first_cmd = save(cbuf); + break; + case A_OPT_TOGGLE: + toggle_option(curropt, opt_lower, cbuf, optflag); + curropt = NULL; + break; + case A_F_BRACKET: + match_brac(cbuf[0], cbuf[1], 1, (int) number); + break; + case A_B_BRACKET: + match_brac(cbuf[1], cbuf[0], 0, (int) number); + break; +#if EXAMINE + case A_EXAMINE: + if (secure) + break; + edit_list(cbuf); +#if TAGS + /* If tag structure is loaded then clean it up. */ + cleantags(); +#endif + break; +#endif +#if SHELL_ESCAPE + case A_SHELL: + /* + * !! just uses whatever is in shellcmd. + * Otherwise, copy cmdbuf to shellcmd, + * expanding any special characters ("%" or "#"). + */ + if (*cbuf != '!') + { + if (shellcmd != NULL) + free(shellcmd); + shellcmd = fexpand(cbuf); + } + + if (secure) + break; + if (shellcmd == NULL) + lsystem("", "!done"); + else + lsystem(shellcmd, "!done"); + break; + case A_PSHELL: + if (secure) + break; + lsystem(pr_expand(cbuf), "#done"); + break; +#endif +#if PIPEC + case A_PIPE: + if (secure) + break; + (void) pipe_mark(pipec, cbuf); + error("|done", NULL_PARG); + break; +#endif + } +} + +/* + * Is a character an erase or kill char? + */ +static int is_erase_char(int c) +{ + return (c == erase_char || c == erase2_char || c == kill_char); +} + +/* + * Is a character a carriage return or newline? + */ +static int is_newline_char(int c) +{ + return (c == '\n' || c == '\r'); +} + +/* + * Handle the first char of an option (after the initial dash). + */ +static int mca_opt_first_char(int c) +{ + int no_prompt = (optflag & OPT_NO_PROMPT); + int flag = (optflag & ~OPT_NO_PROMPT); + if (flag == OPT_NO_TOGGLE) + { + switch (c) + { + case '_': + /* "__" = long option name. */ + optgetname = TRUE; + mca_opt_toggle(); + return (MCA_MORE); + } + } else + { + switch (c) + { + case '+': + /* "-+" = UNSET. */ + optflag = no_prompt | ((flag == OPT_UNSET) ? + OPT_TOGGLE : OPT_UNSET); + mca_opt_toggle(); + return (MCA_MORE); + case '!': + /* "-!" = SET */ + optflag = no_prompt | ((flag == OPT_SET) ? + OPT_TOGGLE : OPT_SET); + mca_opt_toggle(); + return (MCA_MORE); + case CONTROL('P'): + optflag ^= OPT_NO_PROMPT; + mca_opt_toggle(); + return (MCA_MORE); + case '-': + /* "--" = long option name. */ + optgetname = TRUE; + mca_opt_toggle(); + return (MCA_MORE); + } + } + /* Char was not handled here. */ + return (NO_MCA); +} + +/* + * Add a char to a long option name. + * See if we've got a match for an option name yet. + * If so, display the complete name and stop + * accepting chars until user hits RETURN. + */ +static int mca_opt_nonfirst_char(int c) +{ + char *p; + char *oname; + int err; + + if (curropt != NULL) + { + /* + * Already have a match for the name. + * Don't accept anything but erase/kill. + */ + if (is_erase_char(c)) + return (MCA_DONE); + return (MCA_MORE); + } + /* + * Add char to cmd buffer and try to match + * the option name. + */ + if (cmd_char(c) == CC_QUIT) + return (MCA_DONE); + p = get_cmdbuf(); + if (p == NULL) + return (MCA_MORE); + opt_lower = ASCII_IS_LOWER(p[0]); + err = 0; + curropt = findopt_name(&p, &oname, &err); + if (curropt != NULL) + { + /* + * Got a match. + * Remember the option and + * display the full option name. + */ + cmd_reset(); + mca_opt_toggle(); + for (p = oname; *p != '\0'; p++) + { + c = *p; + if (!opt_lower && ASCII_IS_LOWER(c)) + c = ASCII_TO_UPPER(c); + if (cmd_char(c) != CC_OK) + return (MCA_DONE); + } + } else if (err != OPT_AMBIG) + { + bell(); + } + return (MCA_MORE); +} + +/* + * Handle a char of an option toggle command. + */ +static int mca_opt_char(int c) +{ + PARG parg; + + /* + * This may be a short option (single char), + * or one char of a long option name, + * or one char of the option parameter. + */ + if (curropt == NULL && len_cmdbuf() == 0) + { + int ret = mca_opt_first_char(c); + if (ret != NO_MCA) + return (ret); + } + if (optgetname) + { + /* We're getting a long option name. */ + if (!is_newline_char(c) && c != '=') + return (mca_opt_nonfirst_char(c)); + if (curropt == NULL) + { + parg.p_string = get_cmdbuf(); + if (parg.p_string == NULL) + return (MCA_MORE); + error("There is no --%s option", &parg); + return (MCA_DONE); + } + optgetname = FALSE; + cmd_reset(); + } else + { + if (is_erase_char(c)) + return (NO_MCA); + if (curropt != NULL) + /* We're getting the option parameter. */ + return (NO_MCA); + curropt = findopt(c); + if (curropt == NULL) + { + parg.p_string = propt(c); + error("There is no %s option", &parg); + return (MCA_DONE); + } + opt_lower = ASCII_IS_LOWER(c); + } + /* + * If the option which was entered does not take a + * parameter, toggle the option immediately, + * so user doesn't have to hit RETURN. + */ + if ((optflag & ~OPT_NO_PROMPT) != OPT_TOGGLE || + !opt_has_param(curropt)) + { + toggle_option(curropt, opt_lower, "", optflag); + return (MCA_DONE); + } + /* + * Display a prompt appropriate for the option parameter. + */ + start_mca(A_OPT_TOGGLE, opt_prompt(curropt), (void*)NULL, 0); + return (MCA_MORE); +} + +/* + * Normalize search type. + */ +public int norm_search_type(int st) +{ + /* WRAP and PAST_EOF are mutually exclusive. */ + if ((st & (SRCH_PAST_EOF|SRCH_WRAP)) == (SRCH_PAST_EOF|SRCH_WRAP)) + st ^= SRCH_PAST_EOF; + return st; +} + +/* + * Handle a char of a search command. + */ +static int mca_search_char(int c) +{ + int flag = 0; + + /* + * Certain characters as the first char of + * the pattern have special meaning: + * ! Toggle the NO_MATCH flag + * * Toggle the PAST_EOF flag + * @ Toggle the FIRST_FILE flag + */ + if (len_cmdbuf() > 0) + return (NO_MCA); + + switch (c) + { + case '*': + if (less_is_more) + break; + case CONTROL('E'): /* ignore END of file */ + if (mca != A_FILTER) + flag = SRCH_PAST_EOF; ++ search_type &= ~SRCH_WRAP; + break; + case '@': + if (less_is_more) + break; + case CONTROL('F'): /* FIRST file */ + if (mca != A_FILTER) + flag = SRCH_FIRST_FILE; + break; + case CONTROL('K'): /* KEEP position */ + if (mca != A_FILTER) + flag = SRCH_NO_MOVE; + break; + case CONTROL('S'): { /* SUBSEARCH */ - char buf[32]; ++ char buf[INT_STRLEN_BOUND(int)+24]; + SNPRINTF1(buf, sizeof(buf), "Sub-pattern (1-%d):", NUM_SEARCH_COLORS); + clear_bot(); + cmd_putstr(buf); + flush(); + c = getcc(); + if (c >= '1' && c <= '0'+NUM_SEARCH_COLORS) + flag = SRCH_SUBSEARCH(c-'0'); + else + flag = -1; /* calls mca_search() below to repaint */ + break; } + case CONTROL('W'): /* WRAP around */ + if (mca != A_FILTER) + flag = SRCH_WRAP; + break; + case CONTROL('R'): /* Don't use REGULAR EXPRESSIONS */ + flag = SRCH_NO_REGEX; + break; + case CONTROL('N'): /* NOT match */ + case '!': + flag = SRCH_NO_MATCH; + break; + } + + if (flag != 0) + { + if (flag != -1) + search_type = norm_search_type(search_type ^ flag); + mca_search(); + return (MCA_MORE); + } + return (NO_MCA); +} + +/* + * Handle a character of a multi-character command. + */ +static int mca_char(int c) +{ + int ret; + + switch (mca) + { + case 0: + /* + * We're not in a multicharacter command. + */ + return (NO_MCA); + + case A_PREFIX: + /* + * In the prefix of a command. + * This not considered a multichar command + * (even tho it uses cmdbuf, etc.). + * It is handled in the commands() switch. + */ + return (NO_MCA); + + case A_DIGIT: + /* + * Entering digits of a number. + * Terminated by a non-digit. + */ + if ((c >= '0' && c <= '9') || c == '.') + break; + switch (editchar(c, ECF_PEEK|ECF_NOHISTORY|ECF_NOCOMPLETE|ECF_NORIGHTLEFT)) + { + case A_NOACTION: + /* + * Ignore this char and get another one. + */ + return (MCA_MORE); + case A_INVALID: + /* + * Not part of the number. + * End the number and treat this char + * as a normal command character. + */ + number = cmd_int(&fraction); + clear_mca(); + cmd_accept(); + return (NO_MCA); + } + break; + + case A_OPT_TOGGLE: + ret = mca_opt_char(c); + if (ret != NO_MCA) + return (ret); + break; + + case A_F_SEARCH: + case A_B_SEARCH: + case A_FILTER: + ret = mca_search_char(c); + if (ret != NO_MCA) + return (ret); + break; + + default: + /* Other multicharacter command. */ + break; + } + + /* + * The multichar command is terminated by a newline. + */ + if (is_newline_char(c)) + { + /* + * Execute the command. + */ + exec_mca(); + return (MCA_DONE); + } + + /* + * Append the char to the command buffer. + */ + if (cmd_char(c) == CC_QUIT) + /* + * Abort the multi-char command. + */ + return (MCA_DONE); + + switch (mca) + { + case A_F_BRACKET: + case A_B_BRACKET: + if (len_cmdbuf() >= 2) + { + /* + * Special case for the bracket-matching commands. + * Execute the command after getting exactly two + * characters from the user. + */ + exec_mca(); + return (MCA_DONE); + } + break; + case A_F_SEARCH: + case A_B_SEARCH: + if (incr_search) + { + /* Incremental search: do a search after every input char. */ + int st = (search_type & (SRCH_FORW|SRCH_BACK|SRCH_NO_MATCH|SRCH_NO_REGEX|SRCH_NO_MOVE|SRCH_WRAP|SRCH_SUBSEARCH_ALL)); + char *pattern = get_cmdbuf(); + if (pattern == NULL) + return (MCA_MORE); + /* + * Must save updown_match because mca_search + * reinits it. That breaks history scrolling. + * {{ This is ugly. mca_search probably shouldn't call set_mlist. }} + */ + int save_updown_match = updown_match; + cmd_exec(); + if (*pattern == '\0') + { + /* User has backspaced to an empty pattern. */ + undo_search(1); + } else + { + if (search(st | SRCH_INCR, pattern, 1) != 0) + /* No match, invalid pattern, etc. */ + undo_search(1); + } + /* Redraw the search prompt and search string. */ + if (!full_screen) + { + clear(); + repaint(); + } + mca_search1(); + updown_match = save_updown_match; + cmd_repaint(NULL); + } + break; + } + + /* + * Need another character. + */ + return (MCA_MORE); +} + +/* + * Discard any buffered file data. + */ +static void clear_buffers(void) +{ + if (!(ch_getflags() & CH_CANSEEK)) + return; + ch_flush(); + clr_linenum(); +#if HILITE_SEARCH + clr_hilite(); +#endif +} + +/* + * Make sure the screen is displayed. + */ +static void make_display(void) +{ + /* + * If not full_screen, we can't rely on scrolling to fill the screen. + * We need to clear and repaint screen before any change. + */ + if (!full_screen && !(quit_if_one_screen && one_screen)) + clear(); + /* + * If nothing is displayed yet, display starting from initial_scrpos. + */ + if (empty_screen()) + { + if (initial_scrpos.pos == NULL_POSITION) + jump_loc(ch_zero(), 1); + else + jump_loc(initial_scrpos.pos, initial_scrpos.ln); + } else if (screen_trashed || !full_screen) + { + int save_top_scroll = top_scroll; + int save_ignore_eoi = ignore_eoi; + top_scroll = 1; + ignore_eoi = 0; + if (screen_trashed == 2) + { + /* Special case used by ignore_eoi: re-open the input file + * and jump to the end of the file. */ + reopen_curr_ifile(); + jump_forw(); + } + repaint(); + top_scroll = save_top_scroll; + ignore_eoi = save_ignore_eoi; + } +} + +/* + * Display the appropriate prompt. + */ +static void prompt(void) +{ + constant char *p; + + if (ungot != NULL && ungot->ug_char != CHAR_END_COMMAND) + { + /* + * No prompt necessary if commands are from + * ungotten chars rather than from the user. + */ + return; + } + + /* + * Make sure the screen is displayed. + */ + make_display(); + bottompos = position(BOTTOM_PLUS_ONE); + + /* + * If we've hit EOF on the last file and the -E flag is set, quit. + */ + if (get_quit_at_eof() == OPT_ONPLUS && + eof_displayed() && !(ch_getflags() & CH_HELPFILE) && + next_ifile(curr_ifile) == NULL_IFILE) + quit(QUIT_OK); + + /* + * If the entire file is displayed and the -F flag is set, quit. + */ + if (quit_if_one_screen && + entire_file_displayed() && !(ch_getflags() & CH_HELPFILE) && + next_ifile(curr_ifile) == NULL_IFILE) + quit(QUIT_OK); + quit_if_one_screen = FALSE; /* only get one chance at this */ + +#if MSDOS_COMPILER==WIN32C + /* + * In Win32, display the file name in the window title. + */ + if (!(ch_getflags() & CH_HELPFILE)) + { + WCHAR w[MAX_PATH+16]; + p = pr_expand("Less?f - %f."); + MultiByteToWideChar(CP_ACP, 0, p, -1, w, sizeof(w)/sizeof(*w)); + SetConsoleTitleW(w); + } +#endif + + /* + * Select the proper prompt and display it. + */ + /* + * If the previous action was a forward movement, + * don't clear the bottom line of the display; + * just print the prompt since the forward movement guarantees + * that we're in the right position to display the prompt. + * Clearing the line could cause a problem: for example, if the last + * line displayed ended at the right screen edge without a newline, + * then clearing would clear the last displayed line rather than + * the prompt line. + */ + if (!forw_prompt) + clear_bot(); + clear_cmd(); + forw_prompt = 0; + p = pr_string(); +#if HILITE_SEARCH + if (is_filtering()) + putstr("& "); +#endif + if (p == NULL || *p == '\0') + { + at_enter(AT_NORMAL|AT_COLOR_PROMPT); + putchr(':'); + at_exit(); + } else + { +#if MSDOS_COMPILER==WIN32C + WCHAR w[MAX_PATH*2]; + char a[MAX_PATH*2]; + MultiByteToWideChar(CP_ACP, 0, p, -1, w, sizeof(w)/sizeof(*w)); + WideCharToMultiByte(utf_mode ? CP_UTF8 : GetConsoleOutputCP(), + 0, w, -1, a, sizeof(a), NULL, NULL); + p = a; +#endif + load_line(p); + put_line(); + } + clear_eol(); +} + +/* + * Display the less version message. + */ +public void dispversion(void) +{ + PARG parg; + + parg.p_string = version; + error("less %s", &parg); +} + +/* + * Return a character to complete a partial command, if possible. + */ +static LWCHAR getcc_end_command(void) +{ + switch (mca) + { + case A_DIGIT: + /* We have a number but no command. Treat as #g. */ + return ('g'); + case A_F_SEARCH: + case A_B_SEARCH: + case A_FILTER: + /* We have "/string" but no newline. Add the \n. */ + return ('\n'); + default: + /* Some other incomplete command. Let user complete it. */ + return ((ungot == NULL) ? getchr() : 0); + } +} + +/* + * Get command character. + * The character normally comes from the keyboard, + * but may come from ungotten characters + * (characters previously given to ungetcc or ungetsc). *** 2666 LINES SKIPPED ***