From 2c54fc604be0e4ae0effe0fd6ad8110a2d64e244 Mon Sep 17 00:00:00 2001 From: Lauri Tirkkonen Date: Sun, 28 Jan 2018 12:46:00 +0200 Subject: [PATCH] import less(1) taken from openbsd as they already use bsd make; gdamore's upstream build system is more complicated. --- bin/Makefile | 7 +- bin/less/LICENSE | 27 + bin/less/Makefile | 5 + bin/less/Makefile.inc | 16 + bin/less/brac.c | 90 + bin/less/ch.c | 834 +++++++++ bin/less/charset.c | 879 +++++++++ bin/less/charset.h | 20 + bin/less/cmd.h | 135 ++ bin/less/cmdbuf.c | 1330 ++++++++++++++ bin/less/command.c | 1642 +++++++++++++++++ bin/less/compat/include/sys/time.h | 8 + bin/less/cvt.c | 103 ++ bin/less/decode.c | 781 ++++++++ bin/less/defines.h | 36 + bin/less/edit.c | 722 ++++++++ bin/less/filename.c | 756 ++++++++ bin/less/forwback.c | 362 ++++ bin/less/funcs.h | 286 +++ bin/less/ifile.c | 302 +++ bin/less/input.c | 403 +++++ bin/less/jump.c | 278 +++ bin/less/less.h | 194 ++ bin/less/less.hlp | 229 +++ bin/less/less/Makefile | 32 + bin/less/less/less.1 | 1913 ++++++++++++++++++++ bin/less/less/more.1 | 322 ++++ bin/less/lesskey.c | 803 ++++++++ bin/less/lesskey.h | 40 + bin/less/lesskey/Makefile | 12 + bin/less/lesskey/lesskey.1 | 447 +++++ bin/less/line.c | 1125 ++++++++++++ bin/less/linenum.c | 451 +++++ bin/less/linenum.dep | 34 + bin/less/lsystem.c | 249 +++ bin/less/main.c | 398 ++++ bin/less/mark.c | 245 +++ bin/less/more.hlp | 75 + bin/less/optfunc.c | 547 ++++++ bin/less/option.c | 683 +++++++ bin/less/option.h | 65 + bin/less/opttbl.c | 556 ++++++ bin/less/os.c | 98 + bin/less/output.c | 330 ++++ bin/less/pattern.c | 136 ++ bin/less/pattern.h | 15 + bin/less/position.c | 222 +++ bin/less/position.h | 19 + bin/less/prompt.c | 528 ++++++ bin/less/screen.c | 780 ++++++++ bin/less/screen.d | 113 ++ bin/less/search.c | 1101 +++++++++++ bin/less/signal.c | 161 ++ bin/less/tags.c | 437 +++++ bin/less/ttyin.c | 69 + bin/less/version.c | 15 + share/man/man1/Makefile | 1 - share/man/man1/more.1 | 508 ------ usr/src/Targetdirs | 1 + usr/src/cmd/Makefile | 2 - usr/src/cmd/more/Makefile | 54 - usr/src/cmd/more/more.c | 1803 ------------------ usr/src/cmd/more/more.help | 25 - usr/src/msg/Makefile | 3 - usr/src/pkg/manifests/SUNWcs.man1.inc | 2 + .../consolidation-osnet-osnet-message-files.mf | 1 - usr/src/pkg/manifests/system-core-os.mf | 9 +- usr/src/pkg/manifests/text-less.mf | 3 + 68 files changed, 21475 insertions(+), 2403 deletions(-) create mode 100644 bin/less/LICENSE create mode 100644 bin/less/Makefile create mode 100644 bin/less/Makefile.inc create mode 100644 bin/less/brac.c create mode 100644 bin/less/ch.c create mode 100644 bin/less/charset.c create mode 100644 bin/less/charset.h create mode 100644 bin/less/cmd.h create mode 100644 bin/less/cmdbuf.c create mode 100644 bin/less/command.c create mode 100644 bin/less/compat/include/sys/time.h create mode 100644 bin/less/cvt.c create mode 100644 bin/less/decode.c create mode 100644 bin/less/defines.h create mode 100644 bin/less/edit.c create mode 100644 bin/less/filename.c create mode 100644 bin/less/forwback.c create mode 100644 bin/less/funcs.h create mode 100644 bin/less/ifile.c create mode 100644 bin/less/input.c create mode 100644 bin/less/jump.c create mode 100644 bin/less/less.h create mode 100644 bin/less/less.hlp create mode 100644 bin/less/less/Makefile create mode 100644 bin/less/less/less.1 create mode 100644 bin/less/less/more.1 create mode 100644 bin/less/lesskey.c create mode 100644 bin/less/lesskey.h create mode 100644 bin/less/lesskey/Makefile create mode 100644 bin/less/lesskey/lesskey.1 create mode 100644 bin/less/line.c create mode 100644 bin/less/linenum.c create mode 100644 bin/less/linenum.dep create mode 100644 bin/less/lsystem.c create mode 100644 bin/less/main.c create mode 100644 bin/less/mark.c create mode 100644 bin/less/more.hlp create mode 100644 bin/less/optfunc.c create mode 100644 bin/less/option.c create mode 100644 bin/less/option.h create mode 100644 bin/less/opttbl.c create mode 100644 bin/less/os.c create mode 100644 bin/less/output.c create mode 100644 bin/less/pattern.c create mode 100644 bin/less/pattern.h create mode 100644 bin/less/position.c create mode 100644 bin/less/position.h create mode 100644 bin/less/prompt.c create mode 100644 bin/less/screen.c create mode 100644 bin/less/screen.d create mode 100644 bin/less/search.c create mode 100644 bin/less/signal.c create mode 100644 bin/less/tags.c create mode 100644 bin/less/ttyin.c create mode 100644 bin/less/version.c delete mode 100644 share/man/man1/more.1 delete mode 100644 usr/src/cmd/more/Makefile delete mode 100644 usr/src/cmd/more/more.c delete mode 100644 usr/src/cmd/more/more.help create mode 100644 usr/src/pkg/manifests/text-less.mf diff --git a/bin/Makefile b/bin/Makefile index 86939ac74d..5597054362 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -1,5 +1,6 @@ -SUBDIR= awk banner cat clear dis echo env false grep id infocmp localedef mandoc make nc openssl pax \ - puname pwd sed size stat tabs tic time toe tput true tset tsort tzselect uudecode uuencode \ - uname utmp_update xinstall yes zic +SUBDIR= awk banner cat clear dis echo env false grep id infocmp less \ + localedef mandoc make nc openssl pax puname pwd sed size stat tabs \ + tic time toe tput true tset tsort tzselect uudecode uuencode uname \ + utmp_update xinstall yes zic .include diff --git a/bin/less/LICENSE b/bin/less/LICENSE new file mode 100644 index 0000000000..3fe715f170 --- /dev/null +++ b/bin/less/LICENSE @@ -0,0 +1,27 @@ + Less License + ------------ + +Less +Copyright (C) 1984-2012 Mark Nudelman + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice in the documentation and/or other materials provided with + the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/bin/less/Makefile b/bin/less/Makefile new file mode 100644 index 0000000000..4bb557fd85 --- /dev/null +++ b/bin/less/Makefile @@ -0,0 +1,5 @@ +# $OpenBSD: Makefile,v 1.1 2011/09/16 18:12:09 shadchin Exp $ + +SUBDIR= less lesskey + +.include diff --git a/bin/less/Makefile.inc b/bin/less/Makefile.inc new file mode 100644 index 0000000000..e2e6d01762 --- /dev/null +++ b/bin/less/Makefile.inc @@ -0,0 +1,16 @@ +# $OpenBSD: Makefile.inc,v 1.4 2015/11/23 09:14:44 nicm Exp $ + +COPTS+= -Werror-implicit-function-declaration + +UNLEASHED_OBJ?= /usr/obj/${MACHINE} +# we don't have this tcsetattr flag +COPTS+= -DTCSASOFT=0 +# timespeccmp +COPTS+= -I${.CURDIR:H}/compat/include +# pledge +LCRYPTO_SRC= ${SRCTOP}/lib/libcrypto +COPTS+= -I${LCRYPTO_SRC}/compat/include +# XXX should move this to sys.mk +SHAREDIR?= /usr/share +.include +.include "../Makefile.inc" diff --git a/bin/less/brac.c b/bin/less/brac.c new file mode 100644 index 0000000000..b27ea6162f --- /dev/null +++ b/bin/less/brac.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to perform bracket matching functions. + */ + +#include "less.h" +#include "position.h" + +/* + * Try to match the n-th open bracket + * which appears in the top displayed line (forwdir), + * or the n-th close bracket + * which appears in the bottom displayed line (!forwdir). + * The characters which serve as "open bracket" and + * "close bracket" are given. + */ +void +match_brac(int obrac, int cbrac, int forwdir, int n) +{ + int c; + int nest; + off_t pos; + int (*chget)(void); + + /* + * Seek to the line containing the open bracket. + * This is either the top or bottom line on the screen, + * depending on the type of bracket. + */ + pos = position((forwdir) ? TOP : BOTTOM); + if (pos == -1 || ch_seek(pos)) { + if (forwdir) + error("Nothing in top line", NULL); + else + error("Nothing in bottom line", NULL); + return; + } + + /* + * Look thru the line to find the open bracket to match. + */ + do { + if ((c = ch_forw_get()) == '\n' || c == EOI) { + if (forwdir) + error("No bracket in top line", NULL); + else + error("No bracket in bottom line", NULL); + return; + } + } while (c != obrac || --n > 0); + + /* + * Position the file just "after" the open bracket + * (in the direction in which we will be searching). + * If searching forward, we are already after the bracket. + * If searching backward, skip back over the open bracket. + */ + if (!forwdir) + (void) ch_back_get(); + + /* + * Search the file for the matching bracket. + */ + chget = (forwdir) ? ch_forw_get : ch_back_get; + nest = 0; + while ((c = (*chget)()) != EOI) { + if (c == obrac) { + nest++; + } else if (c == cbrac && --nest < 0) { + /* + * Found the matching bracket. + * If searching backward, put it on the top line. + * If searching forward, put it on the bottom line. + */ + jump_line_loc(ch_tell(), forwdir ? -1 : 1); + return; + } + } + error("No matching bracket", NULL); +} diff --git a/bin/less/ch.c b/bin/less/ch.c new file mode 100644 index 0000000000..58f08d3deb --- /dev/null +++ b/bin/less/ch.c @@ -0,0 +1,834 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Low level character input from the input file. + * We use these special purpose routines which optimize moving + * both forward and backward from the current read pointer. + */ + +#include + +#include "less.h" + +extern dev_t curr_dev; +extern ino_t curr_ino; +extern int less_is_more; + +typedef off_t BLOCKNUM; + +int ignore_eoi; + +/* + * Pool of buffers holding the most recently used blocks of the input file. + * The buffer pool is kept as a doubly-linked circular list, + * in order from most- to least-recently used. + * The circular list is anchored by the file state "thisfile". + */ +struct bufnode { + struct bufnode *next, *prev; + struct bufnode *hnext, *hprev; +}; + +#define LBUFSIZE 8192 +struct buf { + struct bufnode node; + BLOCKNUM block; + unsigned int datasize; + unsigned char data[LBUFSIZE]; +}; +#define bufnode_buf(bn) ((struct buf *)bn) + +/* + * The file state is maintained in a filestate structure. + * A pointer to the filestate is kept in the ifile structure. + */ +#define BUFHASH_SIZE 64 +struct filestate { + struct bufnode buflist; + struct bufnode hashtbl[BUFHASH_SIZE]; + int file; + int flags; + off_t fpos; + int nbufs; + BLOCKNUM block; + unsigned int offset; + off_t fsize; +}; + +#define ch_bufhead thisfile->buflist.next +#define ch_buftail thisfile->buflist.prev +#define ch_nbufs thisfile->nbufs +#define ch_block thisfile->block +#define ch_offset thisfile->offset +#define ch_fpos thisfile->fpos +#define ch_fsize thisfile->fsize +#define ch_flags thisfile->flags +#define ch_file thisfile->file + +#define END_OF_CHAIN (&thisfile->buflist) +#define END_OF_HCHAIN(h) (&thisfile->hashtbl[h]) +#define BUFHASH(blk) ((blk) & (BUFHASH_SIZE-1)) + +/* + * Macros to manipulate the list of buffers in thisfile->buflist. + */ +#define FOR_BUFS(bn) \ + for ((bn) = ch_bufhead; (bn) != END_OF_CHAIN; (bn) = (bn)->next) + +#define BUF_RM(bn) \ + (bn)->next->prev = (bn)->prev; \ + (bn)->prev->next = (bn)->next; + +#define BUF_INS_HEAD(bn) \ + (bn)->next = ch_bufhead; \ + (bn)->prev = END_OF_CHAIN; \ + ch_bufhead->prev = (bn); \ + ch_bufhead = (bn); + +#define BUF_INS_TAIL(bn) \ + (bn)->next = END_OF_CHAIN; \ + (bn)->prev = ch_buftail; \ + ch_buftail->next = (bn); \ + ch_buftail = (bn); + +/* + * Macros to manipulate the list of buffers in thisfile->hashtbl[n]. + */ +#define FOR_BUFS_IN_CHAIN(h, bn) \ + for ((bn) = thisfile->hashtbl[h].hnext; \ + (bn) != END_OF_HCHAIN(h); (bn) = (bn)->hnext) + +#define BUF_HASH_RM(bn) \ + (bn)->hnext->hprev = (bn)->hprev; \ + (bn)->hprev->hnext = (bn)->hnext; + +#define BUF_HASH_INS(bn, h) \ + (bn)->hnext = thisfile->hashtbl[h].hnext; \ + (bn)->hprev = END_OF_HCHAIN(h); \ + thisfile->hashtbl[h].hnext->hprev = (bn); \ + thisfile->hashtbl[h].hnext = (bn); + +static struct filestate *thisfile; +static int ch_ungotchar = -1; +static int maxbufs = -1; + +extern int autobuf; +extern volatile sig_atomic_t sigs; +extern int secure; +extern int screen_trashed; +extern int follow_mode; +extern IFILE curr_ifile; +extern int logfile; +extern char *namelogfile; + +static int ch_addbuf(void); + + +/* + * Get the character pointed to by the read pointer. + */ +int +ch_get(void) +{ + struct buf *bp; + struct bufnode *bn; + int n; + int slept; + int h; + off_t pos; + off_t len; + + if (thisfile == NULL) + return (EOI); + + /* + * Quick check for the common case where + * the desired char is in the head buffer. + */ + if (ch_bufhead != END_OF_CHAIN) { + bp = bufnode_buf(ch_bufhead); + if (ch_block == bp->block && ch_offset < bp->datasize) + return (bp->data[ch_offset]); + } + + slept = FALSE; + + /* + * Look for a buffer holding the desired block. + */ + h = BUFHASH(ch_block); + FOR_BUFS_IN_CHAIN(h, bn) { + bp = bufnode_buf(bn); + if (bp->block == ch_block) { + if (ch_offset >= bp->datasize) + /* + * Need more data in this buffer. + */ + break; + goto found; + } + } + if (bn == END_OF_HCHAIN(h)) { + /* + * Block is not in a buffer. + * Take the least recently used buffer + * and read the desired block into it. + * If the LRU buffer has data in it, + * then maybe allocate a new buffer. + */ + if (ch_buftail == END_OF_CHAIN || + bufnode_buf(ch_buftail)->block != -1) { + /* + * There is no empty buffer to use. + * Allocate a new buffer if: + * 1. We can't seek on this file and -b is not in + * effect; or + * 2. We haven't allocated the max buffers for this + * file yet. + */ + if ((autobuf && !(ch_flags & CH_CANSEEK)) || + (maxbufs < 0 || ch_nbufs < maxbufs)) + if (ch_addbuf()) + /* + * Allocation failed: turn off autobuf. + */ + autobuf = OPT_OFF; + } + bn = ch_buftail; + bp = bufnode_buf(bn); + BUF_HASH_RM(bn); /* Remove from old hash chain. */ + bp->block = ch_block; + bp->datasize = 0; + BUF_HASH_INS(bn, h); /* Insert into new hash chain. */ + } + +read_more: + pos = (ch_block * LBUFSIZE) + bp->datasize; + if ((len = ch_length()) != -1 && pos >= len) + /* + * At end of file. + */ + return (EOI); + + if (pos != ch_fpos) { + /* + * Not at the correct position: must seek. + * If input is a pipe, we're in trouble (can't seek on a pipe). + * Some data has been lost: just return "?". + */ + if (!(ch_flags & CH_CANSEEK)) + return ('?'); + if (lseek(ch_file, (off_t)pos, SEEK_SET) == (off_t)-1) { + error("seek error", NULL); + clear_eol(); + return (EOI); + } + ch_fpos = pos; + } + + /* + * Read the block. + * If we read less than a full block, that's ok. + * We use partial block and pick up the rest next time. + */ + if (ch_ungotchar != -1) { + bp->data[bp->datasize] = (unsigned char)ch_ungotchar; + n = 1; + ch_ungotchar = -1; + } else { + n = iread(ch_file, &bp->data[bp->datasize], + (unsigned int)(LBUFSIZE - bp->datasize)); + } + + if (n == READ_INTR) + return (EOI); + if (n < 0) { + error("read error", NULL); + clear_eol(); + n = 0; + } + + /* + * If we have a log file, write the new data to it. + */ + if (!secure && logfile >= 0 && n > 0) + (void) write(logfile, (char *)&bp->data[bp->datasize], n); + + ch_fpos += n; + bp->datasize += n; + + /* + * If we have read to end of file, set ch_fsize to indicate + * the position of the end of file. + */ + if (n == 0) { + ch_fsize = pos; + if (ignore_eoi) { + /* + * We are ignoring EOF. + * Wait a while, then try again. + */ + if (!slept) { + PARG parg; + parg.p_string = wait_message(); + ierror("%s", &parg); + } + sleep(1); + slept = TRUE; + + if (follow_mode == FOLLOW_NAME) { + /* + * See whether the file's i-number has changed. + * If so, force the file to be closed and + * reopened. + */ + struct stat st; + int r = stat(get_filename(curr_ifile), &st); + if (r == 0 && (st.st_ino != curr_ino || + st.st_dev != curr_dev)) { + /* + * screen_trashed=2 causes + * make_display to reopen the file. + */ + screen_trashed = 2; + return (EOI); + } + } + } + if (sigs) + return (EOI); + } + +found: + if (ch_bufhead != bn) { + /* + * Move the buffer to the head of the buffer chain. + * This orders the buffer chain, most- to least-recently used. + */ + BUF_RM(bn); + BUF_INS_HEAD(bn); + + /* + * Move to head of hash chain too. + */ + BUF_HASH_RM(bn); + BUF_HASH_INS(bn, h); + } + + if (ch_offset >= bp->datasize) + /* + * After all that, we still don't have enough data. + * Go back and try again. + */ + goto read_more; + + return (bp->data[ch_offset]); +} + +/* + * ch_ungetchar is a rather kludgy and limited way to push + * a single char onto an input file descriptor. + */ +void +ch_ungetchar(int c) +{ + if (c != -1 && ch_ungotchar != -1) + error("ch_ungetchar overrun", NULL); + ch_ungotchar = c; +} + +/* + * Close the logfile. + * If we haven't read all of standard input into it, do that now. + */ +void +end_logfile(void) +{ + static int tried = FALSE; + + if (logfile < 0) + return; + if (!tried && ch_fsize == -1) { + tried = TRUE; + ierror("Finishing logfile", NULL); + while (ch_forw_get() != EOI) + if (ABORT_SIGS()) + break; + } + close(logfile); + logfile = -1; + namelogfile = NULL; +} + +/* + * Start a log file AFTER less has already been running. + * Invoked from the - command; see toggle_option(). + * Write all the existing buffered data to the log file. + */ +void +sync_logfile(void) +{ + struct buf *bp; + struct bufnode *bn; + int warned = FALSE; + BLOCKNUM block; + BLOCKNUM nblocks; + + nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE; + for (block = 0; block < nblocks; block++) { + int wrote = FALSE; + FOR_BUFS(bn) { + bp = bufnode_buf(bn); + if (bp->block == block) { + (void) write(logfile, (char *)bp->data, + bp->datasize); + wrote = TRUE; + break; + } + } + if (!wrote && !warned) { + error("Warning: log file is incomplete", NULL); + warned = TRUE; + } + } +} + +/* + * Determine if a specific block is currently in one of the buffers. + */ +static int +buffered(BLOCKNUM block) +{ + struct buf *bp; + struct bufnode *bn; + int h; + + h = BUFHASH(block); + FOR_BUFS_IN_CHAIN(h, bn) { + bp = bufnode_buf(bn); + if (bp->block == block) + return (TRUE); + } + return (FALSE); +} + +/* + * Seek to a specified position in the file. + * Return 0 if successful, non-zero if can't seek there. + */ +int +ch_seek(off_t pos) +{ + BLOCKNUM new_block; + off_t len; + + if (thisfile == NULL) + return (0); + + len = ch_length(); + if (pos < ch_zero() || (len != -1 && pos > len)) + return (1); + + new_block = pos / LBUFSIZE; + if (!(ch_flags & CH_CANSEEK) && pos != ch_fpos && + !buffered(new_block)) { + if (ch_fpos > pos) + return (1); + while (ch_fpos < pos) { + if (ch_forw_get() == EOI) + return (1); + if (ABORT_SIGS()) + return (1); + } + return (0); + } + /* + * Set read pointer. + */ + ch_block = new_block; + ch_offset = pos % LBUFSIZE; + return (0); +} + +/* + * Seek to the end of the file. + */ +int +ch_end_seek(void) +{ + off_t len; + + if (thisfile == NULL) + return (0); + + if (ch_flags & CH_CANSEEK) + ch_fsize = filesize(ch_file); + + len = ch_length(); + if (len != -1) + return (ch_seek(len)); + + /* + * Do it the slow way: read till end of data. + */ + while (ch_forw_get() != EOI) + if (ABORT_SIGS()) + return (1); + return (0); +} + +/* + * Seek to the beginning of the file, or as close to it as we can get. + * We may not be able to seek there if input is a pipe and the + * beginning of the pipe is no longer buffered. + */ +int +ch_beg_seek(void) +{ + struct bufnode *bn; + struct bufnode *firstbn; + + /* + * Try a plain ch_seek first. + */ + if (ch_seek(ch_zero()) == 0) + return (0); + + /* + * Can't get to position 0. + * Look thru the buffers for the one closest to position 0. + */ + firstbn = ch_bufhead; + if (firstbn == END_OF_CHAIN) + return (1); + FOR_BUFS(bn) { + if (bufnode_buf(bn)->block < bufnode_buf(firstbn)->block) + firstbn = bn; + } + ch_block = bufnode_buf(firstbn)->block; + ch_offset = 0; + return (0); +} + +/* + * Return the length of the file, if known. + */ +off_t +ch_length(void) +{ + if (thisfile == NULL) + return (-1); + if (ignore_eoi) + return (-1); + if (ch_flags & CH_NODATA) + return (0); + return (ch_fsize); +} + +/* + * Return the current position in the file. + */ +off_t +ch_tell(void) +{ + if (thisfile == NULL) + return (-1); + return ((ch_block * LBUFSIZE) + ch_offset); +} + +/* + * Get the current char and post-increment the read pointer. + */ +int +ch_forw_get(void) +{ + int c; + + if (thisfile == NULL) + return (EOI); + c = ch_get(); + if (c == EOI) + return (EOI); + if (ch_offset < LBUFSIZE-1) { + ch_offset++; + } else { + ch_block ++; + ch_offset = 0; + } + return (c); +} + +/* + * Pre-decrement the read pointer and get the new current char. + */ +int +ch_back_get(void) +{ + if (thisfile == NULL) + return (EOI); + if (ch_offset > 0) { + ch_offset --; + } else { + if (ch_block <= 0) + return (EOI); + if (!(ch_flags & CH_CANSEEK) && !buffered(ch_block-1)) + return (EOI); + ch_block--; + ch_offset = LBUFSIZE-1; + } + return (ch_get()); +} + +/* + * Set max amount of buffer space. + * bufspace is in units of 1024 bytes. -1 mean no limit. + */ +void +ch_setbufspace(int bufspace) +{ + if (bufspace < 0) { + maxbufs = -1; + } else { + maxbufs = ((bufspace * 1024) + LBUFSIZE-1) / LBUFSIZE; + if (maxbufs < 1) + maxbufs = 1; + } +} + +/* + * Flush (discard) any saved file state, including buffer contents. + */ +void +ch_flush(void) +{ + struct bufnode *bn; + + if (thisfile == NULL) + return; + + if (!(ch_flags & CH_CANSEEK)) { + /* + * If input is a pipe, we don't flush buffer contents, + * since the contents can't be recovered. + */ + ch_fsize = -1; + return; + } + + /* + * Initialize all the buffers. + */ + FOR_BUFS(bn) { + bufnode_buf(bn)->block = -1; + } + + /* + * Figure out the size of the file, if we can. + */ + ch_fsize = filesize(ch_file); + + /* + * Seek to a known position: the beginning of the file. + */ + ch_fpos = 0; + ch_block = 0; /* ch_fpos / LBUFSIZE; */ + ch_offset = 0; /* ch_fpos % LBUFSIZE; */ + +#if 1 + /* + * This is a kludge to workaround a Linux kernel bug: files in + * /proc have a size of 0 according to fstat() but have readable + * data. They are sometimes, but not always, seekable. + * Force them to be non-seekable here. + */ + if (ch_fsize == 0) { + ch_fsize = -1; + ch_flags &= ~CH_CANSEEK; + } +#endif + + if (lseek(ch_file, (off_t)0, SEEK_SET) == (off_t)-1) { + /* + * Warning only; even if the seek fails for some reason, + * there's a good chance we're at the beginning anyway. + * {{ I think this is bogus reasoning. }} + */ + error("seek error to 0", NULL); + } +} + +/* + * Allocate a new buffer. + * The buffer is added to the tail of the buffer chain. + */ +static int +ch_addbuf(void) +{ + struct buf *bp; + struct bufnode *bn; + + /* + * Allocate and initialize a new buffer and link it + * onto the tail of the buffer list. + */ + bp = calloc(1, sizeof (struct buf)); + if (bp == NULL) + return (1); + ch_nbufs++; + bp->block = -1; + bn = &bp->node; + + BUF_INS_TAIL(bn); + BUF_HASH_INS(bn, 0); + return (0); +} + +/* + * + */ +static void +init_hashtbl(void) +{ + int h; + + for (h = 0; h < BUFHASH_SIZE; h++) { + thisfile->hashtbl[h].hnext = END_OF_HCHAIN(h); + thisfile->hashtbl[h].hprev = END_OF_HCHAIN(h); + } +} + +/* + * Delete all buffers for this file. + */ +static void +ch_delbufs(void) +{ + struct bufnode *bn; + + while (ch_bufhead != END_OF_CHAIN) { + bn = ch_bufhead; + BUF_RM(bn); + free(bufnode_buf(bn)); + } + ch_nbufs = 0; + init_hashtbl(); +} + +/* + * Is it possible to seek on a file descriptor? + */ +int +seekable(int f) +{ + return (lseek(f, (off_t)1, SEEK_SET) != (off_t)-1); +} + +/* + * Force EOF to be at the current read position. + * This is used after an ignore_eof read, during which the EOF may change. + */ +void +ch_set_eof(void) +{ + ch_fsize = ch_fpos; +} + + +/* + * Initialize file state for a new file. + */ +void +ch_init(int f, int flags) +{ + /* + * See if we already have a filestate for this file. + */ + thisfile = get_filestate(curr_ifile); + if (thisfile == NULL) { + /* + * Allocate and initialize a new filestate. + */ + thisfile = calloc(1, sizeof (struct filestate)); + thisfile->buflist.next = thisfile->buflist.prev = END_OF_CHAIN; + thisfile->nbufs = 0; + thisfile->flags = 0; + thisfile->fpos = 0; + thisfile->block = 0; + thisfile->offset = 0; + thisfile->file = -1; + thisfile->fsize = -1; + ch_flags = flags; + init_hashtbl(); + /* + * Try to seek; set CH_CANSEEK if it works. + */ + if ((flags & CH_CANSEEK) && !seekable(f)) + ch_flags &= ~CH_CANSEEK; + set_filestate(curr_ifile, (void *) thisfile); + } + if (thisfile->file == -1) + thisfile->file = f; + ch_flush(); +} + +/* + * Close a filestate. + */ +void +ch_close(void) +{ + int keepstate = FALSE; + + if (thisfile == NULL) + return; + + if (ch_flags & (CH_CANSEEK|CH_POPENED|CH_HELPFILE)) { + /* + * We can seek or re-open, so we don't need to keep buffers. + */ + ch_delbufs(); + } else { + keepstate = TRUE; + } + if (!(ch_flags & CH_KEEPOPEN)) { + /* + * We don't need to keep the file descriptor open + * (because we can re-open it.) + * But don't really close it if it was opened via popen(), + * because pclose() wants to close it. + */ + if (!(ch_flags & CH_POPENED)) + close(ch_file); + ch_file = -1; + } else { + keepstate = TRUE; + } + if (!keepstate) { + /* + * We don't even need to keep the filestate structure. + */ + free(thisfile); + thisfile = NULL; + set_filestate(curr_ifile, NULL); + } +} + +/* + * Return ch_flags for the current file. + */ +int +ch_getflags(void) +{ + if (thisfile == NULL) + return (0); + return (ch_flags); +} diff --git a/bin/less/charset.c b/bin/less/charset.c new file mode 100644 index 0000000000..4c8db50d55 --- /dev/null +++ b/bin/less/charset.c @@ -0,0 +1,879 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Functions to define the character set + * and do things specific to the character set. + */ + +#include +#include +#include + +#include "charset.h" +#include "less.h" + +int utf_mode = 0; + +static const char *binfmt = NULL; +static const char *utfbinfmt = NULL; +int binattr = AT_STANDOUT; + +static int +checkfmt(const char *s) +{ + char c; + int seen = 0; + + /* %[][][.][] */ + + if (*s == '*') { /* skip leading attribute if there */ + s++; + if (*s == '\0' || strchr("dksu", *s) == NULL) { + return (-1); + } + s++; + } + + while ((c = *s++) != 0) { + if (!isascii(c) || !isprint(c)) { + return (-1); + } + if (c != '%') { + continue; + } + if (*s == '%') { /* % escaped with second % */ + s++; + continue; + } + if (seen) { + return (-1); /* 2nd % format item! */ + } + /* skip flags */ + while (*s != '\0' && strchr(" '+-0#", *s) != NULL) { + s++; + } + while (isdigit(*s)) { /* skip width */ + s++; + } + if (*s == '.') { /* skip precision */ + s++; + while (isdigit(*s)) { + s++; + } + } + /* type width specifications, only "l", "h", and "hh" valid */ + if (*s == 'l') { + s++; + } else if (*s == 'h') { + s++; + if (*s == 'h') + s++; + } + + if (*s == '\0' || strchr("cCdiouxX", *s) == NULL) { + /* bad or evil format character (%s, %n, etc.) */ + return (-1); + } + + seen = 1; + } + + return (0); +} + +/* + * Define the printing format for control (or binary utf) chars. + */ +static void +setbinfmt(char *e, const char **fmtvarptr, const char *default_fmt) +{ + const char *s; + + if (((s = lgetenv(e)) == NULL) || (*s == 0)) { + s = default_fmt; + goto attr; + } + + if (s != NULL && *s != 0) { + if (checkfmt(s) < 0) { + s = default_fmt; + goto attr; + } + } + + /* + * Select the attributes if it starts with "*". + */ +attr: + if (*s == '*') { + switch (s[1]) { + case 'd': binattr = AT_BOLD; break; + case 'k': binattr = AT_BLINK; break; + case 's': binattr = AT_STANDOUT; break; + case 'u': binattr = AT_UNDERLINE; break; + default: binattr = AT_NORMAL; break; + } + s += 2; + } + *fmtvarptr = s; +} + +/* + * Initialize charset data structures. + */ +void +init_charset(void) +{ + char *s; + + setlocale(LC_ALL, ""); + + s = nl_langinfo(CODESET); + if (s && strcasecmp(s, "utf-8") == 0) + utf_mode = 1; + + setbinfmt("LESSBINFMT", &binfmt, "*s<%02X>"); + setbinfmt("LESSUTFBINFMT", &utfbinfmt, ""); +} + +/* + * Is a given character a "binary" character? + */ +int +binary_char(LWCHAR c) +{ + if (utf_mode) + return (is_ubin_char(c)); + c &= 0377; + return (!isprint((unsigned char)c) && !iscntrl((unsigned char)c)); +} + +/* + * Is a given character a "control" character? + */ +int +control_char(LWCHAR c) +{ + c &= 0377; + if (utf_mode) + return (iscntrl((unsigned char)c)); + return (iscntrl((unsigned char)c) || !isprint((unsigned char)c)); +} + +/* + * Return the printable form of a character. + * For example, in the "ascii" charset '\3' is printed as "^C". + */ +char * +prchar(LWCHAR c) +{ + /* {{ This buffer can be overrun if LESSBINFMT is a long string. }} */ + static char buf[32]; + + c &= 0377; + if ((c < 128 || !utf_mode) && !control_char(c)) + (void) snprintf(buf, sizeof (buf), "%c", (int)c); + else if (c == ESC) + (void) strlcpy(buf, "ESC", sizeof (buf)); + else if (c < 128 && !control_char(c ^ 0100)) + (void) snprintf(buf, sizeof (buf), "^%c", (int)(c ^ 0100)); + else + (void) snprintf(buf, sizeof (buf), binfmt, c); + return (buf); +} + +/* + * Return the printable form of a UTF-8 character. + */ +char * +prutfchar(LWCHAR ch) +{ + static char buf[32]; + + if (ch == ESC) { + (void) strlcpy(buf, "ESC", sizeof (buf)); + } else if (ch < 128 && control_char(ch)) { + if (!control_char(ch ^ 0100)) + (void) snprintf(buf, sizeof (buf), "^%c", + ((char)ch) ^ 0100); + else + (void) snprintf(buf, sizeof (buf), binfmt, (char)ch); + } else if (is_ubin_char(ch)) { + (void) snprintf(buf, sizeof (buf), utfbinfmt, ch); + } else { + int len; + if (ch >= 0x80000000) { + len = 3; + ch = 0xFFFD; + } else { + len = (ch < 0x80) ? 1 + : (ch < 0x800) ? 2 + : (ch < 0x10000) ? 3 + : (ch < 0x200000) ? 4 + : (ch < 0x4000000) ? 5 + : 6; + } + buf[len] = '\0'; + if (len == 1) { + *buf = (char)ch; + } else { + *buf = ((1 << len) - 1) << (8 - len); + while (--len > 0) { + buf[len] = (char)(0x80 | (ch & 0x3F)); + ch >>= 6; + } + *buf |= ch; + } + } + return (buf); +} + +/* + * Get the length of a UTF-8 character in bytes. + */ +int +utf_len(char ch) +{ + if ((ch & 0x80) == 0) + return (1); + if ((ch & 0xE0) == 0xC0) + return (2); + if ((ch & 0xF0) == 0xE0) + return (3); + if ((ch & 0xF8) == 0xF0) + return (4); + if ((ch & 0xFC) == 0xF8) + return (5); + if ((ch & 0xFE) == 0xFC) + return (6); + /* Invalid UTF-8 encoding. */ + return (1); +} + +/* + * Is a UTF-8 character well-formed? + */ +int +is_utf8_well_formed(const char *s) +{ + int i; + int len; + + if (IS_UTF8_INVALID(s[0])) + return (0); + + len = utf_len((char)s[0]); + if (len == 1) + return (1); + if (len == 2) { + if ((unsigned char)(s[0]) < 0xC2) + return (0); + } else { + unsigned char mask; + mask = (~((1 << (8-len)) - 1)) & 0xFF; + if (s[0] == mask && (s[1] & mask) == 0x80) + return (0); + } + + for (i = 1; i < len; i++) + if (!IS_UTF8_TRAIL(s[i])) + return (0); + return (1); +} + +/* + * Get the value of a UTF-8 character. + */ +LWCHAR +get_wchar(const char *p) +{ + switch (utf_len(p[0])) { + case 1: + default: + /* 0xxxxxxx */ + return (LWCHAR) + (p[0] & 0xFF); + case 2: + /* 110xxxxx 10xxxxxx */ + return (LWCHAR) ( + ((p[0] & 0x1F) << 6) | + (p[1] & 0x3F)); + case 3: + /* 1110xxxx 10xxxxxx 10xxxxxx */ + return (LWCHAR) ( + ((p[0] & 0x0F) << 12) | + ((p[1] & 0x3F) << 6) | + (p[2] & 0x3F)); + case 4: + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + return (LWCHAR) ( + ((p[0] & 0x07) << 18) | + ((p[1] & 0x3F) << 12) | + ((p[2] & 0x3F) << 6) | + (p[3] & 0x3F)); + case 5: + /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + return (LWCHAR) ( + ((p[0] & 0x03) << 24) | + ((p[1] & 0x3F) << 18) | + ((p[2] & 0x3F) << 12) | + ((p[3] & 0x3F) << 6) | + (p[4] & 0x3F)); + case 6: + /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + return (LWCHAR) ( + ((p[0] & 0x01) << 30) | + ((p[1] & 0x3F) << 24) | + ((p[2] & 0x3F) << 18) | + ((p[3] & 0x3F) << 12) | + ((p[4] & 0x3F) << 6) | + (p[5] & 0x3F)); + } +} + +/* + * Store a character into a UTF-8 string. + */ +void +put_wchar(char **pp, LWCHAR ch) +{ + if (!utf_mode || ch < 0x80) { + /* 0xxxxxxx */ + *(*pp)++ = (char)ch; + } else if (ch < 0x800) { + /* 110xxxxx 10xxxxxx */ + *(*pp)++ = (char)(0xC0 | ((ch >> 6) & 0x1F)); + *(*pp)++ = (char)(0x80 | (ch & 0x3F)); + } else if (ch < 0x10000) { + /* 1110xxxx 10xxxxxx 10xxxxxx */ + *(*pp)++ = (char)(0xE0 | ((ch >> 12) & 0x0F)); + *(*pp)++ = (char)(0x80 | ((ch >> 6) & 0x3F)); + *(*pp)++ = (char)(0x80 | (ch & 0x3F)); + } else if (ch < 0x200000) { + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + *(*pp)++ = (char)(0xF0 | ((ch >> 18) & 0x07)); + *(*pp)++ = (char)(0x80 | ((ch >> 12) & 0x3F)); + *(*pp)++ = (char)(0x80 | ((ch >> 6) & 0x3F)); + *(*pp)++ = (char)(0x80 | (ch & 0x3F)); + } else if (ch < 0x4000000) { + /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + *(*pp)++ = (char)(0xF0 | ((ch >> 24) & 0x03)); + *(*pp)++ = (char)(0x80 | ((ch >> 18) & 0x3F)); + *(*pp)++ = (char)(0x80 | ((ch >> 12) & 0x3F)); + *(*pp)++ = (char)(0x80 | ((ch >> 6) & 0x3F)); + *(*pp)++ = (char)(0x80 | (ch & 0x3F)); + } else { + /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + *(*pp)++ = (char)(0xF0 | ((ch >> 30) & 0x01)); + *(*pp)++ = (char)(0x80 | ((ch >> 24) & 0x3F)); + *(*pp)++ = (char)(0x80 | ((ch >> 18) & 0x3F)); + *(*pp)++ = (char)(0x80 | ((ch >> 12) & 0x3F)); + *(*pp)++ = (char)(0x80 | ((ch >> 6) & 0x3F)); + *(*pp)++ = (char)(0x80 | (ch & 0x3F)); + } +} + +/* + * Step forward or backward one character in a string. + */ +LWCHAR +step_char(char **pp, int dir, char *limit) +{ + LWCHAR ch; + int len; + char *p = *pp; + + if (!utf_mode) { + /* It's easy if chars are one byte. */ + if (dir > 0) + ch = (LWCHAR) ((p < limit) ? *p++ : 0); + else + ch = (LWCHAR) ((p > limit) ? *--p : 0); + } else if (dir > 0) { + len = utf_len(*p); + if (p + len > limit) { + ch = 0; + p = limit; + } else { + ch = get_wchar(p); + p += len; + } + } else { + while (p > limit && IS_UTF8_TRAIL(p[-1])) + p--; + if (p > limit) + ch = get_wchar(--p); + else + ch = 0; + } + *pp = p; + return (ch); +} + +/* + * Unicode characters data + */ +struct wchar_range { LWCHAR first, last; }; + +/* + * Characters with general category values + * Mn: Mark, Nonspacing + * Me: Mark, Enclosing + * Last synched with + * + * dated 2005-11-30T00:58:48Z + */ +static struct wchar_range comp_table[] = { + { 0x0300, 0x036F} /* Mn */, { 0x0483, 0x0486} /* Mn */, + { 0x0488, 0x0489} /* Me */, + { 0x0591, 0x05BD} /* Mn */, { 0x05BF, 0x05BF} /* Mn */, + { 0x05C1, 0x05C2} /* Mn */, { 0x05C4, 0x05C5} /* Mn */, + { 0x05C7, 0x05C7} /* Mn */, { 0x0610, 0x0615} /* Mn */, + { 0x064B, 0x065E} /* Mn */, { 0x0670, 0x0670} /* Mn */, + { 0x06D6, 0x06DC} /* Mn */, + { 0x06DE, 0x06DE} /* Me */, + { 0x06DF, 0x06E4} /* Mn */, { 0x06E7, 0x06E8} /* Mn */, + { 0x06EA, 0x06ED} /* Mn */, { 0x0711, 0x0711} /* Mn */, + { 0x0730, 0x074A} /* Mn */, { 0x07A6, 0x07B0} /* Mn */, + { 0x07EB, 0x07F3} /* Mn */, { 0x0901, 0x0902} /* Mn */, + { 0x093C, 0x093C} /* Mn */, { 0x0941, 0x0948} /* Mn */, + { 0x094D, 0x094D} /* Mn */, { 0x0951, 0x0954} /* Mn */, + { 0x0962, 0x0963} /* Mn */, { 0x0981, 0x0981} /* Mn */, + { 0x09BC, 0x09BC} /* Mn */, { 0x09C1, 0x09C4} /* Mn */, + { 0x09CD, 0x09CD} /* Mn */, { 0x09E2, 0x09E3} /* Mn */, + { 0x0A01, 0x0A02} /* Mn */, { 0x0A3C, 0x0A3C} /* Mn */, + { 0x0A41, 0x0A42} /* Mn */, { 0x0A47, 0x0A48} /* Mn */, + { 0x0A4B, 0x0A4D} /* Mn */, { 0x0A70, 0x0A71} /* Mn */, + { 0x0A81, 0x0A82} /* Mn */, { 0x0ABC, 0x0ABC} /* Mn */, + { 0x0AC1, 0x0AC5} /* Mn */, { 0x0AC7, 0x0AC8} /* Mn */, + { 0x0ACD, 0x0ACD} /* Mn */, { 0x0AE2, 0x0AE3} /* Mn */, + { 0x0B01, 0x0B01} /* Mn */, { 0x0B3C, 0x0B3C} /* Mn */, + { 0x0B3F, 0x0B3F} /* Mn */, { 0x0B41, 0x0B43} /* Mn */, + { 0x0B4D, 0x0B4D} /* Mn */, { 0x0B56, 0x0B56} /* Mn */, + { 0x0B82, 0x0B82} /* Mn */, { 0x0BC0, 0x0BC0} /* Mn */, + { 0x0BCD, 0x0BCD} /* Mn */, { 0x0C3E, 0x0C40} /* Mn */, + { 0x0C46, 0x0C48} /* Mn */, { 0x0C4A, 0x0C4D} /* Mn */, + { 0x0C55, 0x0C56} /* Mn */, { 0x0CBC, 0x0CBC} /* Mn */, + { 0x0CBF, 0x0CBF} /* Mn */, { 0x0CC6, 0x0CC6} /* Mn */, + { 0x0CCC, 0x0CCD} /* Mn */, { 0x0CE2, 0x0CE3} /* Mn */, + { 0x0D41, 0x0D43} /* Mn */, { 0x0D4D, 0x0D4D} /* Mn */, + { 0x0DCA, 0x0DCA} /* Mn */, { 0x0DD2, 0x0DD4} /* Mn */, + { 0x0DD6, 0x0DD6} /* Mn */, { 0x0E31, 0x0E31} /* Mn */, + { 0x0E34, 0x0E3A} /* Mn */, { 0x0E47, 0x0E4E} /* Mn */, + { 0x0EB1, 0x0EB1} /* Mn */, { 0x0EB4, 0x0EB9} /* Mn */, + { 0x0EBB, 0x0EBC} /* Mn */, { 0x0EC8, 0x0ECD} /* Mn */, + { 0x0F18, 0x0F19} /* Mn */, { 0x0F35, 0x0F35} /* Mn */, + { 0x0F37, 0x0F37} /* Mn */, { 0x0F39, 0x0F39} /* Mn */, + { 0x0F71, 0x0F7E} /* Mn */, { 0x0F80, 0x0F84} /* Mn */, + { 0x0F86, 0x0F87} /* Mn */, { 0x0F90, 0x0F97} /* Mn */, + { 0x0F99, 0x0FBC} /* Mn */, { 0x0FC6, 0x0FC6} /* Mn */, + { 0x102D, 0x1030} /* Mn */, { 0x1032, 0x1032} /* Mn */, + { 0x1036, 0x1037} /* Mn */, { 0x1039, 0x1039} /* Mn */, + { 0x1058, 0x1059} /* Mn */, { 0x135F, 0x135F} /* Mn */, + { 0x1712, 0x1714} /* Mn */, { 0x1732, 0x1734} /* Mn */, + { 0x1752, 0x1753} /* Mn */, { 0x1772, 0x1773} /* Mn */, + { 0x17B7, 0x17BD} /* Mn */, { 0x17C6, 0x17C6} /* Mn */, + { 0x17C9, 0x17D3} /* Mn */, { 0x17DD, 0x17DD} /* Mn */, + { 0x180B, 0x180D} /* Mn */, { 0x18A9, 0x18A9} /* Mn */, + { 0x1920, 0x1922} /* Mn */, { 0x1927, 0x1928} /* Mn */, + { 0x1932, 0x1932} /* Mn */, { 0x1939, 0x193B} /* Mn */, + { 0x1A17, 0x1A18} /* Mn */, { 0x1B00, 0x1B03} /* Mn */, + { 0x1B34, 0x1B34} /* Mn */, { 0x1B36, 0x1B3A} /* Mn */, + { 0x1B3C, 0x1B3C} /* Mn */, { 0x1B42, 0x1B42} /* Mn */, + { 0x1B6B, 0x1B73} /* Mn */, { 0x1DC0, 0x1DCA} /* Mn */, + { 0x1DFE, 0x1DFF} /* Mn */, { 0x20D0, 0x20DC} /* Mn */, + { 0x20DD, 0x20E0} /* Me */, + { 0x20E1, 0x20E1} /* Mn */, + { 0x20E2, 0x20E4} /* Me */, + { 0x20E5, 0x20EF} /* Mn */, { 0x302A, 0x302F} /* Mn */, + { 0x3099, 0x309A} /* Mn */, { 0xA806, 0xA806} /* Mn */, + { 0xA80B, 0xA80B} /* Mn */, { 0xA825, 0xA826} /* Mn */, + { 0xFB1E, 0xFB1E} /* Mn */, { 0xFE00, 0xFE0F} /* Mn */, + { 0xFE20, 0xFE23} /* Mn */, { 0x10A01, 0x10A03} /* Mn */, + { 0x10A05, 0x10A06} /* Mn */, { 0x10A0C, 0x10A0F} /* Mn */, + { 0x10A38, 0x10A3A} /* Mn */, { 0x10A3F, 0x10A3F} /* Mn */, + { 0x1D167, 0x1D169} /* Mn */, { 0x1D17B, 0x1D182} /* Mn */, + { 0x1D185, 0x1D18B} /* Mn */, { 0x1D1AA, 0x1D1AD} /* Mn */, + { 0x1D242, 0x1D244} /* Mn */, { 0xE0100, 0xE01EF} /* Mn */, +}; + +/* + * Special pairs, not ranges. + */ +static struct wchar_range comb_table[] = { + {0x0644, 0x0622}, {0x0644, 0x0623}, {0x0644, 0x0625}, {0x0644, 0x0627}, +}; + +/* + * Characters with general category values + * Cc: Other, Control + * Cf: Other, Format + * Cs: Other, Surrogate + * Co: Other, Private Use + * Cn: Other, Not Assigned + * Zl: Separator, Line + * Zp: Separator, Paragraph + * Last synched with + * + * dated 2005-11-30T00:58:48Z + */ +static struct wchar_range ubin_table[] = { + { 0x0000, 0x0007} /* Cc */, + { 0x000B, 0x000C} /* Cc */, + { 0x000E, 0x001A} /* Cc */, + { 0x001C, 0x001F} /* Cc */, + { 0x007F, 0x009F} /* Cc */, +#if 0 + { 0x00AD, 0x00AD} /* Cf */, +#endif + { 0x0370, 0x0373} /* Cn */, { 0x0376, 0x0379} /* Cn */, + { 0x037F, 0x0383} /* Cn */, { 0x038B, 0x038B} /* Cn */, + { 0x038D, 0x038D} /* Cn */, { 0x03A2, 0x03A2} /* Cn */, + { 0x03CF, 0x03CF} /* Cn */, { 0x0487, 0x0487} /* Cn */, + { 0x0514, 0x0530} /* Cn */, { 0x0557, 0x0558} /* Cn */, + { 0x0560, 0x0560} /* Cn */, { 0x0588, 0x0588} /* Cn */, + { 0x058B, 0x0590} /* Cn */, { 0x05C8, 0x05CF} /* Cn */, + { 0x05EB, 0x05EF} /* Cn */, { 0x05F5, 0x05FF} /* Cn */, +#if 0 + { 0x0600, 0x0603} /* Cf */, +#endif + { 0x0604, 0x060A} /* Cn */, { 0x0616, 0x061A} /* Cn */, + { 0x061C, 0x061D} /* Cn */, { 0x0620, 0x0620} /* Cn */, + { 0x063B, 0x063F} /* Cn */, { 0x065F, 0x065F} /* Cn */, +#if 0 + { 0x06DD, 0x06DD} /* Cf */, +#endif + { 0x070E, 0x070E} /* Cn */, +#if 0 + { 0x070F, 0x070F} /* Cf */, +#endif + { 0x074B, 0x074C} /* Cn */, { 0x076E, 0x077F} /* Cn */, + { 0x07B2, 0x07BF} /* Cn */, { 0x07FB, 0x0900} /* Cn */, + { 0x093A, 0x093B} /* Cn */, { 0x094E, 0x094F} /* Cn */, + { 0x0955, 0x0957} /* Cn */, { 0x0971, 0x097A} /* Cn */, + { 0x0980, 0x0980} /* Cn */, { 0x0984, 0x0984} /* Cn */, + { 0x098D, 0x098E} /* Cn */, { 0x0991, 0x0992} /* Cn */, + { 0x09A9, 0x09A9} /* Cn */, { 0x09B1, 0x09B1} /* Cn */, + { 0x09B3, 0x09B5} /* Cn */, { 0x09BA, 0x09BB} /* Cn */, + { 0x09C5, 0x09C6} /* Cn */, { 0x09C9, 0x09CA} /* Cn */, + { 0x09CF, 0x09D6} /* Cn */, { 0x09D8, 0x09DB} /* Cn */, + { 0x09DE, 0x09DE} /* Cn */, { 0x09E4, 0x09E5} /* Cn */, + { 0x09FB, 0x0A00} /* Cn */, { 0x0A04, 0x0A04} /* Cn */, + { 0x0A0B, 0x0A0E} /* Cn */, { 0x0A11, 0x0A12} /* Cn */, + { 0x0A29, 0x0A29} /* Cn */, { 0x0A31, 0x0A31} /* Cn */, + { 0x0A34, 0x0A34} /* Cn */, { 0x0A37, 0x0A37} /* Cn */, + { 0x0A3A, 0x0A3B} /* Cn */, { 0x0A3D, 0x0A3D} /* Cn */, + { 0x0A43, 0x0A46} /* Cn */, { 0x0A49, 0x0A4A} /* Cn */, + { 0x0A4E, 0x0A58} /* Cn */, { 0x0A5D, 0x0A5D} /* Cn */, + { 0x0A5F, 0x0A65} /* Cn */, { 0x0A75, 0x0A80} /* Cn */, + { 0x0A84, 0x0A84} /* Cn */, { 0x0A8E, 0x0A8E} /* Cn */, + { 0x0A92, 0x0A92} /* Cn */, { 0x0AA9, 0x0AA9} /* Cn */, + { 0x0AB1, 0x0AB1} /* Cn */, { 0x0AB4, 0x0AB4} /* Cn */, + { 0x0ABA, 0x0ABB} /* Cn */, { 0x0AC6, 0x0AC6} /* Cn */, + { 0x0ACA, 0x0ACA} /* Cn */, { 0x0ACE, 0x0ACF} /* Cn */, + { 0x0AD1, 0x0ADF} /* Cn */, { 0x0AE4, 0x0AE5} /* Cn */, + { 0x0AF0, 0x0AF0} /* Cn */, { 0x0AF2, 0x0B00} /* Cn */, + { 0x0B04, 0x0B04} /* Cn */, { 0x0B0D, 0x0B0E} /* Cn */, + { 0x0B11, 0x0B12} /* Cn */, { 0x0B29, 0x0B29} /* Cn */, + { 0x0B31, 0x0B31} /* Cn */, { 0x0B34, 0x0B34} /* Cn */, + { 0x0B3A, 0x0B3B} /* Cn */, { 0x0B44, 0x0B46} /* Cn */, + { 0x0B49, 0x0B4A} /* Cn */, { 0x0B4E, 0x0B55} /* Cn */, + { 0x0B58, 0x0B5B} /* Cn */, { 0x0B5E, 0x0B5E} /* Cn */, + { 0x0B62, 0x0B65} /* Cn */, { 0x0B72, 0x0B81} /* Cn */, + { 0x0B84, 0x0B84} /* Cn */, { 0x0B8B, 0x0B8D} /* Cn */, + { 0x0B91, 0x0B91} /* Cn */, { 0x0B96, 0x0B98} /* Cn */, + { 0x0B9B, 0x0B9B} /* Cn */, { 0x0B9D, 0x0B9D} /* Cn */, + { 0x0BA0, 0x0BA2} /* Cn */, { 0x0BA5, 0x0BA7} /* Cn */, + { 0x0BAB, 0x0BAD} /* Cn */, { 0x0BBA, 0x0BBD} /* Cn */, + { 0x0BC3, 0x0BC5} /* Cn */, { 0x0BC9, 0x0BC9} /* Cn */, + { 0x0BCE, 0x0BD6} /* Cn */, { 0x0BD8, 0x0BE5} /* Cn */, + { 0x0BFB, 0x0C00} /* Cn */, { 0x0C04, 0x0C04} /* Cn */, + { 0x0C0D, 0x0C0D} /* Cn */, { 0x0C11, 0x0C11} /* Cn */, + { 0x0C29, 0x0C29} /* Cn */, { 0x0C34, 0x0C34} /* Cn */, + { 0x0C3A, 0x0C3D} /* Cn */, { 0x0C45, 0x0C45} /* Cn */, + { 0x0C49, 0x0C49} /* Cn */, { 0x0C4E, 0x0C54} /* Cn */, + { 0x0C57, 0x0C5F} /* Cn */, { 0x0C62, 0x0C65} /* Cn */, + { 0x0C70, 0x0C81} /* Cn */, { 0x0C84, 0x0C84} /* Cn */, + { 0x0C8D, 0x0C8D} /* Cn */, { 0x0C91, 0x0C91} /* Cn */, + { 0x0CA9, 0x0CA9} /* Cn */, { 0x0CB4, 0x0CB4} /* Cn */, + { 0x0CBA, 0x0CBB} /* Cn */, { 0x0CC5, 0x0CC5} /* Cn */, + { 0x0CC9, 0x0CC9} /* Cn */, { 0x0CCE, 0x0CD4} /* Cn */, + { 0x0CD7, 0x0CDD} /* Cn */, { 0x0CDF, 0x0CDF} /* Cn */, + { 0x0CE4, 0x0CE5} /* Cn */, { 0x0CF0, 0x0CF0} /* Cn */, + { 0x0CF3, 0x0D01} /* Cn */, { 0x0D04, 0x0D04} /* Cn */, + { 0x0D0D, 0x0D0D} /* Cn */, { 0x0D11, 0x0D11} /* Cn */, + { 0x0D29, 0x0D29} /* Cn */, { 0x0D3A, 0x0D3D} /* Cn */, + { 0x0D44, 0x0D45} /* Cn */, { 0x0D49, 0x0D49} /* Cn */, + { 0x0D4E, 0x0D56} /* Cn */, { 0x0D58, 0x0D5F} /* Cn */, + { 0x0D62, 0x0D65} /* Cn */, { 0x0D70, 0x0D81} /* Cn */, + { 0x0D84, 0x0D84} /* Cn */, { 0x0D97, 0x0D99} /* Cn */, + { 0x0DB2, 0x0DB2} /* Cn */, { 0x0DBC, 0x0DBC} /* Cn */, + { 0x0DBE, 0x0DBF} /* Cn */, { 0x0DC7, 0x0DC9} /* Cn */, + { 0x0DCB, 0x0DCE} /* Cn */, { 0x0DD5, 0x0DD5} /* Cn */, + { 0x0DD7, 0x0DD7} /* Cn */, { 0x0DE0, 0x0DF1} /* Cn */, + { 0x0DF5, 0x0E00} /* Cn */, { 0x0E3B, 0x0E3E} /* Cn */, + { 0x0E5C, 0x0E80} /* Cn */, { 0x0E83, 0x0E83} /* Cn */, + { 0x0E85, 0x0E86} /* Cn */, { 0x0E89, 0x0E89} /* Cn */, + { 0x0E8B, 0x0E8C} /* Cn */, { 0x0E8E, 0x0E93} /* Cn */, + { 0x0E98, 0x0E98} /* Cn */, { 0x0EA0, 0x0EA0} /* Cn */, + { 0x0EA4, 0x0EA4} /* Cn */, { 0x0EA6, 0x0EA6} /* Cn */, + { 0x0EA8, 0x0EA9} /* Cn */, { 0x0EAC, 0x0EAC} /* Cn */, + { 0x0EBA, 0x0EBA} /* Cn */, { 0x0EBE, 0x0EBF} /* Cn */, + { 0x0EC5, 0x0EC5} /* Cn */, { 0x0EC7, 0x0EC7} /* Cn */, + { 0x0ECE, 0x0ECF} /* Cn */, { 0x0EDA, 0x0EDB} /* Cn */, + { 0x0EDE, 0x0EFF} /* Cn */, { 0x0F48, 0x0F48} /* Cn */, + { 0x0F6B, 0x0F70} /* Cn */, { 0x0F8C, 0x0F8F} /* Cn */, + { 0x0F98, 0x0F98} /* Cn */, { 0x0FBD, 0x0FBD} /* Cn */, + { 0x0FCD, 0x0FCE} /* Cn */, { 0x0FD2, 0x0FFF} /* Cn */, + { 0x1022, 0x1022} /* Cn */, { 0x1028, 0x1028} /* Cn */, + { 0x102B, 0x102B} /* Cn */, { 0x1033, 0x1035} /* Cn */, + { 0x103A, 0x103F} /* Cn */, { 0x105A, 0x109F} /* Cn */, + { 0x10C6, 0x10CF} /* Cn */, { 0x10FD, 0x10FF} /* Cn */, + { 0x115A, 0x115E} /* Cn */, { 0x11A3, 0x11A7} /* Cn */, + { 0x11FA, 0x11FF} /* Cn */, { 0x1249, 0x1249} /* Cn */, + { 0x124E, 0x124F} /* Cn */, { 0x1257, 0x1257} /* Cn */, + { 0x1259, 0x1259} /* Cn */, { 0x125E, 0x125F} /* Cn */, + { 0x1289, 0x1289} /* Cn */, { 0x128E, 0x128F} /* Cn */, + { 0x12B1, 0x12B1} /* Cn */, { 0x12B6, 0x12B7} /* Cn */, + { 0x12BF, 0x12BF} /* Cn */, { 0x12C1, 0x12C1} /* Cn */, + { 0x12C6, 0x12C7} /* Cn */, { 0x12D7, 0x12D7} /* Cn */, + { 0x1311, 0x1311} /* Cn */, { 0x1316, 0x1317} /* Cn */, + { 0x135B, 0x135E} /* Cn */, { 0x137D, 0x137F} /* Cn */, + { 0x139A, 0x139F} /* Cn */, { 0x13F5, 0x1400} /* Cn */, + { 0x1677, 0x167F} /* Cn */, { 0x169D, 0x169F} /* Cn */, + { 0x16F1, 0x16FF} /* Cn */, { 0x170D, 0x170D} /* Cn */, + { 0x1715, 0x171F} /* Cn */, { 0x1737, 0x173F} /* Cn */, + { 0x1754, 0x175F} /* Cn */, { 0x176D, 0x176D} /* Cn */, + { 0x1771, 0x1771} /* Cn */, { 0x1774, 0x177F} /* Cn */, +#if 0 + { 0x17B4, 0x17B5} /* Cf */, +#endif + { 0x17DE, 0x17DF} /* Cn */, { 0x17EA, 0x17EF} /* Cn */, + { 0x17FA, 0x17FF} /* Cn */, { 0x180F, 0x180F} /* Cn */, + { 0x181A, 0x181F} /* Cn */, { 0x1878, 0x187F} /* Cn */, + { 0x18AA, 0x18FF} /* Cn */, { 0x191D, 0x191F} /* Cn */, + { 0x192C, 0x192F} /* Cn */, { 0x193C, 0x193F} /* Cn */, + { 0x1941, 0x1943} /* Cn */, { 0x196E, 0x196F} /* Cn */, + { 0x1975, 0x197F} /* Cn */, { 0x19AA, 0x19AF} /* Cn */, + { 0x19CA, 0x19CF} /* Cn */, { 0x19DA, 0x19DD} /* Cn */, + { 0x1A1C, 0x1A1D} /* Cn */, { 0x1A20, 0x1AFF} /* Cn */, + { 0x1B4C, 0x1B4F} /* Cn */, { 0x1B7D, 0x1CFF} /* Cn */, + { 0x1DCB, 0x1DFD} /* Cn */, { 0x1E9C, 0x1E9F} /* Cn */, + { 0x1EFA, 0x1EFF} /* Cn */, { 0x1F16, 0x1F17} /* Cn */, + { 0x1F1E, 0x1F1F} /* Cn */, { 0x1F46, 0x1F47} /* Cn */, + { 0x1F4E, 0x1F4F} /* Cn */, { 0x1F58, 0x1F58} /* Cn */, + { 0x1F5A, 0x1F5A} /* Cn */, { 0x1F5C, 0x1F5C} /* Cn */, + { 0x1F5E, 0x1F5E} /* Cn */, { 0x1F7E, 0x1F7F} /* Cn */, + { 0x1FB5, 0x1FB5} /* Cn */, { 0x1FC5, 0x1FC5} /* Cn */, + { 0x1FD4, 0x1FD5} /* Cn */, { 0x1FDC, 0x1FDC} /* Cn */, + { 0x1FF0, 0x1FF1} /* Cn */, { 0x1FF5, 0x1FF5} /* Cn */, + { 0x1FFF, 0x1FFF} /* Cn */, + { 0x200B, 0x200F} /* Cf */, + { 0x2028, 0x2028} /* Zl */, + { 0x2029, 0x2029} /* Zp */, + { 0x202A, 0x202E} /* Cf */, + { 0x2060, 0x2063} /* Cf */, + { 0x2064, 0x2069} /* Cn */, + { 0x206A, 0x206F} /* Cf */, + { 0x2072, 0x2073} /* Cn */, { 0x208F, 0x208F} /* Cn */, + { 0x2095, 0x209F} /* Cn */, { 0x20B6, 0x20CF} /* Cn */, + { 0x20F0, 0x20FF} /* Cn */, { 0x214F, 0x2152} /* Cn */, + { 0x2185, 0x218F} /* Cn */, { 0x23E8, 0x23FF} /* Cn */, + { 0x2427, 0x243F} /* Cn */, { 0x244B, 0x245F} /* Cn */, + { 0x269D, 0x269F} /* Cn */, { 0x26B3, 0x2700} /* Cn */, + { 0x2705, 0x2705} /* Cn */, { 0x270A, 0x270B} /* Cn */, + { 0x2728, 0x2728} /* Cn */, { 0x274C, 0x274C} /* Cn */, + { 0x274E, 0x274E} /* Cn */, { 0x2753, 0x2755} /* Cn */, + { 0x2757, 0x2757} /* Cn */, { 0x275F, 0x2760} /* Cn */, + { 0x2795, 0x2797} /* Cn */, { 0x27B0, 0x27B0} /* Cn */, + { 0x27BF, 0x27BF} /* Cn */, { 0x27CB, 0x27CF} /* Cn */, + { 0x27EC, 0x27EF} /* Cn */, { 0x2B1B, 0x2B1F} /* Cn */, + { 0x2B24, 0x2BFF} /* Cn */, { 0x2C2F, 0x2C2F} /* Cn */, + { 0x2C5F, 0x2C5F} /* Cn */, { 0x2C6D, 0x2C73} /* Cn */, + { 0x2C78, 0x2C7F} /* Cn */, { 0x2CEB, 0x2CF8} /* Cn */, + { 0x2D26, 0x2D2F} /* Cn */, { 0x2D66, 0x2D6E} /* Cn */, + { 0x2D70, 0x2D7F} /* Cn */, { 0x2D97, 0x2D9F} /* Cn */, + { 0x2DA7, 0x2DA7} /* Cn */, { 0x2DAF, 0x2DAF} /* Cn */, + { 0x2DB7, 0x2DB7} /* Cn */, { 0x2DBF, 0x2DBF} /* Cn */, + { 0x2DC7, 0x2DC7} /* Cn */, { 0x2DCF, 0x2DCF} /* Cn */, + { 0x2DD7, 0x2DD7} /* Cn */, { 0x2DDF, 0x2DFF} /* Cn */, + { 0x2E18, 0x2E1B} /* Cn */, { 0x2E1E, 0x2E7F} /* Cn */, + { 0x2E9A, 0x2E9A} /* Cn */, { 0x2EF4, 0x2EFF} /* Cn */, + { 0x2FD6, 0x2FEF} /* Cn */, { 0x2FFC, 0x2FFF} /* Cn */, + { 0x3040, 0x3040} /* Cn */, { 0x3097, 0x3098} /* Cn */, + { 0x3100, 0x3104} /* Cn */, { 0x312D, 0x3130} /* Cn */, + { 0x318F, 0x318F} /* Cn */, { 0x31B8, 0x31BF} /* Cn */, + { 0x31D0, 0x31EF} /* Cn */, { 0x321F, 0x321F} /* Cn */, + { 0x3244, 0x324F} /* Cn */, { 0x32FF, 0x32FF} /* Cn */, + { 0x4DB6, 0x4DBF} /* Cn */, { 0x9FBC, 0x9FFF} /* Cn */, + { 0xA48D, 0xA48F} /* Cn */, { 0xA4C7, 0xA6FF} /* Cn */, + { 0xA71B, 0xA71F} /* Cn */, { 0xA722, 0xA7FF} /* Cn */, + { 0xA82C, 0xA83F} /* Cn */, { 0xA878, 0xABFF} /* Cn */, + { 0xD7A4, 0xD7FF} /* Cn */, + { 0xD800, 0xDFFF} /* Cs */, + { 0xE000, 0xF8FF} /* Co */, + { 0xFA2E, 0xFA2F} /* Cn */, { 0xFA6B, 0xFA6F} /* Cn */, + { 0xFADA, 0xFAFF} /* Cn */, { 0xFB07, 0xFB12} /* Cn */, + { 0xFB18, 0xFB1C} /* Cn */, { 0xFB37, 0xFB37} /* Cn */, + { 0xFB3D, 0xFB3D} /* Cn */, { 0xFB3F, 0xFB3F} /* Cn */, + { 0xFB42, 0xFB42} /* Cn */, { 0xFB45, 0xFB45} /* Cn */, + { 0xFBB2, 0xFBD2} /* Cn */, { 0xFD40, 0xFD4F} /* Cn */, + { 0xFD90, 0xFD91} /* Cn */, { 0xFDC8, 0xFDEF} /* Cn */, + { 0xFDFE, 0xFDFF} /* Cn */, { 0xFE1A, 0xFE1F} /* Cn */, + { 0xFE24, 0xFE2F} /* Cn */, { 0xFE53, 0xFE53} /* Cn */, + { 0xFE67, 0xFE67} /* Cn */, { 0xFE6C, 0xFE6F} /* Cn */, + { 0xFE75, 0xFE75} /* Cn */, { 0xFEFD, 0xFEFE} /* Cn */, + { 0xFEFF, 0xFEFF} /* Cf */, + { 0xFF00, 0xFF00} /* Cn */, { 0xFFBF, 0xFFC1} /* Cn */, + { 0xFFC8, 0xFFC9} /* Cn */, { 0xFFD0, 0xFFD1} /* Cn */, + { 0xFFD8, 0xFFD9} /* Cn */, { 0xFFDD, 0xFFDF} /* Cn */, + { 0xFFE7, 0xFFE7} /* Cn */, { 0xFFEF, 0xFFF8} /* Cn */, + { 0xFFF9, 0xFFFB} /* Cf */, + { 0xFFFE, 0xFFFF} /* Cn */, { 0x1000C, 0x1000C} /* Cn */, + { 0x10027, 0x10027} /* Cn */, { 0x1003B, 0x1003B} /* Cn */, + { 0x1003E, 0x1003E} /* Cn */, { 0x1004E, 0x1004F} /* Cn */, + { 0x1005E, 0x1007F} /* Cn */, { 0x100FB, 0x100FF} /* Cn */, + { 0x10103, 0x10106} /* Cn */, { 0x10134, 0x10136} /* Cn */, + { 0x1018B, 0x102FF} /* Cn */, { 0x1031F, 0x1031F} /* Cn */, + { 0x10324, 0x1032F} /* Cn */, { 0x1034B, 0x1037F} /* Cn */, + { 0x1039E, 0x1039E} /* Cn */, { 0x103C4, 0x103C7} /* Cn */, + { 0x103D6, 0x103FF} /* Cn */, + { 0x1049E, 0x1049F} /* Cn */, { 0x104AA, 0x107FF} /* Cn */, + { 0x10806, 0x10807} /* Cn */, { 0x10809, 0x10809} /* Cn */, + { 0x10836, 0x10836} /* Cn */, { 0x10839, 0x1083B} /* Cn */, + { 0x1083D, 0x1083E} /* Cn */, { 0x10840, 0x108FF} /* Cn */, + { 0x1091A, 0x1091E} /* Cn */, { 0x10920, 0x109FF} /* Cn */, + { 0x10A04, 0x10A04} /* Cn */, { 0x10A07, 0x10A0B} /* Cn */, + { 0x10A14, 0x10A14} /* Cn */, { 0x10A18, 0x10A18} /* Cn */, + { 0x10A34, 0x10A37} /* Cn */, { 0x10A3B, 0x10A3E} /* Cn */, + { 0x10A48, 0x10A4F} /* Cn */, { 0x10A59, 0x11FFF} /* Cn */, + { 0x1236F, 0x123FF} /* Cn */, { 0x12463, 0x1246F} /* Cn */, + { 0x12474, 0x1CFFF} /* Cn */, { 0x1D0F6, 0x1D0FF} /* Cn */, + { 0x1D127, 0x1D129} /* Cn */, + { 0x1D173, 0x1D17A} /* Cf */, + { 0x1D1DE, 0x1D1FF} /* Cn */, { 0x1D246, 0x1D2FF} /* Cn */, + { 0x1D357, 0x1D35F} /* Cn */, { 0x1D372, 0x1D3FF} /* Cn */, + { 0x1D455, 0x1D455} /* Cn */, { 0x1D49D, 0x1D49D} /* Cn */, + { 0x1D4A0, 0x1D4A1} /* Cn */, { 0x1D4A3, 0x1D4A4} /* Cn */, + { 0x1D4A7, 0x1D4A8} /* Cn */, { 0x1D4AD, 0x1D4AD} /* Cn */, + { 0x1D4BA, 0x1D4BA} /* Cn */, { 0x1D4BC, 0x1D4BC} /* Cn */, + { 0x1D4C4, 0x1D4C4} /* Cn */, { 0x1D506, 0x1D506} /* Cn */, + { 0x1D50B, 0x1D50C} /* Cn */, { 0x1D515, 0x1D515} /* Cn */, + { 0x1D51D, 0x1D51D} /* Cn */, { 0x1D53A, 0x1D53A} /* Cn */, + { 0x1D53F, 0x1D53F} /* Cn */, { 0x1D545, 0x1D545} /* Cn */, + { 0x1D547, 0x1D549} /* Cn */, { 0x1D551, 0x1D551} /* Cn */, + { 0x1D6A6, 0x1D6A7} /* Cn */, { 0x1D7CC, 0x1D7CD} /* Cn */, + { 0x1D800, 0x1FFFF} /* Cn */, { 0x2A6D7, 0x2F7FF} /* Cn */, + { 0x2FA1E, 0xE0000} /* Cn */, + { 0xE0001, 0xE0001} /* Cf */, + { 0xE0002, 0xE001F} /* Cn */, + { 0xE0020, 0xE007F} /* Cf */, + { 0xE0080, 0xE00FF} /* Cn */, { 0xE01F0, 0xEFFFF} /* Cn */, + { 0xF0000, 0xFFFFD} /* Co */, + { 0xFFFFE, 0xFFFFF} /* Cn */, + { 0x100000, 0x10FFFD} /* Co */, + { 0x10FFFE, 0x10FFFF} /* Cn */, + { 0x110000, 0x7FFFFFFF} /* ISO 10646?? */ +}; + +/* + * Double width characters + * W: East Asian Wide + * F: East Asian Full-width + * Unassigned code points may be included when they allow ranges to be merged. + * Last synched with + * + * dated 2005-11-08T01:32:56Z + */ +static struct wchar_range wide_table[] = { + { 0x1100, 0x115F} /* W */, { 0x2329, 0x232A} /* W */, + { 0x2E80, 0x2FFB} /* W */, + { 0x3000, 0x3000} /* F */, + { 0x3001, 0x303E} /* W */, { 0x3041, 0x4DB5} /* W */, + { 0x4E00, 0x9FBB} /* W */, { 0xA000, 0xA4C6} /* W */, + { 0xAC00, 0xD7A3} /* W */, { 0xF900, 0xFAD9} /* W */, + { 0xFE10, 0xFE19} /* W */, { 0xFE30, 0xFE6B} /* W */, + { 0xFF01, 0xFF60} /* F */, { 0xFFE0, 0xFFE6} /* F */, + { 0x20000, 0x2FFFD} /* W */, { 0x30000, 0x3FFFD} /* W */, +}; + +static int +is_in_table(LWCHAR ch, struct wchar_range table[], int tsize) +{ + int hi; + int lo; + + /* Binary search in the table. */ + if (ch < table[0].first) + return (0); + lo = 0; + hi = tsize - 1; + while (lo <= hi) { + int mid = (lo + hi) / 2; + if (ch > table[mid].last) + lo = mid + 1; + else if (ch < table[mid].first) + hi = mid - 1; + else + return (1); + } + return (0); +} + +/* + * Is a character a UTF-8 composing character? + * If a composing character follows any char, the two combine into one glyph. + */ +int +is_composing_char(LWCHAR ch) +{ + return (is_in_table(ch, comp_table, + (sizeof (comp_table) / sizeof (*comp_table)))); +} + +/* + * Should this UTF-8 character be treated as binary? + */ +int +is_ubin_char(LWCHAR ch) +{ + return (is_in_table(ch, ubin_table, + (sizeof (ubin_table) / sizeof (*ubin_table)))); +} + +/* + * Is this a double width UTF-8 character? + */ +int +is_wide_char(LWCHAR ch) +{ + return (is_in_table(ch, wide_table, + (sizeof (wide_table) / sizeof (*wide_table)))); +} + +/* + * Is a character a UTF-8 combining character? + * A combining char acts like an ordinary char, but if it follows + * a specific char (not any char), the two combine into one glyph. + */ +int +is_combining_char(LWCHAR ch1, LWCHAR ch2) +{ + /* The table is small; use linear search. */ + int i; + for (i = 0; i < sizeof (comb_table) / sizeof (*comb_table); i++) { + if (ch1 == comb_table[i].first && + ch2 == comb_table[i].last) + return (1); + } + return (0); +} diff --git a/bin/less/charset.h b/bin/less/charset.h new file mode 100644 index 0000000000..73455d0ab6 --- /dev/null +++ b/bin/less/charset.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#define IS_ASCII_OCTET(c) (((c) & 0x80) == 0) +#define IS_UTF8_TRAIL(c) (((c) & 0xC0) == 0x80) +#define IS_UTF8_LEAD2(c) (((c) & 0xE0) == 0xC0) +#define IS_UTF8_LEAD3(c) (((c) & 0xF0) == 0xE0) +#define IS_UTF8_LEAD4(c) (((c) & 0xF8) == 0xF0) +#define IS_UTF8_LEAD5(c) (((c) & 0xFC) == 0xF8) +#define IS_UTF8_LEAD6(c) (((c) & 0xFE) == 0xFC) +#define IS_UTF8_INVALID(c) (((c) & 0xFE) == 0xFE) +#define IS_UTF8_LEAD(c) (((c) & 0xC0) == 0xC0 && !IS_UTF8_INVALID(c)) diff --git a/bin/less/cmd.h b/bin/less/cmd.h new file mode 100644 index 0000000000..cd5277a1e7 --- /dev/null +++ b/bin/less/cmd.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#define MAX_USERCMD 1000 +#define MAX_CMDLEN 16 + +#define A_B_LINE 2 +#define A_B_SCREEN 3 +#define A_B_SCROLL 4 +#define A_B_SEARCH 5 +#define A_DIGIT 6 +#define A_DISP_OPTION 7 +#define A_DEBUG 8 +#define A_EXAMINE 9 +#define A_FIRSTCMD 10 +#define A_FREPAINT 11 +#define A_F_LINE 12 +#define A_F_SCREEN 13 +#define A_F_SCROLL 14 +#define A_F_SEARCH 15 +#define A_GOEND 16 +#define A_GOLINE 17 +#define A_GOMARK 18 +#define A_HELP 19 +#define A_NEXT_FILE 20 +#define A_PERCENT 21 +#define A_PREFIX 22 +#define A_PREV_FILE 23 +#define A_QUIT 24 +#define A_REPAINT 25 +#define A_SETMARK 26 +/* 27 unused */ +#define A_STAT 28 +#define A_FF_LINE 29 +#define A_BF_LINE 30 +#define A_VERSION 31 +#define A_VISUAL 32 +#define A_F_WINDOW 33 +#define A_B_WINDOW 34 +#define A_F_BRACKET 35 +#define A_B_BRACKET 36 +#define A_PIPE 37 +#define A_INDEX_FILE 38 +#define A_UNDO_SEARCH 39 +#define A_FF_SCREEN 40 +#define A_LSHIFT 41 +#define A_RSHIFT 42 +#define A_AGAIN_SEARCH 43 +#define A_T_AGAIN_SEARCH 44 +#define A_REVERSE_SEARCH 45 +#define A_T_REVERSE_SEARCH 46 +#define A_OPT_TOGGLE 47 +#define A_OPT_SET 48 +#define A_OPT_UNSET 49 +#define A_F_FOREVER 50 +#define A_GOPOS 51 +#define A_REMOVE_FILE 52 +#define A_NEXT_TAG 53 +#define A_PREV_TAG 54 +#define A_FILTER 55 +#define A_F_UNTIL_HILITE 56 +#define A_F_SKIP 57 + +#define A_INVALID 100 +#define A_NOACTION 101 +#define A_UINVALID 102 +#define A_END_LIST 103 +#define A_SPECIAL_KEY 104 + +#define A_SKIP 127 + +#define A_EXTRA 0200 + + +/* Line editing characters */ + +#define EC_BACKSPACE 1 +#define EC_LINEKILL 2 +#define EC_RIGHT 3 +#define EC_LEFT 4 +#define EC_W_LEFT 5 +#define EC_W_RIGHT 6 +#define EC_INSERT 7 +#define EC_DELETE 8 +#define EC_HOME 9 +#define EC_END 10 +#define EC_W_BACKSPACE 11 +#define EC_W_DELETE 12 +#define EC_UP 13 +#define EC_DOWN 14 +#define EC_EXPAND 15 +#define EC_F_COMPLETE 17 +#define EC_B_COMPLETE 18 +#define EC_LITERAL 19 +#define EC_ABORT 20 + +#define EC_NOACTION 101 +#define EC_UINVALID 102 + +/* Flags for editchar() */ +#define EC_PEEK 01 +#define EC_NOHISTORY 02 +#define EC_NOCOMPLETE 04 +#define EC_NORIGHTLEFT 010 + +/* Environment variable stuff */ +#define EV_OK 01 + +/* Special keys (keys which output different strings on different terminals) */ +#define SK_SPECIAL_KEY CONTROL('K') +#define SK_RIGHT_ARROW 1 +#define SK_LEFT_ARROW 2 +#define SK_UP_ARROW 3 +#define SK_DOWN_ARROW 4 +#define SK_PAGE_UP 5 +#define SK_PAGE_DOWN 6 +#define SK_HOME 7 +#define SK_END 8 +#define SK_DELETE 9 +#define SK_INSERT 10 +#define SK_CTL_LEFT_ARROW 11 +#define SK_CTL_RIGHT_ARROW 12 +#define SK_CTL_DELETE 13 +#define SK_F1 14 +#define SK_BACKTAB 15 +#define SK_CTL_BACKSPACE 16 +#define SK_CONTROL_K 40 diff --git a/bin/less/cmdbuf.c b/bin/less/cmdbuf.c new file mode 100644 index 0000000000..233ab24609 --- /dev/null +++ b/bin/less/cmdbuf.c @@ -0,0 +1,1330 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Functions which manipulate the command buffer. + * Used only by command() and related functions. + */ + +#include + +#include "charset.h" +#include "cmd.h" +#include "less.h" + +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 */ +static int prompt_col; /* Column of cursor just after prompt */ +static char *cp; /* Pointer into cmdbuf */ +static int cmd_offset; /* Index into cmdbuf of first displayed char */ +static int literal; /* Next input char should not be interpreted */ +static int updown_match = -1; /* Prefix length in up/down movement */ + +static int cmd_complete(int); +/* + * These variables are statics used by cmd_complete. + */ +static int in_completion = 0; +static char *tk_text; +static char *tk_original; +static char *tk_ipoint; +static char *tk_trial; +static struct textlist tk_tlist; + +static int cmd_left(void); +static int cmd_right(void); + +char openquote = '"'; +char closequote = '"'; + +/* 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. + */ +struct mlist { + struct mlist *next; + 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, 0 }; +void * const ml_search = (void *) &mlist_search; + +struct mlist mlist_examine = + { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 }; +void * const ml_examine = (void *) &mlist_examine; + +struct mlist mlist_shell = + { &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 }; +void * const ml_shell = (void *) &mlist_shell; + +/* + * History for the current command. + */ +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). + */ +void +cmd_reset(void) +{ + cp = cmdbuf; + *cp = '\0'; + cmd_col = 0; + cmd_offset = 0; + literal = 0; + cmd_mbc_buf_len = 0; + updown_match = -1; +} + +/* + * Clear command line. + */ +void +clear_cmd(void) +{ + cmd_col = prompt_col = 0; + cmd_mbc_buf_len = 0; + updown_match = -1; +} + +/* + * Display a string, usually as a prompt for input into the command buffer. + */ +void +cmd_putstr(char *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; + } +} + +/* + * How many characters are in the command buffer? + */ +int +len_cmdbuf(void) +{ + 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(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 prlen = strlen(pr); + if (pwidth != NULL) + *pwidth = prlen; + if (bswidth != NULL) + *bswidth = prlen; + } + } 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 prlen = strlen(pr); + if (pwidth != NULL) + *pwidth = prlen; + if (bswidth != NULL) + *bswidth = prlen; + } 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(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(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). + */ +static void +cmd_repaint(char *old_cp) +{ + /* + * Repaint the line from the current position. + */ + clear_eol(); + while (*cp != '\0') { + char *np = cp; + int width; + char *pr = cmd_step_right(&np, &width, NULL); + if (cmd_col + width >= sc_width) + break; + 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. + */ + while (cp > old_cp) + cmd_left(); +} + +/* + * Put the cursor at "home" (just after the prompt), + * and set cp to the corresponding char in cmdbuf. + */ +static void +cmd_home(void) +{ + while (cmd_col > prompt_col) { + int width, bswidth; + + cmd_step_left(&cp, &width, &bswidth); + while (bswidth-- > 0) + putbs(); + cmd_col -= width; + } + + cp = &cmdbuf[cmd_offset]; +} + +/* + * Shift the cmdbuf display left a half-screen. + */ +static void +cmd_lshift(void) +{ + char *s; + char *save_cp; + int cols; + + /* + * Start at the first displayed char, count how far to the + * right we'd have to move to reach the center of the screen. + */ + s = cmdbuf + cmd_offset; + cols = 0; + while (cols < (sc_width - prompt_col) / 2 && *s != '\0') { + 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; + cmd_home(); + cmd_repaint(save_cp); +} + +/* + * Shift the cmdbuf display right a half-screen. + */ +static void +cmd_rshift(void) +{ + char *s; + char *save_cp; + int cols; + + /* + * Start at the first displayed char, count how far to the + * left we'd have to move to traverse a half-screen width + * of displayed characters. + */ + s = cmdbuf + cmd_offset; + cols = 0; + while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf) { + int width; + cmd_step_left(&s, &width, NULL); + cols += width; + } + + cmd_offset = s - cmdbuf; + save_cp = cp; + cmd_home(); + cmd_repaint(save_cp); +} + +/* + * Move cursor right one character. + */ +static int +cmd_right(void) +{ + char *pr; + char *ncp; + int width; + + if (*cp == '\0') { + /* Already at the end of the line. */ + return (CC_OK); + } + ncp = cp; + pr = cmd_step_right(&ncp, &width, NULL); + if (cmd_col + width >= sc_width) + cmd_lshift(); + else if (cmd_col + width == sc_width - 1 && cp[1] != '\0') + cmd_lshift(); + 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); +} + +/* + * Move cursor left one character. + */ +static int +cmd_left(void) +{ + char *ncp; + int width, bswidth; + + if (cp <= cmdbuf) { + /* Already at the beginning of the line */ + return (CC_OK); + } + ncp = cp; + while (ncp > cmdbuf) { + cmd_step_left(&ncp, &width, &bswidth); + if (width > 0) + break; + } + if (cmd_col < prompt_col + width) + cmd_rshift(); + cp = ncp; + cmd_col -= width; + while (bswidth-- > 0) + putbs(); + return (CC_OK); +} + +/* + * Insert a char into the command buffer, at the current position. + */ +static int +cmd_ichar(char *cs, int clen) +{ + char *s; + + if (strlen(cmdbuf) + clen >= sizeof (cmdbuf)-1) { + /* No room in the command buffer for another char. */ + ring_bell(); + return (CC_ERROR); + } + + /* + * Make room for the new character (shift the tail of the buffer right). + */ + for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--) + 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. + */ + updown_match = -1; + cmd_repaint(cp); + cmd_right(); + return (CC_OK); +} + +/* + * Backspace in the command buffer. + * Delete the char to the left of the cursor. + */ +static int +cmd_erase(void) +{ + char *s; + int clen; + + if (cp == cmdbuf) { + /* + * Backspace past beginning of the buffer: + * this usually means abort the command. + */ + return (CC_QUIT); + } + /* + * 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++) { + s[0] = s[clen]; + if (s[0] == '\0') + break; + } + + /* + * Repaint the buffer after the erased char. + */ + updown_match = -1; + cmd_repaint(cp); + + /* + * We say that erasing the entire command string causes us + * to abort the current command, if CF_QUIT_ON_ERASE is set. + */ + if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0') + return (CC_QUIT); + return (CC_OK); +} + +/* + * Delete the char under the cursor. + */ +static int +cmd_delete(void) +{ + if (*cp == '\0') { + /* At end of string; there is no char under the cursor. */ + return (CC_OK); + } + /* + * Move right, then use cmd_erase. + */ + cmd_right(); + cmd_erase(); + return (CC_OK); +} + +/* + * Delete the "word" to the left of the cursor. + */ +static int +cmd_werase(void) +{ + if (cp > cmdbuf && cp[-1] == ' ') { + /* + * If the char left of cursor is a space, + * erase all the spaces left of cursor (to the first non-space). + */ + while (cp > cmdbuf && cp[-1] == ' ') + (void) cmd_erase(); + } else { + /* + * If the char left of cursor is not a space, + * erase all the nonspaces left of cursor (the whole "word"). + */ + while (cp > cmdbuf && cp[-1] != ' ') + (void) cmd_erase(); + } + return (CC_OK); +} + +/* + * Delete the "word" under the cursor. + */ +static int +cmd_wdelete(void) +{ + if (*cp == ' ') { + /* + * If the char under the cursor is a space, + * delete it and all the spaces right of cursor. + */ + while (*cp == ' ') + (void) cmd_delete(); + } else { + /* + * If the char under the cursor is not a space, + * delete it and all nonspaces right of cursor (the whole word). + */ + while (*cp != ' ' && *cp != '\0') + (void) cmd_delete(); + } + return (CC_OK); +} + +/* + * Delete all chars in the command buffer. + */ +static int +cmd_kill(void) +{ + if (cmdbuf[0] == '\0') { + /* Buffer is already empty; abort the current command. */ + return (CC_QUIT); + } + cmd_offset = 0; + cmd_home(); + *cp = '\0'; + updown_match = -1; + cmd_repaint(cp); + + /* + * We say that erasing the entire command string causes us + * to abort the current command, if CF_QUIT_ON_ERASE is set. + */ + if (curr_cmdflags & CF_QUIT_ON_ERASE) + return (CC_QUIT); + return (CC_OK); +} + +/* + * Select an mlist structure to be the current command history. + */ +void +set_mlist(void *mlist, int cmdflags) +{ + 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; +} + +/* + * Move up or down in the currently selected command history list. + * Only consider entries whose first updown_match chars are equal to + * cmdbuf's corresponding chars. + */ +static int +cmd_updown(int action) +{ + char *s; + struct mlist *ml; + + if (curr_mlist == NULL) { + /* + * The current command has no history list. + */ + ring_bell(); + return (CC_OK); + } + + if (updown_match < 0) { + updown_match = cp - cmdbuf; + } + + /* + * Find the next history entry which matches. + */ + for (ml = curr_mlist->curr_mp; ; ) { + ml = (action == EC_UP) ? ml->prev : ml->next; + if (ml == curr_mlist) { + /* + * We reached the end (or beginning) of the list. + */ + break; + } + if (strncmp(cmdbuf, ml->string, updown_match) == 0) { + /* + * This entry matches; stop here. + * Copy the entry into cmdbuf and echo it on the screen. + */ + curr_mlist->curr_mp = ml; + s = ml->string; + if (s == NULL) + s = ""; + cmd_home(); + clear_eol(); + strlcpy(cmdbuf, s, sizeof (cmdbuf)); + for (cp = cmdbuf; *cp != '\0'; ) + cmd_right(); + return (CC_OK); + } + } + /* + * We didn't find a history entry that matches. + */ + ring_bell(); + return (CC_OK); +} + +/* + * Add a string to a history list. + */ +void +cmd_addhist(struct mlist *mlist, const char *cmd) +{ + struct mlist *ml; + + /* + * Don't save a trivial command. + */ + if (strlen(cmd) == 0) + return; + + /* + * Save the command unless it's a duplicate of the + * last command in the history. + */ + ml = mlist->prev; + if (ml == mlist || strcmp(ml->string, cmd) != 0) { + /* + * Did not find command in history. + * Save the command and put it at the end of the history list. + */ + ml = ecalloc(1, sizeof (struct mlist)); + ml->string = estrdup(cmd); + ml->next = mlist; + ml->prev = mlist->prev; + mlist->prev->next = ml; + mlist->prev = ml; + } + /* + * Point to the cmd just after the just-accepted command. + * Thus, an UPARROW will always retrieve the previous command. + */ + mlist->curr_mp = ml->next; +} + +/* + * Accept the command in the command buffer. + * Add it to the currently selected history list. + */ +void +cmd_accept(void) +{ + /* + * Nothing to do if there is no currently selected history list. + */ + if (curr_mlist == NULL) + return; + cmd_addhist(curr_mlist, cmdbuf); + curr_mlist->modified = 1; +} + +/* + * Try to perform a line-edit function on the command buffer, + * using a specified char as a line-editing command. + * Returns: + * CC_PASS The char does not invoke a line edit function. + * CC_OK Line edit function done. + * CC_QUIT The char requests the current command to be aborted. + */ +static int +cmd_edit(int c) +{ + int action; + int flags; + +#define not_in_completion() in_completion = 0 + + /* + * See if the char is indeed a line-editing command. + */ + flags = 0; + if (curr_mlist == NULL) + /* + * No current history; don't accept history manipulation cmds. + */ + flags |= EC_NOHISTORY; + if (curr_mlist == ml_search) + /* + * In a search command; don't accept file-completion cmds. + */ + flags |= EC_NOCOMPLETE; + + action = editchar(c, flags); + + switch (action) { + case EC_RIGHT: + not_in_completion(); + return (cmd_right()); + case EC_LEFT: + not_in_completion(); + return (cmd_left()); + case EC_W_RIGHT: + not_in_completion(); + while (*cp != '\0' && *cp != ' ') + cmd_right(); + while (*cp == ' ') + cmd_right(); + return (CC_OK); + case EC_W_LEFT: + not_in_completion(); + while (cp > cmdbuf && cp[-1] == ' ') + cmd_left(); + while (cp > cmdbuf && cp[-1] != ' ') + cmd_left(); + return (CC_OK); + case EC_HOME: + not_in_completion(); + cmd_offset = 0; + cmd_home(); + cmd_repaint(cp); + return (CC_OK); + case EC_END: + not_in_completion(); + while (*cp != '\0') + cmd_right(); + return (CC_OK); + case EC_INSERT: + not_in_completion(); + return (CC_OK); + case EC_BACKSPACE: + not_in_completion(); + return (cmd_erase()); + 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()); + case EC_DELETE: + not_in_completion(); + return (cmd_delete()); + case EC_W_DELETE: + not_in_completion(); + return (cmd_wdelete()); + case EC_LITERAL: + literal = 1; + return (CC_OK); + case EC_UP: + case EC_DOWN: + not_in_completion(); + return (cmd_updown(action)); + case EC_F_COMPLETE: + case EC_B_COMPLETE: + case EC_EXPAND: + return (cmd_complete(action)); + case EC_NOACTION: + return (CC_OK); + default: + not_in_completion(); + return (CC_PASS); + } +} + +/* + * Insert a string into the command buffer, at the current position. + */ +static int +cmd_istr(char *str) +{ + char *s; + int action; + char *endline = str + strlen(str); + + for (s = str; *s != '\0'; ) { + char *os = s; + step_char(&s, +1, endline); + action = cmd_ichar(os, s - os); + if (action != CC_OK) { + ring_bell(); + return (action); + } + } + return (CC_OK); +} + +/* + * Find the beginning and end of the "current" word. + * This is the word which the cursor (cp) is inside or at the end of. + * Return pointer to the beginning of the word and put the + * cursor at the end of the word. + */ +static char * +delimit_word(void) +{ + char *word; + char *p; + int delim_quoted = 0; + int meta_quoted = 0; + char *esc = get_meta_escape(); + int esclen = strlen(esc); + + /* + * Move cursor to end of word. + */ + if (*cp != ' ' && *cp != '\0') { + /* + * Cursor is on a nonspace. + * Move cursor right to the next space. + */ + while (*cp != ' ' && *cp != '\0') + cmd_right(); + } + + /* + * Find the beginning of the word which the cursor is in. + */ + if (cp == cmdbuf) + return (NULL); + /* + * If we have an unbalanced quote (that is, an open quote + * without a corresponding close quote), we return everything + * from the open quote, including spaces. + */ + for (word = cmdbuf; word < cp; word++) + if (*word != ' ') + break; + if (word >= cp) + return (cp); + for (p = cmdbuf; p < cp; p++) { + if (meta_quoted) { + meta_quoted = 0; + } else if (esclen > 0 && p + esclen < cp && + strncmp(p, esc, esclen) == 0) { + meta_quoted = 1; + p += esclen - 1; + } else if (delim_quoted) { + if (*p == closequote) + delim_quoted = 0; + } else { /* (!delim_quoted) */ + if (*p == openquote) + delim_quoted = 1; + else if (*p == ' ') + word = p+1; + } + } + return (word); +} + +/* + * Set things up to enter completion mode. + * Expand the word under the cursor into a list of filenames + * which start with that word, and set tk_text to that list. + */ +static void +init_compl(void) +{ + char *word; + char c; + + free(tk_text); + tk_text = NULL; + /* + * Find the original (uncompleted) word in the command buffer. + */ + word = delimit_word(); + if (word == NULL) + return; + /* + * Set the insertion point to the point in the command buffer + * where the original (uncompleted) word now sits. + */ + tk_ipoint = word; + /* + * Save the original (uncompleted) word + */ + free(tk_original); + tk_original = ecalloc(cp-word+1, sizeof (char)); + (void) strncpy(tk_original, word, cp-word); + /* + * Get the expanded filename. + * This may result in a single filename, or + * a blank-separated list of filenames. + */ + c = *cp; + *cp = '\0'; + if (*word != openquote) { + tk_text = fcomplete(word); + } else { + char *qword = shell_quote(word+1); + if (qword == NULL) + tk_text = fcomplete(word+1); + else + tk_text = fcomplete(qword); + free(qword); + } + *cp = c; +} + +/* + * Return the next word in the current completion list. + */ +static char * +next_compl(int action, char *prev) +{ + switch (action) { + case EC_F_COMPLETE: + return (forw_textlist(&tk_tlist, prev)); + case EC_B_COMPLETE: + return (back_textlist(&tk_tlist, prev)); + } + /* Cannot happen */ + return ("?"); +} + +/* + * Complete the filename before (or under) the cursor. + * cmd_complete may be called multiple times. The global in_completion + * remembers whether this call is the first time (create the list), + * or a subsequent time (step thru the list). + */ +static int +cmd_complete(int action) +{ + char *s; + + if (!in_completion || action == EC_EXPAND) { + /* + * Expand the word under the cursor and + * use the first word in the expansion + * (or the entire expansion if we're doing EC_EXPAND). + */ + init_compl(); + if (tk_text == NULL) { + ring_bell(); + return (CC_OK); + } + if (action == EC_EXPAND) { + /* + * Use the whole list. + */ + tk_trial = tk_text; + } else { + /* + * Use the first filename in the list. + */ + in_completion = 1; + init_textlist(&tk_tlist, tk_text); + tk_trial = next_compl(action, NULL); + } + } else { + /* + * We already have a completion list. + * Use the next/previous filename from the list. + */ + tk_trial = next_compl(action, tk_trial); + } + + /* + * Remove the original word, or the previous trial completion. + */ + while (cp > tk_ipoint) + (void) cmd_erase(); + + if (tk_trial == NULL) { + /* + * There are no more trial completions. + * Insert the original (uncompleted) filename. + */ + in_completion = 0; + if (cmd_istr(tk_original) != CC_OK) + goto fail; + } else { + /* + * Insert trial completion. + */ + if (cmd_istr(tk_trial) != CC_OK) + goto fail; + /* + * If it is a directory, append a slash. + */ + if (is_dir(tk_trial)) { + if (cp > cmdbuf && cp[-1] == closequote) + (void) cmd_erase(); + s = lgetenv("LESSSEPARATOR"); + if (s == NULL) + s = "/"; + if (cmd_istr(s) != CC_OK) + goto fail; + } + } + + return (CC_OK); + +fail: + in_completion = 0; + ring_bell(); + return (CC_OK); +} + +/* + * Process a single character of a multi-character command, such as + * a number, or the pattern of a search command. + * Returns: + * CC_OK The char was accepted. + * CC_QUIT The char requests the command to be aborted. + * CC_ERROR The char could not be accepted due to an error. + */ +int +cmd_char(int c) +{ + int action; + int len; + + if (!utf_mode) { + cmd_mbc_buf[0] = c & 0xff; + 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 & 0xff; + 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 */ + ring_bell(); + return (CC_ERROR); + } + } else if (IS_UTF8_TRAIL(c)) { + cmd_mbc_buf[cmd_mbc_buf_index++] = c & 0xff; + 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; + ring_bell(); + return (CC_ERROR); + } + } else { + /* Flush incomplete (truncated) sequence. */ + cmd_mbc_buf_len = 0; + ring_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(cmd_mbc_buf, len)); + } + + /* + * See if it is a line-editing character. + */ + if (in_mca() && len == 1) { + action = cmd_edit(c); + switch (action) { + case CC_OK: + case CC_QUIT: + return (action); + case CC_PASS: + break; + } + } + + /* + * Insert the char into the command buffer. + */ + return (cmd_ichar(cmd_mbc_buf, len)); +} + +/* + * Return the number currently in the command buffer. + */ +off_t +cmd_int(long *frac) +{ + char *p; + off_t n = 0; + int err; + + 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); +} + +/* + * Return a pointer to the command buffer. + */ +char * +get_cmdbuf(void) +{ + return (cmdbuf); +} + +/* + * Return the last (most recent) string in the current command history. + */ +char * +cmd_lastpattern(void) +{ + if (curr_mlist == NULL) + return (NULL); + return (curr_mlist->curr_mp->prev->string); +} + +/* + * Get the name of the history file. + */ +static char * +histfile_name(void) +{ + char *home; + char *name; + + /* 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 history file */ + return (NULL); + return (estrdup(name)); + } + + /* Otherwise, file is in $HOME if enabled. */ + if (strcmp(LESSHISTFILE, "-") == 0) + return (NULL); + home = lgetenv("HOME"); + if (home == NULL || *home == '\0') { + return (NULL); + } + return (easprintf("%s/%s", home, LESSHISTFILE)); +} + +/* + * Initialize history from a .lesshist file. + */ +void +init_cmdhist(void) +{ + 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) { + (void) 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) { + ml = &mlist_shell; + } else if (*line == '"') { + if (ml != NULL) + cmd_addhist(ml, line+1); + } + } + (void) fclose(f); +} + +/* + * + */ +static void +save_mlist(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) + (void) fprintf(f, "\"%s\n", ml->string); +} + +/* + * + */ +void +save_cmdhist(void) +{ + char *filename; + FILE *f; + int modified = 0; + int do_chmod = 1; + struct stat statbuf; + int r; + + if (mlist_search.modified) + modified = 1; + if (mlist_shell.modified) + modified = 1; + if (!modified) + return; + filename = histfile_name(); + if (filename == NULL) + return; + f = fopen(filename, "w"); + free(filename); + if (f == NULL) + return; + + /* Make history file readable only by owner. */ + r = fstat(fileno(f), &statbuf); + if (r < 0 || !S_ISREG(statbuf.st_mode)) + /* Don't chmod if not a regular file. */ + do_chmod = 0; + if (do_chmod) + (void) fchmod(fileno(f), 0600); + + (void) fprintf(f, "%s\n", HISTFILE_FIRST_LINE); + + (void) fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION); + save_mlist(&mlist_search, f); + + (void) fprintf(f, "%s\n", HISTFILE_SHELL_SECTION); + save_mlist(&mlist_shell, f); + + (void) fclose(f); +} diff --git a/bin/less/command.c b/bin/less/command.c new file mode 100644 index 0000000000..2447887bcd --- /dev/null +++ b/bin/less/command.c @@ -0,0 +1,1642 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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 "cmd.h" +#include "less.h" +#include "option.h" +#include "position.h" + +extern int erase_char, erase2_char, kill_char; +extern volatile sig_atomic_t sigs; +extern int quit_if_one_screen; +extern int less_is_more; +extern int squished; +extern int sc_width; +extern int sc_height; +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 show_attn; +extern off_t highest_hilite; +extern char *every_first_cmd; +extern char *curr_altfilename; +extern char version[]; +extern struct scrpos initial_scrpos; +extern IFILE curr_ifile; +extern void *ml_search; +extern void *ml_examine; +extern void *ml_shell; +extern char *editor; +extern char *editproto; +extern int screen_trashed; /* The screen has been overwritten */ +extern int shift_count; +extern int oldbot; +extern int forw_prompt; + +static int mca; /* The multicharacter command (action) */ +static int search_type; /* The previous type of search */ +static off_t 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 off_t bottompos; +static int save_hshift; +static int pipec; + +struct ungot { + struct ungot *ug_next; + int ug_char; +}; +static struct ungot *ungot = NULL; +static int unget_end = 0; + +static void multi_search(char *, int); + +/* + * 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(0); +} + +/* + * Set up the display to start a new multi-character command. + */ +static void +start_mca(int action, const char *prompt, void *mlist, int cmdflags) +{ + mca = action; + clear_bot(); + clear_cmd(); + cmd_putstr((char *)prompt); + set_mlist(mlist, cmdflags); +} + +int +in_mca(void) +{ + return (mca != 0 && mca != A_PREFIX); +} + +/* + * Set up the display to start a new search command. + */ +static void +mca_search(void) +{ + if (search_type & SRCH_FILTER) + mca = A_FILTER; + else if (search_type & SRCH_FORW) + mca = A_F_SEARCH; + else + mca = A_B_SEARCH; + + clear_bot(); + clear_cmd(); + + 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_FILTER) + cmd_putstr("&/"); + else if (search_type & SRCH_FORW) + cmd_putstr("/"); + else + cmd_putstr("?"); + 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) ? "_" : "-"; + + mca = A_OPT_TOGGLE; + clear_bot(); + clear_cmd(); + 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; + } + set_mlist(NULL, 0); +} + +/* + * Execute a multicharacter command. + */ +static void +exec_mca(void) +{ + char *cbuf; + + cmd_exec(); + cbuf = get_cmdbuf(); + + switch (mca) { + case A_F_SEARCH: + case A_B_SEARCH: + multi_search(cbuf, (int)number); + break; + case A_FILTER: + search_type ^= SRCH_NO_MATCH; + set_filter_pattern(cbuf, search_type); + break; + case A_FIRSTCMD: + /* + * Skip leading spaces or + signs in the string. + */ + while (*cbuf == '+' || *cbuf == ' ') + cbuf++; + free(every_first_cmd); + if (*cbuf == '\0') + every_first_cmd = NULL; + else + every_first_cmd = estrdup(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; + case A_EXAMINE: + if (secure) + break; + + /* POSIX behavior, but possibly generally useful */ + if (strlen(cbuf) == 0) { + reopen_curr_ifile(); + jump_back(1); + break; + } + /* POSIX behavior - probably not generally useful */ + if (less_is_more && (strcmp(cbuf, "#") == 0)) { + if (ntags()) { + error("No previous file", NULL); + break; + } + if (edit_prev(1)) { + error("No previous file", NULL); + } else { + jump_back(1); + } + break; + } + edit_list(cbuf); + /* If tag structure is loaded then clean it up. */ + cleantags(); + break; + case A_PIPE: + if (secure) + break; + (void) pipe_mark(pipec, cbuf); + error("|done", NULL); + break; + } +} + +/* + * 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); +} + +/* + * Handle the first char of an option (after the initial dash). + */ +static int +mca_opt_first_char(int c) +{ + 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 = (flag == OPT_UNSET) ? OPT_TOGGLE : OPT_UNSET; + mca_opt_toggle(); + return (MCA_MORE); + case '!': + /* "-!" = SET */ + optflag = (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; + + 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(); + opt_lower = islower(p[0]); + curropt = findopt_name(&p, &oname, NULL); + 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 && islower(c)) + c = toupper(c); + if (cmd_char(c) != CC_OK) + return (MCA_DONE); + } + } + 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 (c != '\n' && c != '\r') + return (mca_opt_nonfirst_char(c)); + if (curropt == NULL) { + parg.p_string = get_cmdbuf(); + 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); + } + } + /* + * 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, islower(c), "", optflag); + return (MCA_DONE); + } + /* + * Display a prompt appropriate for the option parameter. + */ + start_mca(A_OPT_TOGGLE, opt_prompt(curropt), NULL, 0); + return (MCA_MORE); +} + +/* + * 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 CONTROL('E'): /* ignore END of file */ + case '*': + if (mca != A_FILTER) + flag = SRCH_PAST_EOF; + break; + case CONTROL('F'): /* FIRST file */ + case '@': + 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('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) { + 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 == '.') && editchar(c, + EC_PEEK|EC_NOHISTORY|EC_NOCOMPLETE|EC_NORIGHTLEFT) == + A_INVALID) { + /* + * Not part of the number. + * End the number and treat this char + * as a normal command character. + */ + number = cmd_int(&fraction); + mca = 0; + 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 (c == '\n' || c == '\r') { + /* + * 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); + + if ((mca == A_F_BRACKET || mca == A_B_BRACKET) && 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); + } + + /* + * 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(); + clr_hilite(); +} + +/* + * Make sure the screen is displayed. + */ +static void +make_display(void) +{ + /* + * If nothing is displayed yet, display starting from initial_scrpos. + */ + if (empty_screen()) { + if (initial_scrpos.pos == -1) + /* + * {{ Maybe this should be: + * jump_loc(ch_zero(), jump_sline); + * but this behavior seems rather unexpected + * on the first screen. }} + */ + jump_loc(ch_zero(), 1); + else + jump_loc(initial_scrpos.pos, initial_scrpos.ln); + } else if (screen_trashed) { + 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) +{ + const char *p; + + if (ungot != NULL) { + /* + * 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) + 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) + quit(QUIT_OK); + + /* + * 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 = prompt_string(); + if (is_filtering()) + putstr("& "); + if (p == NULL || *p == '\0') { + putchr(':'); + } else { + at_enter(AT_STANDOUT); + putstr(p); + at_exit(); + } + clear_eol(); +} + +/* + * Display the less version message. + */ +void +dispversion(void) +{ + PARG parg; + + parg.p_string = version; + error("less %s", &parg); +} + +/* + * Get command character. + * The character normally comes from the keyboard, + * but may come from ungotten characters + * (characters previously given to ungetcc or ungetsc). + */ +int +getcc(void) +{ + if (unget_end) { + /* + * We have just run out of ungotten chars. + */ + unget_end = 0; + if (len_cmdbuf() == 0 || !empty_screen()) + return (getchr()); + /* + * Command is incomplete, so try to complete it. + */ + 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: + /* + * We have "/string" but no newline. Add the \n. + */ + return ('\n'); + + default: + /* + * Some other incomplete command. Let user complete it. + */ + return (getchr()); + } + } + + if (ungot == NULL) { + /* + * Normal case: no ungotten chars, so get one from the user. + */ + return (getchr()); + } + + /* + * Return the next ungotten char. + */ + { + struct ungot *ug = ungot; + int c = ug->ug_char; + ungot = ug->ug_next; + free(ug); + unget_end = (ungot == NULL); + return (c); + } +} + +/* + * "Unget" a command character. + * The next getcc() will return this character. + */ +void +ungetcc(int c) +{ + struct ungot *ug = ecalloc(1, sizeof (struct ungot)); + + ug->ug_char = c; + ug->ug_next = ungot; + ungot = ug; + unget_end = 0; +} + +/* + * Unget a whole string of command characters. + * The next sequence of getcc()'s will return this string. + */ +void +ungetsc(char *s) +{ + char *p; + + for (p = s + strlen(s) - 1; p >= s; p--) + ungetcc(*p); +} + +/* + * Search for a pattern, possibly in multiple files. + * If SRCH_FIRST_FILE is set, begin searching at the first file. + * If SRCH_PAST_EOF is set, continue the search thru multiple files. + */ +static void +multi_search(char *pattern, int n) +{ + int nomore; + IFILE save_ifile; + int changed_file; + + changed_file = 0; + save_ifile = save_curr_ifile(); + + if (search_type & SRCH_FIRST_FILE) { + /* + * Start at the first (or last) file + * in the command line list. + */ + if (search_type & SRCH_FORW) + nomore = edit_first(); + else + nomore = edit_last(); + if (nomore) { + unsave_ifile(save_ifile); + return; + } + changed_file = 1; + search_type &= ~SRCH_FIRST_FILE; + } + + for (;;) { + n = search(search_type, pattern, n); + /* + * The SRCH_NO_MOVE flag doesn't "stick": it gets cleared + * after being used once. This allows "n" to work after + * using a /@@ search. + */ + search_type &= ~SRCH_NO_MOVE; + if (n == 0) { + /* + * Found it. + */ + unsave_ifile(save_ifile); + return; + } + + if (n < 0) + /* + * Some kind of error in the search. + * Error message has been printed by search(). + */ + break; + + if ((search_type & SRCH_PAST_EOF) == 0) + /* + * We didn't find a match, but we're + * supposed to search only one file. + */ + break; + /* + * Move on to the next file. + */ + if (search_type & SRCH_FORW) + nomore = edit_next(1); + else + nomore = edit_prev(1); + if (nomore) + break; + changed_file = 1; + } + + /* + * Didn't find it. + * Print an error message if we haven't already. + */ + if (n > 0) + error("Pattern not found", NULL); + + if (changed_file) { + /* + * Restore the file we were originally viewing. + */ + reedit_ifile(save_ifile); + } else { + unsave_ifile(save_ifile); + } +} + +/* + * Forward forever, or until a highlighted line appears. + */ +static int +forw_loop(int until_hilite) +{ + off_t curr_len; + + if (ch_getflags() & CH_HELPFILE) + return (A_NOACTION); + + cmd_exec(); + jump_forw(); + curr_len = ch_length(); + highest_hilite = until_hilite ? curr_len : -1; + ignore_eoi = 1; + while (!sigs) { + if (until_hilite && highest_hilite > curr_len) { + ring_bell(); + break; + } + make_display(); + forward(1, 0, 0); + } + ignore_eoi = 0; + ch_set_eof(); + + /* + * This gets us back in "F mode" after processing + * a non-abort signal (e.g. window-change). + */ + if (sigs && !ABORT_SIGS()) + return (until_hilite ? A_F_UNTIL_HILITE : A_F_FOREVER); + + return (A_NOACTION); +} + +/* + * Main command processor. + * Accept and execute commands until a quit command. + */ +void +commands(void) +{ + int c = 0; + int action; + char *cbuf; + int newaction; + int save_search_type; + char *extra; + char tbuf[2]; + PARG parg; + IFILE old_ifile; + IFILE new_ifile; + char *tagfile; + + search_type = SRCH_FORW; + wscroll = (sc_height + 1) / 2; + newaction = A_NOACTION; + + for (;;) { + mca = 0; + cmd_accept(); + number = 0; + curropt = NULL; + + /* + * See if any signals need processing. + */ + if (sigs) { + psignals(); + if (quitting) + quit(QUIT_SAVED_STATUS); + } + + /* + * Display prompt and accept a character. + */ + cmd_reset(); + prompt(); + if (sigs) + continue; + if (newaction == A_NOACTION) + c = getcc(); + +again: + if (sigs) + continue; + + if (newaction != A_NOACTION) { + action = newaction; + newaction = A_NOACTION; + } else { + /* + * If we are in a multicharacter command, call mca_char. + * Otherwise we call fcmd_decode to determine the + * action to be performed. + */ + if (mca) + switch (mca_char(c)) { + case MCA_MORE: + /* + * Need another character. + */ + c = getcc(); + goto again; + case MCA_DONE: + /* + * Command has been handled by mca_char. + * Start clean with a prompt. + */ + continue; + case NO_MCA: + /* + * Not a multi-char command + * (at least, not anymore). + */ + break; + } + + /* + * Decode the command character and decide what to do. + */ + if (mca) { + /* + * We're in a multichar command. + * Add the character to the command buffer + * and display it on the screen. + * If the user backspaces past the start + * of the line, abort the command. + */ + if (cmd_char(c) == CC_QUIT || len_cmdbuf() == 0) + continue; + cbuf = get_cmdbuf(); + } else { + /* + * Don't use cmd_char if we're starting fresh + * at the beginning of a command, because we + * don't want to echo the command until we know + * it is a multichar command. We also don't + * want erase_char/kill_char to be treated + * as line editing characters. + */ + tbuf[0] = (char)c; + tbuf[1] = '\0'; + cbuf = tbuf; + } + extra = NULL; + action = fcmd_decode(cbuf, &extra); + /* + * If an "extra" string was returned, + * process it as a string of command characters. + */ + if (extra != NULL) + ungetsc(extra); + } + /* + * Clear the cmdbuf string. + * (But not if we're in the prefix of a command, + * because the partial command string is kept there.) + */ + if (action != A_PREFIX) + cmd_reset(); + + switch (action) { + case A_DIGIT: + /* + * First digit of a number. + */ + start_mca(A_DIGIT, ":", (void*)NULL, CF_QUIT_ON_ERASE); + goto again; + + case A_F_WINDOW: + /* + * Forward one window (and set the window size). + */ + if (number > 0) + swindow = (int)number; + /* FALLTHRU */ + case A_F_SCREEN: + /* + * Forward one screen. + */ + if (number <= 0) + number = get_swindow(); + cmd_exec(); + if (show_attn) + set_attnpos(bottompos); + forward((int)number, 0, 1); + break; + + case A_B_WINDOW: + /* + * Backward one window (and set the window size). + */ + if (number > 0) + swindow = (int)number; + /* FALLTHRU */ + case A_B_SCREEN: + /* + * Backward one screen. + */ + if (number <= 0) + number = get_swindow(); + cmd_exec(); + backward((int)number, 0, 1); + break; + + case A_F_LINE: + /* + * Forward N (default 1) line. + */ + if (number <= 0) + number = 1; + cmd_exec(); + if (show_attn == OPT_ONPLUS && number > 1) + set_attnpos(bottompos); + forward((int)number, 0, 0); + break; + + case A_B_LINE: + /* + * Backward N (default 1) line. + */ + if (number <= 0) + number = 1; + cmd_exec(); + backward((int)number, 0, 0); + break; + + case A_F_SKIP: + /* + * Skip ahead one screen, and then number lines. + */ + if (number <= 0) { + number = get_swindow(); + } else { + number += get_swindow(); + } + cmd_exec(); + if (show_attn == OPT_ONPLUS) + set_attnpos(bottompos); + forward((int)number, 0, 1); + break; + + case A_FF_LINE: + /* + * Force forward N (default 1) line. + */ + if (number <= 0) + number = 1; + cmd_exec(); + if (show_attn == OPT_ONPLUS && number > 1) + set_attnpos(bottompos); + forward((int)number, 1, 0); + break; + + case A_BF_LINE: + /* + * Force backward N (default 1) line. + */ + if (number <= 0) + number = 1; + cmd_exec(); + backward((int)number, 1, 0); + break; + + case A_FF_SCREEN: + /* + * Force forward one screen. + */ + if (number <= 0) + number = get_swindow(); + cmd_exec(); + if (show_attn == OPT_ONPLUS) + set_attnpos(bottompos); + forward((int)number, 1, 0); + break; + + case A_F_FOREVER: + /* + * Forward forever, ignoring EOF. + */ + newaction = forw_loop(0); + break; + + case A_F_UNTIL_HILITE: + newaction = forw_loop(1); + break; + + case A_F_SCROLL: + /* + * Forward N lines + * (default same as last 'd' or 'u' command). + */ + if (number > 0) + wscroll = (int)number; + cmd_exec(); + if (show_attn == OPT_ONPLUS) + set_attnpos(bottompos); + forward(wscroll, 0, 0); + break; + + case A_B_SCROLL: + /* + * Forward N lines + * (default same as last 'd' or 'u' command). + */ + if (number > 0) + wscroll = (int)number; + cmd_exec(); + backward(wscroll, 0, 0); + break; + + case A_FREPAINT: + /* + * Flush buffers, then repaint screen. + * Don't flush the buffers on a pipe! + */ + clear_buffers(); + /* FALLTHRU */ + case A_REPAINT: + /* + * Repaint screen. + */ + cmd_exec(); + repaint(); + break; + + case A_GOLINE: + /* + * Go to line N, default beginning of file. + */ + if (number <= 0) + number = 1; + cmd_exec(); + jump_back(number); + break; + + case A_PERCENT: + /* + * Go to a specified percentage into the file. + */ + if (number < 0) { + number = 0; + fraction = 0; + } + if (number > 100) { + number = 100; + fraction = 0; + } + cmd_exec(); + jump_percent((int)number, fraction); + break; + + case A_GOEND: + /* + * Go to line N, default end of file. + */ + cmd_exec(); + if (number <= 0) + jump_forw(); + else + jump_back(number); + break; + + case A_GOPOS: + /* + * Go to a specified byte position in the file. + */ + cmd_exec(); + if (number < 0) + number = 0; + jump_line_loc((off_t) number, jump_sline); + break; + + case A_STAT: + /* + * Print file name, etc. + */ + if (ch_getflags() & CH_HELPFILE) + break; + cmd_exec(); + parg.p_string = eq_message(); + error("%s", &parg); + break; + + case A_VERSION: + /* + * Print version number, without the "@(#)". + */ + cmd_exec(); + dispversion(); + break; + + case A_QUIT: + /* + * Exit. + */ + if (curr_ifile != NULL && + ch_getflags() & CH_HELPFILE) { + /* + * Quit while viewing the help file + * just means return to viewing the + * previous file. + */ + hshift = save_hshift; + if (edit_prev(1) == 0) + break; + } + if (extra != NULL) + quit(*extra); + quit(QUIT_OK); + break; + +/* + * Define abbreviation for a commonly used sequence below. + */ +#define DO_SEARCH() \ + if (number <= 0) number = 1; \ + mca_search(); \ + cmd_exec(); \ + multi_search(NULL, (int)number); + + + case A_F_SEARCH: + /* + * Search forward for a pattern. + * Get the first char of the pattern. + */ + search_type = SRCH_FORW; + if (number <= 0) + number = 1; + mca_search(); + c = getcc(); + goto again; + + case A_B_SEARCH: + /* + * Search backward for a pattern. + * Get the first char of the pattern. + */ + search_type = SRCH_BACK; + if (number <= 0) + number = 1; + mca_search(); + c = getcc(); + goto again; + + case A_FILTER: + search_type = SRCH_FORW | SRCH_FILTER; + mca_search(); + c = getcc(); + goto again; + + case A_AGAIN_SEARCH: + /* + * Repeat previous search. + */ + DO_SEARCH(); + break; + + case A_T_AGAIN_SEARCH: + /* + * Repeat previous search, multiple files. + */ + search_type |= SRCH_PAST_EOF; + DO_SEARCH(); + break; + + case A_REVERSE_SEARCH: + /* + * Repeat previous search, in reverse direction. + */ + save_search_type = search_type; + search_type = SRCH_REVERSE(search_type); + DO_SEARCH(); + search_type = save_search_type; + break; + + case A_T_REVERSE_SEARCH: + /* + * Repeat previous search, + * multiple files in reverse direction. + */ + save_search_type = search_type; + search_type = SRCH_REVERSE(search_type); + search_type |= SRCH_PAST_EOF; + DO_SEARCH(); + search_type = save_search_type; + break; + + case A_UNDO_SEARCH: + undo_search(); + break; + + case A_HELP: + /* + * Help. + */ + if (ch_getflags() & CH_HELPFILE) + break; + if (ungot != NULL || unget_end) { + error(less_is_more + ? "Invalid option -p h" + : "Invalid option ++h", + NULL); + break; + } + cmd_exec(); + save_hshift = hshift; + hshift = 0; + (void) edit(helpfile()); + break; + + case A_EXAMINE: + /* + * Edit a new file. Get the filename. + */ + if (secure) { + error("Command not available", NULL); + break; + } + start_mca(A_EXAMINE, "Examine: ", ml_examine, 0); + c = getcc(); + goto again; + + case A_VISUAL: + /* + * Invoke an editor on the input file. + */ + if (secure) { + error("Command not available", NULL); + break; + } + if (ch_getflags() & CH_HELPFILE) + break; + if (strcmp(get_filename(curr_ifile), "-") == 0) { + error("Cannot edit standard input", NULL); + break; + } + if (curr_altfilename != NULL) { + error("WARNING: This file was viewed via " + "LESSOPEN", NULL); + } + /* + * Expand the editor prototype string + * and pass it to the system to execute. + * (Make sure the screen is displayed so the + * expansion of "+%lm" works.) + */ + make_display(); + cmd_exec(); + lsystem(pr_expand(editproto, 0), NULL); + break; + + case A_NEXT_FILE: + /* + * Examine next file. + */ + if (ntags()) { + error("No next file", NULL); + break; + } + if (number <= 0) + number = 1; + if (edit_next((int)number)) { + if (get_quit_at_eof() && eof_displayed() && + !(ch_getflags() & CH_HELPFILE)) + quit(QUIT_OK); + parg.p_string = (number > 1) ? "(N-th) " : ""; + error("No %snext file", &parg); + } + break; + + case A_PREV_FILE: + /* + * Examine previous file. + */ + if (ntags()) { + error("No previous file", NULL); + break; + } + if (number <= 0) + number = 1; + if (edit_prev((int)number)) { + parg.p_string = (number > 1) ? "(N-th) " : ""; + error("No %sprevious file", &parg); + } + break; + + case A_NEXT_TAG: + if (number <= 0) + number = 1; + cmd_exec(); + tagfile = nexttag((int)number); + if (tagfile == NULL) { + error("No next tag", NULL); + break; + } + if (edit(tagfile) == 0) { + off_t pos = tagsearch(); + if (pos != -1) + jump_loc(pos, jump_sline); + } + break; + + case A_PREV_TAG: + if (number <= 0) + number = 1; + tagfile = prevtag((int)number); + if (tagfile == NULL) { + error("No previous tag", NULL); + break; + } + if (edit(tagfile) == 0) { + off_t pos = tagsearch(); + if (pos != -1) + jump_loc(pos, jump_sline); + } + break; + + case A_INDEX_FILE: + /* + * Examine a particular file. + */ + if (number <= 0) + number = 1; + if (edit_index((int)number)) + error("No such file", NULL); + break; + + case A_REMOVE_FILE: + if (ch_getflags() & CH_HELPFILE) + break; + old_ifile = curr_ifile; + new_ifile = getoff_ifile(curr_ifile); + if (new_ifile == NULL) { + ring_bell(); + break; + } + if (edit_ifile(new_ifile) != 0) { + reedit_ifile(old_ifile); + break; + } + del_ifile(old_ifile); + break; + + case A_OPT_TOGGLE: + optflag = OPT_TOGGLE; + optgetname = FALSE; + mca_opt_toggle(); + c = getcc(); + goto again; + + case A_DISP_OPTION: + /* + * Report a flag setting. + */ + optflag = OPT_NO_TOGGLE; + optgetname = FALSE; + mca_opt_toggle(); + c = getcc(); + goto again; + + case A_FIRSTCMD: + /* + * Set an initial command for new files. + */ + start_mca(A_FIRSTCMD, "+", NULL, 0); + c = getcc(); + goto again; + + case A_SETMARK: + /* + * Set a mark. + */ + if (ch_getflags() & CH_HELPFILE) + break; + start_mca(A_SETMARK, "mark: ", (void*)NULL, 0); + c = getcc(); + if (c == erase_char || c == erase2_char || + c == kill_char || c == '\n' || c == '\r') + break; + setmark(c); + break; + + case A_GOMARK: + /* + * Go to a mark. + */ + start_mca(A_GOMARK, "goto mark: ", (void*)NULL, 0); + c = getcc(); + if (c == erase_char || c == erase2_char || + c == kill_char || c == '\n' || c == '\r') + break; + cmd_exec(); + gomark(c); + break; + + case A_PIPE: + if (secure) { + error("Command not available", NULL); + break; + } + start_mca(A_PIPE, "|mark: ", (void*)NULL, 0); + c = getcc(); + if (c == erase_char || c == erase2_char || + c == kill_char) + break; + if (c == '\n' || c == '\r') + c = '.'; + if (badmark(c)) + break; + pipec = c; + start_mca(A_PIPE, "!", ml_shell, 0); + c = getcc(); + goto again; + + case A_B_BRACKET: + case A_F_BRACKET: + start_mca(action, "Brackets: ", (void*)NULL, 0); + c = getcc(); + goto again; + + case A_LSHIFT: + if (number > 0) + shift_count = number; + else + number = (shift_count > 0) ? + shift_count : sc_width / 2; + if (number > hshift) + number = hshift; + hshift -= number; + screen_trashed = 1; + break; + + case A_RSHIFT: + if (number > 0) + shift_count = number; + else + number = (shift_count > 0) ? + shift_count : sc_width / 2; + hshift += number; + screen_trashed = 1; + break; + + case A_PREFIX: + /* + * The command is incomplete (more chars are needed). + * Display the current char, so the user knows + * what's going on, and get another character. + */ + if (mca != A_PREFIX) { + cmd_reset(); + start_mca(A_PREFIX, " ", (void*)NULL, + CF_QUIT_ON_ERASE); + (void) cmd_char(c); + } + c = getcc(); + goto again; + + case A_NOACTION: + break; + + default: + ring_bell(); + break; + } + } +} diff --git a/bin/less/compat/include/sys/time.h b/bin/less/compat/include/sys/time.h new file mode 100644 index 0000000000..592f647ccb --- /dev/null +++ b/bin/less/compat/include/sys/time.h @@ -0,0 +1,8 @@ +#ifndef COMPAT_SYS_TIME_H +#define COMPAT_SYS_TIME_H +#include_next +#define timespeccmp(tsp, usp, cmp) \ + (((tsp)->tv_sec == (usp)->tv_sec) ? \ + ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ + ((tsp)->tv_sec cmp (usp)->tv_sec)) +#endif diff --git a/bin/less/cvt.c b/bin/less/cvt.c new file mode 100644 index 0000000000..d47ef133ed --- /dev/null +++ b/bin/less/cvt.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to convert text in various ways. Used by search. + */ + +#include "charset.h" +#include "less.h" + +extern int utf_mode; + +/* + * Get the length of a buffer needed to convert a string. + */ +int +cvt_length(int len) +{ + if (utf_mode) + /* + * Just copying a string in UTF-8 mode can cause it to grow + * in length. + * Four output bytes for one input byte is the worst case. + */ + len *= 4; + return (len + 1); +} + +/* + * Allocate a chpos array for use by cvt_text. + */ +int * +cvt_alloc_chpos(int len) +{ + int i; + int *chpos = ecalloc(sizeof (int), len); + /* Initialize all entries to an invalid position. */ + for (i = 0; i < len; i++) + chpos[i] = -1; + return (chpos); +} + +/* + * Convert text. Perform the transformations specified by ops. + * Returns converted text in odst. The original offset of each + * odst character (when it was in osrc) is returned in the chpos array. + */ +void +cvt_text(char *odst, char *osrc, int *chpos, int *lenp, int ops) +{ + char *dst; + char *edst = odst; + char *src; + char *src_end; + LWCHAR ch; + + if (lenp != NULL) + src_end = osrc + *lenp; + else + src_end = osrc + strlen(osrc); + + for (src = osrc, dst = odst; src < src_end; ) { + int src_pos = src - osrc; + int dst_pos = dst - odst; + ch = step_char(&src, +1, src_end); + if ((ops & CVT_BS) && ch == '\b' && dst > odst) { + /* Delete backspace and preceding char. */ + do { + dst--; + } while (dst > odst && + !IS_ASCII_OCTET(*dst) && !IS_UTF8_LEAD(*dst)); + } else if ((ops & CVT_ANSI) && IS_CSI_START(ch)) { + /* Skip to end of ANSI escape sequence. */ + src++; /* skip the CSI start char */ + while (src < src_end) + if (!is_ansi_middle(*src++)) + break; + } else { + /* Just copy the char to the destination buffer. */ + if ((ops & CVT_TO_LC) && iswupper(ch)) + ch = towlower(ch); + put_wchar(&dst, ch); + /* Record the original position of the char. */ + if (chpos != NULL) + chpos[dst_pos] = src_pos; + } + if (dst > edst) + edst = dst; + } + if ((ops & CVT_CRLF) && edst > odst && edst[-1] == '\r') + edst--; + *edst = '\0'; + if (lenp != NULL) + *lenp = edst - odst; +} diff --git a/bin/less/decode.c b/bin/less/decode.c new file mode 100644 index 0000000000..ba7a2e8e02 --- /dev/null +++ b/bin/less/decode.c @@ -0,0 +1,781 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to decode user commands. + * + * This is all table driven. + * A command table is a sequence of command descriptors. + * Each command descriptor is a sequence of bytes with the following format: + * ...<0> + * The characters c1,c2,...,cN are the command string; that is, + * the characters which the user must type. + * It is terminated by a null <0> byte. + * The byte after the null byte is the action code associated + * with the command string. + * If an action byte is OR-ed with A_EXTRA, this indicates + * that the option byte is followed by an extra string. + * + * There may be many command tables. + * The first (default) table is built-in. + * Other tables are read in from "lesskey" files. + * All the tables are linked together and are searched in order. + */ + +#include "cmd.h" +#include "less.h" +#include "lesskey.h" + +extern int erase_char, erase2_char, kill_char; +extern int secure, less_is_more; + +#define SK(k) \ + SK_SPECIAL_KEY, (k), 6, 1, 1, 1 +/* + * Command table is ordered roughly according to expected + * frequency of use, so the common commands are near the beginning. + */ + +static unsigned char cmdtable[] = +{ + '\r', 0, A_F_LINE, + '\n', 0, A_F_LINE, + 'e', 0, A_F_LINE, + 'j', 0, A_F_LINE, + SK(SK_DOWN_ARROW), 0, A_F_LINE, + CONTROL('E'), 0, A_F_LINE, + CONTROL('N'), 0, A_F_LINE, + 'k', 0, A_B_LINE, + 'y', 0, A_B_LINE, + CONTROL('Y'), 0, A_B_LINE, + SK(SK_CONTROL_K), 0, A_B_LINE, + CONTROL('P'), 0, A_B_LINE, + SK(SK_UP_ARROW), 0, A_B_LINE, + 'J', 0, A_FF_LINE, + 'K', 0, A_BF_LINE, + 'Y', 0, A_BF_LINE, + 'd', 0, A_F_SCROLL, + CONTROL('D'), 0, A_F_SCROLL, + 'u', 0, A_B_SCROLL, + CONTROL('U'), 0, A_B_SCROLL, + ' ', 0, A_F_SCREEN, + 'f', 0, A_F_SCREEN, + CONTROL('F'), 0, A_F_SCREEN, + CONTROL('V'), 0, A_F_SCREEN, + SK(SK_PAGE_DOWN), 0, A_F_SCREEN, + 'b', 0, A_B_SCREEN, + CONTROL('B'), 0, A_B_SCREEN, + ESC, 'v', 0, A_B_SCREEN, + SK(SK_PAGE_UP), 0, A_B_SCREEN, + 'z', 0, A_F_WINDOW, + 'w', 0, A_B_WINDOW, + ESC, ' ', 0, A_FF_SCREEN, + 'F', 0, A_F_FOREVER, + ESC, 'F', 0, A_F_UNTIL_HILITE, + 'R', 0, A_FREPAINT, + 'r', 0, A_REPAINT, + CONTROL('R'), 0, A_REPAINT, + CONTROL('L'), 0, A_REPAINT, + ESC, 'u', 0, A_UNDO_SEARCH, + 'g', 0, A_GOLINE, + SK(SK_HOME), 0, A_GOLINE, + '<', 0, A_GOLINE, + ESC, '<', 0, A_GOLINE, + 'p', 0, A_PERCENT, + '%', 0, A_PERCENT, + ESC, '[', 0, A_LSHIFT, + ESC, ']', 0, A_RSHIFT, + ESC, '(', 0, A_LSHIFT, + ESC, ')', 0, A_RSHIFT, + SK(SK_RIGHT_ARROW), 0, A_RSHIFT, + SK(SK_LEFT_ARROW), 0, A_LSHIFT, + '{', 0, A_F_BRACKET|A_EXTRA, '{', '}', 0, + '}', 0, A_B_BRACKET|A_EXTRA, '{', '}', 0, + '(', 0, A_F_BRACKET|A_EXTRA, '(', ')', 0, + ')', 0, A_B_BRACKET|A_EXTRA, '(', ')', 0, + '[', 0, A_F_BRACKET|A_EXTRA, '[', ']', 0, + ']', 0, A_B_BRACKET|A_EXTRA, '[', ']', 0, + ESC, CONTROL('F'), 0, A_F_BRACKET, + ESC, CONTROL('B'), 0, A_B_BRACKET, + 'G', 0, A_GOEND, + ESC, '>', 0, A_GOEND, + '>', 0, A_GOEND, + SK(SK_END), 0, A_GOEND, + 'P', 0, A_GOPOS, + + '0', 0, A_DIGIT, + '1', 0, A_DIGIT, + '2', 0, A_DIGIT, + '3', 0, A_DIGIT, + '4', 0, A_DIGIT, + '5', 0, A_DIGIT, + '6', 0, A_DIGIT, + '7', 0, A_DIGIT, + '8', 0, A_DIGIT, + '9', 0, A_DIGIT, + '.', 0, A_DIGIT, + + '=', 0, A_STAT, + CONTROL('G'), 0, A_STAT, + ':', 'f', 0, A_STAT, + '/', 0, A_F_SEARCH, + '?', 0, A_B_SEARCH, + ESC, '/', 0, A_F_SEARCH|A_EXTRA, '*', 0, + ESC, '?', 0, A_B_SEARCH|A_EXTRA, '*', 0, + 'n', 0, A_AGAIN_SEARCH, + ESC, 'n', 0, A_T_AGAIN_SEARCH, + 'N', 0, A_REVERSE_SEARCH, + ESC, 'N', 0, A_T_REVERSE_SEARCH, + '&', 0, A_FILTER, + 'm', 0, A_SETMARK, + '\'', 0, A_GOMARK, + CONTROL('X'), CONTROL('X'), 0, A_GOMARK, + 'E', 0, A_EXAMINE, + ':', 'e', 0, A_EXAMINE, + CONTROL('X'), CONTROL('V'), 0, A_EXAMINE, + ':', 'n', 0, A_NEXT_FILE, + ':', 'p', 0, A_PREV_FILE, + 't', 0, A_NEXT_TAG, + 'T', 0, A_PREV_TAG, + ':', 'x', 0, A_INDEX_FILE, + ':', 'd', 0, A_REMOVE_FILE, + ':', 't', 0, A_OPT_TOGGLE|A_EXTRA, 't', 0, + '|', 0, A_PIPE, + 'v', 0, A_VISUAL, + '+', 0, A_FIRSTCMD, + + 'H', 0, A_HELP, + 'h', 0, A_HELP, + SK(SK_F1), 0, A_HELP, + 'V', 0, A_VERSION, + 'q', 0, A_QUIT, + 'Q', 0, A_QUIT, + ':', 'q', 0, A_QUIT, + ':', 'Q', 0, A_QUIT, + 'Z', 'Z', 0, A_QUIT +}; + +static unsigned char lesstable[] = { + '-', 0, A_OPT_TOGGLE, + 's', 0, A_OPT_TOGGLE|A_EXTRA, 'o', 0, + '_', 0, A_DISP_OPTION +}; + +static unsigned char moretable[] = { + 's', 0, A_F_SKIP +}; + +static unsigned char edittable[] = +{ + '\t', 0, EC_F_COMPLETE, /* TAB */ + '\17', 0, EC_B_COMPLETE, /* BACKTAB */ + SK(SK_BACKTAB), 0, EC_B_COMPLETE, /* BACKTAB */ + ESC, '\t', 0, EC_B_COMPLETE, /* ESC TAB */ + CONTROL('L'), 0, EC_EXPAND, /* CTRL-L */ + CONTROL('V'), 0, EC_LITERAL, /* BACKSLASH */ + CONTROL('A'), 0, EC_LITERAL, /* BACKSLASH */ + ESC, 'l', 0, EC_RIGHT, /* ESC l */ + SK(SK_RIGHT_ARROW), 0, EC_RIGHT, /* RIGHTARROW */ + ESC, 'h', 0, EC_LEFT, /* ESC h */ + SK(SK_LEFT_ARROW), 0, EC_LEFT, /* LEFTARROW */ + ESC, 'b', 0, EC_W_LEFT, /* ESC b */ + ESC, SK(SK_LEFT_ARROW), 0, EC_W_LEFT, /* ESC LEFTARROW */ + SK(SK_CTL_LEFT_ARROW), 0, EC_W_LEFT, /* CTRL-LEFTARROW */ + ESC, 'w', 0, EC_W_RIGHT, /* ESC w */ + ESC, SK(SK_RIGHT_ARROW), 0, EC_W_RIGHT, /* ESC RIGHTARROW */ + SK(SK_CTL_RIGHT_ARROW), 0, EC_W_RIGHT, /* CTRL-RIGHTARROW */ + ESC, 'i', 0, EC_INSERT, /* ESC i */ + SK(SK_INSERT), 0, EC_INSERT, /* INSERT */ + ESC, 'x', 0, EC_DELETE, /* ESC x */ + SK(SK_DELETE), 0, EC_DELETE, /* DELETE */ + ESC, 'X', 0, EC_W_DELETE, /* ESC X */ + ESC, SK(SK_DELETE), 0, EC_W_DELETE, /* ESC DELETE */ + SK(SK_CTL_DELETE), 0, EC_W_DELETE, /* CTRL-DELETE */ + SK(SK_CTL_BACKSPACE), 0, EC_W_BACKSPACE, /* CTRL-BACKSPACE */ + ESC, '\b', 0, EC_W_BACKSPACE, /* ESC BACKSPACE */ + ESC, '0', 0, EC_HOME, /* ESC 0 */ + SK(SK_HOME), 0, EC_HOME, /* HOME */ + ESC, '$', 0, EC_END, /* ESC $ */ + SK(SK_END), 0, EC_END, /* END */ + ESC, 'k', 0, EC_UP, /* ESC k */ + SK(SK_UP_ARROW), 0, EC_UP, /* UPARROW */ + ESC, 'j', 0, EC_DOWN, /* ESC j */ + SK(SK_DOWN_ARROW), 0, EC_DOWN, /* DOWNARROW */ + CONTROL('G'), 0, EC_ABORT, /* CTRL-G */ +}; + +/* + * Structure to support a list of command tables. + */ +struct tablelist { + struct tablelist *t_next; + char *t_start; + char *t_end; +}; + +/* + * List of command tables and list of line-edit tables. + */ +static struct tablelist *list_fcmd_tables = NULL; +static struct tablelist *list_ecmd_tables = NULL; +static struct tablelist *list_var_tables = NULL; +static struct tablelist *list_sysvar_tables = NULL; + + +/* + * Expand special key abbreviations in a command table. + */ +static void +expand_special_keys(char *table, int len) +{ + char *fm; + char *to; + int a; + char *repl; + int klen; + + for (fm = table; fm < table + len; ) { + /* + * Rewrite each command in the table with any + * special key abbreviations expanded. + */ + for (to = fm; *fm != '\0'; ) { + if (*fm != SK_SPECIAL_KEY) { + *to++ = *fm++; + continue; + } + /* + * After SK_SPECIAL_KEY, next byte is the type + * of special key (one of the SK_* contants), + * and the byte after that is the number of bytes, + * N, reserved by the abbreviation (including the + * SK_SPECIAL_KEY and key type bytes). + * Replace all N bytes with the actual bytes + * output by the special key on this terminal. + */ + repl = special_key_str(fm[1]); + klen = fm[2] & 0377; + fm += klen; + if (repl == NULL || strlen(repl) > klen) + repl = "\377"; + while (*repl != '\0') + *to++ = *repl++; + } + *to++ = '\0'; + /* + * Fill any unused bytes between end of command and + * the action byte with A_SKIP. + */ + while (to <= fm) + *to++ = A_SKIP; + fm++; + a = *fm++ & 0377; + if (a & A_EXTRA) { + while (*fm++ != '\0') + continue; + } + } +} + +/* + * Initialize the command lists. + */ +void +init_cmds(void) +{ + /* + * Add the default command tables. + */ + add_fcmd_table((char *)cmdtable, sizeof (cmdtable)); + add_ecmd_table((char *)edittable, sizeof (edittable)); + if (less_is_more) { + add_fcmd_table((char *)moretable, sizeof (moretable)); + return; + } else { + add_fcmd_table((char *)lesstable, sizeof (lesstable)); + } + + /* + * Try to add the tables in the system lesskey file. + */ + add_hometable("LESSKEY_SYSTEM", LESSKEYFILE_SYS, 1); + /* + * Try to add the tables in the standard lesskey file "$HOME/.less". + */ + add_hometable("LESSKEY", LESSKEYFILE, 0); +} + +/* + * Add a command table. + */ +static int +add_cmd_table(struct tablelist **tlist, char *buf, int len) +{ + struct tablelist *t; + + if (len == 0) + return (0); + /* + * Allocate a tablelist structure, initialize it, + * and link it into the list of tables. + */ + if ((t = calloc(1, sizeof (struct tablelist))) == NULL) { + return (-1); + } + expand_special_keys(buf, len); + t->t_start = buf; + t->t_end = buf + len; + t->t_next = *tlist; + *tlist = t; + return (0); +} + +/* + * Add a command table. + */ +void +add_fcmd_table(char *buf, int len) +{ + if (add_cmd_table(&list_fcmd_tables, buf, len) < 0) + error("Warning: some commands disabled", NULL); +} + +/* + * Add an editing command table. + */ +void +add_ecmd_table(char *buf, int len) +{ + if (add_cmd_table(&list_ecmd_tables, buf, len) < 0) + error("Warning: some edit commands disabled", NULL); +} + +/* + * Add an environment variable table. + */ +static void +add_var_table(struct tablelist **tlist, char *buf, int len) +{ + if (add_cmd_table(tlist, buf, len) < 0) + error("Warning: environment variables from " + "lesskey file unavailable", NULL); +} + +/* + * Search a single command table for the command string in cmd. + */ +static int +cmd_search(const char *cmd, char *table, char *endtable, char **sp) +{ + char *p; + const char *q; + int a; + + *sp = NULL; + for (p = table, q = cmd; p < endtable; p++, q++) { + if (*p == *q) { + /* + * Current characters match. + * If we're at the end of the string, we've found it. + * Return the action code, which is the character + * after the null at the end of the string + * in the command table. + */ + if (*p == '\0') { + a = *++p & 0377; + while (a == A_SKIP) + a = *++p & 0377; + if (a == A_END_LIST) { + /* + * We get here only if the original + * cmd string passed in was empty (""). + * I don't think that can happen, + * but just in case ... + */ + return (A_UINVALID); + } + /* + * Check for an "extra" string. + */ + if (a & A_EXTRA) { + *sp = ++p; + a &= ~A_EXTRA; + } + return (a); + } + } else if (*q == '\0') { + /* + * Hit the end of the user's command, + * but not the end of the string in the command table. + * The user's command is incomplete. + */ + return (A_PREFIX); + } else { + /* + * Not a match. + * Skip ahead to the next command in the + * command table, and reset the pointer + * to the beginning of the user's command. + */ + if (*p == '\0' && p[1] == A_END_LIST) { + /* + * A_END_LIST is a special marker that tells + * us to abort the cmd search. + */ + return (A_UINVALID); + } + while (*p++ != '\0') + continue; + while (*p == A_SKIP) + p++; + if (*p & A_EXTRA) + while (*++p != '\0') + continue; + q = cmd-1; + } + } + /* + * No match found in the entire command table. + */ + return (A_INVALID); +} + +/* + * Decode a command character and return the associated action. + * The "extra" string, if any, is returned in sp. + */ +static int +cmd_decode(struct tablelist *tlist, const char *cmd, char **sp) +{ + struct tablelist *t; + int action = A_INVALID; + + /* + * Search thru all the command tables. + * Stop when we find an action which is not A_INVALID. + */ + for (t = tlist; t != NULL; t = t->t_next) { + action = cmd_search(cmd, t->t_start, t->t_end, sp); + if (action != A_INVALID) + break; + } + if (action == A_UINVALID) + action = A_INVALID; + return (action); +} + +/* + * Decode a command from the cmdtables list. + */ +int +fcmd_decode(const char *cmd, char **sp) +{ + return (cmd_decode(list_fcmd_tables, cmd, sp)); +} + +/* + * Decode a command from the edittables list. + */ +int +ecmd_decode(const char *cmd, char **sp) +{ + return (cmd_decode(list_ecmd_tables, cmd, sp)); +} + +/* + * Get the value of an environment variable. + * Looks first in the lesskey file, then in the real environment. + */ +char * +lgetenv(char *var) +{ + int a; + char *s; + + /* + * Ignore lookups of any LESS* setting when we are more, and ignore + * the less key files + */ + if (less_is_more) { + if (strncmp(var, "LESS", 4) == 0) { + return (NULL); + } + return (getenv(var)); + } + a = cmd_decode(list_var_tables, var, &s); + if (a == EV_OK) + return (s); + s = getenv(var); + if (s != NULL && *s != '\0') + return (s); + a = cmd_decode(list_sysvar_tables, var, &s); + if (a == EV_OK) + return (s); + return (NULL); +} + +/* + * Get an "integer" from a lesskey file. + * Integers are stored in a funny format: + * two bytes, low order first, in radix KRADIX. + */ +static int +gint(char **sp) +{ + int n; + + n = *(*sp)++; + n += *(*sp)++ * KRADIX; + return (n); +} + +/* + * Process an old (pre-v241) lesskey file. + */ +static int +old_lesskey(char *buf, int len) +{ + /* + * Old-style lesskey file. + * The file must end with either + * ..,cmd,0,action + * or ...,cmd,0,action|A_EXTRA,string,0 + * So the last byte or the second to last byte must be zero. + */ + if (buf[len-1] != '\0' && buf[len-2] != '\0') + return (-1); + add_fcmd_table(buf, len); + return (0); +} + +/* + * Process a new (post-v241) lesskey file. + */ +static int +new_lesskey(char *buf, int len, int sysvar) +{ + char *p; + int c; + int n; + + /* + * New-style lesskey file. + * Extract the pieces. + */ + if (buf[len-3] != C0_END_LESSKEY_MAGIC || + buf[len-2] != C1_END_LESSKEY_MAGIC || + buf[len-1] != C2_END_LESSKEY_MAGIC) + return (-1); + p = buf + 4; + for (;;) { + c = *p++; + switch (c) { + case CMD_SECTION: + n = gint(&p); + add_fcmd_table(p, n); + p += n; + break; + case EDIT_SECTION: + n = gint(&p); + add_ecmd_table(p, n); + p += n; + break; + case VAR_SECTION: + n = gint(&p); + add_var_table((sysvar) ? + &list_sysvar_tables : &list_var_tables, p, n); + p += n; + break; + case END_SECTION: + return (0); + default: + /* + * Unrecognized section type. + */ + return (-1); + } + } +} + +/* + * Set up a user command table, based on a "lesskey" file. + */ +int +lesskey(char *filename, int sysvar) +{ + char *buf; + off_t len; + long n; + int f; + + if (secure) + return (1); + /* + * Try to open the lesskey file. + */ + filename = shell_unquote(filename); + f = open(filename, O_RDONLY); + free(filename); + if (f < 0) + return (1); + + /* + * Read the file into a buffer. + * We first figure out the size of the file and allocate space for it. + * {{ Minimal error checking is done here. + * A garbage .less file will produce strange results. + * To avoid a large amount of error checking code here, we + * rely on the lesskey program to generate a good .less file. }} + */ + len = filesize(f); + if (len == -1 || len < 3) { + /* + * Bad file (valid file must have at least 3 chars). + */ + (void) close(f); + return (-1); + } + if ((buf = calloc((int)len, sizeof (char))) == NULL) { + (void) close(f); + return (-1); + } + if (lseek(f, (off_t)0, SEEK_SET) == (off_t)-1) { + free(buf); + (void) close(f); + return (-1); + } + n = read(f, buf, (unsigned int) len); + close(f); + if (n != len) { + free(buf); + return (-1); + } + + /* + * Figure out if this is an old-style (before version 241) + * or new-style lesskey file format. + */ + if (buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC || + buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC) + return (old_lesskey(buf, (int)len)); + return (new_lesskey(buf, (int)len, sysvar)); +} + +/* + * Add the standard lesskey file "$HOME/.less" + */ +void +add_hometable(char *envname, char *def_filename, int sysvar) +{ + char *filename; + PARG parg; + + if (envname != NULL && (filename = lgetenv(envname)) != NULL) + filename = estrdup(filename); + else if (sysvar) + filename = estrdup(def_filename); + else + filename = homefile(def_filename); + if (filename == NULL) + return; + if (lesskey(filename, sysvar) < 0) { + parg.p_string = filename; + error("Cannot use lesskey file \"%s\"", &parg); + } + free(filename); +} + +/* + * See if a char is a special line-editing command. + */ +int +editchar(int c, int flags) +{ + int action; + int nch; + char *s; + char usercmd[MAX_CMDLEN+1]; + + /* + * An editing character could actually be a sequence of characters; + * for example, an escape sequence sent by pressing the uparrow key. + * To match the editing string, we use the command decoder + * but give it the edit-commands command table + * This table is constructed to match the user's keyboard. + */ + if (c == erase_char || c == erase2_char) + return (EC_BACKSPACE); + if (c == kill_char) + return (EC_LINEKILL); + + /* + * Collect characters in a buffer. + * Start with the one we have, and get more if we need them. + */ + nch = 0; + do { + if (nch > 0) + c = getcc(); + usercmd[nch] = (char)c; + usercmd[nch+1] = '\0'; + nch++; + action = ecmd_decode(usercmd, &s); + } while (action == A_PREFIX); + + if (flags & EC_NORIGHTLEFT) { + switch (action) { + case EC_RIGHT: + case EC_LEFT: + action = A_INVALID; + break; + } + } + if (flags & EC_NOHISTORY) { + /* + * The caller says there is no history list. + * Reject any history-manipulation action. + */ + switch (action) { + case EC_UP: + case EC_DOWN: + action = A_INVALID; + break; + } + } + if (flags & EC_NOCOMPLETE) { + /* + * The caller says we don't want any filename completion cmds. + * Reject them. + */ + switch (action) { + case EC_F_COMPLETE: + case EC_B_COMPLETE: + case EC_EXPAND: + action = A_INVALID; + break; + } + } + if ((flags & EC_PEEK) || action == A_INVALID) { + /* + * We're just peeking, or we didn't understand the command. + * Unget all the characters we read in the loop above. + * This does NOT include the original character that was + * passed in as a parameter. + */ + while (nch > 1) { + ungetcc(usercmd[--nch]); + } + } else { + if (s != NULL) + ungetsc(s); + } + return (action); +} diff --git a/bin/less/defines.h b/bin/less/defines.h new file mode 100644 index 0000000000..03542f8659 --- /dev/null +++ b/bin/less/defines.h @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Garrett D'Amore + * + * This file is made available under the terms of the Less License. + */ + +/* + * LESSKEYFILE is the filename of the default lesskey output file + * (in the HOME directory). + * LESSKEYFILE_SYS is the filename of the system-wide lesskey output file. + * DEF_LESSKEYINFILE is the filename of the default lesskey input + * (in the HOME directory). + * LESSHISTFILE is the filename of the history file + * (in the HOME directory). + */ +#define LESSKEYFILE ".less" +#define LESSKEYFILE_SYS SYSDIR "/sysless" +#define DEF_LESSKEYINFILE ".lesskey" +#define LESSHISTFILE "-" +#define TGETENT_OK 1 /* "OK" from curses.h */ + +/* + * Default shell metacharacters and meta-escape character. + */ +#define DEF_METACHARS "; *?\t\n'\"()<>[]|&^`#\\$%=~" + +#define CMDBUF_SIZE 2048 /* Buffer for multichar commands */ +#define UNGOT_SIZE 200 /* Max chars to unget() */ +#define LINEBUF_SIZE 1024 /* Initial max size of line in input file */ +#define OUTBUF_SIZE 1024 /* Output buffer */ +#define PROMPT_SIZE 2048 /* Max size of prompt string */ +#define TERMBUF_SIZE 2048 /* Termcap buffer for tgetent */ +#define TERMSBUF_SIZE 1024 /* Buffer to hold termcap strings */ +#define TAGLINE_SIZE 1024 /* Max size of line in tags file */ +#define TABSTOP_MAX 128 /* Max number of custom tab stops */ +#define EDIT_PGM "vi" /* Editor program */ diff --git a/bin/less/edit.c b/bin/less/edit.c new file mode 100644 index 0000000000..6d08adcc74 --- /dev/null +++ b/bin/less/edit.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#include + +#include "less.h" + +static int fd0 = 0; + +extern int new_file; +extern int errmsgs; +extern char *every_first_cmd; +extern int any_display; +extern int force_open; +extern int is_tty; +extern volatile sig_atomic_t sigs; +extern IFILE curr_ifile; +extern IFILE old_ifile; +extern struct scrpos initial_scrpos; +extern void *ml_examine; +extern char openquote; +extern char closequote; +extern int less_is_more; +extern int logfile; +extern int force_logfile; +extern char *namelogfile; + +dev_t curr_dev; +ino_t curr_ino; + +char *curr_altfilename = NULL; +static void *curr_altpipe; + + +/* + * Textlist functions deal with a list of words separated by spaces. + * init_textlist sets up a textlist structure. + * forw_textlist uses that structure to iterate thru the list of + * words, returning each one as a standard null-terminated string. + * back_textlist does the same, but runs thru the list backwards. + */ +void +init_textlist(struct textlist *tlist, char *str) +{ + char *s; + int meta_quoted = 0; + int delim_quoted = 0; + char *esc = get_meta_escape(); + int esclen = strlen(esc); + + tlist->string = skipsp(str); + tlist->endstring = tlist->string + strlen(tlist->string); + for (s = str; s < tlist->endstring; s++) { + if (meta_quoted) { + meta_quoted = 0; + } else if (esclen > 0 && s + esclen < tlist->endstring && + strncmp(s, esc, esclen) == 0) { + meta_quoted = 1; + s += esclen - 1; + } else if (delim_quoted) { + if (*s == closequote) + delim_quoted = 0; + } else /* (!delim_quoted) */ { + if (*s == openquote) + delim_quoted = 1; + else if (*s == ' ') + *s = '\0'; + } + } +} + +char * +forw_textlist(struct textlist *tlist, char *prev) +{ + char *s; + + /* + * prev == NULL means return the first word in the list. + * Otherwise, return the word after "prev". + */ + if (prev == NULL) + s = tlist->string; + else + s = prev + strlen(prev); + if (s >= tlist->endstring) + return (NULL); + while (*s == '\0') + s++; + if (s >= tlist->endstring) + return (NULL); + return (s); +} + +char * +back_textlist(struct textlist *tlist, char *prev) +{ + char *s; + + /* + * prev == NULL means return the last word in the list. + * Otherwise, return the word before "prev". + */ + if (prev == NULL) + s = tlist->endstring; + else if (prev <= tlist->string) + return (NULL); + else + s = prev - 1; + while (*s == '\0') + s--; + if (s <= tlist->string) + return (NULL); + while (s[-1] != '\0' && s > tlist->string) + s--; + return (s); +} + +/* + * Close the current input file. + */ +static void +close_file(void) +{ + struct scrpos scrpos; + + if (curr_ifile == NULL) + return; + + /* + * Save the current position so that we can return to + * the same position if we edit this file again. + */ + get_scrpos(&scrpos); + if (scrpos.pos != -1) { + store_pos(curr_ifile, &scrpos); + lastmark(); + } + /* + * Close the file descriptor, unless it is a pipe. + */ + ch_close(); + /* + * If we opened a file using an alternate name, + * do special stuff to close it. + */ + if (curr_altfilename != NULL) { + close_altfile(curr_altfilename, get_filename(curr_ifile), + curr_altpipe); + free(curr_altfilename); + curr_altfilename = NULL; + } + curr_ifile = NULL; + curr_ino = curr_dev = 0; +} + +/* + * Edit a new file (given its name). + * Filename == "-" means standard input. + * Filename == NULL means just close the current file. + */ +int +edit(char *filename) +{ + if (filename == NULL) + return (edit_ifile(NULL)); + return (edit_ifile(get_ifile(filename, curr_ifile))); +} + +/* + * Edit a new file (given its IFILE). + * ifile == NULL means just close the current file. + */ +int +edit_ifile(IFILE ifile) +{ + int f; + int answer; + int no_display; + int chflags; + char *filename; + char *open_filename; + char *qopen_filename; + char *alt_filename; + void *alt_pipe; + IFILE was_curr_ifile; + PARG parg; + + if (ifile == curr_ifile) { + /* + * Already have the correct file open. + */ + return (0); + } + + /* + * We must close the currently open file now. + * This is necessary to make the open_altfile/close_altfile pairs + * nest properly (or rather to avoid nesting at all). + * {{ Some stupid implementations of popen() mess up if you do: + * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }} + */ + end_logfile(); + was_curr_ifile = save_curr_ifile(); + if (curr_ifile != NULL) { + chflags = ch_getflags(); + close_file(); + if ((chflags & CH_HELPFILE) && + held_ifile(was_curr_ifile) <= 1) { + /* + * Don't keep the help file in the ifile list. + */ + del_ifile(was_curr_ifile); + was_curr_ifile = old_ifile; + } + } + + if (ifile == NULL) { + /* + * No new file to open. + * (Don't set old_ifile, because if you call edit_ifile(NULL), + * you're supposed to have saved curr_ifile yourself, + * and you'll restore it if necessary.) + */ + unsave_ifile(was_curr_ifile); + return (0); + } + + filename = estrdup(get_filename(ifile)); + /* + * See if LESSOPEN specifies an "alternate" file to open. + */ + alt_pipe = NULL; + alt_filename = open_altfile(filename, &f, &alt_pipe); + open_filename = (alt_filename != NULL) ? alt_filename : filename; + qopen_filename = shell_unquote(open_filename); + + chflags = 0; + if (strcmp(open_filename, helpfile()) == 0) + chflags |= CH_HELPFILE; + if (alt_pipe != NULL) { + /* + * The alternate "file" is actually a pipe. + * f has already been set to the file descriptor of the pipe + * in the call to open_altfile above. + * Keep the file descriptor open because it was opened + * via popen(), and pclose() wants to close it. + */ + chflags |= CH_POPENED; + } else if (strcmp(open_filename, "-") == 0) { + /* + * Use standard input. + * Keep the file descriptor open because we can't reopen it. + */ + f = fd0; + chflags |= CH_KEEPOPEN; + } else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0) { + f = -1; + chflags |= CH_NODATA; + } else if ((parg.p_string = bad_file(open_filename)) != NULL) { + /* + * It looks like a bad file. Don't try to open it. + */ + error("%s", &parg); + free(parg.p_string); +err1: + if (alt_filename != NULL) { + close_altfile(alt_filename, filename, alt_pipe); + free(alt_filename); + } + del_ifile(ifile); + free(qopen_filename); + free(filename); + /* + * Re-open the current file. + */ + if (was_curr_ifile == ifile) { + /* + * Whoops. The "current" ifile is the one we just + * deleted. Just give up. + */ + quit(QUIT_ERROR); + } + reedit_ifile(was_curr_ifile); + return (1); + } else if ((f = open(qopen_filename, O_RDONLY)) < 0) { + /* + * Got an error trying to open it. + */ + parg.p_string = errno_message(filename); + error("%s", &parg); + free(parg.p_string); + goto err1; + } else { + chflags |= CH_CANSEEK; + if (!force_open && !opened(ifile) && bin_file(f)) { + /* + * Looks like a binary file. + * Ask user if we should proceed. + */ + parg.p_string = filename; + answer = query("\"%s\" may be a binary file. " + "See it anyway? ", &parg); + if (answer != 'y' && answer != 'Y') { + (void) close(f); + goto err1; + } + } + } + + /* + * Get the new ifile. + * Get the saved position for the file. + */ + if (was_curr_ifile != NULL) { + old_ifile = was_curr_ifile; + unsave_ifile(was_curr_ifile); + } + curr_ifile = ifile; + curr_altfilename = alt_filename; + curr_altpipe = alt_pipe; + set_open(curr_ifile); /* File has been opened */ + get_pos(curr_ifile, &initial_scrpos); + new_file = TRUE; + ch_init(f, chflags); + + if (!(chflags & CH_HELPFILE)) { + struct stat statbuf; + int r; + + if (namelogfile != NULL && is_tty) + use_logfile(namelogfile); + /* Remember the i-number and device of opened file. */ + r = stat(qopen_filename, &statbuf); + if (r == 0) { + curr_ino = statbuf.st_ino; + curr_dev = statbuf.st_dev; + } + if (every_first_cmd != NULL) + ungetsc(every_first_cmd); + } + free(qopen_filename); + no_display = !any_display; + flush(0); + any_display = TRUE; + + if (is_tty) { + /* + * Output is to a real tty. + */ + + /* + * Indicate there is nothing displayed yet. + */ + pos_clear(); + clr_linenum(); + clr_hilite(); + cmd_addhist(ml_examine, filename); + if (no_display && errmsgs > 0) { + /* + * We displayed some messages on error output + * (file descriptor 2; see error() function). + * Before erasing the screen contents, + * display the file name and wait for a keystroke. + */ + parg.p_string = filename; + error("%s", &parg); + } + } + free(filename); + return (0); +} + +/* + * Edit a space-separated list of files. + * For each filename in the list, enter it into the ifile list. + * Then edit the first one. + */ +int +edit_list(char *filelist) +{ + IFILE save_ifile; + char *good_filename; + char *filename; + char *gfilelist; + char *gfilename; + struct textlist tl_files; + struct textlist tl_gfiles; + + save_ifile = save_curr_ifile(); + good_filename = NULL; + + /* + * Run thru each filename in the list. + * Try to glob the filename. + * If it doesn't expand, just try to open the filename. + * If it does expand, try to open each name in that list. + */ + init_textlist(&tl_files, filelist); + filename = NULL; + while ((filename = forw_textlist(&tl_files, filename)) != NULL) { + gfilelist = lglob(filename); + init_textlist(&tl_gfiles, gfilelist); + gfilename = NULL; + while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != + NULL) { + if (edit(gfilename) == 0 && good_filename == NULL) + good_filename = get_filename(curr_ifile); + } + free(gfilelist); + } + /* + * Edit the first valid filename in the list. + */ + if (good_filename == NULL) { + unsave_ifile(save_ifile); + return (1); + } + if (get_ifile(good_filename, curr_ifile) == curr_ifile) { + /* + * Trying to edit the current file; don't reopen it. + */ + unsave_ifile(save_ifile); + return (0); + } + reedit_ifile(save_ifile); + return (edit(good_filename)); +} + +/* + * Edit the first file in the command line (ifile) list. + */ +int +edit_first(void) +{ + curr_ifile = NULL; + return (edit_next(1)); +} + +/* + * Edit the last file in the command line (ifile) list. + */ +int +edit_last(void) +{ + curr_ifile = NULL; + return (edit_prev(1)); +} + + +/* + * Edit the n-th next or previous file in the command line (ifile) list. + */ +static int +edit_istep(IFILE h, int n, int dir) +{ + IFILE next; + + /* + * Skip n filenames, then try to edit each filename. + */ + for (;;) { + next = (dir > 0) ? next_ifile(h) : prev_ifile(h); + if (--n < 0) { + if (edit_ifile(h) == 0) + break; + } + if (next == NULL) { + /* + * Reached end of the ifile list. + */ + return (1); + } + if (ABORT_SIGS()) { + /* + * Interrupt breaks out, if we're in a long + * list of files that can't be opened. + */ + return (1); + } + h = next; + } + /* + * Found a file that we can edit. + */ + return (0); +} + +static int +edit_inext(IFILE h, int n) +{ + return (edit_istep(h, n, +1)); +} + +int +edit_next(int n) +{ + return (edit_istep(curr_ifile, n, +1)); +} + +static int +edit_iprev(IFILE h, int n) +{ + return (edit_istep(h, n, -1)); +} + +int +edit_prev(int n) +{ + return (edit_istep(curr_ifile, n, -1)); +} + +/* + * Edit a specific file in the command line (ifile) list. + */ +int +edit_index(int n) +{ + IFILE h; + + h = NULL; + do { + if ((h = next_ifile(h)) == NULL) { + /* + * Reached end of the list without finding it. + */ + return (1); + } + } while (get_index(h) != n); + + return (edit_ifile(h)); +} + +IFILE +save_curr_ifile(void) +{ + if (curr_ifile != NULL) + hold_ifile(curr_ifile, 1); + return (curr_ifile); +} + +void +unsave_ifile(IFILE save_ifile) +{ + if (save_ifile != NULL) + hold_ifile(save_ifile, -1); +} + +/* + * Reedit the ifile which was previously open. + */ +void +reedit_ifile(IFILE save_ifile) +{ + IFILE next; + IFILE prev; + + /* + * Try to reopen the ifile. + * Note that opening it may fail (maybe the file was removed), + * in which case the ifile will be deleted from the list. + * So save the next and prev ifiles first. + */ + unsave_ifile(save_ifile); + next = next_ifile(save_ifile); + prev = prev_ifile(save_ifile); + if (edit_ifile(save_ifile) == 0) + return; + /* + * If can't reopen it, open the next input file in the list. + */ + if (next != NULL && edit_inext(next, 0) == 0) + return; + /* + * If can't open THAT one, open the previous input file in the list. + */ + if (prev != NULL && edit_iprev(prev, 0) == 0) + return; + /* + * If can't even open that, we're stuck. Just quit. + */ + quit(QUIT_ERROR); +} + +void +reopen_curr_ifile(void) +{ + IFILE save_ifile = save_curr_ifile(); + close_file(); + reedit_ifile(save_ifile); +} + +/* + * Edit standard input. + */ +int +edit_stdin(void) +{ + if (isatty(fd0)) { + if (less_is_more) { + error("Missing filename (\"more -h\" for help)", + NULL); + } else { + error("Missing filename (\"less --help\" for help)", + NULL); + } + quit(QUIT_OK); + } + return (edit("-")); +} + +/* + * Copy a file directly to standard output. + * Used if standard output is not a tty. + */ +void +cat_file(void) +{ + int c; + + while ((c = ch_forw_get()) != EOI) + putchr(c); + flush(0); +} + +/* + * If the user asked for a log file and our input file + * is standard input, create the log file. + * We take care not to blindly overwrite an existing file. + */ +void +use_logfile(char *filename) +{ + int exists; + int answer; + PARG parg; + + if (ch_getflags() & CH_CANSEEK) + /* + * Can't currently use a log file on a file that can seek. + */ + return; + + /* + * {{ We could use access() here. }} + */ + filename = shell_unquote(filename); + exists = open(filename, O_RDONLY); + close(exists); + exists = (exists >= 0); + + /* + * Decide whether to overwrite the log file or append to it. + * If it doesn't exist we "overwrite" it. + */ + if (!exists || force_logfile) { + /* + * Overwrite (or create) the log file. + */ + answer = 'O'; + } else { + /* + * Ask user what to do. + */ + parg.p_string = filename; + answer = query("Warning: \"%s\" exists; " + "Overwrite, Append or Don't log? ", &parg); + } + +loop: + switch (answer) { + case 'O': case 'o': + /* + * Overwrite: create the file. + */ + logfile = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644); + break; + case 'A': case 'a': + /* + * Append: open the file and seek to the end. + */ + logfile = open(filename, O_WRONLY | O_APPEND); + if (lseek(logfile, (off_t)0, SEEK_END) == (off_t)-1) { + close(logfile); + logfile = -1; + } + break; + case 'D': case 'd': + /* + * Don't do anything. + */ + free(filename); + return; + case 'q': + quit(QUIT_OK); + default: + /* + * Eh? + */ + answer = query("Overwrite, Append, or Don't log? " + "(Type \"O\", \"A\", \"D\" or \"q\") ", NULL); + goto loop; + } + + if (logfile < 0) { + /* + * Error in opening logfile. + */ + parg.p_string = filename; + error("Cannot write to \"%s\"", &parg); + free(filename); + return; + } + free(filename); +} diff --git a/bin/less/filename.c b/bin/less/filename.c new file mode 100644 index 0000000000..00bf8573ce --- /dev/null +++ b/bin/less/filename.c @@ -0,0 +1,756 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to mess around with filenames (and files). + * Much of this is very OS dependent. + * + * Modified for illumos/POSIX -- it uses native glob(3C) rather than + * popen to a shell to perform the expansion. + */ + +#include + +#include +#include + +#include "less.h" + +extern int force_open; +extern int secure; +extern int use_lessopen; +extern int ctldisp; +extern int utf_mode; +extern IFILE curr_ifile; +extern IFILE old_ifile; +extern char openquote; +extern char closequote; + +/* + * Remove quotes around a filename. + */ +char * +shell_unquote(char *str) +{ + char *name; + char *p; + + name = p = ecalloc(strlen(str)+1, sizeof (char)); + if (*str == openquote) { + str++; + while (*str != '\0') { + if (*str == closequote) { + if (str[1] != closequote) + break; + str++; + } + *p++ = *str++; + } + } else { + char *esc = get_meta_escape(); + int esclen = strlen(esc); + while (*str != '\0') { + if (esclen > 0 && strncmp(str, esc, esclen) == 0) + str += esclen; + *p++ = *str++; + } + } + *p = '\0'; + return (name); +} + +/* + * Get the shell's escape character. + */ +char * +get_meta_escape(void) +{ + char *s; + + s = lgetenv("LESSMETAESCAPE"); + if (s == NULL) + s = "\\"; + return (s); +} + +/* + * Get the characters which the shell considers to be "metacharacters". + */ +static char * +metachars(void) +{ + static char *mchars = NULL; + + if (mchars == NULL) { + mchars = lgetenv("LESSMETACHARS"); + if (mchars == NULL) + mchars = DEF_METACHARS; + } + return (mchars); +} + +/* + * Is this a shell metacharacter? + */ +static int +metachar(char c) +{ + return (strchr(metachars(), c) != NULL); +} + +/* + * Insert a backslash before each metacharacter in a string. + */ +char * +shell_quote(const char *s) +{ + const char *p; + char *r; + char *newstr; + int len; + char *esc = get_meta_escape(); + int esclen = strlen(esc); + int use_quotes = 0; + int have_quotes = 0; + + /* + * Determine how big a string we need to allocate. + */ + len = 1; /* Trailing null byte */ + for (p = s; *p != '\0'; p++) { + len++; + if (*p == openquote || *p == closequote) + have_quotes = 1; + if (metachar(*p)) { + if (esclen == 0) { + /* + * We've got a metachar, but this shell + * doesn't support escape chars. Use quotes. + */ + use_quotes = 1; + } else { + /* + * Allow space for the escape char. + */ + len += esclen; + } + } + } + /* + * Allocate and construct the new string. + */ + if (use_quotes) { + /* We can't quote a string that contains quotes. */ + if (have_quotes) + return (NULL); + newstr = easprintf("%c%s%c", openquote, s, closequote); + } else { + newstr = r = ecalloc(len, sizeof (char)); + while (*s != '\0') { + if (metachar(*s)) { + /* + * Add the escape char. + */ + (void) strlcpy(r, esc, newstr + len - p); + r += esclen; + } + *r++ = *s++; + } + *r = '\0'; + } + return (newstr); +} + +/* + * Return a pathname that points to a specified file in a specified directory. + * Return NULL if the file does not exist in the directory. + */ +static char * +dirfile(const char *dirname, const char *filename) +{ + char *pathname; + char *qpathname; + int f; + + if (dirname == NULL || *dirname == '\0') + return (NULL); + /* + * Construct the full pathname. + */ + pathname = easprintf("%s/%s", dirname, filename); + /* + * Make sure the file exists. + */ + qpathname = shell_unquote(pathname); + f = open(qpathname, O_RDONLY); + if (f < 0) { + free(pathname); + pathname = NULL; + } else { + (void) close(f); + } + free(qpathname); + return (pathname); +} + +/* + * Return the full pathname of the given file in the "home directory". + */ +char * +homefile(char *filename) +{ + return (dirfile(lgetenv("HOME"), filename)); +} + +/* + * Expand a string, substituting any "%" with the current filename, + * and any "#" with the previous filename. + * But a string of N "%"s is just replaced with N-1 "%"s. + * Likewise for a string of N "#"s. + * {{ This is a lot of work just to support % and #. }} + */ +char * +fexpand(char *s) +{ + char *fr, *to; + int n; + char *e; + IFILE ifile; + +#define fchar_ifile(c) \ + ((c) == '%' ? curr_ifile : (c) == '#' ? old_ifile : NULL) + + /* + * Make one pass to see how big a buffer we + * need to allocate for the expanded string. + */ + n = 0; + for (fr = s; *fr != '\0'; fr++) { + switch (*fr) { + case '%': + case '#': + if (fr > s && fr[-1] == *fr) { + /* + * Second (or later) char in a string + * of identical chars. Treat as normal. + */ + n++; + } else if (fr[1] != *fr) { + /* + * Single char (not repeated). Treat specially. + */ + ifile = fchar_ifile(*fr); + if (ifile == NULL) + n++; + else + n += strlen(get_filename(ifile)); + } + /* + * Else it is the first char in a string of + * identical chars. Just discard it. + */ + break; + default: + n++; + break; + } + } + + e = ecalloc(n+1, sizeof (char)); + + /* + * Now copy the string, expanding any "%" or "#". + */ + to = e; + for (fr = s; *fr != '\0'; fr++) { + switch (*fr) { + case '%': + case '#': + if (fr > s && fr[-1] == *fr) { + *to++ = *fr; + } else if (fr[1] != *fr) { + ifile = fchar_ifile(*fr); + if (ifile == NULL) { + *to++ = *fr; + } else { + (void) strlcpy(to, get_filename(ifile), + e + n + 1 - to); + to += strlen(to); + } + } + break; + default: + *to++ = *fr; + break; + } + } + *to = '\0'; + return (e); +} + +/* + * Return a blank-separated list of filenames which "complete" + * the given string. + */ +char * +fcomplete(char *s) +{ + char *fpat; + char *qs; + + if (secure) + return (NULL); + /* + * Complete the filename "s" by globbing "s*". + */ + fpat = easprintf("%s*", s); + + qs = lglob(fpat); + s = shell_unquote(qs); + if (strcmp(s, fpat) == 0) { + /* + * The filename didn't expand. + */ + free(qs); + qs = NULL; + } + free(s); + free(fpat); + return (qs); +} + +/* + * Try to determine if a file is "binary". + * This is just a guess, and we need not try too hard to make it accurate. + */ +int +bin_file(int f) +{ + int n; + int bin_count = 0; + char data[256]; + char *p; + char *pend; + + if (!seekable(f)) + return (0); + if (lseek(f, (off_t)0, SEEK_SET) == (off_t)-1) + return (0); + n = read(f, data, sizeof (data)); + pend = &data[n]; + for (p = data; p < pend; ) { + LWCHAR c = step_char(&p, +1, pend); + if (ctldisp == OPT_ONPLUS && IS_CSI_START(c)) { + do { + c = step_char(&p, +1, pend); + } while (p < pend && is_ansi_middle(c)); + } else if (binary_char(c)) + bin_count++; + } + /* + * Call it a binary file if there are more than 5 binary characters + * in the first 256 bytes of the file. + */ + return (bin_count > 5); +} + +/* + * Read a string from a file. + * Return a pointer to the string in memory. + */ +static char * +readfd(FILE *fd) +{ + int len; + int ch; + char *buf; + char *p; + + /* + * Make a guess about how many chars in the string + * and allocate a buffer to hold it. + */ + len = 100; + buf = ecalloc(len, sizeof (char)); + for (p = buf; ; p++) { + if ((ch = getc(fd)) == '\n' || ch == EOF) + break; + if (p >= buf + len-1) { + /* + * The string is too big to fit in the buffer we have. + * Allocate a new buffer, twice as big. + */ + len *= 2; + *p = '\0'; + p = ecalloc(len, sizeof (char)); + strlcpy(p, buf, len); + free(buf); + buf = p; + p = buf + strlen(buf); + } + *p = (char)ch; + } + *p = '\0'; + return (buf); +} + +/* + * Execute a shell command. + * Return a pointer to a pipe connected to the shell command's standard output. + */ +static FILE * +shellcmd(char *cmd) +{ + FILE *fd; + + char *shell; + + shell = lgetenv("SHELL"); + if (shell != NULL && *shell != '\0') { + char *scmd; + char *esccmd; + + /* + * Read the output of <$SHELL -c cmd>. + * Escape any metacharacters in the command. + */ + esccmd = shell_quote(cmd); + if (esccmd == NULL) { + fd = popen(cmd, "r"); + } else { + scmd = easprintf("%s -c %s", shell, esccmd); + free(esccmd); + fd = popen(scmd, "r"); + free(scmd); + } + } else { + fd = popen(cmd, "r"); + } + /* + * Redirection in `popen' might have messed with the + * standard devices. Restore binary input mode. + */ + return (fd); +} + +/* + * Expand a filename, doing any system-specific metacharacter substitutions. + */ +char * +lglob(char *filename) +{ + char *gfilename; + char *ofilename; + glob_t list; + int i; + int length; + char *p; + char *qfilename; + + ofilename = fexpand(filename); + if (secure) + return (ofilename); + filename = shell_unquote(ofilename); + + /* + * The globbing function returns a list of names. + */ + +#ifndef GLOB_TILDE +#define GLOB_TILDE 0 +#endif +#ifndef GLOB_LIMIT +#define GLOB_LIMIT 0 +#endif + if (glob(filename, GLOB_TILDE | GLOB_LIMIT, NULL, &list) != 0) { + free(filename); + return (ofilename); + } + length = 1; /* Room for trailing null byte */ + for (i = 0; i < list.gl_pathc; i++) { + p = list.gl_pathv[i]; + qfilename = shell_quote(p); + if (qfilename != NULL) { + length += strlen(qfilename) + 1; + free(qfilename); + } + } + gfilename = ecalloc(length, sizeof (char)); + for (i = 0; i < list.gl_pathc; i++) { + p = list.gl_pathv[i]; + qfilename = shell_quote(p); + if (qfilename != NULL) { + if (i != 0) { + (void) strlcat(gfilename, " ", length); + } + (void) strlcat(gfilename, qfilename, length); + free(qfilename); + } + } + globfree(&list); + free(filename); + free(ofilename); + return (gfilename); +} + +/* + * Expand LESSOPEN or LESSCLOSE. Returns a newly allocated string + * on success, NULL otherwise. + */ +static char * +expand_pct_s(const char *fmt, ...) +{ + int n; + int len; + char *r, *d; + const char *f[3]; /* max expansions + 1 for NULL */ + va_list ap; + + va_start(ap, fmt); + for (n = 0; n < ((sizeof (f)/sizeof (f[0])) - 1); n++) { + f[n] = (const char *)va_arg(ap, const char *); + if (f[n] == NULL) { + break; + } + } + va_end(ap); + f[n] = NULL; /* terminate list */ + + len = strlen(fmt) + 1; + for (n = 0; f[n] != NULL; n++) { + len += strlen(f[n]); /* technically could -2 for "%s" */ + } + r = ecalloc(len, sizeof (char)); + + for (n = 0, d = r; *fmt != 0; ) { + if (*fmt != '%') { + *d++ = *fmt++; + continue; + } + fmt++; + /* Permit embedded "%%" */ + switch (*fmt) { + case '%': + *d++ = '%'; + fmt++; + break; + case 's': + if (f[n] == NULL) { + va_end(ap); + free(r); + return (NULL); + } + (void) strlcpy(d, f[n++], r + len - d); + fmt++; + d += strlen(d); + break; + default: + va_end(ap); + free(r); + return (NULL); + } + } + *d = '\0'; + return (r); +} + +/* + * See if we should open a "replacement file" + * instead of the file we're about to open. + */ +char * +open_altfile(char *filename, int *pf, void **pfd) +{ + char *lessopen; + char *cmd; + FILE *fd; + int returnfd = 0; + + if (!use_lessopen || secure) + return (NULL); + ch_ungetchar(-1); + if ((lessopen = lgetenv("LESSOPEN")) == NULL) + return (NULL); + while (*lessopen == '|') { + /* + * If LESSOPEN starts with a |, it indicates + * a "pipe preprocessor". + */ + lessopen++; + returnfd++; + } + if (*lessopen == '-') { + /* + * Lessopen preprocessor will accept "-" as a filename. + */ + lessopen++; + } else { + if (strcmp(filename, "-") == 0) + return (NULL); + } + + if ((cmd = expand_pct_s(lessopen, filename, NULL)) == NULL) { + error("Invalid LESSOPEN variable", NULL); + return (NULL); + } + fd = shellcmd(cmd); + free(cmd); + if (fd == NULL) { + /* + * Cannot create the pipe. + */ + return (NULL); + } + if (returnfd) { + int f; + char c; + + /* + * Read one char to see if the pipe will produce any data. + * If it does, push the char back on the pipe. + */ + f = fileno(fd); + if (read(f, &c, 1) != 1) { + /* + * Pipe is empty. + * If more than 1 pipe char was specified, + * the exit status tells whether the file itself + * is empty, or if there is no alt file. + * If only one pipe char, just assume no alt file. + */ + int status = pclose(fd); + if (returnfd > 1 && status == 0) { + *pfd = NULL; + *pf = -1; + return (estrdup(FAKE_EMPTYFILE)); + } + return (NULL); + } + ch_ungetchar(c); + *pfd = (void *) fd; + *pf = f; + return (estrdup("-")); + } + cmd = readfd(fd); + pclose(fd); + if (*cmd == '\0') + /* + * Pipe is empty. This means there is no alt file. + */ + return (NULL); + return (cmd); +} + +/* + * Close a replacement file. + */ +void +close_altfile(char *altfilename, char *filename, void *pipefd) +{ + char *lessclose; + FILE *fd; + char *cmd; + + if (secure) + return; + if (pipefd != NULL) { + pclose((FILE *)pipefd); + } + if ((lessclose = lgetenv("LESSCLOSE")) == NULL) + return; + cmd = expand_pct_s(lessclose, filename, altfilename, NULL); + if (cmd == NULL) { + error("Invalid LESSCLOSE variable", NULL); + return; + } + fd = shellcmd(cmd); + free(cmd); + if (fd != NULL) + (void) pclose(fd); +} + +/* + * Is the specified file a directory? + */ +int +is_dir(char *filename) +{ + int isdir = 0; + int r; + struct stat statbuf; + + filename = shell_unquote(filename); + + r = stat(filename, &statbuf); + isdir = (r >= 0 && S_ISDIR(statbuf.st_mode)); + free(filename); + return (isdir); +} + +/* + * Returns NULL if the file can be opened and + * is an ordinary file, otherwise an error message + * (if it cannot be opened or is a directory, etc.) + */ +char * +bad_file(char *filename) +{ + char *m = NULL; + + filename = shell_unquote(filename); + if (!force_open && is_dir(filename)) { + m = easprintf("%s is a directory", filename); + } else { + int r; + struct stat statbuf; + + r = stat(filename, &statbuf); + if (r < 0) { + m = errno_message(filename); + } else if (force_open) { + m = NULL; + } else if (!S_ISREG(statbuf.st_mode)) { + m = easprintf("%s is not a regular file (use -f to " + "see it)", filename); + } + } + free(filename); + return (m); +} + +/* + * Return the size of a file, as cheaply as possible. + */ +off_t +filesize(int f) +{ + struct stat statbuf; + + if (fstat(f, &statbuf) >= 0) + return (statbuf.st_size); + return (-1); +} + +/* + * Return last component of a pathname. + */ +char * +last_component(char *name) +{ + char *slash; + + for (slash = name + strlen(name); slash > name; ) { + --slash; + if (*slash == '/') + return (slash + 1); + } + return (name); +} diff --git a/bin/less/forwback.c b/bin/less/forwback.c new file mode 100644 index 0000000000..bc31a5ea27 --- /dev/null +++ b/bin/less/forwback.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Primitives for displaying the file on the screen, + * scrolling either forward or backward. + */ + +#include "less.h" +#include "position.h" + +int screen_trashed; +int squished; +int no_back_scroll = 0; +int forw_prompt; + +extern volatile sig_atomic_t sigs; +extern int top_scroll; +extern int quiet; +extern int sc_width, sc_height; +extern int plusoption; +extern int forw_scroll; +extern int back_scroll; +extern int ignore_eoi; +extern int clear_bg; +extern int final_attr; +extern int oldbot; +extern char *tagoption; + +/* + * Sound the bell to indicate user is trying to move past end of file. + */ +static void +eof_bell(void) +{ + if (quiet == NOT_QUIET) + ring_bell(); + else + vbell(); +} + +/* + * Check to see if the end of file is currently displayed. + */ +int +eof_displayed(void) +{ + off_t pos; + + if (ignore_eoi) + return (0); + + if (ch_length() == -1) + /* + * If the file length is not known, + * we can't possibly be displaying EOF. + */ + return (0); + + /* + * If the bottom line is empty, we are at EOF. + * If the bottom line ends at the file length, + * we must be just at EOF. + */ + pos = position(BOTTOM_PLUS_ONE); + return (pos == -1 || pos == ch_length()); +} + +/* + * Check to see if the entire file is currently displayed. + */ +int +entire_file_displayed(void) +{ + off_t pos; + + /* Make sure last line of file is displayed. */ + if (!eof_displayed()) + return (0); + + /* Make sure first line of file is displayed. */ + pos = position(0); + return (pos == -1 || pos == 0); +} + +/* + * If the screen is "squished", repaint it. + * "Squished" means the first displayed line is not at the top + * of the screen; this can happen when we display a short file + * for the first time. + */ +void +squish_check(void) +{ + if (!squished) + return; + squished = 0; + repaint(); +} + +/* + * Display n lines, scrolling forward, + * starting at position pos in the input file. + * "force" means display the n lines even if we hit end of file. + * "only_last" means display only the last screenful if n > screen size. + * "nblank" is the number of blank lines to draw before the first + * real line. If nblank > 0, the pos must be -1. + * The first real line after the blanks will start at ch_zero(). + */ +void +forw(int n, off_t pos, int force, int only_last, int nblank) +{ + int nlines = 0; + int do_repaint; + static int first_time = 1; + + squish_check(); + + /* + * do_repaint tells us not to display anything till the end, + * then just repaint the entire screen. + * We repaint if we are supposed to display only the last + * screenful and the request is for more than a screenful. + * Also if the request exceeds the forward scroll limit + * (but not if the request is for exactly a screenful, since + * repainting itself involves scrolling forward a screenful). + */ + do_repaint = (only_last && n > sc_height-1) || + (forw_scroll >= 0 && n > forw_scroll && n != sc_height-1); + + if (!do_repaint) { + if (top_scroll && n >= sc_height - 1 && pos != ch_length()) { + /* + * Start a new screen. + * {{ This is not really desirable if we happen + * to hit eof in the middle of this screen, + * but we don't yet know if that will happen. }} + */ + pos_clear(); + add_forw_pos(pos); + force = 1; + do_clear(); + home(); + } + + if (pos != position(BOTTOM_PLUS_ONE) || empty_screen()) { + /* + * This is not contiguous with what is + * currently displayed. Clear the screen image + * (position table) and start a new screen. + */ + pos_clear(); + add_forw_pos(pos); + force = 1; + if (top_scroll) { + do_clear(); + home(); + } else if (!first_time) { + putstr("...skipping...\n"); + } + } + } + + while (--n >= 0) { + /* + * Read the next line of input. + */ + if (nblank > 0) { + /* + * Still drawing blanks; don't get a line + * from the file yet. + * If this is the last blank line, get ready to + * read a line starting at ch_zero() next time. + */ + if (--nblank == 0) + pos = ch_zero(); + } else { + /* + * Get the next line from the file. + */ + pos = forw_line(pos); + if (pos == -1) { + /* + * End of file: stop here unless the top line + * is still empty, or "force" is true. + * Even if force is true, stop when the last + * line in the file reaches the top of screen. + */ + if (!force && position(TOP) != -1) + break; + if (!empty_lines(0, 0) && + !empty_lines(1, 1) && + empty_lines(2, sc_height-1)) + break; + } + } + /* + * Add the position of the next line to the position table. + * Display the current line on the screen. + */ + add_forw_pos(pos); + nlines++; + if (do_repaint) + continue; + /* + * If this is the first screen displayed and + * we hit an early EOF (i.e. before the requested + * number of lines), we "squish" the display down + * at the bottom of the screen. + * But don't do this if a + option or a -t option + * was given. These options can cause us to + * start the display after the beginning of the file, + * and it is not appropriate to squish in that case. + */ + if (first_time && pos == -1 && !top_scroll && + tagoption == NULL && !plusoption) { + squished = 1; + continue; + } + put_line(); + forw_prompt = 1; + } + + if (nlines == 0) + eof_bell(); + else if (do_repaint) + repaint(); + first_time = 0; + (void) currline(BOTTOM); +} + +/* + * Display n lines, scrolling backward. + */ +void +back(int n, off_t pos, int force, int only_last) +{ + int nlines = 0; + int do_repaint; + + squish_check(); + do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1)); + while (--n >= 0) { + /* + * Get the previous line of input. + */ + pos = back_line(pos); + if (pos == -1) { + /* + * Beginning of file: stop here unless "force" is true. + */ + if (!force) + break; + } + /* + * Add the position of the previous line to the position table. + * Display the line on the screen. + */ + add_back_pos(pos); + nlines++; + if (!do_repaint) { + home(); + add_line(); + put_line(); + } + } + + if (nlines == 0) + eof_bell(); + else if (do_repaint) + repaint(); + else if (!oldbot) + lower_left(); + (void) currline(BOTTOM); +} + +/* + * Display n more lines, forward. + * Start just after the line currently displayed at the bottom of the screen. + */ +void +forward(int n, int force, int only_last) +{ + off_t pos; + + if (get_quit_at_eof() && eof_displayed() && + !(ch_getflags() & CH_HELPFILE)) { + /* + * If the -e flag is set and we're trying to go + * forward from end-of-file, go on to the next file. + */ + if (edit_next(1)) + quit(QUIT_OK); + return; + } + + pos = position(BOTTOM_PLUS_ONE); + if (pos == -1 && (!force || empty_lines(2, sc_height-1))) { + if (ignore_eoi) { + /* + * ignore_eoi is to support A_F_FOREVER. + * Back up until there is a line at the bottom + * of the screen. + */ + if (empty_screen()) { + pos = ch_zero(); + } else { + do { + back(1, position(TOP), 1, 0); + pos = position(BOTTOM_PLUS_ONE); + } while (pos == -1); + } + } else { + eof_bell(); + return; + } + } + forw(n, pos, force, only_last, 0); +} + +/* + * Display n more lines, backward. + * Start just before the line currently displayed at the top of the screen. + */ +void +backward(int n, int force, int only_last) +{ + off_t pos; + + pos = position(TOP); + if (pos == -1 && (!force || position(BOTTOM) == 0)) { + eof_bell(); + return; + } + back(n, pos, force, only_last); +} + +/* + * Get the backwards scroll limit. + * Must call this function instead of just using the value of + * back_scroll, because the default case depends on sc_height and + * top_scroll, as well as back_scroll. + */ +int +get_back_scroll(void) +{ + if (no_back_scroll) + return (0); + if (back_scroll >= 0) + return (back_scroll); + if (top_scroll) + return (sc_height - 2); + return (10000); /* infinity */ +} diff --git a/bin/less/funcs.h b/bin/less/funcs.h new file mode 100644 index 0000000000..00eaf9b809 --- /dev/null +++ b/bin/less/funcs.h @@ -0,0 +1,286 @@ +/* + * Copyright 2014 Garrett D'Amore + * + * This file is made available under the terms of the Less License. + */ + +#include + +struct mlist; +struct loption; + +void *ecalloc(int, unsigned int); +char *easprintf(const char *, ...); +char *estrdup(const char *); +char *skipsp(char *); +int sprefix(char *, char *, int); +void quit(int); +void raw_mode(int); +char *special_key_str(int); +void get_term(void); +void init(void); +void deinit(void); +void home(void); +void add_line(void); +void lower_left(void); +void line_left(void); +void goto_line(int); +void vbell(void); +void ring_bell(void); +void do_clear(void); +void clear_eol(void); +void clear_bot(void); +void at_enter(int); +void at_exit(void); +void at_switch(int); +int is_at_equiv(int, int); +int apply_at_specials(int); +void putbs(void); +void match_brac(int, int, int, int); +int ch_get(void); +void ch_ungetchar(int); +void end_logfile(void); +void sync_logfile(void); +int ch_seek(off_t); +int ch_end_seek(void); +int ch_beg_seek(void); +off_t ch_length(void); +off_t ch_tell(void); +int ch_forw_get(void); +int ch_back_get(void); +void ch_setbufspace(int); +void ch_flush(void); +int seekable(int); +void ch_set_eof(void); +void ch_init(int, int); +void ch_close(void); +int ch_getflags(void); +void init_charset(void); +int binary_char(LWCHAR); +int control_char(LWCHAR); +char *prchar(LWCHAR); +char *prutfchar(LWCHAR); +int utf_len(char); +int is_utf8_well_formed(const char *); +LWCHAR get_wchar(const char *); +void put_wchar(char **, LWCHAR); +LWCHAR step_char(char **, int, char *); +int is_composing_char(LWCHAR); +int is_ubin_char(LWCHAR); +int is_wide_char(LWCHAR); +int is_combining_char(LWCHAR, LWCHAR); +void cmd_reset(void); +void clear_cmd(void); +void cmd_putstr(char *); +int len_cmdbuf(void); +void set_mlist(void *, int); +void cmd_addhist(struct mlist *, const char *); +void cmd_accept(void); +int cmd_char(int); +off_t cmd_int(long *); +char *get_cmdbuf(void); +char *cmd_lastpattern(void); +void init_cmdhist(void); +void save_cmdhist(void); +int in_mca(void); +void dispversion(void); +int getcc(void); +void ungetcc(int); +void ungetsc(char *); +void commands(void); +int cvt_length(int); +int *cvt_alloc_chpos(int); +void cvt_text(char *, char *, int *, int *, int); +void init_cmds(void); +void add_fcmd_table(char *, int); +void add_ecmd_table(char *, int); +int fcmd_decode(const char *, char **); +int ecmd_decode(const char *, char **); +char *lgetenv(char *); +int lesskey(char *, int); +void add_hometable(char *, char *, int); +int editchar(int, int); +void init_textlist(struct textlist *, char *); +char *forw_textlist(struct textlist *, char *); +char *back_textlist(struct textlist *, char *); +int edit(char *); +int edit_ifile(IFILE); +int edit_list(char *); +int edit_first(void); +int edit_last(void); +int edit_next(int); +int edit_prev(int); +int edit_index(int); +IFILE save_curr_ifile(void); +void unsave_ifile(IFILE); +void reedit_ifile(IFILE); +void reopen_curr_ifile(void); +int edit_stdin(void); +void cat_file(void); +void use_logfile(char *); +char *shell_unquote(char *); +char *get_meta_escape(void); +char *shell_quote(const char *); +char *homefile(char *); +char *fexpand(char *); +char *fcomplete(char *); +int bin_file(int f); +char *lglob(char *); +char *open_altfile(char *, int *, void **); +void close_altfile(char *, char *, void *); +int is_dir(char *); +char *bad_file(char *); +off_t filesize(int); +char *last_component(char *); +int eof_displayed(void); +int entire_file_displayed(void); +void squish_check(void); +void forw(int, off_t, int, int, int); +void back(int, off_t, int, int); +void forward(int, int, int); +void backward(int, int, int); +int get_back_scroll(void); +void del_ifile(IFILE); +IFILE next_ifile(IFILE); +IFILE prev_ifile(IFILE); +IFILE getoff_ifile(IFILE); +int nifile(void); +IFILE get_ifile(char *, IFILE); +char *get_filename(IFILE); +int get_index(IFILE); +void store_pos(IFILE, struct scrpos *); +void get_pos(IFILE, struct scrpos *); +int opened(IFILE); +void hold_ifile(IFILE, int); +int held_ifile(IFILE); +void set_open(IFILE); +void *get_filestate(IFILE); +void set_filestate(IFILE, void *); +off_t forw_line(off_t); +off_t back_line(off_t); +void set_attnpos(off_t); +void jump_forw(void); +void jump_back(off_t); +void repaint(void); +void jump_percent(int, long); +void jump_line_loc(off_t, int); +void jump_loc(off_t, int); +void init_line(void); +int is_ascii_char(LWCHAR); +void prewind(void); +void plinenum(off_t); +void pshift_all(void); +int is_ansi_end(LWCHAR); +int is_ansi_middle(LWCHAR); +int pappend(char, off_t); +int pflushmbc(void); +void pdone(int, int); +void set_status_col(char); +int gline(int, int *); +void null_line(void); +off_t forw_raw_line(off_t, char **, int *); +off_t back_raw_line(off_t, char **, int *); +void clr_linenum(void); +void add_lnum(off_t, off_t); +off_t find_linenum(off_t); +off_t find_pos(off_t); +off_t currline(int); +void lsystem(const char *, const char *); +int pipe_mark(int, char *); +void init_mark(void); +int badmark(int); +void setmark(int); +void lastmark(void); +void gomark(int); +off_t markpos(int); +void unmark(IFILE); +void opt_o(int, char *); +void opt__O(int, char *); +void opt_j(int, char *); +void calc_jump_sline(void); +void opt_shift(int, char *); +void calc_shift_count(void); +void opt_k(int, char *); +void opt_t(int, char *); +void opt__T(int, char *); +void opt_p(int, char *); +void opt__P(int, char *); +void opt_b(int, char *); +void opt_i(int, char *); +void opt__V(int, char *); +void opt_x(int, char *); +void opt_quote(int, char *); +void opt_query(int, char *); +int get_swindow(void); +char *propt(int); +void scan_option(char *); +void toggle_option(struct loption *, int, char *, int); +int opt_has_param(struct loption *); +char *opt_prompt(struct loption *); +int isoptpending(void); +void nopendopt(void); +int getnum(char **, char *, int *); +long getfraction(char **, char *, int *); +int get_quit_at_eof(void); +void init_option(void); +struct loption *findopt(int); +struct loption *findopt_name(char **, char **, int *); +int iread(int, unsigned char *, unsigned int); +char *errno_message(char *); +int percentage(off_t, off_t); +off_t percent_pos(off_t, int, long); +void put_line(void); +void flush(int); +int putchr(int); +void putstr(const char *); +void get_return(void); +void error(const char *, PARG *); +void ierror(const char *, PARG *); +int query(const char *, PARG *); +int compile_pattern(char *, int, regex_t **); +void uncompile_pattern(regex_t **); +int match_pattern(void *, char *, char *, int, char **, char **, + int, int); +off_t position(int); +void add_forw_pos(off_t); +void add_back_pos(off_t); +void pos_clear(void); +void pos_init(void); +int onscreen(off_t); +int empty_screen(void); +int empty_lines(int, int); +void get_scrpos(struct scrpos *); +int adjsline(int); +void init_prompt(void); +char *pr_expand(const char *, int); +char *eq_message(void); +char *prompt_string(void); +char *wait_message(void); +void init_search(void); +void repaint_hilite(int); +void clear_attn(void); +void undo_search(void); +void clr_hilite(void); +int is_filtered(off_t); +int is_hilited(off_t, off_t, int, int *); +void chg_caseless(void); +void chg_hilite(void); +int search(int, char *, int); +void prep_hilite(off_t, off_t, int); +void set_filter_pattern(char *, int); +int is_filtering(void); +void sigwinch(int); +void init_signals(int); +void psignals(void); +void cleantags(void); +void findtag(char *); +off_t tagsearch(void); +char *nexttag(int); +char *prevtag(int); +int ntags(void); +int curr_tag(void); +int edit_tagfile(void); +void open_getchr(void); +int getchr(void); +void *lsignal(int, void (*)(int)); +char *helpfile(void); diff --git a/bin/less/ifile.c b/bin/less/ifile.c new file mode 100644 index 0000000000..62e0013957 --- /dev/null +++ b/bin/less/ifile.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * An IFILE represents an input file. + * + * It is actually a pointer to an ifile structure, + * but is opaque outside this module. + * Ifile structures are kept in a linked list in the order they + * appear on the command line. + * Any new file which does not already appear in the list is + * inserted after the current file. + */ + +#include "less.h" + +extern IFILE curr_ifile; + +struct ifile { + struct ifile *h_next; /* Links for command line list */ + struct ifile *h_prev; + char *h_filename; /* Name of the file */ + void *h_filestate; /* File state (used in ch.c) */ + int h_index; /* Index within command line list */ + int h_hold; /* Hold count */ + char h_opened; /* Has this ifile been opened? */ + struct scrpos h_scrpos; /* Saved position within the file */ +}; + +/* + * Convert an IFILE (external representation) + * to a struct file (internal representation), and vice versa. + */ +#define int_ifile(h) ((struct ifile *)(h)) +#define ext_ifile(h) ((IFILE)(h)) + +/* + * Anchor for linked list. + */ +static struct ifile anchor = { &anchor, &anchor, NULL, NULL, 0, 0, '\0', + { -1, 0 } }; +static int ifiles = 0; + +static void +incr_index(struct ifile *p, int incr) +{ + for (; p != &anchor; p = p->h_next) + p->h_index += incr; +} + +/* + * Link an ifile into the ifile list. + */ +static void +link_ifile(struct ifile *p, struct ifile *prev) +{ + /* + * Link into list. + */ + if (prev == NULL) + prev = &anchor; + p->h_next = prev->h_next; + p->h_prev = prev; + prev->h_next->h_prev = p; + prev->h_next = p; + /* + * Calculate index for the new one, + * and adjust the indexes for subsequent ifiles in the list. + */ + p->h_index = prev->h_index + 1; + incr_index(p->h_next, 1); + ifiles++; +} + +/* + * Unlink an ifile from the ifile list. + */ +static void +unlink_ifile(struct ifile *p) +{ + p->h_next->h_prev = p->h_prev; + p->h_prev->h_next = p->h_next; + incr_index(p->h_next, -1); + ifiles--; +} + +/* + * Allocate a new ifile structure and stick a filename in it. + * It should go after "prev" in the list + * (or at the beginning of the list if "prev" is NULL). + * Return a pointer to the new ifile structure. + */ +static struct ifile * +new_ifile(char *filename, struct ifile *prev) +{ + struct ifile *p; + + /* + * Allocate and initialize structure. + */ + p = ecalloc(1, sizeof (struct ifile)); + p->h_filename = estrdup(filename); + p->h_scrpos.pos = -1; + p->h_opened = 0; + p->h_hold = 0; + p->h_filestate = NULL; + link_ifile(p, prev); + return (p); +} + +/* + * Delete an existing ifile structure. + */ +void +del_ifile(IFILE h) +{ + struct ifile *p; + + if (h == NULL) + return; + /* + * If the ifile we're deleting is the currently open ifile, + * move off it. + */ + unmark(h); + if (h == curr_ifile) + curr_ifile = getoff_ifile(curr_ifile); + p = int_ifile(h); + unlink_ifile(p); + free(p->h_filename); + free(p); +} + +/* + * Get the ifile after a given one in the list. + */ +IFILE +next_ifile(IFILE h) +{ + struct ifile *p; + + p = (h == NULL) ? &anchor : int_ifile(h); + if (p->h_next == &anchor) + return (NULL); + return (ext_ifile(p->h_next)); +} + +/* + * Get the ifile before a given one in the list. + */ +IFILE +prev_ifile(IFILE h) +{ + struct ifile *p; + + p = (h == NULL) ? &anchor : int_ifile(h); + if (p->h_prev == &anchor) + return (NULL); + return (ext_ifile(p->h_prev)); +} + +/* + * Return a different ifile from the given one. + */ +IFILE +getoff_ifile(IFILE ifile) +{ + IFILE newifile; + + if ((newifile = prev_ifile(ifile)) != NULL) + return (newifile); + if ((newifile = next_ifile(ifile)) != NULL) + return (newifile); + return (NULL); +} + +/* + * Return the number of ifiles. + */ +int +nifile(void) +{ + return (ifiles); +} + +/* + * Find an ifile structure, given a filename. + */ +static struct ifile * +find_ifile(const char *filename) +{ + struct ifile *p; + + for (p = anchor.h_next; p != &anchor; p = p->h_next) + if (strcmp(filename, p->h_filename) == 0) + return (p); + return (NULL); +} + +/* + * Get the ifile associated with a filename. + * If the filename has not been seen before, + * insert the new ifile after "prev" in the list. + */ +IFILE +get_ifile(char *filename, IFILE prev) +{ + struct ifile *p; + + if ((p = find_ifile(filename)) == NULL) + p = new_ifile(filename, int_ifile(prev)); + return (ext_ifile(p)); +} + +/* + * Get the filename associated with a ifile. + */ +char * +get_filename(IFILE ifile) +{ + if (ifile == NULL) + return (NULL); + return (int_ifile(ifile)->h_filename); +} + +/* + * Get the index of the file associated with a ifile. + */ +int +get_index(IFILE ifile) +{ + return (int_ifile(ifile)->h_index); +} + +/* + * Save the file position to be associated with a given file. + */ +void +store_pos(IFILE ifile, struct scrpos *scrpos) +{ + int_ifile(ifile)->h_scrpos = *scrpos; +} + +/* + * Recall the file position associated with a file. + * If no position has been associated with the file, return -1. + */ +void +get_pos(IFILE ifile, struct scrpos *scrpos) +{ + *scrpos = int_ifile(ifile)->h_scrpos; +} + +/* + * Mark the ifile as "opened". + */ +void +set_open(IFILE ifile) +{ + int_ifile(ifile)->h_opened = 1; +} + +/* + * Return whether the ifile has been opened previously. + */ +int +opened(IFILE ifile) +{ + return (int_ifile(ifile)->h_opened); +} + +void +hold_ifile(IFILE ifile, int incr) +{ + int_ifile(ifile)->h_hold += incr; +} + +int +held_ifile(IFILE ifile) +{ + return (int_ifile(ifile)->h_hold); +} + +void * +get_filestate(IFILE ifile) +{ + return (int_ifile(ifile)->h_filestate); +} + +void +set_filestate(IFILE ifile, void *filestate) +{ + int_ifile(ifile)->h_filestate = filestate; +} diff --git a/bin/less/input.c b/bin/less/input.c new file mode 100644 index 0000000000..dd5f622e07 --- /dev/null +++ b/bin/less/input.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * High level routines dealing with getting lines of input + * from the file being viewed. + * + * When we speak of "lines" here, we mean PRINTABLE lines; + * lines processed with respect to the screen width. + * We use the term "raw line" to refer to lines simply + * delimited by newlines; not processed with respect to screen width. + */ + +#include "less.h" + +extern int squeeze; +extern int chopline; +extern int hshift; +extern int quit_if_one_screen; +extern volatile sig_atomic_t sigs; +extern int ignore_eoi; +extern int status_col; +extern off_t start_attnpos; +extern off_t end_attnpos; +extern int hilite_search; +extern int size_linebuf; + +/* + * Get the next line. + * A "current" position is passed and a "new" position is returned. + * The current position is the position of the first character of + * a line. The new position is the position of the first character + * of the NEXT line. The line obtained is the line starting at curr_pos. + */ +off_t +forw_line(off_t curr_pos) +{ + off_t base_pos; + off_t new_pos; + int c; + int blankline; + int endline; + int backchars; + +get_forw_line: + if (curr_pos == -1) { + null_line(); + return (-1); + } + if (hilite_search == OPT_ONPLUS || is_filtering() || status_col) + /* + * If we are ignoring EOI (command F), only prepare + * one line ahead, to avoid getting stuck waiting for + * slow data without displaying the data we already have. + * If we're not ignoring EOI, we *could* do the same, but + * for efficiency we prepare several lines ahead at once. + */ + prep_hilite(curr_pos, curr_pos + 3*size_linebuf, + ignore_eoi ? 1 : -1); + if (ch_seek(curr_pos)) { + null_line(); + return (-1); + } + + /* + * Step back to the beginning of the line. + */ + base_pos = curr_pos; + for (;;) { + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + c = ch_back_get(); + if (c == EOI) + break; + if (c == '\n') { + (void) ch_forw_get(); + break; + } + --base_pos; + } + + /* + * Read forward again to the position we should start at. + */ + prewind(); + plinenum(base_pos); + (void) ch_seek(base_pos); + new_pos = base_pos; + while (new_pos < curr_pos) { + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + c = ch_forw_get(); + backchars = pappend(c, new_pos); + new_pos++; + if (backchars > 0) { + pshift_all(); + new_pos -= backchars; + while (--backchars >= 0) + (void) ch_back_get(); + } + } + (void) pflushmbc(); + pshift_all(); + + /* + * Read the first character to display. + */ + c = ch_forw_get(); + if (c == EOI) { + null_line(); + return (-1); + } + blankline = (c == '\n' || c == '\r'); + + /* + * Read each character in the line and append to the line buffer. + */ + for (;;) { + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + if (c == '\n' || c == EOI) { + /* + * End of the line. + */ + backchars = pflushmbc(); + new_pos = ch_tell(); + if (backchars > 0 && !chopline && hshift == 0) { + new_pos -= backchars + 1; + endline = FALSE; + } else + endline = TRUE; + break; + } + if (c != '\r') + blankline = 0; + + /* + * Append the char to the line and get the next char. + */ + backchars = pappend(c, ch_tell()-1); + if (backchars > 0) { + /* + * The char won't fit in the line; the line + * is too long to print in the screen width. + * End the line here. + */ + if (chopline || hshift > 0) { + do { + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + c = ch_forw_get(); + } while (c != '\n' && c != EOI); + new_pos = ch_tell(); + endline = TRUE; + quit_if_one_screen = FALSE; + } else { + new_pos = ch_tell() - backchars; + endline = FALSE; + } + break; + } + c = ch_forw_get(); + } + + pdone(endline, 1); + + if (is_filtered(base_pos)) { + /* + * We don't want to display this line. + * Get the next line. + */ + curr_pos = new_pos; + goto get_forw_line; + } + + if (status_col && is_hilited(base_pos, ch_tell()-1, 1, NULL)) + set_status_col('*'); + + if (squeeze && blankline) { + /* + * This line is blank. + * Skip down to the last contiguous blank line + * and pretend it is the one which we are returning. + */ + while ((c = ch_forw_get()) == '\n' || c == '\r') + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + if (c != EOI) + (void) ch_back_get(); + new_pos = ch_tell(); + } + + return (new_pos); +} + +/* + * Get the previous line. + * A "current" position is passed and a "new" position is returned. + * The current position is the position of the first character of + * a line. The new position is the position of the first character + * of the PREVIOUS line. The line obtained is the one starting at new_pos. + */ +off_t +back_line(off_t curr_pos) +{ + off_t new_pos, begin_new_pos, base_pos; + int c; + int endline; + int backchars; + +get_back_line: + if (curr_pos == -1 || curr_pos <= ch_zero()) { + null_line(); + return (-1); + } + if (hilite_search == OPT_ONPLUS || is_filtering() || status_col) + prep_hilite((curr_pos < 3*size_linebuf) ? + 0 : curr_pos - 3*size_linebuf, curr_pos, -1); + if (ch_seek(curr_pos-1)) { + null_line(); + return (-1); + } + + if (squeeze) { + /* + * Find out if the "current" line was blank. + */ + (void) ch_forw_get(); /* Skip the newline */ + c = ch_forw_get(); /* First char of "current" line */ + (void) ch_back_get(); /* Restore our position */ + (void) ch_back_get(); + + if (c == '\n' || c == '\r') { + /* + * The "current" line was blank. + * Skip over any preceding blank lines, + * since we skipped them in forw_line(). + */ + while ((c = ch_back_get()) == '\n' || c == '\r') + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + if (c == EOI) { + null_line(); + return (-1); + } + (void) ch_forw_get(); + } + } + + /* + * Scan backwards until we hit the beginning of the line. + */ + for (;;) { + if (ABORT_SIGS()) { + null_line(); + return (-1); + } + c = ch_back_get(); + if (c == '\n') { + /* + * This is the newline ending the previous line. + * We have hit the beginning of the line. + */ + base_pos = ch_tell() + 1; + break; + } + if (c == EOI) { + /* + * We have hit the beginning of the file. + * This must be the first line in the file. + * This must, of course, be the beginning of the line. + */ + base_pos = ch_tell(); + break; + } + } + + /* + * Now scan forwards from the beginning of this line. + * We keep discarding "printable lines" (based on screen width) + * until we reach the curr_pos. + * + * {{ This algorithm is pretty inefficient if the lines + * are much longer than the screen width, + * but I don't know of any better way. }} + */ + new_pos = base_pos; + if (ch_seek(new_pos)) { + null_line(); + return (-1); + } + endline = FALSE; + prewind(); + plinenum(new_pos); +loop: + begin_new_pos = new_pos; + (void) ch_seek(new_pos); + + do { + c = ch_forw_get(); + if (c == EOI || ABORT_SIGS()) { + null_line(); + return (-1); + } + new_pos++; + if (c == '\n') { + backchars = pflushmbc(); + if (backchars > 0 && !chopline && hshift == 0) { + backchars++; + goto shift; + } + endline = TRUE; + break; + } + backchars = pappend(c, ch_tell()-1); + if (backchars > 0) { + /* + * Got a full printable line, but we haven't + * reached our curr_pos yet. Discard the line + * and start a new one. + */ + if (chopline || hshift > 0) { + endline = TRUE; + quit_if_one_screen = FALSE; + break; + } + shift: + pshift_all(); + while (backchars-- > 0) { + (void) ch_back_get(); + new_pos--; + } + goto loop; + } + } while (new_pos < curr_pos); + + pdone(endline, 0); + + if (is_filtered(base_pos)) { + /* + * We don't want to display this line. + * Get the previous line. + */ + curr_pos = begin_new_pos; + goto get_back_line; + } + + if (status_col && curr_pos > 0 && + is_hilited(base_pos, curr_pos-1, 1, NULL)) + set_status_col('*'); + + return (begin_new_pos); +} + +/* + * Set attnpos. + */ +void +set_attnpos(off_t pos) +{ + int c; + + if (pos != -1) { + if (ch_seek(pos)) + return; + for (;;) { + c = ch_forw_get(); + if (c == EOI) + return; + if (c != '\n' && c != '\r') + break; + pos++; + } + } + start_attnpos = pos; + for (;;) { + c = ch_forw_get(); + pos++; + if (c == EOI || c == '\n' || c == '\r') + break; + } + end_attnpos = pos; +} diff --git a/bin/less/jump.c b/bin/less/jump.c new file mode 100644 index 0000000000..a4a60eeb99 --- /dev/null +++ b/bin/less/jump.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines which jump to a new location in the file. + */ + +#include "less.h" +#include "position.h" + +extern int jump_sline; +extern int squished; +extern int screen_trashed; +extern int sc_width, sc_height; +extern int show_attn; +extern int top_scroll; + +/* + * Jump to the end of the file. + */ +void +jump_forw(void) +{ + off_t pos; + off_t end_pos; + + if (ch_end_seek()) { + error("Cannot seek to end of file", NULL); + return; + } + /* + * Note; lastmark will be called later by jump_loc, but it fails + * because the position table has been cleared by pos_clear below. + * So call it here before calling pos_clear. + */ + lastmark(); + /* + * Position the last line in the file at the last screen line. + * Go back one line from the end of the file + * to get to the beginning of the last line. + */ + pos_clear(); + end_pos = ch_tell(); + pos = back_line(end_pos); + if (pos == -1) { + jump_loc(0, sc_height-1); + } else { + jump_loc(pos, sc_height-1); + if (position(sc_height-1) != end_pos) + repaint(); + } +} + +/* + * Jump to line n in the file. + */ +void +jump_back(off_t linenum) +{ + off_t pos; + PARG parg; + + /* + * Find the position of the specified line. + * If we can seek there, just jump to it. + * If we can't seek, but we're trying to go to line number 1, + * use ch_beg_seek() to get as close as we can. + */ + pos = find_pos(linenum); + if (pos != -1 && ch_seek(pos) == 0) { + if (show_attn) + set_attnpos(pos); + jump_loc(pos, jump_sline); + } else if (linenum <= 1 && ch_beg_seek() == 0) { + jump_loc(ch_tell(), jump_sline); + error("Cannot seek to beginning of file", NULL); + } else { + parg.p_linenum = linenum; + error("Cannot seek to line number %n", &parg); + } +} + +/* + * Repaint the screen. + */ +void +repaint(void) +{ + struct scrpos scrpos; + /* + * Start at the line currently at the top of the screen + * and redisplay the screen. + */ + get_scrpos(&scrpos); + pos_clear(); + jump_loc(scrpos.pos, scrpos.ln); +} + +/* + * Jump to a specified percentage into the file. + */ +void +jump_percent(int percent, long fraction) +{ + off_t pos, len; + + /* + * Determine the position in the file + * (the specified percentage of the file's length). + */ + if ((len = ch_length()) == -1) { + ierror("Determining length of file", NULL); + ch_end_seek(); + } + if ((len = ch_length()) == -1) { + error("Don't know length of file", NULL); + return; + } + pos = percent_pos(len, percent, fraction); + if (pos >= len) + pos = len-1; + + jump_line_loc(pos, jump_sline); +} + +/* + * Jump to a specified position in the file. + * Like jump_loc, but the position need not be + * the first character in a line. + */ +void +jump_line_loc(off_t pos, int sline) +{ + int c; + + if (ch_seek(pos) == 0) { + /* + * Back up to the beginning of the line. + */ + while ((c = ch_back_get()) != '\n' && c != EOI) + ; + if (c == '\n') + (void) ch_forw_get(); + pos = ch_tell(); + } + if (show_attn) + set_attnpos(pos); + jump_loc(pos, sline); +} + +/* + * Jump to a specified position in the file. + * The position must be the first character in a line. + * Place the target line on a specified line on the screen. + */ +void +jump_loc(off_t pos, int sline) +{ + int nline; + off_t tpos; + off_t bpos; + + /* + * Normalize sline. + */ + sline = adjsline(sline); + + if ((nline = onscreen(pos)) >= 0) { + /* + * The line is currently displayed. + * Just scroll there. + */ + nline -= sline; + if (nline > 0) + forw(nline, position(BOTTOM_PLUS_ONE), 1, 0, 0); + else + back(-nline, position(TOP), 1, 0); + if (show_attn) + repaint_hilite(1); + return; + } + + /* + * Line is not on screen. + * Seek to the desired location. + */ + if (ch_seek(pos)) { + error("Cannot seek to that file position", NULL); + return; + } + + /* + * See if the desired line is before or after + * the currently displayed screen. + */ + tpos = position(TOP); + bpos = position(BOTTOM_PLUS_ONE); + if (tpos == -1 || pos >= tpos) { + /* + * The desired line is after the current screen. + * Move back in the file far enough so that we can + * call forw() and put the desired line at the + * sline-th line on the screen. + */ + for (nline = 0; nline < sline; nline++) { + if (bpos != -1 && pos <= bpos) { + /* + * Surprise! The desired line is + * close enough to the current screen + * that we can just scroll there after all. + */ + forw(sc_height-sline+nline-1, bpos, 1, 0, 0); + if (show_attn) + repaint_hilite(1); + return; + } + pos = back_line(pos); + if (pos == -1) { + /* + * Oops. Ran into the beginning of the file. + * Exit the loop here and rely on forw() + * below to draw the required number of + * blank lines at the top of the screen. + */ + break; + } + } + lastmark(); + squished = 0; + screen_trashed = 0; + forw(sc_height-1, pos, 1, 0, sline-nline); + } else { + /* + * The desired line is before the current screen. + * Move forward in the file far enough so that we + * can call back() and put the desired line at the + * sline-th line on the screen. + */ + for (nline = sline; nline < sc_height - 1; nline++) { + pos = forw_line(pos); + if (pos == -1) { + /* + * Ran into end of file. + * This shouldn't normally happen, + * but may if there is some kind of read error. + */ + break; + } + if (pos >= tpos) { + /* + * Surprise! The desired line is + * close enough to the current screen + * that we can just scroll there after all. + */ + back(nline + 1, tpos, 1, 0); + if (show_attn) + repaint_hilite(1); + return; + } + } + lastmark(); + if (!top_scroll) + do_clear(); + else + home(); + screen_trashed = 0; + add_back_pos(pos); + back(sc_height-1, pos, 1, 0); + } +} diff --git a/bin/less/less.h b/bin/less/less.h new file mode 100644 index 0000000000..a7fe322053 --- /dev/null +++ b/bin/less/less.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Standard include file for "less". + */ + +#include "defines.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Simple lowercase test which can be used during option processing + * (before options are parsed which might tell us what charset to use). + */ + +#undef IS_SPACE +#undef IS_DIGIT + +#define IS_SPACE(c) isspace((unsigned char)(c)) +#define IS_DIGIT(c) isdigit((unsigned char)(c)) + +#define IS_CSI_START(c) (((LWCHAR)(c)) == ESC || (((LWCHAR)(c)) == CSI)) + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define OPT_OFF 0 +#define OPT_ON 1 +#define OPT_ONPLUS 2 + +/* + * Special types and constants. + */ +typedef unsigned long LWCHAR; +#define MIN_LINENUM_WIDTH 7 /* Min printing width of a line number */ +#define MAX_UTF_CHAR_LEN 6 /* Max bytes in one UTF-8 char */ + +#define SHELL_META_QUEST 1 + +/* + * An IFILE represents an input file. + */ +#define IFILE void * + +/* + * The structure used to represent a "screen position". + * This consists of a file position, and a screen line number. + * The meaning is that the line starting at the given file + * position is displayed on the ln-th line of the screen. + * (Screen lines before ln are empty.) + */ +struct scrpos { + off_t pos; + int ln; +}; + +typedef union parg { + char *p_string; + int p_int; + off_t p_linenum; +} PARG; + +struct textlist { + char *string; + char *endstring; +}; + +#define EOI (-1) + +#define READ_INTR (-2) + +/* A fraction is represented by an int n; the fraction is n/NUM_FRAC_DENOM */ +#define NUM_FRAC_DENOM 1000000 +#define NUM_LOG_FRAC_DENOM 6 + +/* How quiet should we be? */ +#define NOT_QUIET 0 /* Ring bell at eof and for errors */ +#define LITTLE_QUIET 1 /* Ring bell only for errors */ +#define VERY_QUIET 2 /* Never ring bell */ + +/* How should we prompt? */ +#define PR_SHORT 0 /* Prompt with colon */ +#define PR_MEDIUM 1 /* Prompt with message */ +#define PR_LONG 2 /* Prompt with longer message */ + +/* How should we handle backspaces? */ +#define BS_SPECIAL 0 /* Do special things for underlining and bold */ +#define BS_NORMAL 1 /* \b treated as normal char; actually output */ +#define BS_CONTROL 2 /* \b treated as control char; prints as ^H */ + +/* How should we search? */ +#define SRCH_FORW (1 << 0) /* Search forward from current position */ +#define SRCH_BACK (1 << 1) /* Search backward from current position */ +#define SRCH_NO_MOVE (1 << 2) /* Highlight, but don't move */ +#define SRCH_FIND_ALL (1 << 4) /* Find and highlight all matches */ +#define SRCH_NO_MATCH (1 << 8) /* Search for non-matching lines */ +#define SRCH_PAST_EOF (1 << 9) /* Search past end-of-file, into next file */ +#define SRCH_FIRST_FILE (1 << 10) /* Search starting at the first file */ +#define SRCH_NO_REGEX (1 << 12) /* Don't use regular expressions */ +#define SRCH_FILTER (1 << 13) /* Search is for '&' (filter) command */ +#define SRCH_AFTER_TARGET (1 << 14) /* Start search after the target line */ + +#define SRCH_REVERSE(t) (((t) & SRCH_FORW) ? \ + (((t) & ~SRCH_FORW) | SRCH_BACK) : \ + (((t) & ~SRCH_BACK) | SRCH_FORW)) + +/* */ +#define NO_MCA 0 +#define MCA_DONE 1 +#define MCA_MORE 2 + +#define CC_OK 0 /* Char was accepted & processed */ +#define CC_QUIT 1 /* Char was a request to abort current cmd */ +#define CC_ERROR 2 /* Char could not be accepted due to error */ +#define CC_PASS 3 /* Char was rejected (internal) */ + +#define CF_QUIT_ON_ERASE 0001 /* Abort cmd if its entirely erased */ + +/* Special char bit-flags used to tell put_line() to do something special */ +#define AT_NORMAL (0) +#define AT_UNDERLINE (1 << 0) +#define AT_BOLD (1 << 1) +#define AT_BLINK (1 << 2) +#define AT_STANDOUT (1 << 3) +#define AT_ANSI (1 << 4) /* Content-supplied "ANSI" escape sequence */ +#define AT_BINARY (1 << 5) /* LESS*BINFMT representation */ +#define AT_HILITE (1 << 6) /* Internal highlights (e.g., for search) */ +#define AT_INDET (1 << 7) /* Indeterminate: either bold or underline */ + +#define CONTROL(c) ((c)&037) + +#define ESC CONTROL('[') +#define CSI ((unsigned char)'\233') + +#define S_INTERRUPT 01 +#define S_STOP 02 +#define S_WINCH 04 +#define ABORT_SIGS() (sigs & (S_INTERRUPT|S_STOP)) + +#define QUIT_OK 0 +#define QUIT_ERROR 1 +#define QUIT_INTERRUPT 2 +#define QUIT_SAVED_STATUS (-1) + +#define FOLLOW_DESC 0 +#define FOLLOW_NAME 1 + +/* filestate flags */ +#define CH_CANSEEK 001 +#define CH_KEEPOPEN 002 +#define CH_POPENED 004 +#define CH_HELPFILE 010 +#define CH_NODATA 020 /* Special case for zero length files */ + + +#define ch_zero() (0) + +#define FAKE_EMPTYFILE "@/\\less/\\empty/\\file/\\@" + +/* Flags for cvt_text */ +#define CVT_TO_LC 01 /* Convert upper-case to lower-case */ +#define CVT_BS 02 /* Do backspace processing */ +#define CVT_CRLF 04 /* Remove CR after LF */ +#define CVT_ANSI 010 /* Remove ANSI escape sequences */ + +#include "funcs.h" + +/* Functions not included in funcs.h */ +void postoa(off_t, char *, size_t); +void inttoa(int, char *, size_t); diff --git a/bin/less/less.hlp b/bin/less/less.hlp new file mode 100644 index 0000000000..2bc53df374 --- /dev/null +++ b/bin/less/less.hlp @@ -0,0 +1,229 @@ + + SSUUMMMMAARRYY OOFF CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Left one half screen width (or _N positions). + ESC-( LeftArrow * Right one half screen width (or _N positions). + F Forward forever; like "tail -f". + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ESC-u Undo (toggle) search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines + --------------------------------------------------- + A search pattern may be preceded by one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_> + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current position with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D [_x_n_._n] . --color=_x_n_._n + Set screen colors. (MS-DOS only) + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] + Use a lesskey file. + -K --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n -N .... --line-numbers --LINE-NUMBERS + Don't use line numbers. + -o [_f_i_l_e] . --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t [_t_a_g] .. --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Horizontal scroll amount (0 = one half screen width) + ........ --no-keypad + Don't send termcap keypad init/deinit strings. + ........ --follow-name + The F command changes files if the input file is renamed. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ESC-l Move cursor right one character. + LeftArrow ESC-h Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b Move cursor left one word. + HOME ESC-0 Move cursor to start of line. + END ESC-$ Move cursor to end of line. + BACKSPACE Delete char to left of cursor. + DELETE ESC-x Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE Delete word to left of cursor. + ctrl-DELETE ESC-DELETE ESC-X Delete word under cursor. + ctrl-U ESC (MS-DOS only) Delete entire line. + UpArrow ESC-k Retrieve previous command line. + DownArrow ESC-j Retrieve next command line. + TAB Complete filename & cycle. + SHIFT-TAB ESC-TAB Complete filename & reverse cycle. + ctrl-L Complete filename, list all. + diff --git a/bin/less/less/Makefile b/bin/less/less/Makefile new file mode 100644 index 0000000000..38530d4663 --- /dev/null +++ b/bin/less/less/Makefile @@ -0,0 +1,32 @@ +# $OpenBSD: Makefile,v 1.6 2015/11/23 09:24:48 nicm Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= less +SRCS= main.c screen.c brac.c ch.c charset.c cmdbuf.c command.c cvt.c \ + decode.c edit.c filename.c forwback.c ifile.c input.c jump.c \ + line.c linenum.c lsystem.c mark.c optfunc.c option.c opttbl.c \ + os.c output.c pattern.c position.c prompt.c search.c signal.c \ + tags.c ttyin.c version.c + +LDADD= -lcurses +DPADD= ${LIBCURSES} + +LINKS= ${BINDIR}/less ${BINDIR}/more + +MAN= less.1 more.1 +BINDIR= /usr/bin + +HELPDIR=${SHAREDIR}/misc + +CFLAGS+=-I${.CURDIR}/.. -DSYSDIR=\"/etc\" -DHELPDIR=\"${HELPDIR}\" + +beforeinstall: + ${INSTALL} ${INSTALL_COPY} -o ${SHAREOWN} -g ${SHAREGRP} \ + -m ${SHAREMODE} ${.CURDIR}/../less.hlp \ + ${DESTDIR}${HELPDIR}/less.help + ${INSTALL} ${INSTALL_COPY} -o ${SHAREOWN} -g ${SHAREGRP} \ + -m ${SHAREMODE} ${.CURDIR}/../more.hlp \ + ${DESTDIR}${HELPDIR}/more.help + +.include diff --git a/bin/less/less/less.1 b/bin/less/less/less.1 new file mode 100644 index 0000000000..0a220ad570 --- /dev/null +++ b/bin/less/less/less.1 @@ -0,0 +1,1913 @@ +.\" $OpenBSD: less.1,v 1.52 2016/10/24 13:46:58 schwarze Exp $ +.\" +.\" Copyright (C) 1984-2012 Mark Nudelman +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice in the documentation and/or other materials provided with +.\" the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY +.\" EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +.\" OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd $Mdocdate: October 24 2016 $ +.Dt LESS 1 +.Os +.Sh NAME +.Nm less +.Nd view files +.Sh SYNOPSIS +.Nm less +.Op Fl #?~AaBCcdEeFfGgIiJKLMmNnQqRrSsUuVWwX +.Op Fl b Ar n +.Op Fl h Ar n +.Op Fl j Ar n +.Op Fl k Ar keyfile +.Op Fl O | o Ar logfile +.Op Fl P Ar prompt +.Op Fl p Ar pattern +.Op Fl T Ar tagsfile +.Op Fl t Ar tag +.Op Fl x Ar n , Ns Ar ... +.Op Fl y Ar n +.Op Fl z Ar n +.Op Ar +.Sh DESCRIPTION +.Nm +is a program similar to the traditional +.Xr more 1 , +but with many more features. +It displays text one screenful at a time. +After showing each screenful, it prompts the user for a command. +When showing the last line of a file, +.Nm +displays a prompt indicating end of file and the name of the next file +to examine, if any. +It then waits for input from the user. +.Pp +Commands are based on both traditional +.Xr more 1 +and +.Xr vi 1 . +Commands may be preceded by a decimal number, +called +.Ar N +in the descriptions below. +The number is used by some commands, as indicated. +.Pp +This version of +.Nm +also acts as +.Xr more 1 +if it is called as +.Nm more , +or if the +.Ev LESS_IS_MORE +environment variable is set. +The main differences between the two are summarized in the +.Sx COMPATIBILITY WITH MORE +section, below. +.Pp +A long option name may be abbreviated as long as the abbreviation is +unambiguous. +Such option names need only have their first letter capitalized; +the remainder of the name may be in either case. +For example, +.Fl -Quit-at-eof +is equivalent to +.Fl -QUIT-AT-EOF . +.Pp +The options are as follows: +.Bl -tag -width XXXX +.It Fl \&? | -help +This option displays a summary of the commands accepted by +.Nm +(the same as the +.Ic h +command). +(Depending on how your shell interprets the question mark, +it may be necessary to quote the question mark, thus: +.Ql Fl Ns \e? . ) +.It Fl A | -SEARCH-SKIP-SCREEN +Causes all forward searches (not just non-repeated searches) +to start just after the target line, and all backward searches +to start just before the target line. +Thus, forward searches will skip part of the displayed screen +(from the first line up to and including the target line). +Similarly backwards searches will skip the displayed screen +from the last line up to and including the target line. +This was the default behavior in +.Nm +versions prior to 441. +.It Fl a | -search-skip-screen +By default, forward searches start at the top of the displayed screen +and backwards searches start at the bottom of the displayed screen +(except for repeated searches invoked by the +.Ic n +or +.Ic N +commands, +which start after or before the +.Dq target +line respectively; see the +.Fl j +option for more about the target line). +The +.Fl a +option causes forward searches to instead start at the bottom of the screen +and backward searches to start at the top of the screen, +thus skipping all lines displayed on the screen. +.It Fl B | -auto-buffers +By default, when data is read from a pipe, +buffers are allocated automatically as needed. +If a large amount of data is read from the pipe, this can cause +a large amount of memory to be allocated. +The +.Fl B +option disables this automatic allocation of buffers for pipes, +so that only 64K (or the amount of space specified by the +.Fl b +option) is used for the pipe. +.Sy Warning : +use of +.Fl B +can result in erroneous display, since only the +most recently viewed part of the piped data is kept in memory; +any earlier data is lost. +.It Fl b Ar n | Fl -buffers Ns = Ns Ar n +Specifies the amount of buffer space +.Nm +will use for each file, in units of kilobytes (1024 bytes). +By default 64K of buffer space is used for each file +(unless the file is a pipe; see the +.Fl B +option). +The +.Fl b +option specifies instead that +.Ar n +kilobytes of buffer space should be used for each file. +If +.Ar n +is -1, buffer space is unlimited; that is, +the entire file can be read into memory. +.It Fl C | -CLEAR-SCREEN +Same as +.Fl c , +for compatibility with older versions of +.Nm . +.It Fl c | -clear-screen +Causes full screen repaints to be painted from the bottom of the screen. +By default, full screen repaints are done from the top line down +to avoid the position of the display being moved +when using interactive commands. +.It Fl d | -dumb +The +.Fl d +option suppresses the error message normally displayed if the terminal is dumb; +that is, if the terminal lacks some important capability, +such as the ability to clear the screen or scroll backward. +The +.Fl d +option does not otherwise change the behavior of +.Nm +on a dumb terminal. +.It Fl E | -QUIT-AT-EOF +Causes +.Nm +to automatically exit the first time it reaches end-of-file. +.It Fl e | -quit-at-eof +Causes +.Nm +to automatically exit the second time it reaches end-of-file. +By default, the only way to exit +.Nm +is via the +.Ic q +command. +.It Fl F | -quit-if-one-screen +Causes +.Nm +to automatically exit if the entire file can be displayed on the first screen. +.It Fl f | -force +Forces non-regular files to be opened. +(A non-regular file is a directory or a device special file.) +Also suppresses the warning message when a binary file is opened. +By default, +.Nm +will refuse to open non-regular files. +.It Fl G | -HILITE-SEARCH +The +.Fl G +option suppresses all highlighting of strings found by search commands. +.It Fl g | -hilite-search +Normally, +.Nm +will highlight all strings which match the last search command. +The +.Fl g +option changes this behavior to highlight only the particular string +which was found by the last search command. +This can cause +.Nm +to run somewhat faster than the default. +.It Fl h Ar n | Fl -max-back-scroll Ns = Ns Ar n +Specifies a maximum number of lines to scroll backward. +If it is necessary to scroll backward more than n lines, +the screen is repainted in a forward direction instead. +(If the terminal does not have the ability to scroll backward, +.Sq Fl h Ns 0 +is implied.) +.It Fl I | -IGNORE-CASE +Like +.Fl i , +but searches ignore case even if the pattern contains uppercase +letters. +.It Fl i | -ignore-case +Causes searches to ignore case; that is, +uppercase and lowercase are considered identical. +This option is ignored if any uppercase letters appear in the search pattern; +in other words, +if a pattern contains uppercase letters, then that search does not ignore case. +.It Fl J | -status-column +Displays a status column at the left edge of the screen. +The status column shows the lines that matched the current search. +The status column is also used if the +.Fl w +or +.Fl W +option is in effect. +.It Fl j Ar n | Fl -jump-target Ns = Ns Ar n +Specifies a line on the screen where the +.Dq target +line is to be positioned. +The target line is the line specified by any command to +search for a pattern, jump to a line number, +jump to a file percentage or jump to a tag. +The screen line may be specified by a number: the top line on the screen +is 1, the next is 2, and so on. +The number may be negative to specify a line relative to the bottom +of the screen: the bottom line on the screen is -1, the second +to the bottom is -2, and so on. +Alternately, the screen line may be specified as a fraction of the height +of the screen, starting with a decimal point: .5 is in the middle of the +screen, .3 is three tenths down from the first line, and so on. +If the line is specified as a fraction, the actual line number +is recalculated if the terminal window is resized, so that the +target line remains at the specified fraction of the screen height. +If any form of the +.Fl j +option is used, +forward searches begin at the line immediately after the target line, +and backward searches begin at the target line, +unless changed by +.Fl a +or +.Fl A . +For example, if +.Sq Fl j Ns 4 +is used, the target line is the fourth line on the screen, +so forward searches begin at the fifth line on the screen. +.It Fl K | -quit-on-intr +Causes +.Nm +to exit immediately (with status 2) when an interrupt character (usually +.Ic ^C ) +is typed. +Normally, an interrupt character causes +.Nm +to stop whatever it is doing and return to its command prompt. +Note that use of this option makes it impossible to return to the +command prompt from the +.Ic F +command. +.It Fl k Ar keyfile | Fl -lesskey-file Ns = Ns Ar keyfile +Causes +.Nm +to open and interpret the named file as a +.Xr lesskey 1 +file. +Multiple +.Fl k +options may be specified. +If the +.Ev LESSKEY +or +.Ev LESSKEY_SYSTEM +environment variable is set, or if a lesskey file is found in a standard place +(see +.Sx KEY BINDINGS ) , +it is also used as a lesskey file. +.It Fl L | -no-lessopen +Ignore the +.Ev LESSOPEN +environment variable (see the +.Sx INPUT PREPROCESSOR +section below). +This option can be set from within +.Nm less , +but it will apply only to files opened subsequently, not to the +file which is currently open. +.It Fl M | -LONG-PROMPT +Causes +.Nm +to prompt even more verbosely than +.Xr more 1 . +.It Fl m | -long-prompt +Causes +.Nm +to prompt verbosely, like +.Xr more 1 , +with the percent into the file. +By default, +.Nm +prompts with a colon. +.It Fl N | -LINE-NUMBERS +Causes a line number to be displayed at the beginning of each line in the +display. +.It Fl n | -line-numbers +Suppresses line numbers. +The default (to use line numbers) may cause +.Nm +to run more slowly in some cases, especially with a very large input file. +Suppressing line numbers with the +.Fl n +option will avoid this problem. +Using line numbers means: the line number will be displayed in the verbose +prompt and in the +.Ic = +command, and the +.Ic v +command will pass the current line +number to the editor (see also the discussion of +.Ev LESSEDIT +in +.Sx PROMPTS +below). +.It Fl O Ar logfile | Fl -LOG-FILE Ns = Ns Ar logfile +The +.Fl O +option is like +.Fl o , +but it will overwrite an existing file without asking for confirmation. +.Pp +If no log file has been specified, +the +.Fl o +and +.Fl O +options can be used from within +.Nm +to specify a log file. +Without a file name, they will simply report the name of the log file. +The +.Ic s +command is equivalent to specifying +.Fl o +from within +.Nm . +.It Fl o Ar logfile | Fl -log-file Ns = Ns Ar logfile +Causes +.Nm +to copy its input to the named file as it is being viewed. +This applies only when the input file is a pipe, not an ordinary file. +If the file already exists, +.Nm +will ask for confirmation before overwriting it. +.It Fl P Ar prompt | Fl -prompt Ns = Ns Ar prompt +Provides a way to tailor the three prompt styles to your own preference. +This option would normally be put in the +.Ev LESS +environment variable, rather than being typed in with each +.Nm +command. +Such an option must either be the last option in the +.Ev LESS +variable, or be terminated by a dollar sign. +.Bl -item +.It +.Fl Ps Ar string +changes the default (short) prompt to +.Ar string . +.It +.Fl Pm +changes the medium +.Pq Fl m +prompt. +.It +.Fl PM +changes the long +.Pq Fl M +prompt. +.It +.Fl Ph +changes the prompt for the help screen. +.It +.Fl P= +changes the message printed by the +.Ic = +command. +.It +.Fl Pw +changes the message printed while waiting for data (in the +.Ic F +command). +.El +All prompt strings consist of a sequence of letters and special escape +sequences. +See the section on +.Sx PROMPTS +for more details. +.It Fl p Ar pattern | Fl -pattern Ns = Ns Ar pattern +The +.Fl p +option on the command line is equivalent to specifying +.Cm +/ Ns Ar pattern ; +that is, it tells +.Nm +to start at the first occurrence of pattern in the file. +.It Fl Q | -QUIET | -SILENT +Causes totally quiet operation: the terminal bell is never rung. +.It Fl q | -quiet | -silent +Causes moderately quiet operation: +the terminal bell is not rung if an attempt is made to scroll past the end +of the file or before the beginning of the file. +If the terminal has a visual bell, it is used instead. +The bell will be rung on certain other errors, +such as typing an invalid character. +The default is to ring the terminal bell in all such cases. +.It Fl R | -RAW-CONTROL-CHARS +Like +.Fl r , +but only ANSI color escape sequences are output in raw form. +Unlike +.Fl r , +the screen appearance is maintained correctly in most cases. +ANSI color escape sequences are sequences of the form: +.Pp +.Dl ESC \&[ ... m +.Pp +where the +.Dq ... +is zero or more color specification characters. +For the purpose of keeping track of screen appearance, +ANSI color escape sequences are assumed to not move the cursor. +You can make +.Nm +think that characters other than +.Sq m +can end ANSI color escape sequences by setting the environment variable +.Ev LESSANSIENDCHARS +to the list of characters which can end a color escape sequence. +And you can make +.Nm +think that characters other than the standard ones may appear between +the +.Cm ESC +and the +.Cm m +by setting the environment variable +.Ev LESSANSIMIDCHARS +to the list of characters which can appear. +.It Fl r | -raw-control-chars +Causes raw control characters to be displayed. +The default is to display control characters using the caret notation; +for example, a control-A (octal 001) is displayed as +.Sq ^A . +.Sy Warning : +when the +.Fl r +option is used, +.Nm +cannot keep track of the actual appearance of the screen +(since this depends on how the screen responds to +each type of control character). +Thus, various display problems may result, +such as long lines being split in the wrong place. +.It Fl S | -chop-long-lines +Causes lines longer than the screen width to be +chopped (truncated) rather than wrapped. +That is, the portion of a long line that does not fit in +the screen width is not shown. +The default is to wrap long lines; that is, display the remainder +on the next line. +.It Fl s | -squeeze-blank-lines +Causes consecutive blank lines to be squeezed into a single blank line. +.It Fl T Ar tagsfile | Fl -tag-file Ns = Ns Ar tagsfile +Specifies a tags file to be used instead of +.Pa tags . +.It Xo +.Fl t Ar tag | +.Fl -tag Ns = Ns Ar tag +.Xc +The +.Fl t +option, followed immediately by a +.Ar tag , +will edit the file containing that tag. +For this to work, tag information must be available; +for example, there may be a file in the current directory called +.Pa tags , +which was previously built by +.Xr ctags 1 +or an equivalent command. +The +.Fl t +option may also be specified from within +.Nm +(using the +.Ic - +command) as a way of examining a new file. +The command +.Ic :t +is equivalent to specifying +.Fl t +from within +.Nm . +.It Fl U | -UNDERLINE-SPECIAL +Causes backspaces, tabs and carriage returns to be +treated as control characters; +that is, they are handled as specified by the +.Fl r +option. +.Pp +By default, if neither +.Fl u +nor +.Fl U +is given, backspaces which appear adjacent +to an underscore character are treated specially: +the underlined text is displayed +using the terminal's hardware underlining capability. +Also, backspaces which appear between two identical characters +are treated specially: +the overstruck text is printed +using the terminal's hardware boldface capability. +Other backspaces are deleted, along with the preceding character. +Carriage returns immediately followed by a newline are deleted. +Other carriage returns are handled as specified by the +.Fl r +option. +Text which is overstruck or underlined can be searched for +if neither +.Fl u +nor +.Fl U +is in effect. +.It Fl u | -underline-special +Causes backspaces and carriage returns to be treated as printable characters; +that is, they are sent to the terminal when they appear in the input. +.It Fl V | -version +Displays the version number of +.Nm . +.It Fl W | -HILITE-UNREAD +Like +.Fl w , +but temporarily highlights the first new line after any +forward movement command larger than one line. +.It Fl w | -hilite-unread +Temporarily highlights the first new line after a forward movement +of a full page. +The first new line is the line immediately following the line previously +at the bottom of the screen. +Also highlights the target line after a +.Ic g +or +.Ic p +command. +The highlight is removed at the next command which causes movement. +The entire line is highlighted, unless the +.Fl J +option is in effect, +in which case only the status column is highlighted. +.It Fl X | -no-init +Disables sending the termcap initialization and deinitialization strings +to the terminal. +This is sometimes desirable if the deinitialization string does +something unnecessary, like clearing the screen. +.It Xo +.Fl x Ar n , Ns Ar ... | +.Fl -tabs Ns = Ns Ar n , Ns Ar ... +.Xc +Sets tab stops. +If only one +.Ar n +is specified, tab stops are set at multiples of +.Ar n . +If multiple values separated by commas are specified, tab stops are set at +those positions, and then continue with the same spacing as the last two. +For example, +.Sq Fl x Ns 9,17 +will set tabs at positions 9, 17, 25, 33, etc. +The default for +.Ar n +is 8. +.It Fl y Ar n | Fl -max-forw-scroll Ns = Ns Ar n +Specifies a maximum number of lines to scroll forward. +If it is necessary to scroll forward more than n lines, +the screen is repainted instead. +The +.Fl c +or +.Fl C +option may be used to repaint from the top of the screen if desired. +By default, any forward movement causes scrolling. +.It Fl z Ar n | Fl -window Ns = Ns Ar n +Changes the default scrolling window size to +.Ar n +lines. +The default is one screenful. +The +.Ic z +and +.Ic w +commands can also be used to change the window size. +The +.Cm z +may be omitted for compatibility with some versions of +.Xr more 1 . +If the number +.Ar n +is negative, it indicates +.Ar n +lines less than the current screen size. +For example, if the screen is 24 lines, +.Fl z Ns -4 +sets the scrolling window to 20 lines. +If the screen is resized to 40 lines, +the scrolling window automatically changes to 36 lines. +.It Fl -follow-name +Normally, if the input file is renamed while an +.Ic F +command is executing, +.Nm +will continue to display the contents of the original file despite +its name change. +If +.Fl -follow-name +is specified, during an +.Ic F +command +.Nm +will periodically attempt to reopen the file by name. +If the reopen succeeds and the file is a different file from the original +(which means that a new file has been created +with the same name as the original (now renamed) file), +.Nm +will display the contents of that new file. +.It Fl -no-keypad +Disables sending the keypad initialization and deinitialization strings +to the terminal. +This is sometimes useful if the keypad strings make the numeric +keypad behave in an undesirable manner. +.It Fl -use-backslash +This option changes the interpretations of options which follow this one. +After the +.Fl -use-backslash +option, any backslash in an option string is +removed and the following character is taken literally. +This allows a dollar sign to be included in option strings. +.It Fl \&" Ar cc | Fl -quotes Ns = Ns Ar cc +Changes the filename quoting character. +This may be necessary if you are trying to name a file +which contains both spaces and quote characters. +If +.Ar cc +is a single character, this changes the quote character to that character. +Filenames containing a space should then be surrounded by that character +rather than by double quotes. +If +.Ar cc +consists of two characters, this changes the open quote to the first character, +and the close quote to the second character. +Filenames containing a space should then be preceded by the open quote +character and followed by the close quote character. +Note that even after the quote characters are changed, this option +remains +.Fl \&" +(a dash followed by a double quote). +.It Fl ~ | -tilde +Normally lines after end of file are displayed as a single tilde (~). +This option causes lines after end of file to be displayed as blank lines. +.It Fl # | -shift +Specifies the default number of positions to scroll horizontally +in the RIGHTARROW and LEFTARROW commands. +If the number specified is zero, it sets the default number of +positions to one half of the screen width. +Alternately, the number may be specified as a fraction of the width +of the screen, starting with a decimal point: .5 is half of the +screen width, .3 is three tenths of the screen width, and so on. +If the number is specified as a fraction, the actual number of +scroll positions is recalculated if the terminal window is resized, +so that the actual scroll remains at the specified fraction +of the screen width. +.It Fl - +A command line argument of +.Fl - +marks the end of option arguments. +Any arguments following this are interpreted as filenames. +This can be useful when viewing a file whose name begins with a +.Sq - +or +.Sq + . +.It Cm + +If a command line option begins with +, +the remainder of that option is taken to be an initial command to +.Nm . +For example, +.Cm +G +tells +.Nm +to start at the end of the file rather than the beginning, +and +.Cm +/xyz +tells it to start at the first occurrence of +.Dq xyz +in the file. +As a special case, +.Cm + Ns Ar number +acts like +.Cm + Ns Ar number Ns g ; +that is, it starts the display at the specified line number +(however, see the caveat under the +.Ic g +command below). +If the option starts with +.Cm ++ , +the initial command applies to every file being viewed, not just the first one. +The +.Cm + +command described previously +may also be used to set (or change) an initial command for every file. +.El +.Sh COMMANDS +In the following descriptions, ^X means control-X. +ESC stands for the ESCAPE key; for example ESC-v means the +two character sequence "ESCAPE", then "v". +.Bl -tag -width XXXX +.It Ic h | H +Help: display a summary of these commands. +If you forget all the other commands, remember this one. +.It Ic SPACE | ^V | f | ^F +Scroll forward N lines, default one window (see option +.Fl z +above). +If N is more than the screen size, only the final screenful is displayed. +Warning: some systems use ^V as a special literalization character. +.It Ic z +Like SPACE, but if N is specified, it becomes the new window size. +.It Ic ESC-SPACE +Like SPACE, but scrolls a full screenful, even if it reaches +end-of-file in the process. +.It Ic ENTER | RETURN | ^N | e | ^E | j | ^J +Scroll forward N lines, default 1. +The entire N lines are displayed, even if N is more than the screen size. +.It Ic d | ^D +Scroll forward N lines, default one half of the screen size. +If N is specified, it becomes the new default for subsequent d and u commands. +.It Ic b | ^B | ESC-v +Scroll backward N lines, default one window (see option +.Fl z +above). +If N is more than the screen size, only the final screenful is displayed. +.It Ic w +Like ESC-v, but if N is specified, it becomes the new window size. +.It Ic y | ^Y | ^P | k | ^K +Scroll backward N lines, default 1. +The entire N lines are displayed, even if N is more than the screen size. +Warning: some systems use ^Y as a special job control character. +.It Ic u | ^U +Scroll backward N lines, default one half of the screen size. +If N is specified, it becomes the new default for subsequent d and u commands. +.It Ic ESC-) | RIGHTARROW +Scroll horizontally right N characters, default half the screen width +(see the +.Fl # +option). +If a number N is specified, it becomes the default for future +RIGHTARROW and LEFTARROW commands. +While the text is scrolled, it acts as though the +.Fl S +option (chop lines) were in effect. +.It Ic ESC-( | LEFTARROW +Scroll horizontally left N +characters, default half the screen width (see the +.Fl # +option). +If a number N is specified, it becomes the default for future +RIGHTARROW and LEFTARROW commands. +.It Ic r | ^R | ^L +Repaint the screen. +.It Ic R +Repaint the screen, discarding any buffered input. +Useful if the file is changing while it is being viewed. +.It Ic F +Scroll forward, and keep trying to read when the end of file is reached. +Normally this command would be used when already at the end of the file. +It is a way to monitor the tail of a file which is growing +while it is being viewed. +(The behavior is similar to the "tail -f" command.) +.It Ic ESC-F +Like F, but as soon as a line is found which matches +the last search pattern, the terminal bell is rung +and forward scrolling stops. +.It Ic g | < | ESC-< +Go to line N in the file, default 1 (beginning of file). +(Warning: this may be slow if N is large.) +.It Ic G | > | ESC-> +Go to line N in the file, default the end of the file. +(Warning: this may be slow if N is large, +or if N is not specified and standard input, rather than a file, +is being read.) +.It Ic p | % +Go to a position N percent into the file. +N should be between 0 and 100, and may contain a decimal point. +.It Ic P +Go to the line containing byte offset N in the file. +.It Ic { +If a left curly bracket appears in the top line displayed +on the screen, the { command will go to the matching right curly bracket. +The matching right curly bracket is positioned on the bottom +line of the screen. +If there is more than one left curly bracket on the top line, a number N +may be used to specify the N-th bracket on the line. +.It Ic } +If a right curly bracket appears in the bottom line displayed on the screen, +the } command will go to the matching left curly bracket. +The matching left curly bracket is positioned on the top +line of the screen. +If there is more than one right curly bracket on the top line, +a number N may be used to specify the N-th bracket on the line. +.It Ic \&( +Like {, but applies to parentheses rather than curly brackets. +.It Ic \&) +Like }, but applies to parentheses rather than curly brackets. +.It Ic \&[ +Like {, but applies to square brackets rather than curly brackets. +.It Ic \&] +Like }, but applies to square brackets rather than curly brackets. +.It Ic ESC-^F +Followed by two characters, acts like {, +but uses the two characters as open and close brackets, respectively. +For example, "ESC ^F < >" could be used to +go forward to the > which matches the < in the top displayed line. +.It Ic ESC-^B +Followed by two characters, acts like }, +but uses the two characters as open and close brackets, respectively. +For example, "ESC ^B < >" could be used to +go backward to the < which matches the > in the bottom displayed line. +.It Ic m +Followed by any lowercase letter, marks the current position with that letter. +.It Ic ' +(Single quote.) +Followed by any lowercase letter, returns to the position which +was previously marked with that letter. +Followed by another single quote, returns to the position at +which the last "large" movement command was executed. +Followed by a ^ or $, jumps to the beginning or end of the file respectively. +Marks are preserved when a new file is examined, +so the ' command can be used to switch between input files. +.It Ic ^X^X +Same as single quote. +.It Ic /pattern +Search forward in the file for the N-th line containing the pattern. +N defaults to 1. +The pattern is a regular expression, as recognized by +the regular expression library supplied by your system. +The search starts at the first line displayed +(but see the +.Fl a +and +.Fl j +options, which change this). +.Pp +Certain characters are special if entered at the beginning of the pattern; +they modify the type of search rather than become part of the pattern: +.Bl -tag -width Ds +.It Ic ^N | \&! +Search for lines which do NOT match the pattern. +.It Ic ^E | * +Search multiple files. +That is, if the search reaches the END of the current file +without finding a match, +the search continues in the next file in the command line list. +.It Ic ^F | @ +Begin the search at the first line of the FIRST file +in the command line list, +regardless of what is currently displayed on the screen +or the settings of the +.Fl a +or +.Fl j +options. +.It Ic ^K +Highlight any text which matches the pattern on the current screen, +but don't move to the first match (KEEP current position). +.It Ic ^R +Don't interpret regular expression metacharacters; +that is, do a simple textual comparison. +.El +.It Ic ?pattern +Search backward in the file for the N-th line containing the pattern. +The search starts at the line immediately before the top line displayed. +.Pp +Certain characters are special, as in the / command: +.Bl -tag -width Ds +.It Ic ^N | \&! +Search for lines which do NOT match the pattern. +.It Ic ^E | * +Search multiple files. +That is, if the search reaches the beginning of the current file +without finding a match, +the search continues in the previous file in the command line list. +.It Ic ^F | @ +Begin the search at the last line of the last file +in the command line list, +regardless of what is currently displayed on the screen +or the settings of the +.Fl a +or +.Fl j +options. +.It Ic ^K +As in forward searches. +.It Ic ^R +As in forward searches. +.El +.It Ic ESC-/pattern +Same as "/*". +.It Ic ESC-?pattern +Same as "?*". +.It Ic n +Repeat previous search, for N-th line containing the last pattern. +If the previous search was modified by ^N, the search is made for the +N-th line NOT containing the pattern. +If the previous search was modified by ^E, the search continues +in the next (or previous) file if not satisfied in the current file. +If the previous search was modified by ^R, the search is done +without using regular expressions. +There is no effect if the previous search was modified by ^F or ^K. +.It Ic N +Repeat previous search, but in the reverse direction. +.It Ic ESC-n +Repeat previous search, but crossing file boundaries. +The effect is as if the previous search were modified by *. +.It Ic ESC-N +Repeat previous search, but in the reverse direction +and crossing file boundaries. +.It Ic ESC-u +Undo search highlighting. +Turn off highlighting of strings matching the current search pattern. +If highlighting is already off because of a previous ESC-u command, +turn highlighting back on. +Any search command will also turn highlighting back on. +(Highlighting can also be disabled by toggling the +.Fl G +option; +in that case search commands do not turn highlighting back on.) +.It Ic &pattern +Display only lines which match the pattern; +lines which do not match the pattern are not displayed. +If pattern is empty (if you type & immediately followed by ENTER), +any filtering is turned off, and all lines are displayed. +While filtering is in effect, an ampersand is displayed at the +beginning of the prompt, +as a reminder that some lines in the file may be hidden. +.Pp +Certain characters are special as in the / command: +.Bl -tag -width Ds +.It Ic ^N | ! +Display only lines which do NOT match the pattern. +.It Ic ^R +Don't interpret regular expression metacharacters; +that is, do a simple textual comparison. +.El +.It Ic :e Op Ar filename +Examine a new file. +If the filename is missing, the "current" file (see the :n and :p commands +below) from the list of files in the command line is re-examined. +A percent sign (%) in the filename is replaced by the name of the +current file. +A pound sign (#) is replaced by the name of the previously examined file. +However, two consecutive percent signs are simply +replaced with a single percent sign. +This allows you to enter a filename that contains a percent sign +in the name. +Similarly, two consecutive pound signs are replaced with a single pound sign. +The filename is inserted into the command line list of files +so that it can be seen by subsequent :n and :p commands. +If the filename consists of several files, they are all inserted into +the list of files and the first one is examined. +If the filename contains one or more spaces, +the entire filename should be enclosed in double quotes +(also see the +.Fl \&" +option). +.It Ic ^X^V | E +Same as :e. +Warning: some systems use ^V as a special literalization character. +On such systems, you may not be able to use ^V. +.It Ic :n +Examine the next file (from the list of files given in the command line). +If a number N is specified, the N-th next file is examined. +.It Ic :p +Examine the previous file in the command line list. +If a number N is specified, the N-th previous file is examined. +.It Ic :t +Go to the specified tag. +.It Ic :x +Examine the first file in the command line list. +If a number N is specified, the N-th file in the list is examined. +.It Ic :d +Remove the current file from the list of files. +.It Ic t +Go to the next tag, if there were more than one matches for the current tag. +See the +.Fl t +option for more details about tags. +.It Ic T +Go to the previous tag, if there were more than one matches for the current tag. +.It Ic = | ^G | :f +Prints some information about the file being viewed, including its name +and the line number and byte offset of the bottom line being displayed. +If possible, it also prints the length of the file, +the number of lines in the file +and the percent of the file above the last displayed line. +.It Ic \- +Followed by one of the command line option letters (see +.Sx DESCRIPTION +above), +this will change the setting of that option +and print a message describing the new setting. +If a ^P (CONTROL-P) is entered immediately after the dash, +the setting of the option is changed but no message is printed. +If the option letter has a numeric value (such as +.Fl b +or +.Fl h ) , +or a string value (such as +.Fl P +or +.Fl t ) , +a new value may be entered after the option letter. +If no new value is entered, a message describing +the current setting is printed and nothing is changed. +.It Ic \-\- +Like the \- command, but takes a long option name (see +.Sx DESCRIPTION +above) +rather than a single option letter. +You must press ENTER or RETURN after typing the option name. +A ^P immediately after the second dash suppresses printing of a +message describing the new setting, as in the \- command. +.It Ic \-+ +Followed by one of the command line option letters this will reset the +option to its default setting and print a message describing the new setting. +(The "\-+X" command does the same thing as +.Sq Fl + Ns X +on the command line.) +This does not work for string-valued options. +.It Ic \-\-+ +Like the \-+ command, but takes a long option name +rather than a single option letter. +.It Ic \-! +Followed by one of the command line option letters, this will reset the +option to the "opposite" of its default setting and print a message +describing the new setting. +This does not work for numeric or string-valued options. +.It Ic \-\-! +Like the \-! command, but takes a long option name +rather than a single option letter. +.It Ic _ +(Underscore.) +Followed by one of the command line option letters, +this will print a message describing the current setting of that option. +The setting of the option is not changed. +.It Ic __ +(Double underscore.) +Like the _ (underscore) command, but takes a long option name +rather than a single option letter. +You must press ENTER or RETURN after typing the option name. +.It Ic +cmd +Causes the specified cmd to be executed each time a new file is examined. +For example, +G causes +.Nm +to initially display each file starting at the end rather than the beginning. +.It Ic V +Prints the version number of +.Nm +being run. +.It Ic q | Q | :q | :Q | ZZ +Exits +.Nm less . +.El +.Pp +The following +four +commands may or may not be valid, depending on your particular installation. +.Bl -tag -width XXXX +.It Ic v +Invokes an editor to edit the current file being viewed. +The editor is taken from the environment variable +.Ev VISUAL , +if defined, +or +.Ev EDITOR +if +.Ev VISUAL +is not defined, +or defaults to "vi" if neither +.Ev VISUAL +nor +.Ev EDITOR +is defined. +See also the discussion of LESSEDIT under the section on +.Sx PROMPTS +below. +.It Ic | Ar shell-command + represents any mark letter. +Pipes a section of the input file to the given shell command. +The section of the file to be piped is between the first line on +the current screen and the position marked by the letter. + may also be ^ or $ to indicate beginning or end of file respectively. +If is . or newline, the current screen is piped. +.It Ic s Ar filename +Save the input to a file. +This only works if the input is a pipe, not an ordinary file. +.El +.Sh LINE EDITING +When entering command line at the bottom of the screen +(for example, a filename for the :e command, +or the pattern for a search command), +certain keys can be used to manipulate the command line. +Most commands have an alternate form in [ brackets ] which can be used if +a key does not exist on a particular keyboard. +Any of these special keys may be entered literally by preceding +it with the "literal" character, either ^V or ^A. +A backslash itself may also be entered literally by entering two backslashes. +.Bl -tag -width Ds +.It LEFTARROW [ ESC-h ] +Move the cursor one space to the left. +.It RIGHTARROW [ ESC-l ] +Move the cursor one space to the right. +.It ^LEFTARROW [ ESC-b or ESC-LEFTARROW ] +(That is, CONTROL and LEFTARROW simultaneously.) +Move the cursor one word to the left. +.It ^RIGHTARROW [ ESC-w or ESC-RIGHTARROW ] +(That is, CONTROL and RIGHTARROW simultaneously.) +Move the cursor one word to the right. +.It HOME [ ESC-0 ] +Move the cursor to the beginning of the line. +.It END [ ESC-$ ] +Move the cursor to the end of the line. +.It BACKSPACE +Delete the character to the left of the cursor, +or cancel the command if the command line is empty. +.It DELETE or [ ESC-x ] +Delete the character under the cursor. +.It ^BACKSPACE [ ESC-BACKSPACE ] +(That is, CONTROL and BACKSPACE simultaneously.) +Delete the word to the left of the cursor. +.It ^DELETE [ ESC-X or ESC-DELETE ] +(That is, CONTROL and DELETE simultaneously.) +Delete the word under the cursor. +.It UPARROW [ ESC-k ] +Retrieve the previous command line. +If you first enter some text and then press UPARROW, +it will retrieve the previous command which begins with that text. +.It DOWNARROW [ ESC-j ] +Retrieve the next command line. +If you first enter some text and then press DOWNARROW, +it will retrieve the next command which begins with that text. +.It TAB +Complete the partial filename to the left of the cursor. +If it matches more than one filename, the first match +is entered into the command line. +Repeated TABs will cycle through the other matching filenames. +If the completed filename is a directory, a "/" is appended to the filename. +The environment variable +.Ev LESSSEPARATOR +can be used to specify a different character to append to a directory name. +.It BACKTAB [ ESC-TAB ] +Like TAB, but cycles in the reverse direction through the matching filenames. +.It ^L +Complete the partial filename to the left of the cursor. +If it matches more than one filename, all matches are entered into +the command line (if they fit). +.It ^U +Delete the entire command line, +or cancel the command if the command line is empty. +If you have changed your line-kill character to something +other than ^U, that character is used instead of ^U. +.It "^G" +Delete the entire command line and return to the main prompt. +.El +.Sh KEY BINDINGS +You may define your own +.Nm +commands by using the program +.Xr lesskey 1 +to create a lesskey file. +This file specifies a set of command keys and an action +associated with each key. +You may also use lesskey +to change the line-editing keys (see +.Sx LINE EDITING ) , +and to set environment variables. +If the environment variable +.Ev LESSKEY +is set, +.Nm +uses that as the name of the lesskey file. +Otherwise, +.Nm +looks for a lesskey file called "$HOME/.less". +See the +.Xr lesskey 1 +manual page for more details. +.Pp +A system-wide lesskey file may also be set up to provide key bindings. +If a key is defined in both a local lesskey file and in the +system-wide file, key bindings in the local file take precedence over +those in the system-wide file. +If the environment variable +.Ev LESSKEY_SYSTEM +is set, +.Nm +uses that as the name of the system-wide lesskey file. +Otherwise, +.Nm +looks in a standard place for the system-wide lesskey file: +On +.Ox , +the system-wide lesskey file is +.Pa /etc/sysless . +.Sh INPUT PREPROCESSOR +You may define an "input preprocessor" for +.Nm less . +Before +.Nm less +opens a file, it first gives your input preprocessor a chance to modify the +way the contents of the file are displayed. +An input preprocessor is simply an executable program (or shell script), +which writes the contents of the file to a different file, +called the replacement file. +The contents of the replacement file are then displayed +in place of the contents of the original file. +However, it will appear to the user as if the original file is opened; +that is, +.Nm less +will display the original filename as the name of the current file. +.Pp +An input preprocessor receives one command line argument, the original filename, +as entered by the user. +It should create the replacement file, and when finished +print the name of the replacement file to its standard output. +If the input preprocessor does not output a replacement filename, +.Nm +uses the original file, as normal. +The input preprocessor is not called when viewing standard input. +To set up an input preprocessor, set the +.Ev LESSOPEN +environment variable to a command line which will invoke your +input preprocessor. +This command line should include one occurrence of the string "%s", +which will be replaced by the filename +when the input preprocessor command is invoked. +.Pp +When +.Nm +closes a file opened in such a way, it will call another program, +called the input postprocessor, +which may perform any desired clean-up action (such as deleting the +replacement file created by +.Ev LESSOPEN ) . +This program receives two command line arguments, the original filename +as entered by the user, and the name of the replacement file. +To set up an input postprocessor, set the +.Ev LESSCLOSE +environment variable to a command line which will invoke your +input postprocessor. +It may include two occurrences of the string "%s"; +the first is replaced with the original name of the file and the second +with the name of the replacement file, which was output by +.Ev LESSOPEN . +.Pp +For example, these two scripts will allow you +to keep files in compressed format, but still let +.Nm +view them directly: +.Pp +lessopen.sh: +.Bd -literal -offset indent +#! /bin/sh +case "$1" in +*.Z) uncompress -c $1 >/tmp/less.$$ 2>/dev/null + if [ -s /tmp/less.$$ ]; then + echo /tmp/less.$$ + else + rm -f /tmp/less.$$ + fi + ;; +esac +.Ed +.Pp +lessclose.sh: +.Bd -literal -offset indent +#! /bin/sh +rm $2 +.Ed +.Pp +To use these scripts, put them both where they can be executed and +set LESSOPEN="lessopen.sh\ %s", and LESSCLOSE="lessclose.sh\ %s\ %s". +More complex LESSOPEN and LESSCLOSE scripts may be written +to accept other types of compressed files, and so on. +.Pp +It is also possible to set up an input preprocessor to +pipe the file data directly to +.Nm less , +rather than putting the data into a replacement file. +This avoids the need to decompress the entire file before starting to view it. +An input preprocessor that works this way is called an input pipe. +An input pipe, instead of writing the name of a replacement file on +its standard output, +writes the entire contents of the replacement file on its standard output. +If the input pipe does not write any characters on its standard output, +then there is no replacement file and +.Nm +uses the original file, as normal. +To use an input pipe, make the first character in the +.Ev LESSOPEN +environment variable a vertical bar (|) to signify that the +input preprocessor is an input pipe. +.Pp +For example, this script will work like the previous example scripts: +.Pp +lesspipe.sh: +.Bd -literal -offset indent +#! /bin/sh +case "$1" in +*.Z) uncompress -c $1 2>/dev/null +*) exit 1 + ;; +esac +exit $? +.Ed +.Pp +To use this script, put it where it can be executed and set +LESSOPEN="|lesspipe.sh %s". +.Pp +Note that a preprocessor cannot output an empty file, since that +is interpreted as meaning there is no replacement, and +the original file is used. +To avoid this, if +.Ev LESSOPEN +starts with two vertical bars, +the exit status of the script becomes meaningful. +If the exit status is zero, the output is considered to be +replacement text, even if it empty. +If the exit status is nonzero, any output is ignored and the +original file is used. +For compatibility with previous versions of +.Nm less , +if +.Ev LESSOPEN +starts with only one vertical bar, the exit status +of the preprocessor is ignored. +.Pp +When an input pipe is used, a LESSCLOSE postprocessor can be used, +but it is usually not necessary since there is no replacement file to clean up. +In this case, the replacement file name passed to the LESSCLOSE +postprocessor is "-". +.Pp +For compatibility with previous versions of +.Nm less , +the input preprocessor or pipe is not used if +.Nm +is viewing standard input. +However, if the first character of LESSOPEN is a dash (-), +the input preprocessor is used on standard input as well as other files. +In this case, the dash is not considered to be part of +the preprocessor command. +If standard input is being viewed, the input preprocessor is passed +a file name consisting of a single dash. +Similarly, if the first two characters of LESSOPEN are vertical bar and dash +(|-) or two vertical bars and a dash (||-), +the input pipe is used on standard input as well as other files. +Again, in this case the dash is not considered to be part of +the input pipe command. +.Sh NATIONAL CHARACTER SETS +There are three types of characters in the input file: +.Bl -tag -width "control characters" +.It normal characters +Can be displayed directly to the screen. +.It control characters +Should not be displayed directly, but are expected to be found +in ordinary text files (such as backspace and tab). +.It binary characters +Should not be displayed directly and are not expected to be found +in text files. +.El +.Pp +A "character set" is simply a description of which characters are to +be considered normal, control, and binary. +.Nm +will determine the character set to use from the environment (see +.Xr locale 1 ) . +.Pp +Control and binary characters are displayed in standout (reverse video). +Each such character is displayed in caret notation if possible +(e.g. ^A for control-A). +Caret notation is used only if inverting the 0100 bit results in a +normal printable character. +Otherwise, the character is displayed as a hex number in angle brackets. +This format can be changed by setting the +.Ev LESSBINFMT +environment variable. +LESSBINFMT may begin with a "*" and one character to select +the display attribute: +"*k" is blinking, "*d" is bold, "*u" is underlined, "*s" is standout, +and "*n" is normal. +If LESSBINFMT does not begin with a "*", normal attribute is assumed. +The remainder of LESSBINFMT is a string which may include one +printf-style escape sequence (a % followed by x, X, o, d, etc.). +For example, if LESSBINFMT is "*u[%x]", binary characters +are displayed in underlined hexadecimal surrounded by brackets. +The default if no LESSBINFMT is specified is "*s<%02X>". +Warning: the result of expanding the character via LESSBINFMT must +be less than 31 characters. +.Pp +When the character set is utf-8, the +.Ev LESSUTFBINFMT +environment variable +acts similarly to LESSBINFMT but it applies to Unicode code points +that were successfully decoded but are unsuitable for display (e.g., +unassigned code points). +Its default value is "". +Note that LESSUTFBINFMT and LESSBINFMT share their display attribute +setting ("*x") so specifying one will affect both; +LESSUTFBINFMT is read after LESSBINFMT so its setting, if any, +will have priority. +Problematic octets in a UTF-8 file (octets of a truncated sequence, +octets of a complete but non-shortest form sequence, illegal octets, +and stray trailing octets) +are displayed individually using LESSBINFMT so as to facilitate diagnostic +of how the UTF-8 file is ill-formed. +.Sh PROMPTS +The +.Fl P +option allows you to tailor the prompt to your preference. +The string given to the +.Fl P +option replaces the specified prompt string. +Certain characters in the string are interpreted specially. +The prompt mechanism is rather complicated to provide flexibility, +but the ordinary user need not understand the details of constructing +personalized prompt strings. +.Pp +A percent sign followed by a single character is expanded +according to what the following character is: +.Bl -tag -width Ds +.It %b Ns Ar X +Replaced by the byte offset into the current input file. +The b is followed by a single character (shown as +.Ar X +above) which specifies the line whose byte offset is to be used. +If the character is a "t", the byte offset of the top line in the +display is used, +an "m" means use the middle line, +a "b" means use the bottom line, +a "B" means use the line just after the bottom line, +and a "j" means use the "target" line, as specified by the +.Fl j +option. +.It \&%B +Replaced by the size of the current input file. +.It %c +Replaced by the column number of the text appearing in the first +column of the screen. +.It %d Ns Ar X +Replaced by the page number of a line in the input file. +The line to be used is determined by the +.Ar X , +as with the %b option. +.It \&%D +Replaced by the number of pages in the input file, +or equivalently, the page number of the last line in the input file. +.It %E +Replaced by the name of the editor (from the +.Ev VISUAL +environment variable, or the +.Ev EDITOR +environment variable if +.Ev VISUAL +is not defined). +See the discussion of the LESSEDIT feature below. +.It %f +Replaced by the name of the current input file. +.It %F +Replaced by the last component of the name of the current input file. +.It %i +Replaced by the index of the current file in the list of +input files. +.It %l Ns Ar X +Replaced by the line number of a line in the input file. +The line to be used is determined by the +.Ar X , +as with the %b option. +.It %L +Replaced by the line number of the last line in the input file. +.It %m +Replaced by the total number of input files. +.It %p Ns Ar X +Replaced by the percent into the current input file, based on byte offsets. +The line used is determined by the +.Ar X , +as with the %b option. +.It \&%P Ns Ar X +Replaced by the percent into the current input file, based on line numbers. +The line used is determined by the +.Ar X , +as with the %b option. +.It %s +Same as %B. +.It %t +Causes any trailing spaces to be removed. +Usually used at the end of the string, but may appear anywhere. +.It %x +Replaced by the name of the next input file in the list. +.El +.Pp +If any item is unknown (for example, the file size if input is a pipe), +a question mark is printed instead. +.Pp +The format of the prompt string can be changed depending on certain conditions. +A question mark followed by a single character acts like an "IF": +depending on the following character, a condition is evaluated. +If the condition is true, any characters following the question mark +and condition character, up to a period, are included in the prompt. +If the condition is false, such characters are not included. +A colon appearing between the question mark and the +period can be used to establish an "ELSE": any characters between +the colon and the period are included in the string, if and only if +the IF condition is false. +Condition characters (which follow a question mark) may be: +.Bl -tag -width Ds +.It ?a +True if any characters have been included in the prompt so far. +.It ?b Ns Ar X +True if the byte offset of the specified line is known. +.It ?B +True if the size of the current input file is known. +.It ?c +True if the text is horizontally shifted (%c is not zero). +.It ?d Ns Ar X +True if the page number of the specified line is known. +.It ?e +True if at end-of-file. +.It ?f +True if there is an input filename +(that is, if input is not a pipe). +.It ?l Ns Ar X +True if the line number of the specified line is known. +.It ?L +True if the line number of the last line in the file is known. +.It ?m +True if there is more than one input file. +.It ?n +True if this is the first prompt in a new input file. +.It ?p Ns Ar X +True if the percent into the current input file, based on byte offsets, +of the specified line is known. +.It ?P Ns Ar X +True if the percent into the current input file, based on line numbers, +of the specified line is known. +.It ?s +Same as "?B". +.It ?x +True if there is a next input file +(that is, if the current input file is not the last one). +.El +.Pp +Any characters other than the special ones +(question mark, colon, period, percent, and backslash) +become literally part of the prompt. +Any of the special characters may be included in the prompt literally +by preceding it with a backslash. +.Pp +Some examples: +.Pp +.Dl ?f%f:Standard input. +.Pp +This prompt prints the filename, if known; +otherwise the string "Standard input". +.Pp +.Dl ?f%f .?ltLine %lt:?pt%pt\e%:?btByte %bt:-... +.Pp +This prompt would print the filename, if known. +The filename is followed by the line number, if known, +otherwise the percent if known, otherwise the byte offset if known. +Otherwise, a dash is printed. +Notice how each question mark has a matching period, +and how the % after the %pt +is included literally by escaping it with a backslash. +.Pp +.Dl ?n?f%f\ .?m(file\ %i\ of\ %m)\ ..?e(END)\ ?x-\ Next\e:\ %x..%t +.Pp +This prints the filename if this is the first prompt in a file, +followed by the "file N of N" message if there is more +than one input file. +Then, if we are at end-of-file, the string "(END)" is printed +followed by the name of the next file, if there is one. +Finally, any trailing spaces are truncated. +This is the default prompt. +For reference, here are the defaults for +the other two prompts +.Po +.Fl m +and +.Fl M +respectively +.Pc . +Each is broken into two lines here for readability only. +.Bd -literal -offset indent +?f%f\ .?m(file\ %i\ of\ %m)\ .?e(END)\ ?x-\ Next\e:\ %x.: + ?pB%pB\e%:byte\ %bB?s/%s...%t + +?f%f\ .?n?m(file\ %i\ of\ %m)\ ..?ltlines\ %lt-%lb?L/%L.\ : + byte\ %bB?s/%s.\ .?e(END)\ ?x-\ Next\e:\ %x.:?pB%pB\e%..%t +.Ed +.Pp +And here is the default message produced by the = command: +.Bd -literal -offset indent +?f%f\ .?m(file\ %i\ of\ %m)\ .?ltlines\ %lt-%lb?L/%L.\ . + byte\ %bB?s/%s.\ ?e(END)\ :?pB%pB\e%..%t +.Ed +.Pp +The prompt expansion features are also used for another purpose: +if an environment variable +.Ev LESSEDIT +is defined, it is used as the command to be executed when the v command +is invoked. +The LESSEDIT string is expanded in the same way as the prompt strings. +The default value for LESSEDIT is: +.Pp +.Dl %E\ ?lm+%lm.\ %f +.Pp +Note that this expands to the editor name, followed by a + and the +line number, followed by the file name. +If your editor does not accept the "+linenumber" syntax, or has other +differences in invocation syntax, the +.Ev LESSEDIT +variable can be changed to modify this default. +.Sh SECURITY +When the environment variable +.Ev LESSSECURE +is set to 1, +.Nm +runs in a "secure" mode. +This means these features are disabled: +.Bl -tag -width Ds +.It | +The pipe command. +.It :e +The examine command. +.It v +The editing command. +.It s -o +Log files. +.It Fl k +Use of lesskey files. +.It Fl t +Use of tags files. +.It " " +Metacharacters in filenames, such as "*". +.It " " +Filename completion (TAB, ^L). +.El +.Sh COMPATIBILITY WITH MORE +If the environment variable +.Ev LESS_IS_MORE +is set to 1, +or if the program is invoked via a file link named "more", +.Nm +behaves (mostly) in conformance with the POSIX "more" command specification. +In this mode, less behaves differently in these ways: +.Pp +The sense of the +.Fl c +option is inverted: +when +.Xr more 1 +changes the display, +the default is to scroll from the bottom of the screen, +and the +.Fl c +option causes it to paint from the top line down. +.Pp +The +.Fl e +option works differently: +it causes +.Xr more 1 +to exit the first time it reaches EOF, +not the second. +.Pp +The +.Fl i +option acts like the +.Fl I +option. +The normal behavior of the +.Fl i +option is unavailable in this mode. +.Pp +The +.Fl m +option works differently: +if it is not specified, the medium prompt is used; +if it is specified, the short prompt is used. +.Pp +The +.Fl n +option acts like the +.Fl z +option. +The normal behavior of the +.Fl n +option is unavailable in this mode. +.Pp +The parameter to the +.Fl p +option is taken to be a +command rather than a search pattern. +.Pp +Options to suppress error messages when the terminal is dumb +.Pq Fl d , +suppress highlighting of strings in search results +.Pq Fl G , +and disable termcap initialization +.Pq Fl X +are on by default. +.Pp +The +.Ev LESS +environment variables are ignored, and the +.Ev MORE +environment variable is used in its place. +.Sh ENVIRONMENT +Environment variables may be specified either in the system environment +as usual, or in a +.Xr lesskey 1 +file. +If environment variables are defined in more than one place, +variables defined in a local lesskey file take precedence over +variables defined in the system environment, which take precedence +over variables defined in the system-wide lesskey file. +.Bl -tag -width LESSANSIENDCHARS +.It Ev COLUMNS +Sets the number of columns on the screen. +Takes precedence over the number of columns specified by the +.Ev TERM +variable, +but may be overridden by window systems which support +.Dv TIOCGWINSZ . +.It Ev EDITOR +Specifies the default editor if +.Ev VISUAL +is not set. +If neither are set, +.Xr vi 1 +is used. +.It Ev HOME +Name of the user's home directory +(used to find a lesskey file). +.It Ev LANG +Language for determining the character set. +.It Ev LC_CTYPE +The character encoding +.Xr locale 1 . +It decides which byte sequences form characters, what their display +width is, and which characters are composing or combining characters. +.It Ev LESS +Options which are passed to +.Nm +automatically. +Command line options override the +.Ev LESS +environment variable. +.Pp +Some options like +.Fl k +require a string to follow the option letter. +The string for that option is considered to end when a dollar sign ($) is found. +For example, to separate a prompt value from any other options +with dollar sign between them: +.Pp +.Dl LESS="-Ps--More--$-C -e" +.Pp +If the +.Fl -use-backslash +option appears earlier in the options, then +a dollar sign or backslash may be included literally in an option string +by preceding it with a backslash. +If the +.Fl -use-backslash +option is not in effect, then backslashes are +not treated specially, and there is no way to include a dollar sign +in the option string. +.It Ev LESSANSIENDCHARS +Characters which may end an ANSI color escape sequence +(default "m"). +.It Ev LESSANSIMIDCHARS +Characters which may appear between the ESC character and the +end character in an ANSI color escape sequence +(default "0123456789;[?!"'#%()*+\ "). +.It Ev LESSBINFMT +Format for displaying non-printable, non-control characters. +.It Ev LESSCLOSE +Command line to invoke the (optional) input-postprocessor. +.It Ev LESSEDIT +Editor prototype string (used for the v command). +See discussion under +.Sx PROMPTS . +.It Ev LESSHISTFILE +Name of the history file used to remember search commands and +shell commands between invocations of +.Nm less . +If set to "-" or "/dev/null", a history file is not used. +The default is "-". +.It Ev LESSHISTSIZE +The maximum number of commands to save in the history file. +The default is 100. +.It Ev LESSKEY +Name of the default lesskey(1) file. +.It Ev LESSKEY_SYSTEM +Name of the default system-wide lesskey(1) file. +.It Ev LESSMETACHARS +List of characters which are considered "metacharacters" by the shell. +.It Ev LESSMETAESCAPE +Prefix which +.Nm +will add before each metacharacter in a command sent to the shell. +If LESSMETAESCAPE is an empty string, commands containing +metacharacters will not be passed to the shell. +.It Ev LESSOPEN +Command line to invoke the (optional) input-preprocessor. +.It Ev LESSSECURE +Runs less in "secure" mode. +See discussion under +.Sx SECURITY . +.It Ev LESSSEPARATOR +String to be appended to a directory name in filename completion. +.It Ev LESSUTFBINFMT +Format for displaying non-printable Unicode code points. +.It Ev LESS_IS_MORE +Emulate the +.Xr more 1 +command. +.It Ev LINES +Sets the number of lines on the screen. +Takes precedence over the number of lines specified by the TERM variable, +but may be overridden by window systems which support +.Dv TIOCGWINSZ . +.It Ev MORE +Options which are passed to +.Nm +automatically when running in +.Xr more 1 +compatible mode. +.It Ev SHELL +The shell used to expand filenames. +.It Ev TERM +Specifies the terminal type. +Used by +.Nm +to get the terminal characteristics necessary to manipulate the screen. +.It Ev VISUAL +Specifies the default editor. +If not set, +.Ev EDITOR +is used; +if that is not set, +.Xr vi 1 +is used. +.El +.Sh SEE ALSO +.Xr lesskey 1 , +.Xr more 1 +.Sh AUTHORS +.An Mark Nudelman . diff --git a/bin/less/less/more.1 b/bin/less/less/more.1 new file mode 100644 index 0000000000..66b4ee2397 --- /dev/null +++ b/bin/less/less/more.1 @@ -0,0 +1,322 @@ +.\" $OpenBSD: more.1,v 1.17 2014/04/25 22:28:42 jmc Exp $ +.\" +.\" Copyright (c) 1988, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)more.1 8.2 (Berkeley) 4/18/94 +.\" +.Dd $Mdocdate: April 25 2014 $ +.Dt MORE 1 +.Os +.Sh NAME +.Nm more +.Nd view files +.Sh SYNOPSIS +.Nm more +.Op Fl ceisu +.Op Fl n Ar number +.Op Fl p Ar command +.Op Fl t Ar tag +.Op Ar +.Sh DESCRIPTION +The +.Nm +pager displays text one screenful at a time. +After showing each screenful, it prompts the user for a command. +Most commands scroll the text or move to a different place +in the file, while some switch to another file. +If no +.Ar file +is specified, or if +.Ar file +is a single dash +.Pq Ql - , +the standard input is used. +.Pp +When showing the last line of a file, +.Nm +displays a prompt indicating end of file and the name of the next file +to examine, if any. +It then waits for input from the user. +Scrolling forward switches to the next file, +or exits if there is none. +.Pp +This version of +.Nm +is actually +.Xr less 1 +in disguise. +As such, it will also accept options documented in +.Xr less 1 . +This manual page describes only features +relevant to a POSIX compliant +.Nm . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c +When changing the display, paint from the top line down. +The default is to scroll from the bottom of the screen. +.It Fl e +Exit immediately after showing the last line of the last file, +without prompting the user for a command first. +.It Fl i +Ignore case. +Upper case and lower case are considered identical. +.It Fl n Ar number +Page +.Ar number +of lines per screenful. +By default, +.Nm +uses the terminal window size. +.It Fl p Ar command +Execute the specified +.Nm +commands when a file is first examined (or re-examined, such as with the +.Ic :e +or +.Ic :p +commands). +Multiple commands have to be concatenated into one single argument. +Search patterns may contain blank characters and can be terminated +by newline characters embedded in the +.Ar command +argument. +Any other blank and newline characters contained in the argument are +interpreted as +.Ic SPACE +and +.Ic RETURN +commands, respectively. +.It Fl s +Squeeze consecutive blank lines into a single blank line. +.It Fl t Ar tag +Examine the file containing +.Ar tag . +For more information, see +.Xr ctags 1 . +.It Fl u +Display backspaces as control characters +.Pq Sq ^H +and leave CR-LF sequences alone. +By default, +.Nm +treats backspaces and CR-LF sequences specially: +backspaces which appear adjacent to an underscore character are +displayed as underlined text; +backspaces which appear between two identical characters are displayed +as emboldened text; +and CR-LF sequences are compressed to a single linefeed character. +.El +.Sh COMMANDS +Interactive commands for +.Nm +are based on +.Xr vi 1 . +Some commands may be preceded by a decimal number, called N in the +descriptions below. +In the following descriptions, ^X means control-X. +.Bl -tag -width Ic +.It Ic h +Help: display a summary of these commands. +.It Ic SPACE | f | ^F +Scroll forward N lines, default one window. +If N is more than the screen size, only the final screenful is displayed. +.It Ic b | ^B +Scroll backward N lines, default one window (see the +.Fl n +option). +If N is more than the screen size, only the final screenful is displayed. +.It Ic j | RETURN +Scroll forward N lines, default 1. +The entire N lines are displayed, even if N is more than the screen size. +.It Ic k +Scroll backward N lines, default 1. +The entire N lines are displayed, even if N is more than the screen size. +.It Ic d | ^D +Scroll forward N lines, default one half of the screen size. +If N is specified, it becomes the new default for +subsequent d and u commands. +.It Ic u | ^U +Scroll backward N lines, default one half of the screen size. +If N is specified, it becomes the new default for +subsequent d and u commands. +.It Ic g +Go to line N in the file, default 1 (beginning of file). +.It Ic G +Go to line N in the file, default the end of the file. +.It Ic r | ^L +Repaint the screen. +.It Ic R +Repaint the screen, discarding any buffered input. +Useful if the file is changing while it is being viewed. +.It Ic m +Followed by any lowercase letter, +marks the current position with that letter. +.It Ic ' +(Single quote.) +Followed by any lowercase letter, returns to the position which +was previously marked with that letter. +Followed by another single quote, returns to the position at +which the last "large" movement command was executed, or the +beginning of the file if no such movements have occurred. +All marks are lost when a new file is examined. +.It Ic / Ns Ar pattern +Search forward in the file for the N-th line containing the pattern. +N defaults to 1. +The pattern is a basic regular expression (BRE). +See +.Xr re_format 7 +for more information on regular expressions. +The search starts at the second line displayed. +.It Ic ?\& Ns Ar pattern +Search backward in the file for the N-th line containing the pattern. +The search starts at the line immediately before the top line displayed. +.It Ic /! Ns Ar pattern +Like /, but the search is for the N-th line +which does NOT contain the pattern. +.It Ic ?! Ns Ar pattern +Like ?, but the search is for the N-th line +which does NOT contain the pattern. +.It Ic n +Repeat previous search, for N-th line containing the last pattern +(or NOT containing the last pattern, +if the previous search was /! or ?!). +.It Ic N +Repeat previous search in the opposite direction, +for N-th line containing the last pattern +(or NOT containing the last pattern, +if the previous search was /! or ?!). +.It Ic :e Op Ar filename +Examine a new file. +If the filename is missing, the "current" file (see the +.Ic :n +and +.Ic :p +commands below) +from the list of files in the command line is re-examined. +If the filename is a pound sign (#), the previously examined file is +re-examined. +.It Ic :n +Examine the next file (from the list of files given in the command line). +If a number N is specified (not to be confused with the command N), +the N-th next file is examined. +.It Ic :p +Examine the previous file. +If a number N is specified, the N-th previous file is examined. +.It Ic :t +Go to supplied tag. +.It Ic v +Invokes an editor to edit the current file being viewed. +The editor is taken from the environment variable +.Ev EDITOR , +or defaults to +.Xr vi 1 . +.It Ic = | ^G +These options print out the number of the file currently being displayed +relative to the total number of files there are to display, the current +line number, the current byte number and the total bytes to display, and +what percentage of the file has been displayed. +If +.Nm +is reading from the standard input, +or the file is shorter than a single screen, some +of these items may not be available. +Note, all of these items reference the first byte of the last line +displayed on the screen. +.It Ic q | :q | ZZ +Exits +.Nm . +.El +.Sh ENVIRONMENT +.Bl -tag -width "COLUMNSXXX" +.It Ev COLUMNS +Sets the number of columns on the screen. +Takes precedence over the number of columns specified by the +.Ev TERM +variable, +but may be overridden by window systems which support +.Dv TIOCGWINSZ . +.It Ev EDITOR +Specifies the default editor. +If not set, +.Xr vi 1 +is used. +.It Ev LINES +Sets the number of lines on the screen. +Takes precedence over the number of lines specified by the TERM variable, +but may be overridden by window systems which support +.Dv TIOCGWINSZ . +.It Ev MORE +Default command line options to use with +.Nm . +The options should be space-separated and must be prefixed with a dash +.Pq Ql - . +.It Ev TERM +Specifies the terminal type. +Used by +.Nm +to get the terminal characteristics necessary to manipulate the screen. +.El +.Sh EXIT STATUS +.Ex -std more +.Sh EXAMPLES +Examine the ends of all files in the current directory, showing line +and byte counts for each: +.Pp +.Dl $ more -p G= * +.Pp +Examine several manual pages, starting from the options description +in the DESCRIPTION section: +.Bd -literal -offset indent +$ more -p '/DESCRIPTION +> /options +> ' *.1 +.Ed +.Sh SEE ALSO +.Xr ctags 1 , +.Xr less 1 , +.Xr vi 1 , +.Xr re_format 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification, +though its presence is optional. +.Pp +Functionality allowing the user to skip (as opposed to scroll) +forward is not currently implemented. +.Sh HISTORY +A +.Nm +command appeared in +.Bx 3.0 . +.Sh AUTHORS +.An Mark Nudelman Aq Mt markn@greenwoodsoftware.com diff --git a/bin/less/lesskey.c b/bin/less/lesskey.c new file mode 100644 index 0000000000..6e71efc634 --- /dev/null +++ b/bin/less/lesskey.c @@ -0,0 +1,803 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * lesskey [-o output] [input] + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Make a .less file. + * If no input file is specified, standard input is used. + * If no output file is specified, $HOME/.less is used. + * + * The .less file is used to specify (to "less") user-defined + * key bindings. Basically any sequence of 1 to MAX_CMDLEN + * keystrokes may be bound to an existing less function. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * The input file is an ascii file consisting of a + * sequence of lines of the form: + * string action [chars] + * + * "string" is a sequence of command characters which form + * the new user-defined command. The command + * characters may be: + * 1. The actual character itself. + * 2. A character preceded by ^ to specify a + * control character (e.g. ^X means control-X). + * 3. A backslash followed by one to three octal digits + * to specify a character by its octal value. + * 4. A backslash followed by b, e, n, r or t + * to specify \b, ESC, \n, \r or \t, respectively. + * 5. Any character (other than those mentioned above) preceded + * by a \ to specify the character itself (characters which + * must be preceded by \ include ^, \, and whitespace. + * "action" is the name of a "less" action, from the table below. + * "chars" is an optional sequence of characters which is treated + * as keyboard input after the command is executed. + * + * Blank lines and lines which start with # are ignored, + * except for the special control lines: + * #command Signals the beginning of the command + * keys section. + * #line-edit Signals the beginning of the line-editing + * keys section. + * #env Signals the beginning of the environment + * variable section. + * #stop Stops command parsing in less; + * causes all default keys to be disabled. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * The output file is a non-ascii file, consisting of a header, + * one or more sections, and a trailer. + * Each section begins with a section header, a section length word + * and the section data. Normally there are three sections: + * CMD_SECTION Definition of command keys. + * EDIT_SECTION Definition of editing keys. + * END_SECTION A special section header, with no + * length word or section data. + * + * Section data consists of zero or more byte sequences of the form: + * string <0> + * or + * string <0> chars <0> + * + * "string" is the command string. + * "<0>" is one null byte. + * "" is one byte containing the action code (the A_xxx value). + * If action is ORed with A_EXTRA, the action byte is followed + * by the null-terminated "chars" string. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + +#include + +#include "cmd.h" +#include "less.h" +#include "lesskey.h" + +struct cmdname { + char *cn_name; + int cn_action; +}; + +static void lkerr(char *); + +struct cmdname cmdnames[] = { + { "back-bracket", A_B_BRACKET }, + { "back-line", A_B_LINE }, + { "back-line-force", A_BF_LINE }, + { "back-screen", A_B_SCREEN }, + { "back-scroll", A_B_SCROLL }, + { "back-search", A_B_SEARCH }, + { "back-window", A_B_WINDOW }, + { "debug", A_DEBUG }, + { "digit", A_DIGIT }, + { "display-flag", A_DISP_OPTION }, + { "display-option", A_DISP_OPTION }, + { "end", A_GOEND }, + { "examine", A_EXAMINE }, + { "filter", A_FILTER }, + { "first-cmd", A_FIRSTCMD }, + { "firstcmd", A_FIRSTCMD }, + { "flush-repaint", A_FREPAINT }, + { "forw-bracket", A_F_BRACKET }, + { "forw-forever", A_F_FOREVER }, + { "forw-until-hilite", A_F_UNTIL_HILITE }, + { "forw-line", A_F_LINE }, + { "forw-line-force", A_FF_LINE }, + { "forw-screen", A_F_SCREEN }, + { "forw-screen-force", A_FF_SCREEN }, + { "forw-scroll", A_F_SCROLL }, + { "forw-search", A_F_SEARCH }, + { "forw-skip", A_F_SKIP }, + { "forw-window", A_F_WINDOW }, + { "goto-end", A_GOEND }, + { "goto-line", A_GOLINE }, + { "goto-mark", A_GOMARK }, + { "help", A_HELP }, + { "index-file", A_INDEX_FILE }, + { "invalid", A_UINVALID }, + { "left-scroll", A_LSHIFT }, + { "next-file", A_NEXT_FILE }, + { "next-tag", A_NEXT_TAG }, + { "noaction", A_NOACTION }, + { "percent", A_PERCENT }, + { "pipe", A_PIPE }, + { "prev-file", A_PREV_FILE }, + { "prev-tag", A_PREV_TAG }, + { "quit", A_QUIT }, + { "remove-file", A_REMOVE_FILE }, + { "repaint", A_REPAINT }, + { "repaint-flush", A_FREPAINT }, + { "repeat-search", A_AGAIN_SEARCH }, + { "repeat-search-all", A_T_AGAIN_SEARCH }, + { "reverse-search", A_REVERSE_SEARCH }, + { "reverse-search-all", A_T_REVERSE_SEARCH }, + { "right-scroll", A_RSHIFT }, + { "set-mark", A_SETMARK }, + { "status", A_STAT }, + { "toggle-flag", A_OPT_TOGGLE }, + { "toggle-option", A_OPT_TOGGLE }, + { "undo-hilite", A_UNDO_SEARCH }, + { "version", A_VERSION }, + { "visual", A_VISUAL }, + { NULL, 0 } +}; + +struct cmdname editnames[] = { + { "back-complete", EC_B_COMPLETE }, + { "backspace", EC_BACKSPACE }, + { "delete", EC_DELETE }, + { "down", EC_DOWN }, + { "end", EC_END }, + { "expand", EC_EXPAND }, + { "forw-complete", EC_F_COMPLETE }, + { "home", EC_HOME }, + { "insert", EC_INSERT }, + { "invalid", EC_UINVALID }, + { "kill-line", EC_LINEKILL }, + { "abort", EC_ABORT }, + { "left", EC_LEFT }, + { "literal", EC_LITERAL }, + { "right", EC_RIGHT }, + { "up", EC_UP }, + { "word-backspace", EC_W_BACKSPACE }, + { "word-delete", EC_W_DELETE }, + { "word-left", EC_W_LEFT }, + { "word-right", EC_W_RIGHT }, + { NULL, 0 } +}; + +struct table { + struct cmdname *names; + char *pbuffer; + char buffer[MAX_USERCMD]; +}; + +struct table cmdtable; +struct table edittable; +struct table vartable; +struct table *currtable = &cmdtable; + +char fileheader[] = { + C0_LESSKEY_MAGIC, + C1_LESSKEY_MAGIC, + C2_LESSKEY_MAGIC, + C3_LESSKEY_MAGIC +}; +char filetrailer[] = { + C0_END_LESSKEY_MAGIC, + C1_END_LESSKEY_MAGIC, + C2_END_LESSKEY_MAGIC +}; +char cmdsection[1] = { CMD_SECTION }; +char editsection[1] = { EDIT_SECTION }; +char varsection[1] = { VAR_SECTION }; +char endsection[1] = { END_SECTION }; + +char *infile = NULL; +char *outfile = NULL; + +int linenum; +int errors; + +extern char version[]; + +static void +usage(void) +{ + (void) fprintf(stderr, "usage: lesskey [-o output] [input]\n"); + exit(1); +} + +static char * +mkpathname(char *dirname, char *filename) +{ + char *pathname; + size_t len; + + len = strlen(dirname) + strlen(filename) + 2; + pathname = calloc(1, len); + if (pathname == NULL) { + fprintf(stderr, "mkpathname: out of memory\n"); + exit(1); + } + (void) snprintf(pathname, len, "%s/%s", dirname, filename); + return (pathname); +} + +/* + * Figure out the name of a default file (in the user's HOME directory). + */ +char * +homefile(char *filename) +{ + char *p; + char *pathname; + + if ((p = getenv("HOME")) != NULL && *p != '\0') { + pathname = mkpathname(p, filename); + } else { + (void) fprintf(stderr, "cannot find $HOME - " + "using current directory\n"); + pathname = mkpathname(".", filename); + } + return (pathname); +} + +/* + * Parse command line arguments. + */ +static void +parse_args(int argc, char **argv) +{ + char *arg; + + outfile = NULL; + while (--argc > 0) { + arg = *++argv; + if (arg[0] != '-') + /* Arg does not start with "-"; it's not an option. */ + break; + if (arg[1] == '\0') + /* "-" means standard input. */ + break; + if (arg[1] == '-' && arg[2] == '\0') { + /* "--" means end of options. */ + argc--; + argv++; + break; + } + switch (arg[1]) { + case '-': + if (strncmp(arg, "--output", 8) == 0) { + if (arg[8] == '\0') + outfile = &arg[8]; + else if (arg[8] == '=') + outfile = &arg[9]; + else + usage(); + goto opt_o; + } + if (strcmp(arg, "--version") == 0) { + goto opt_V; + } + usage(); + break; + case 'o': + outfile = &argv[0][2]; + opt_o: + if (*outfile == '\0') { + if (--argc <= 0) + usage(); + outfile = *(++argv); + } + break; + case 'V': + opt_V: + (void) printf("lesskey version %s\n", version); + exit(0); + default: + usage(); + } + } + if (argc > 1) + usage(); + /* + * Open the input file, or use DEF_LESSKEYINFILE if none specified. + */ + if (argc > 0) + infile = *argv; + else + infile = homefile(DEF_LESSKEYINFILE); +} + +/* + * Initialize data structures. + */ +static void +init_tables(void) +{ + cmdtable.names = cmdnames; + cmdtable.pbuffer = cmdtable.buffer; + + edittable.names = editnames; + edittable.pbuffer = edittable.buffer; + + vartable.names = NULL; + vartable.pbuffer = vartable.buffer; +} + +/* + * Parse one character of a string. + */ +static char * +tstr(char **pp, int xlate) +{ + char *p; + char ch; + int i; + static char buf[10]; + static char tstr_control_k[] = + { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' }; + + p = *pp; + switch (*p) { + case '\\': + ++p; + switch (*p) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + /* + * Parse an octal number. + */ + ch = 0; + i = 0; + do + ch = 8*ch + (*p - '0'); + while (*++p >= '0' && *p <= '7' && ++i < 3) + ; + *pp = p; + if (xlate && ch == CONTROL('K')) + return (tstr_control_k); + buf[0] = ch; + buf[1] = '\0'; + return (buf); + case 'b': + *pp = p+1; + return ("\b"); + case 'e': + *pp = p+1; + buf[0] = ESC; + buf[1] = '\0'; + return (buf); + case 'n': + *pp = p+1; + return ("\n"); + case 'r': + *pp = p+1; + return ("\r"); + case 't': + *pp = p+1; + return ("\t"); + case 'k': + if (xlate) { + switch (*++p) { + case 'u': ch = SK_UP_ARROW; break; + case 'd': ch = SK_DOWN_ARROW; break; + case 'r': ch = SK_RIGHT_ARROW; break; + case 'l': ch = SK_LEFT_ARROW; break; + case 'U': ch = SK_PAGE_UP; break; + case 'D': ch = SK_PAGE_DOWN; break; + case 'h': ch = SK_HOME; break; + case 'e': ch = SK_END; break; + case 'x': ch = SK_DELETE; break; + default: + lkerr("illegal char after \\k"); + *pp = p+1; + return (""); + } + *pp = p+1; + buf[0] = SK_SPECIAL_KEY; + buf[1] = ch; + buf[2] = 6; + buf[3] = 1; + buf[4] = 1; + buf[5] = 1; + buf[6] = '\0'; + return (buf); + } + /* FALLTHRU */ + default: + /* + * Backslash followed by any other char + * just means that char. + */ + *pp = p+1; + buf[0] = *p; + buf[1] = '\0'; + if (xlate && buf[0] == CONTROL('K')) + return (tstr_control_k); + return (buf); + } + case '^': + /* + * Caret means CONTROL. + */ + *pp = p+2; + buf[0] = CONTROL(p[1]); + buf[1] = '\0'; + if (buf[0] == CONTROL('K')) + return (tstr_control_k); + return (buf); + } + *pp = p+1; + buf[0] = *p; + buf[1] = '\0'; + if (xlate && buf[0] == CONTROL('K')) + return (tstr_control_k); + return (buf); +} + +/* + * Skip leading spaces in a string. + */ +char * +skipsp(char *s) +{ + while (*s == ' ' || *s == '\t') + s++; + return (s); +} + +/* + * Skip non-space characters in a string. + */ +static char * +skipnsp(char *s) +{ + while (*s != '\0' && *s != ' ' && *s != '\t') + s++; + return (s); +} + +/* + * Clean up an input line: + * strip off the trailing newline & any trailing # comment. + */ +static char * +clean_line(char *s) +{ + int i; + + s = skipsp(s); + for (i = 0; s[i] != '\n' && s[i] != '\r' && s[i] != '\0'; i++) + if (s[i] == '#' && (i == 0 || s[i-1] != '\\')) + break; + s[i] = '\0'; + return (s); +} + +/* + * Add a byte to the output command table. + */ +static void +add_cmd_char(int c) +{ + if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD) { + lkerr("too many commands"); + exit(1); + } + *(currtable->pbuffer)++ = (char)c; +} + +/* + * Add a string to the output command table. + */ +static void +add_cmd_str(char *s) +{ + for (; *s != '\0'; s++) + add_cmd_char(*s); +} + +/* + * See if we have a special "control" line. + */ +static int +control_line(char *s) +{ +#define PREFIX(str, pat) (strncmp(str, pat, strlen(pat)) == 0) + + if (PREFIX(s, "#line-edit")) { + currtable = &edittable; + return (1); + } + if (PREFIX(s, "#command")) { + currtable = &cmdtable; + return (1); + } + if (PREFIX(s, "#env")) { + currtable = &vartable; + return (1); + } + if (PREFIX(s, "#stop")) { + add_cmd_char('\0'); + add_cmd_char(A_END_LIST); + return (1); + } + return (0); +} + +/* + * Output some bytes. + */ +static void +fputbytes(FILE *fd, char *buf, int len) +{ + while (len-- > 0) { + (void) fwrite(buf, sizeof (char), 1, fd); + buf++; + } +} + +/* + * Output an integer, in special KRADIX form. + */ +static void +fputint(FILE *fd, unsigned int val) +{ + char c; + + if (val >= KRADIX*KRADIX) { + (void) fprintf(stderr, "error: integer too big (%d > %d)\n", + val, KRADIX*KRADIX); + exit(1); + } + c = val % KRADIX; + (void) fwrite(&c, sizeof (char), 1, fd); + c = val / KRADIX; + (void) fwrite(&c, sizeof (char), 1, fd); +} + +/* + * Find an action, given the name of the action. + */ +static int +findaction(char *actname) +{ + int i; + + for (i = 0; currtable->names[i].cn_name != NULL; i++) + if (strcmp(currtable->names[i].cn_name, actname) == 0) + return (currtable->names[i].cn_action); + lkerr("unknown action"); + return (A_INVALID); +} + +static void +lkerr(char *s) +{ + (void) fprintf(stderr, "line %d: %s\n", linenum, s); + errors++; +} + + +static void +parse_cmdline(char *p) +{ + int cmdlen; + char *actname; + int action; + char *s; + char c; + + /* + * Parse the command string and store it in the current table. + */ + cmdlen = 0; + do { + s = tstr(&p, 1); + cmdlen += strlen(s); + if (cmdlen > MAX_CMDLEN) + lkerr("command too long"); + else + add_cmd_str(s); + } while (*p != ' ' && *p != '\t' && *p != '\0'); + /* + * Terminate the command string with a null byte. + */ + add_cmd_char('\0'); + + /* + * Skip white space between the command string + * and the action name. + * Terminate the action name with a null byte. + */ + p = skipsp(p); + if (*p == '\0') { + lkerr("missing action"); + return; + } + actname = p; + p = skipnsp(p); + c = *p; + *p = '\0'; + + /* + * Parse the action name and store it in the current table. + */ + action = findaction(actname); + + /* + * See if an extra string follows the action name. + */ + *p = c; + p = skipsp(p); + if (*p == '\0') { + add_cmd_char(action); + } else { + /* + * OR the special value A_EXTRA into the action byte. + * Put the extra string after the action byte. + */ + add_cmd_char(action | A_EXTRA); + while (*p != '\0') + add_cmd_str(tstr(&p, 0)); + add_cmd_char('\0'); + } +} + +static void +parse_varline(char *p) +{ + char *s; + + do { + s = tstr(&p, 0); + add_cmd_str(s); + } while (*p != ' ' && *p != '\t' && *p != '=' && *p != '\0'); + /* + * Terminate the variable name with a null byte. + */ + add_cmd_char('\0'); + + p = skipsp(p); + if (*p++ != '=') { + lkerr("missing ="); + return; + } + + add_cmd_char(EV_OK|A_EXTRA); + + p = skipsp(p); + while (*p != '\0') { + s = tstr(&p, 0); + add_cmd_str(s); + } + add_cmd_char('\0'); +} + +/* + * Parse a line from the lesskey file. + */ +static void +parse_line(char *line) +{ + char *p; + + /* + * See if it is a control line. + */ + if (control_line(line)) + return; + /* + * Skip leading white space. + * Replace the final newline with a null byte. + * Ignore blank lines and comments. + */ + p = clean_line(line); + if (*p == '\0') + return; + + if (currtable == &vartable) + parse_varline(p); + else + parse_cmdline(p); +} + +int +main(int argc, char **argv) +{ + FILE *desc; + FILE *out; + char line[1024]; + + if (pledge("stdio rpath wpath cpath", NULL) == -1) + err(1, "pledge"); + + /* + * Process command line arguments. + */ + parse_args(argc, argv); + init_tables(); + + /* + * Open the input file. + */ + if (strcmp(infile, "-") == 0) + desc = stdin; + else if ((desc = fopen(infile, "r")) == NULL) { + perror(infile); + usage(); + } + + /* + * Read and parse the input file, one line at a time. + */ + errors = 0; + linenum = 0; + while (fgets(line, sizeof (line), desc) != NULL) { + ++linenum; + parse_line(line); + } + fclose(desc); + + /* + * Write the output file. + * If no output file was specified, use "$HOME/.less" + */ + if (errors > 0) { + (void) fprintf(stderr, "%d errors; no output produced\n", + errors); + exit(1); + } + + if (outfile == NULL) + outfile = getenv("LESSKEY"); + if (outfile == NULL) + outfile = homefile(LESSKEYFILE); + if ((out = fopen(outfile, "wb")) == NULL) { + perror(outfile); + exit(1); + } + + /* File header */ + fputbytes(out, fileheader, sizeof (fileheader)); + + /* Command key section */ + fputbytes(out, cmdsection, sizeof (cmdsection)); + fputint(out, cmdtable.pbuffer - cmdtable.buffer); + fputbytes(out, (char *)cmdtable.buffer, + cmdtable.pbuffer-cmdtable.buffer); + + /* Edit key section */ + fputbytes(out, editsection, sizeof (editsection)); + fputint(out, edittable.pbuffer - edittable.buffer); + fputbytes(out, (char *)edittable.buffer, + edittable.pbuffer-edittable.buffer); + + /* Environment variable section */ + fputbytes(out, varsection, sizeof (varsection)); + fputint(out, vartable.pbuffer - vartable.buffer); + fputbytes(out, (char *)vartable.buffer, + vartable.pbuffer-vartable.buffer); + + /* File trailer */ + fputbytes(out, endsection, sizeof (endsection)); + fputbytes(out, filetrailer, sizeof (filetrailer)); + fclose(out); + return (0); +} diff --git a/bin/less/lesskey.h b/bin/less/lesskey.h new file mode 100644 index 0000000000..ee737d9b70 --- /dev/null +++ b/bin/less/lesskey.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Format of a lesskey file: + * + * LESSKEY_MAGIC (4 bytes) + * sections... + * END_LESSKEY_MAGIC (4 bytes) + * + * Each section is: + * + * section_MAGIC (1 byte) + * section_length (2 bytes) + * key table (section_length bytes) + */ +#define C0_LESSKEY_MAGIC '\0' +#define C1_LESSKEY_MAGIC 'M' +#define C2_LESSKEY_MAGIC '+' +#define C3_LESSKEY_MAGIC 'G' + +#define CMD_SECTION 'c' +#define EDIT_SECTION 'e' +#define VAR_SECTION 'v' +#define END_SECTION 'x' + +#define C0_END_LESSKEY_MAGIC 'E' +#define C1_END_LESSKEY_MAGIC 'n' +#define C2_END_LESSKEY_MAGIC 'd' + +/* */ +#define KRADIX 64 diff --git a/bin/less/lesskey/Makefile b/bin/less/lesskey/Makefile new file mode 100644 index 0000000000..2bfbe8cf43 --- /dev/null +++ b/bin/less/lesskey/Makefile @@ -0,0 +1,12 @@ +# $OpenBSD: Makefile,v 1.4 2017/07/09 21:23:19 espie Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= lesskey +SRCS= lesskey.c version.c + +BINDIR= /usr/bin + +CFLAGS+=-I${.CURDIR}/.. + +.include diff --git a/bin/less/lesskey/lesskey.1 b/bin/less/lesskey/lesskey.1 new file mode 100644 index 0000000000..ce1d4e764e --- /dev/null +++ b/bin/less/lesskey/lesskey.1 @@ -0,0 +1,447 @@ +.\" $OpenBSD: lesskey.1,v 1.15 2015/11/23 12:56:13 tb Exp $ +.\" +.\" Copyright (C) 2000-2012 Mark Nudelman +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice in the documentation and/or other materials provided with +.\" the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY +.\" EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +.\" OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.Dd $Mdocdate: November 23 2015 $ +.Dt LESSKEY 1 +.Os +.Sh NAME +.Nm lesskey +.Nd specify key bindings for less +.Sh SYNOPSIS +.Nm lesskey +.Oo Fl o Ar output +.Pf " | " Fl -output Ns = Ns Ar output Oc +.Op Ar input +.Nm lesskey +.Fl V | -version +.Sh DESCRIPTION +.Nm +is used to specify a set of key bindings to be used by +.Xr less 1 . +The input file is a text file which describes the key bindings. +If the input file is +.Sq - , +standard input is read. +If no input file is specified, a standard filename is used +as the name of the input file; by default +.Pa $HOME/.lesskey . +.\" on MS-DOS systems, $HOME/_lesskey is used; +.\" and on OS/2 systems $HOME/lesskey.ini is used, +.\" or $INIT/lesskey.ini if $HOME is undefined. +The output file is a binary file which is used by +.Xr less 1 . +If no output file is specified, and the environment variable +.Ev LESSKEY +is set, the value of +.Ev LESSKEY +is used as the name of the output file. +Otherwise, a standard filename is used as the name of the output file; +by default +.Pa $HOME/.less +is used. +.\" on MS-DOS systems, $HOME/_less is used; +.\" and on OS/2 systems, $HOME/less.ini is used, +.\" or $INIT/less.ini if $HOME is undefined. +If the output file already exists, +.Nm +will overwrite it. +.Pp +A system-wide lesskey file may also be set up to provide key bindings. +If a key is defined in both a local lesskey file and in the +system-wide file, key bindings in the local file take precedence over +those in the system-wide file. +If the environment variable +.Ev LESSKEY_SYSTEM +is set, +.Xr less 1 +uses that as the name of the system-wide lesskey file. +Otherwise, +.Xr less 1 +looks in a standard place for the system-wide lesskey file: +On +.Ox , +the system-wide lesskey file is +.Pa /etc/sysless . +.Pp +The +.Fl V +or +.Fl -version +option causes +.Nm +to print its version number and immediately exit. +If +.Fl V +or +.Fl -version +is present, other options and arguments are ignored. +.Pp +The input file consists of one or more sections. +Each section starts with a line that identifies the type of section. +Possible sections are: +.Bl -tag -width "#line-edit" -offset indent +.It #command +Defines new command keys. +.It #line-edit +Defines new line-editing keys. +.It #env +Defines environment variables. +.El +.Pp +Blank lines and lines which start with a pound sign (#) are ignored, +except for the special section header lines. +.Sh COMMAND SECTION +The command section begins with the line +.Pp +.Dl #command +.Pp +If the command section is the first section in the file, +this line may be omitted. +The command section consists of lines of the form: +.Bd -filled -offset indent +.Ar string +.Aq whitespace +.Ar action +.Bq extra-string +.Aq newline +.Ed +.Pp +Whitespace is any sequence of one or more spaces and/or tabs. +The +.Ar string +is the command key(s) which invoke the action. +The +.Ar string +may be a single command key, or a sequence of up to 15 keys. +The +.Ar action +is the name of the less action, from the list below. +The characters in the +.Ar string +may appear literally, or be prefixed by a caret to indicate a control key. +A backslash followed by one to three octal digits may be used to +specify a character by its octal value. +A backslash followed by certain characters specifies input +characters as follows: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It \eb +BACKSPACE +.It \ee +ESCAPE +.It \en +NEWLINE +.It \er +RETURN +.It \et +TAB +.It \eku +UP ARROW +.It \ekd +DOWN ARROW +.It \ekr +RIGHT ARROW +.It \ekl +LEFT ARROW +.It \ekU +PAGE UP +.It \ekD +PAGE DOWN +.It \ekh +HOME +.It \eke +END +.It \ekx +DELETE +.El +.Pp +A backslash followed by any other character indicates that character is +to be taken literally. +Characters which must be preceded by backslash include +caret, space, tab and the backslash itself. +.Pp +An action may be followed by an +.Qq extra +string. +When such a command is entered while running less, +the action is performed, and then the extra +string is parsed, just as if it were typed in to less. +This feature can be used in certain cases to extend +the functionality of a command. +For example, see the +.Sq { +and +.Sq :t +commands in the example below. +The extra string has a special meaning for the +.Qq quit +action: +when less quits, +first character of the extra string is used as its exit status. +.Pp +The following input file describes the set of +default command keys used by less: +.Bd -literal -offset indent +#command +\er forw-line +\en forw-line +e forw-line +j forw-line +\ekd forw-line +^E forw-line +^N forw-line +k back-line +y back-line +^Y back-line +^K back-line +^P back-line +J forw-line-force +K back-line-force +Y back-line-force +d forw-scroll +^D forw-scroll +u back-scroll +^U back-scroll +\e40 forw-screen +f forw-screen +^F forw-screen +^V forw-screen +\ekD forw-screen +b back-screen +^B back-screen +\eev back-screen +\ekU back-screen +z forw-window +w back-window +\ee\e40 forw-screen-force +F forw-forever +\eeF forw-until-hilite +R repaint-flush +r repaint +^R repaint +^L repaint +\eeu undo-hilite +g goto-line +\ekh goto-line +< goto-line +\ee< goto-line +p percent +% percent +\ee[ left-scroll +\ee] right-scroll +\ee( left-scroll +\ee) right-scroll +{ forw-bracket {} +} back-bracket {} +( forw-bracket () +) back-bracket () +[ forw-bracket [] +] back-bracket [] +\ee^F forw-bracket +\ee^B back-bracket +G goto-end +\ee> goto-end +> goto-end +\eke goto-end += status +^G status +:f status +/ forw-search +? back-search +\ee/ forw-search * +\ee? back-search * +n repeat-search +\een repeat-search-all +N reverse-search +\eeN reverse-search-all +& filter +m set-mark +\' goto-mark +^X^X goto-mark +E examine +:e examine +^X^V examine +:n next-file +:p prev-file +t next-tag +T prev-tag +:x index-file +:d remove-file +- toggle-option +:t toggle-option t +s toggle-option o +_ display-option +| pipe +v visual +! shell ++ firstcmd +H help +h help +V version +0 digit +1 digit +2 digit +3 digit +4 digit +5 digit +6 digit +7 digit +8 digit +9 digit +q quit +Q quit +:q quit +:Q quit +ZZ quit +.Ed +.Sh PRECEDENCE +Commands specified by +.Nm +take precedence over the default commands. +A default command key may be disabled by including it in the +input file with the action +.Qq invalid . +Alternatively, a key may be defined +to do nothing by using the action +.Qq noaction . +.Qq noaction +is similar to +.Qq invalid , +but less will give an error beep for an +.Qq invalid +command, but not for a +.Qq noaction +command. +In addition, ALL default commands may be disabled by +adding this control line to the input file: +.Pp +.Dl #stop +.Pp +This will cause all default commands to be ignored. +The #stop line should be the last line in that section of the file. +.Pp +Be aware that #stop can be dangerous. +Since all default commands are disabled, you must provide sufficient +commands before the #stop line to enable all necessary actions. +For example, failure to provide a +.Qq quit +command can lead to frustration. +.Sh LINE EDITING SECTION +The line-editing section begins with the line: +.Pp +.Dl #line-edit +.Pp +This section specifies new key bindings for the line editing commands, +in a manner similar to the way key bindings for +ordinary commands are specified in the #command section. +The line-editing section consists of a list of keys and actions, +one per line as in the example below. +.Pp +The following input file describes the set of +default line-editing keys used by less: +.Bd -literal -offset indent +#line-edit +\et forw-complete +\e17 back-complete +\ee\et back-complete +^L expand +^V literal +^A literal +\eel right +\ekr right +\eeh left +\ekl left +\eeb word-left +\ee\ekl word-left +\eew word-right +\ee\ekr word-right +\eei insert +\eex delete +\ekx delete +\eeX word-delete +\eekx word-delete +\ee\eb word-backspace +\ee0 home +\ekh home +\ee$ end +\eke end +\eek up +\eku up +\eej down +^G abort +.Ed +.Sh ENVIRONMENT SECTION +The environment variable section begins with the line +.Pp +.Dl #env +.Pp +Following this line is a list of environment variable assignments. +Each line consists of an environment variable name, an equals sign +.Pq Sq = +and the value to be assigned to the environment variable. +Whitespace before and after the equals sign is ignored. +Variables assigned in this way are visible only to less. +If environment variables are defined in more than one place, +variables defined in a local lesskey file take precedence over +variables defined in the system environment, which take precedence +over variables defined in the system-wide lesskey file. +Although the lesskey file can be used to override variables set in the +environment, the main purpose of assigning variables in the lesskey file +is simply to have all less configuration information stored in one file. +.Pp +The following input file sets the -i option whenever less is run: +.Bd -literal -offset indent +#env +LESS = -i +.Ed +.Sh ENVIRONMENT +.Bl -tag -width LESSKEY_SYSTEM -compact +.It Ev LESSKEY +Name of the default +.Nm +file. +.It Ev LESSKEY_SYSTEM +Name of the default system-wide +.Nm +file. +.El +.Sh FILES +.Bl -tag -width "$HOME/.lesskey" -compact +.It $HOME/.less +Default +.Nm +file. +.It $HOME/.lesskey +Default +.Nm +input file. +.It /etc/sysless +Default system-wide +.Nm +file. +.El +.Sh SEE ALSO +.Xr less 1 +.Sh AUTHORS +.An Mark Nudelman diff --git a/bin/less/line.c b/bin/less/line.c new file mode 100644 index 0000000000..42d1a24502 --- /dev/null +++ b/bin/less/line.c @@ -0,0 +1,1125 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to manipulate the "line buffer". + * The line buffer holds a line of output as it is being built + * in preparation for output to the screen. + */ + +#include "charset.h" +#include "less.h" + +static char *linebuf = NULL; /* Buffer which holds the current output line */ +static char *attr = NULL; /* Extension of linebuf to hold attributes */ +int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ + +static int cshift; /* Current left-shift of output line buffer */ +int hshift; /* Desired left-shift of output line buffer */ +int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ +int ntabstops = 1; /* Number of tabstops */ +int tabdefault = 8; /* Default repeated tabstops */ +off_t highest_hilite; /* Pos of last hilite in file found so far */ + +static int curr; /* Index into linebuf */ +static int column; /* Printable length, accounting for backspaces, etc. */ +static int overstrike; /* Next char should overstrike previous char */ +static int is_null_line; /* There is no current line */ +static int lmargin; /* Left margin */ +static char pendc; +static off_t pendpos; +static char *end_ansi_chars; +static char *mid_ansi_chars; + +static int attr_swidth(int); +static int attr_ewidth(int); +static int do_append(LWCHAR, char *, off_t); + +extern volatile sig_atomic_t sigs; +extern int bs_mode; +extern int linenums; +extern int ctldisp; +extern int twiddle; +extern int binattr; +extern int status_col; +extern int auto_wrap, ignaw; +extern int bo_s_width, bo_e_width; +extern int ul_s_width, ul_e_width; +extern int bl_s_width, bl_e_width; +extern int so_s_width, so_e_width; +extern int sc_width, sc_height; +extern int utf_mode; +extern off_t start_attnpos; +extern off_t end_attnpos; + +static char mbc_buf[MAX_UTF_CHAR_LEN]; +static int mbc_buf_len = 0; +static int mbc_buf_index = 0; +static off_t mbc_pos; + +/* + * Initialize from environment variables. + */ +void +init_line(void) +{ + end_ansi_chars = lgetenv("LESSANSIENDCHARS"); + if (end_ansi_chars == NULL || *end_ansi_chars == '\0') + end_ansi_chars = "m"; + + mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); + if (mid_ansi_chars == NULL || *mid_ansi_chars == '\0') + mid_ansi_chars = "0123456789;[?!\"'#%()*+ "; + + linebuf = ecalloc(LINEBUF_SIZE, sizeof (char)); + attr = ecalloc(LINEBUF_SIZE, sizeof (char)); + size_linebuf = LINEBUF_SIZE; +} + +/* + * Expand the line buffer. + */ +static int +expand_linebuf(void) +{ + /* Double the size of the line buffer. */ + int new_size = size_linebuf * 2; + + /* Just realloc to expand the buffer, if we can. */ + char *new_buf = recallocarray(linebuf, size_linebuf, new_size, 1); + char *new_attr = recallocarray(attr, size_linebuf, new_size, 1); + if (new_buf == NULL || new_attr == NULL) { + free(new_attr); + free(new_buf); + return (1); + } + linebuf = new_buf; + attr = new_attr; + size_linebuf = new_size; + return (0); +} + +/* + * Is a character ASCII? + */ +int +is_ascii_char(LWCHAR ch) +{ + return (ch <= 0x7F); +} + +/* + * Rewind the line buffer. + */ +void +prewind(void) +{ + curr = 0; + column = 0; + cshift = 0; + overstrike = 0; + mbc_buf_len = 0; + is_null_line = 0; + pendc = '\0'; + lmargin = 0; + if (status_col) + lmargin += 1; +} + +/* + * Insert the line number (of the given position) into the line buffer. + */ +void +plinenum(off_t pos) +{ + off_t linenum = 0; + int i; + + if (linenums == OPT_ONPLUS) { + /* + * Get the line number and put it in the current line. + * {{ Note: since find_linenum calls forw_raw_line, + * it may seek in the input file, requiring the caller + * of plinenum to re-seek if necessary. }} + * {{ Since forw_raw_line modifies linebuf, we must + * do this first, before storing anything in linebuf. }} + */ + linenum = find_linenum(pos); + } + + /* + * Display a status column if the -J option is set. + */ + if (status_col) { + linebuf[curr] = ' '; + if (start_attnpos != -1 && + pos >= start_attnpos && pos < end_attnpos) + attr[curr] = AT_NORMAL|AT_HILITE; + else + attr[curr] = AT_NORMAL; + curr++; + column++; + } + /* + * Display the line number at the start of each line + * if the -N option is set. + */ + if (linenums == OPT_ONPLUS) { + char buf[23]; + int n; + + postoa(linenum, buf, sizeof(buf)); + n = strlen(buf); + if (n < MIN_LINENUM_WIDTH) + n = MIN_LINENUM_WIDTH; + snprintf(linebuf+curr, size_linebuf-curr, "%*s ", n, buf); + n++; /* One space after the line number. */ + for (i = 0; i < n; i++) + attr[curr+i] = AT_NORMAL; + curr += n; + column += n; + lmargin += n; + } + + /* + * Append enough spaces to bring us to the lmargin. + */ + while (column < lmargin) { + linebuf[curr] = ' '; + attr[curr++] = AT_NORMAL; + column++; + } +} + +/* + * Shift the input line left. + * This means discarding N printable chars at the start of the buffer. + */ +static void +pshift(int shift) +{ + LWCHAR prev_ch = 0; + unsigned char c; + int shifted = 0; + int to; + int from; + int len; + int width; + int prev_attr; + int next_attr; + + if (shift > column - lmargin) + shift = column - lmargin; + if (shift > curr - lmargin) + shift = curr - lmargin; + + to = from = lmargin; + /* + * We keep on going when shifted == shift + * to get all combining chars. + */ + while (shifted <= shift && from < curr) { + c = linebuf[from]; + if (ctldisp == OPT_ONPLUS && IS_CSI_START(c)) { + /* Keep cumulative effect. */ + linebuf[to] = c; + attr[to++] = attr[from++]; + while (from < curr && linebuf[from]) { + linebuf[to] = linebuf[from]; + attr[to++] = attr[from]; + if (!is_ansi_middle(linebuf[from++])) + break; + } + continue; + } + + width = 0; + + if (!IS_ASCII_OCTET(c) && utf_mode) { + /* Assumes well-formedness validation already done. */ + LWCHAR ch; + + len = utf_len(c); + if (from + len > curr) + break; + ch = get_wchar(linebuf + from); + if (!is_composing_char(ch) && + !is_combining_char(prev_ch, ch)) + width = is_wide_char(ch) ? 2 : 1; + prev_ch = ch; + } else { + len = 1; + if (c == '\b') + /* XXX - Incorrect if several '\b' in a row. */ + width = (utf_mode && is_wide_char(prev_ch)) ? + -2 : -1; + else if (!control_char(c)) + width = 1; + prev_ch = 0; + } + + if (width == 2 && shift - shifted == 1) { + /* Should never happen when called by pshift_all(). */ + attr[to] = attr[from]; + /* + * Assume a wide_char will never be the first half of a + * combining_char pair, so reset prev_ch in case we're + * followed by a '\b'. + */ + prev_ch = linebuf[to++] = ' '; + from += len; + shifted++; + continue; + } + + /* Adjust width for magic cookies. */ + prev_attr = (to > 0) ? attr[to-1] : AT_NORMAL; + next_attr = (from + len < curr) ? attr[from + len] : prev_attr; + if (!is_at_equiv(attr[from], prev_attr) && + !is_at_equiv(attr[from], next_attr)) { + width += attr_swidth(attr[from]); + if (from + len < curr) + width += attr_ewidth(attr[from]); + if (is_at_equiv(prev_attr, next_attr)) { + width += attr_ewidth(prev_attr); + if (from + len < curr) + width += attr_swidth(next_attr); + } + } + + if (shift - shifted < width) + break; + from += len; + shifted += width; + if (shifted < 0) + shifted = 0; + } + while (from < curr) { + linebuf[to] = linebuf[from]; + attr[to++] = attr[from++]; + } + curr = to; + column -= shifted; + cshift += shifted; +} + +/* + * + */ +void +pshift_all(void) +{ + pshift(column); +} + +/* + * Return the printing width of the start (enter) sequence + * for a given character attribute. + */ +static int +attr_swidth(int a) +{ + int w = 0; + + a = apply_at_specials(a); + + if (a & AT_UNDERLINE) + w += ul_s_width; + if (a & AT_BOLD) + w += bo_s_width; + if (a & AT_BLINK) + w += bl_s_width; + if (a & AT_STANDOUT) + w += so_s_width; + + return (w); +} + +/* + * Return the printing width of the end (exit) sequence + * for a given character attribute. + */ +static int +attr_ewidth(int a) +{ + int w = 0; + + a = apply_at_specials(a); + + if (a & AT_UNDERLINE) + w += ul_e_width; + if (a & AT_BOLD) + w += bo_e_width; + if (a & AT_BLINK) + w += bl_e_width; + if (a & AT_STANDOUT) + w += so_e_width; + + return (w); +} + +/* + * Return the printing width of a given character and attribute, + * if the character were added to the current position in the line buffer. + * Adding a character with a given attribute may cause an enter or exit + * attribute sequence to be inserted, so this must be taken into account. + */ +static int +pwidth(LWCHAR ch, int a, LWCHAR prev_ch) +{ + int w; + + if (ch == '\b') + /* + * Backspace moves backwards one or two positions. + * XXX - Incorrect if several '\b' in a row. + */ + return ((utf_mode && is_wide_char(prev_ch)) ? -2 : -1); + + if (!utf_mode || is_ascii_char(ch)) { + if (control_char((char)ch)) { + /* + * Control characters do unpredictable things, + * so we don't even try to guess; say it doesn't move. + * This can only happen if the -r flag is in effect. + */ + return (0); + } + } else { + if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) { + /* + * Composing and combining chars take up no space. + * + * Some terminals, upon failure to compose a + * composing character with the character(s) that + * precede(s) it will actually take up one column + * for the composing character; there isn't much + * we could do short of testing the (complex) + * composition process ourselves and printing + * a binary representation when it fails. + */ + return (0); + } + } + + /* + * Other characters take one or two columns, + * plus the width of any attribute enter/exit sequence. + */ + w = 1; + if (is_wide_char(ch)) + w++; + if (curr > 0 && !is_at_equiv(attr[curr-1], a)) + w += attr_ewidth(attr[curr-1]); + if ((apply_at_specials(a) != AT_NORMAL) && + (curr == 0 || !is_at_equiv(attr[curr-1], a))) + w += attr_swidth(a); + return (w); +} + +/* + * Delete to the previous base character in the line buffer. + * Return 1 if one is found. + */ +static int +backc(void) +{ + LWCHAR prev_ch; + char *p = linebuf + curr; + LWCHAR ch = step_char(&p, -1, linebuf + lmargin); + int width; + + /* This assumes that there is no '\b' in linebuf. */ + while (curr > lmargin && column > lmargin && + (!(attr[curr - 1] & (AT_ANSI|AT_BINARY)))) { + curr = p - linebuf; + prev_ch = step_char(&p, -1, linebuf + lmargin); + width = pwidth(ch, attr[curr], prev_ch); + column -= width; + if (width > 0) + return (1); + ch = prev_ch; + } + + return (0); +} + +/* + * Are we currently within a recognized ANSI escape sequence? + */ +static int +in_ansi_esc_seq(void) +{ + char *p; + + /* + * Search backwards for either an ESC (which means we ARE in a seq); + * or an end char (which means we're NOT in a seq). + */ + for (p = &linebuf[curr]; p > linebuf; ) { + LWCHAR ch = step_char(&p, -1, linebuf); + if (IS_CSI_START(ch)) + return (1); + if (!is_ansi_middle(ch)) + return (0); + } + return (0); +} + +/* + * Is a character the end of an ANSI escape sequence? + */ +int +is_ansi_end(LWCHAR ch) +{ + if (!is_ascii_char(ch)) + return (0); + return (strchr(end_ansi_chars, (char)ch) != NULL); +} + +/* + * + */ +int +is_ansi_middle(LWCHAR ch) +{ + if (!is_ascii_char(ch)) + return (0); + if (is_ansi_end(ch)) + return (0); + return (strchr(mid_ansi_chars, (char)ch) != NULL); +} + +/* + * Append a character and attribute to the line buffer. + */ +#define STORE_CHAR(ch, a, rep, pos) \ + if (store_char((ch), (a), (rep), (pos))) \ + return (1) + +static int +store_char(LWCHAR ch, char a, char *rep, off_t pos) +{ + int w; + int replen; + char cs; + int matches; + + if (is_hilited(pos, pos+1, 0, &matches)) { + /* + * This character should be highlighted. + * Override the attribute passed in. + */ + if (a != AT_ANSI) { + if (highest_hilite != -1 && pos > highest_hilite) + highest_hilite = pos; + a |= AT_HILITE; + } + } + + if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) { + if (!is_ansi_end(ch) && !is_ansi_middle(ch)) { + /* Remove whole unrecognized sequence. */ + char *p = &linebuf[curr]; + LWCHAR bch; + do { + bch = step_char(&p, -1, linebuf); + } while (p > linebuf && !IS_CSI_START(bch)); + curr = p - linebuf; + return (0); + } + a = AT_ANSI; /* Will force re-AT_'ing around it. */ + w = 0; + } else if (ctldisp == OPT_ONPLUS && IS_CSI_START(ch)) { + a = AT_ANSI; /* Will force re-AT_'ing around it. */ + w = 0; + } else { + char *p = &linebuf[curr]; + LWCHAR prev_ch = step_char(&p, -1, linebuf); + w = pwidth(ch, a, prev_ch); + } + + if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) + /* + * Won't fit on screen. + */ + return (1); + + if (rep == NULL) { + cs = (char)ch; + rep = &cs; + replen = 1; + } else { + replen = utf_len(rep[0]); + } + if (curr + replen >= size_linebuf-6) { + /* + * Won't fit in line buffer. + * Try to expand it. + */ + if (expand_linebuf()) + return (1); + } + + while (replen-- > 0) { + linebuf[curr] = *rep++; + attr[curr] = a; + curr++; + } + column += w; + return (0); +} + +/* + * Append a tab to the line buffer. + * Store spaces to represent the tab. + */ +#define STORE_TAB(a, pos) \ + if (store_tab((a), (pos))) \ + return (1) + +static int +store_tab(int attr, off_t pos) +{ + int to_tab = column + cshift - lmargin; + int i; + + if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) + to_tab = tabdefault - + ((to_tab - tabstops[ntabstops-1]) % tabdefault); + else { + for (i = ntabstops - 2; i >= 0; i--) + if (to_tab >= tabstops[i]) + break; + to_tab = tabstops[i+1] - to_tab; + } + + if (column + to_tab - 1 + pwidth(' ', attr, 0) + + attr_ewidth(attr) > sc_width) + return (1); + + do { + STORE_CHAR(' ', attr, " ", pos); + } while (--to_tab > 0); + return (0); +} + +#define STORE_PRCHAR(c, pos) \ + if (store_prchar((c), (pos))) \ + return (1) + +static int +store_prchar(char c, off_t pos) +{ + char *s; + + /* + * Convert to printable representation. + */ + s = prchar(c); + + /* + * Make sure we can get the entire representation + * of the character on this line. + */ + if (column + (int)strlen(s) - 1 + + pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width) + return (1); + + for (; *s != 0; s++) { + STORE_CHAR(*s, AT_BINARY, NULL, pos); + } + return (0); +} + +static int +flush_mbc_buf(off_t pos) +{ + int i; + + for (i = 0; i < mbc_buf_index; i++) + if (store_prchar(mbc_buf[i], pos)) + return (mbc_buf_index - i); + + return (0); +} + +/* + * Append a character to the line buffer. + * Expand tabs into spaces, handle underlining, boldfacing, etc. + * Returns 0 if ok, 1 if couldn't fit in buffer. + */ +int +pappend(char c, off_t pos) +{ + int r; + + if (pendc) { + if (do_append(pendc, NULL, pendpos)) + /* + * Oops. We've probably lost the char which + * was in pendc, since caller won't back up. + */ + return (1); + pendc = '\0'; + } + + if (c == '\r' && bs_mode == BS_SPECIAL) { + if (mbc_buf_len > 0) /* utf_mode must be on. */ { + /* Flush incomplete (truncated) sequence. */ + r = flush_mbc_buf(mbc_pos); + mbc_buf_index = r + 1; + mbc_buf_len = 0; + if (r) + return (mbc_buf_index); + } + + /* + * Don't put the CR into the buffer until we see + * the next char. If the next char is a newline, + * discard the CR. + */ + pendc = c; + pendpos = pos; + return (0); + } + + if (!utf_mode) { + r = do_append((LWCHAR) c, NULL, pos); + } else { + /* Perform strict validation in all possible cases. */ + if (mbc_buf_len == 0) { +retry: + mbc_buf_index = 1; + *mbc_buf = c; + if (IS_ASCII_OCTET(c)) { + r = do_append((LWCHAR) c, NULL, pos); + } else if (IS_UTF8_LEAD(c)) { + mbc_buf_len = utf_len(c); + mbc_pos = pos; + return (0); + } else { + /* UTF8_INVALID or stray UTF8_TRAIL */ + r = flush_mbc_buf(pos); + } + } else if (IS_UTF8_TRAIL(c)) { + mbc_buf[mbc_buf_index++] = c; + if (mbc_buf_index < mbc_buf_len) + return (0); + if (is_utf8_well_formed(mbc_buf)) + r = do_append(get_wchar(mbc_buf), mbc_buf, + mbc_pos); + else + /* Complete, but not shortest form, sequence. */ + mbc_buf_index = r = flush_mbc_buf(mbc_pos); + mbc_buf_len = 0; + } else { + /* Flush incomplete (truncated) sequence. */ + r = flush_mbc_buf(mbc_pos); + mbc_buf_index = r + 1; + mbc_buf_len = 0; + /* Handle new char. */ + if (!r) + goto retry; + } + } + + /* + * If we need to shift the line, do it. + * But wait until we get to at least the middle of the screen, + * so shifting it doesn't affect the chars we're currently + * pappending. (Bold & underline can get messed up otherwise.) + */ + if (cshift < hshift && column > sc_width / 2) { + linebuf[curr] = '\0'; + pshift(hshift - cshift); + } + if (r) { + /* How many chars should caller back up? */ + r = (!utf_mode) ? 1 : mbc_buf_index; + } + return (r); +} + +static int +do_append(LWCHAR ch, char *rep, off_t pos) +{ + int a; + LWCHAR prev_ch; + + a = AT_NORMAL; + + if (ch == '\b') { + if (bs_mode == BS_CONTROL) + goto do_control_char; + + /* + * A better test is needed here so we don't + * backspace over part of the printed + * representation of a binary character. + */ + if (curr <= lmargin || + column <= lmargin || + (attr[curr - 1] & (AT_ANSI|AT_BINARY))) { + STORE_PRCHAR('\b', pos); + } else if (bs_mode == BS_NORMAL) { + STORE_CHAR(ch, AT_NORMAL, NULL, pos); + } else if (bs_mode == BS_SPECIAL) { + overstrike = backc(); + } + + return (0); + } + + if (overstrike > 0) { + /* + * Overstrike the character at the current position + * in the line buffer. This will cause either + * underline (if a "_" is overstruck), + * bold (if an identical character is overstruck), + * or just deletion of the character in the buffer. + */ + overstrike = utf_mode ? -1 : 0; + /* To be correct, this must be a base character. */ + prev_ch = get_wchar(linebuf + curr); + a = attr[curr]; + if (ch == prev_ch) { + /* + * Overstriking a char with itself means make it bold. + * But overstriking an underscore with itself is + * ambiguous. It could mean make it bold, or + * it could mean make it underlined. + * Use the previous overstrike to resolve it. + */ + if (ch == '_') { + if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) + a |= (AT_BOLD|AT_UNDERLINE); + else if (curr > 0 && attr[curr - 1] & AT_UNDERLINE) + a |= AT_UNDERLINE; + else if (curr > 0 && attr[curr - 1] & AT_BOLD) + a |= AT_BOLD; + else + a |= AT_INDET; + } else { + a |= AT_BOLD; + } + } else if (ch == '_') { + a |= AT_UNDERLINE; + ch = prev_ch; + rep = linebuf + curr; + } else if (prev_ch == '_') { + a |= AT_UNDERLINE; + } + /* Else we replace prev_ch, but we keep its attributes. */ + } else if (overstrike < 0) { + if (is_composing_char(ch) || + is_combining_char(get_wchar(linebuf + curr), ch)) { + /* Continuation of the same overstrike. */ + if (curr > 0) + a = attr[curr - 1] & (AT_UNDERLINE | AT_BOLD); + else + a = AT_NORMAL; + } else + overstrike = 0; + } + + if (ch == '\t') { + /* + * Expand a tab into spaces. + */ + switch (bs_mode) { + case BS_CONTROL: + goto do_control_char; + case BS_NORMAL: + case BS_SPECIAL: + STORE_TAB(a, pos); + break; + } + } else if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch)) { +do_control_char: + if (ctldisp == OPT_ON || + (ctldisp == OPT_ONPLUS && IS_CSI_START(ch))) { + /* + * Output as a normal character. + */ + STORE_CHAR(ch, AT_NORMAL, rep, pos); + } else { + STORE_PRCHAR((char)ch, pos); + } + } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) { + char *s; + + s = prutfchar(ch); + + if (column + (int)strlen(s) - 1 + + pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width) + return (1); + + for (; *s != 0; s++) + STORE_CHAR(*s, AT_BINARY, NULL, pos); + } else { + STORE_CHAR(ch, a, rep, pos); + } + return (0); +} + +/* + * + */ +int +pflushmbc(void) +{ + int r = 0; + + if (mbc_buf_len > 0) { + /* Flush incomplete (truncated) sequence. */ + r = flush_mbc_buf(mbc_pos); + mbc_buf_len = 0; + } + return (r); +} + +/* + * Terminate the line in the line buffer. + */ +void +pdone(int endline, int forw) +{ + int i; + + (void) pflushmbc(); + + if (pendc && (pendc != '\r' || !endline)) + /* + * If we had a pending character, put it in the buffer. + * But discard a pending CR if we are at end of line + * (that is, discard the CR in a CR/LF sequence). + */ + (void) do_append(pendc, NULL, pendpos); + + for (i = curr - 1; i >= 0; i--) { + if (attr[i] & AT_INDET) { + attr[i] &= ~AT_INDET; + if (i < curr - 1 && attr[i + 1] & AT_BOLD) + attr[i] |= AT_BOLD; + else + attr[i] |= AT_UNDERLINE; + } + } + + /* + * Make sure we've shifted the line, if we need to. + */ + if (cshift < hshift) + pshift(hshift - cshift); + + if (ctldisp == OPT_ONPLUS && is_ansi_end('m')) { + /* Switch to normal attribute at end of line. */ + char *p = "\033[m"; + for (; *p != '\0'; p++) { + linebuf[curr] = *p; + attr[curr++] = AT_ANSI; + } + } + + /* + * Add a newline if necessary, + * and append a '\0' to the end of the line. + * We output a newline if we're not at the right edge of the screen, + * or if the terminal doesn't auto wrap, + * or if this is really the end of the line AND the terminal ignores + * a newline at the right edge. + * (In the last case we don't want to output a newline if the terminal + * doesn't ignore it since that would produce an extra blank line. + * But we do want to output a newline if the terminal ignores it in case + * the next line is blank. In that case the single newline output for + * that blank line would be ignored!) + */ + if (column < sc_width || !auto_wrap || (endline && ignaw) || + ctldisp == OPT_ON) { + linebuf[curr] = '\n'; + attr[curr] = AT_NORMAL; + curr++; + } else if (ignaw && column >= sc_width && forw) { + /* + * Terminals with "ignaw" don't wrap until they *really* need + * to, i.e. when the character *after* the last one to fit on a + * line is output. But they are too hard to deal with when they + * get in the state where a full screen width of characters + * have been output but the cursor is sitting on the right edge + * instead of at the start of the next line. + * So we nudge them into wrapping by outputting a space + * character plus a backspace. But do this only if moving + * forward; if we're moving backward and drawing this line at + * the top of the screen, the space would overwrite the first + * char on the next line. We don't need to do this "nudge" + * at the top of the screen anyway. + */ + linebuf[curr] = ' '; + attr[curr++] = AT_NORMAL; + linebuf[curr] = '\b'; + attr[curr++] = AT_NORMAL; + } + linebuf[curr] = '\0'; + attr[curr] = AT_NORMAL; +} + +/* + * + */ +void +set_status_col(char c) +{ + linebuf[0] = c; + attr[0] = AT_NORMAL|AT_HILITE; +} + +/* + * Get a character from the current line. + * Return the character as the function return value, + * and the character attribute in *ap. + */ +int +gline(int i, int *ap) +{ + if (is_null_line) { + /* + * If there is no current line, we pretend the line is + * either "~" or "", depending on the "twiddle" flag. + */ + if (twiddle) { + if (i == 0) { + *ap = AT_BOLD; + return ('~'); + } + --i; + } + /* Make sure we're back to AT_NORMAL before the '\n'. */ + *ap = AT_NORMAL; + return (i ? '\0' : '\n'); + } + + *ap = attr[i]; + return (linebuf[i] & 0xFF); +} + +/* + * Indicate that there is no current line. + */ +void +null_line(void) +{ + is_null_line = 1; + cshift = 0; +} + +/* + * Analogous to forw_line(), but deals with "raw lines": + * lines which are not split for screen width. + * {{ This is supposed to be more efficient than forw_line(). }} + */ +off_t +forw_raw_line(off_t curr_pos, char **linep, int *line_lenp) +{ + int n; + int c; + off_t new_pos; + + if (curr_pos == -1 || ch_seek(curr_pos) || + (c = ch_forw_get()) == EOI) + return (-1); + + n = 0; + for (;;) { + if (c == '\n' || c == EOI || ABORT_SIGS()) { + new_pos = ch_tell(); + break; + } + if (n >= size_linebuf-1) { + if (expand_linebuf()) { + /* + * Overflowed the input buffer. + * Pretend the line ended here. + */ + new_pos = ch_tell() - 1; + break; + } + } + linebuf[n++] = (char)c; + c = ch_forw_get(); + } + linebuf[n] = '\0'; + if (linep != NULL) + *linep = linebuf; + if (line_lenp != NULL) + *line_lenp = n; + return (new_pos); +} + +/* + * Analogous to back_line(), but deals with "raw lines". + * {{ This is supposed to be more efficient than back_line(). }} + */ +off_t +back_raw_line(off_t curr_pos, char **linep, int *line_lenp) +{ + int n; + int c; + off_t new_pos; + + if (curr_pos == -1 || curr_pos <= ch_zero() || ch_seek(curr_pos - 1)) + return (-1); + + n = size_linebuf; + linebuf[--n] = '\0'; + for (;;) { + c = ch_back_get(); + if (c == '\n' || ABORT_SIGS()) { + /* + * This is the newline ending the previous line. + * We have hit the beginning of the line. + */ + new_pos = ch_tell() + 1; + break; + } + if (c == EOI) { + /* + * We have hit the beginning of the file. + * This must be the first line in the file. + * This must, of course, be the beginning of the line. + */ + new_pos = ch_zero(); + break; + } + if (n <= 0) { + int old_size_linebuf = size_linebuf; + if (expand_linebuf()) { + /* + * Overflowed the input buffer. + * Pretend the line ended here. + */ + new_pos = ch_tell() + 1; + break; + } + /* + * Shift the data to the end of the new linebuf. + */ + n = size_linebuf - old_size_linebuf; + memmove(linebuf + n, linebuf, old_size_linebuf); + } + linebuf[--n] = c; + } + if (linep != NULL) + *linep = &linebuf[n]; + if (line_lenp != NULL) + *line_lenp = size_linebuf - 1 - n; + return (new_pos); +} diff --git a/bin/less/linenum.c b/bin/less/linenum.c new file mode 100644 index 0000000000..14a569cef1 --- /dev/null +++ b/bin/less/linenum.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Code to handle displaying line numbers. + * + * Finding the line number of a given file position is rather tricky. + * We don't want to just start at the beginning of the file and + * count newlines, because that is slow for large files (and also + * wouldn't work if we couldn't get to the start of the file; e.g. + * if input is a long pipe). + * + * So we use the function add_lnum to cache line numbers. + * We try to be very clever and keep only the more interesting + * line numbers when we run out of space in our table. A line + * number is more interesting than another when it is far from + * other line numbers. For example, we'd rather keep lines + * 100,200,300 than 100,101,300. 200 is more interesting than + * 101 because 101 can be derived very cheaply from 100, while + * 200 is more expensive to derive from 100. + * + * The function currline() returns the line number of a given + * position in the file. As a side effect, it calls add_lnum + * to cache the line number. Therefore currline is occasionally + * called to make sure we cache line numbers often enough. + */ + +#include + +#include + +#include "less.h" + +/* + * Structure to keep track of a line number and the associated file position. + * A doubly-linked circular list of line numbers is kept ordered by line number. + */ +struct linenum_info { + struct linenum_info *next; /* Link to next in the list */ + struct linenum_info *prev; /* Line to previous in the list */ + off_t pos; /* File position */ + off_t gap; /* Gap between prev and next */ + off_t line; /* Line number */ +}; +/* + * "gap" needs some explanation: the gap of any particular line number + * is the distance between the previous one and the next one in the list. + * ("Distance" means difference in file position.) In other words, the + * gap of a line number is the gap which would be introduced if this + * line number were deleted. It is used to decide which one to replace + * when we have a new one to insert and the table is full. + */ + +#define NPOOL 200 /* Size of line number pool */ + +#define LONGTIME (2) /* In seconds */ + +static struct linenum_info anchor; /* Anchor of the list */ +static struct linenum_info *freelist; /* Anchor of the unused entries */ +static struct linenum_info pool[NPOOL]; /* The pool itself */ +static struct linenum_info *spare; /* We always keep one spare entry */ + +extern int linenums; +extern volatile sig_atomic_t sigs; +extern int sc_height; +extern int screen_trashed; + +/* + * Initialize the line number structures. + */ +void +clr_linenum(void) +{ + struct linenum_info *p; + + /* + * Put all the entries on the free list. + * Leave one for the "spare". + */ + for (p = pool; p < &pool[NPOOL-2]; p++) + p->next = p+1; + pool[NPOOL-2].next = NULL; + freelist = pool; + + spare = &pool[NPOOL-1]; + + /* + * Initialize the anchor. + */ + anchor.next = anchor.prev = &anchor; + anchor.gap = 0; + anchor.pos = 0; + anchor.line = 1; +} + +/* + * Calculate the gap for an entry. + */ +static void +calcgap(struct linenum_info *p) +{ + /* + * Don't bother to compute a gap for the anchor. + * Also don't compute a gap for the last one in the list. + * The gap for that last one should be considered infinite, + * but we never look at it anyway. + */ + if (p == &anchor || p->next == &anchor) + return; + p->gap = p->next->pos - p->prev->pos; +} + +/* + * Add a new line number to the cache. + * The specified position (pos) should be the file position of the + * FIRST character in the specified line. + */ +void +add_lnum(off_t linenum, off_t pos) +{ + struct linenum_info *p; + struct linenum_info *new; + struct linenum_info *nextp; + struct linenum_info *prevp; + off_t mingap; + + /* + * Find the proper place in the list for the new one. + * The entries are sorted by position. + */ + for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) + if (p->line == linenum) + /* We already have this one. */ + return; + nextp = p; + prevp = p->prev; + + if (freelist != NULL) { + /* + * We still have free (unused) entries. + * Use one of them. + */ + new = freelist; + freelist = freelist->next; + } else { + /* + * No free entries. + * Use the "spare" entry. + */ + new = spare; + spare = NULL; + } + + /* + * Fill in the fields of the new entry, + * and insert it into the proper place in the list. + */ + new->next = nextp; + new->prev = prevp; + new->pos = pos; + new->line = linenum; + + nextp->prev = new; + prevp->next = new; + + /* + * Recalculate gaps for the new entry and the neighboring entries. + */ + calcgap(new); + calcgap(nextp); + calcgap(prevp); + + if (spare == NULL) { + /* + * We have used the spare entry. + * Scan the list to find the one with the smallest + * gap, take it out and make it the spare. + * We should never remove the last one, so stop when + * we get to p->next == &anchor. This also avoids + * looking at the gap of the last one, which is + * not computed by calcgap. + */ + mingap = anchor.next->gap; + for (p = anchor.next; p->next != &anchor; p = p->next) { + if (p->gap <= mingap) { + spare = p; + mingap = p->gap; + } + } + spare->next->prev = spare->prev; + spare->prev->next = spare->next; + } +} + +static int loopcount; +static struct timespec timeout; + +static void +timeout_set(int seconds) +{ + clock_gettime(CLOCK_MONOTONIC, &timeout); + timeout.tv_sec += seconds; +} + +static int +timeout_elapsed(void) +{ + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return timespeccmp(&now, &timeout, >=); +} + +static void +longish(void) +{ + if (loopcount >= 0 && ++loopcount > 100) { + loopcount = 0; + if (timeout_elapsed()) { + ierror("Calculating line numbers", NULL); + loopcount = -1; + } + } +} + +/* + * Turn off line numbers because the user has interrupted + * a lengthy line number calculation. + */ +static void +abort_long(void) +{ + if (linenums == OPT_ONPLUS) + /* + * We were displaying line numbers, so need to repaint. + */ + screen_trashed = 1; + linenums = 0; + error("Line numbers turned off", NULL); +} + +/* + * Find the line number associated with a given position. + * Return 0 if we can't figure it out. + */ +off_t +find_linenum(off_t pos) +{ + struct linenum_info *p; + off_t linenum; + off_t cpos; + + if (!linenums) + /* + * We're not using line numbers. + */ + return (0); + if (pos == -1) + /* + * Caller doesn't know what he's talking about. + */ + return (0); + if (pos <= ch_zero()) + /* + * Beginning of file is always line number 1. + */ + return (1); + + /* + * Find the entry nearest to the position we want. + */ + for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) + continue; + if (p->pos == pos) + /* Found it exactly. */ + return (p->line); + + /* + * This is the (possibly) time-consuming part. + * We start at the line we just found and start + * reading the file forward or backward till we + * get to the place we want. + * + * First decide whether we should go forward from the + * previous one or backwards from the next one. + * The decision is based on which way involves + * traversing fewer bytes in the file. + */ + timeout_set(LONGTIME); + if (p == &anchor || pos - p->prev->pos < p->pos - pos) { + /* + * Go forward. + */ + p = p->prev; + if (ch_seek(p->pos)) + return (0); + loopcount = 0; + for (linenum = p->line, cpos = p->pos; cpos < pos; linenum++) { + /* + * Allow a signal to abort this loop. + */ + cpos = forw_raw_line(cpos, NULL, NULL); + if (ABORT_SIGS()) { + abort_long(); + return (0); + } + if (cpos == -1) + return (0); + longish(); + } + /* + * We might as well cache it. + */ + add_lnum(linenum, cpos); + /* + * If the given position is not at the start of a line, + * make sure we return the correct line number. + */ + if (cpos > pos) + linenum--; + } else { + /* + * Go backward. + */ + if (ch_seek(p->pos)) + return (0); + loopcount = 0; + for (linenum = p->line, cpos = p->pos; cpos > pos; linenum--) { + /* + * Allow a signal to abort this loop. + */ + cpos = back_raw_line(cpos, NULL, NULL); + if (ABORT_SIGS()) { + abort_long(); + return (0); + } + if (cpos == -1) + return (0); + longish(); + } + /* + * We might as well cache it. + */ + add_lnum(linenum, cpos); + } + + return (linenum); +} + +/* + * Find the position of a given line number. + * Return -1 if we can't figure it out. + */ +off_t +find_pos(off_t linenum) +{ + struct linenum_info *p; + off_t cpos; + off_t clinenum; + + if (linenum <= 1) + /* + * Line number 1 is beginning of file. + */ + return (ch_zero()); + + /* + * Find the entry nearest to the line number we want. + */ + for (p = anchor.next; p != &anchor && p->line < linenum; p = p->next) + continue; + if (p->line == linenum) + /* Found it exactly. */ + return (p->pos); + + if (p == &anchor || linenum - p->prev->line < p->line - linenum) { + /* + * Go forward. + */ + p = p->prev; + if (ch_seek(p->pos)) + return (-1); + for (clinenum = p->line, cpos = p->pos; + clinenum < linenum; + clinenum++) { + /* + * Allow a signal to abort this loop. + */ + cpos = forw_raw_line(cpos, NULL, NULL); + if (ABORT_SIGS()) + return (-1); + if (cpos == -1) + return (-1); + } + } else { + /* + * Go backward. + */ + if (ch_seek(p->pos)) + return (-1); + for (clinenum = p->line, cpos = p->pos; + clinenum > linenum; + clinenum--) { + /* + * Allow a signal to abort this loop. + */ + cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL); + if (ABORT_SIGS()) + return (-1); + if (cpos == -1) + return (-1); + } + } + /* + * We might as well cache it. + */ + add_lnum(clinenum, cpos); + return (cpos); +} + +/* + * Return the line number of the "current" line. + * The argument "where" tells which line is to be considered + * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc). + */ +off_t +currline(int where) +{ + off_t pos; + off_t len; + off_t linenum; + + pos = position(where); + len = ch_length(); + while (pos == -1 && where >= 0 && where < sc_height) + pos = position(++where); + if (pos == -1) + pos = len; + linenum = find_linenum(pos); + if (pos == len) + linenum--; + return (linenum); +} diff --git a/bin/less/linenum.dep b/bin/less/linenum.dep new file mode 100644 index 0000000000..b38677d166 --- /dev/null +++ b/bin/less/linenum.dep @@ -0,0 +1,34 @@ +linenum.o: /usr/src/bin/less/less/../linenum.c /usr/include/sys/time.h \ + /usr/include/sys/feature_tests.h /usr/include/sys/ccompile.h \ + /usr/include/sys/isa_defs.h \ + /usr/src/lib/libcrypto/compat/include/sys/types.h \ + /usr/include/sys/types.h /usr/include/sys/machtypes.h \ + /usr/include/ia32/sys/machtypes.h /usr/include/sys/int_types.h \ + /usr/include/sys/select.h /usr/include/sys/time_impl.h \ + /usr/gcc/4.9/lib/gcc/i386-pc-solaris2.11/4.9.4/include/stdint.h \ + /usr/include/stdint.h /usr/include/sys/stdint.h \ + /usr/include/sys/int_limits.h /usr/include/sys/int_const.h \ + /usr/include/time.h /usr/include/iso/time_iso.h /usr/include/sys/null.h \ + /usr/src/bin/less/less/../less.h /usr/src/bin/less/less/../defines.h \ + /usr/include/ctype.h /usr/include/iso/ctype_iso.h /usr/include/fcntl.h \ + /usr/include/sys/fcntl.h /usr/include/libgen.h /usr/include/stdio.h \ + /usr/include/iso/stdio_iso.h /usr/include/sys/va_list.h \ + /usr/include/stdio_tag.h /usr/include/stdio_impl.h \ + /usr/include/iso/stdio_c99.h /usr/include/limits.h \ + /usr/include/iso/limits_iso.h /usr/include/sys/limits.h \ + /usr/include/signal.h /usr/include/iso/signal_iso.h \ + /usr/include/sys/iso/signal_iso.h /usr/include/sys/unistd.h \ + /usr/include/sys/signal.h /usr/include/sys/siginfo.h \ + /usr/include/sys/machsig.h /usr/include/vm/faultcode.h \ + /usr/include/sys/procset.h /usr/include/stdlib.h \ + /usr/include/iso/stdlib_iso.h /usr/include/iso/stdlib_c99.h \ + /usr/include/iso/stdlib_c11.h /usr/include/inttypes.h \ + /usr/include/sys/inttypes.h /usr/include/sys/int_fmtio.h \ + /usr/src/lib/libcrypto/compat/include/string.h /usr/include/string.h \ + /usr/include/iso/string_iso.h /usr/include/strings.h \ + /usr/src/lib/libcrypto/compat/include/unistd.h /usr/include/unistd.h \ + /usr/include/wctype.h /usr/include/iso/wctype_iso.h /usr/include/wchar.h \ + /usr/include/iso/wchar_iso.h /usr/include/wchar_impl.h \ + /usr/gcc/4.9/lib/gcc/i386-pc-solaris2.11/4.9.4/include/stddef.h \ + /usr/include/iso/wchar_c99.h /usr/src/bin/less/less/../funcs.h \ + /usr/include/regex.h diff --git a/bin/less/lsystem.c b/bin/less/lsystem.c new file mode 100644 index 0000000000..f53a5e45e9 --- /dev/null +++ b/bin/less/lsystem.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to execute other programs. + * Necessarily very OS dependent. + */ + +#include + +#include "less.h" +#include "position.h" + +extern int screen_trashed; +extern IFILE curr_ifile; + +static int pipe_data(char *cmd, off_t spos, off_t epos); + +/* + * Pass the specified command to a shell to be executed. + * Like plain "system()", but handles resetting terminal modes, etc. + */ +void +lsystem(const char *cmd, const char *donemsg) +{ + int inp; + char *shell; + char *p; + IFILE save_ifile; + + /* + * Print the command which is to be executed, + * unless the command starts with a "-". + */ + if (cmd[0] == '-') + cmd++; + else { + clear_bot(); + putstr("!"); + putstr(cmd); + putstr("\n"); + } + + /* + * Close the current input file. + */ + save_ifile = save_curr_ifile(); + (void) edit_ifile(NULL); + + /* + * De-initialize the terminal and take out of raw mode. + */ + deinit(); + flush(0); /* Make sure the deinit chars get out */ + raw_mode(0); + + /* + * Restore signals to their defaults. + */ + init_signals(0); + + /* + * Force standard input to be the user's terminal + * (the normal standard input), even if less's standard input + * is coming from a pipe. + */ + inp = dup(0); + (void) close(0); + if (open("/dev/tty", O_RDONLY) < 0) + (void) dup(inp); + + /* + * Pass the command to the system to be executed. + * If we have a SHELL environment variable, use + * <$SHELL -c "command"> instead of just . + * If the command is empty, just invoke a shell. + */ + p = NULL; + if ((shell = lgetenv("SHELL")) != NULL && *shell != '\0') { + if (*cmd == '\0') { + p = estrdup(shell); + } else { + char *esccmd = shell_quote(cmd); + if (esccmd != NULL) { + p = easprintf("%s -c %s", shell, esccmd); + free(esccmd); + } + } + } + if (p == NULL) { + if (*cmd == '\0') + p = estrdup("sh"); + else + p = estrdup(cmd); + } + (void) system(p); + free(p); + + /* + * Restore standard input, reset signals, raw mode, etc. + */ + (void) close(0); + (void) dup(inp); + (void) close(inp); + + init_signals(1); + raw_mode(1); + if (donemsg != NULL) { + putstr(donemsg); + putstr(" (press RETURN)"); + get_return(); + (void) putchr('\n'); + flush(0); + } + init(); + screen_trashed = 1; + + /* + * Reopen the current input file. + */ + reedit_ifile(save_ifile); + + /* + * Since we were ignoring window change signals while we executed + * the system command, we must assume the window changed. + * Warning: this leaves a signal pending (in "sigs"), + * so psignals() should be called soon after lsystem(). + */ + sigwinch(0); +} + +/* + * Pipe a section of the input file into the given shell command. + * The section to be piped is the section "between" the current + * position and the position marked by the given letter. + * + * If the mark is after the current screen, the section between + * the top line displayed and the mark is piped. + * If the mark is before the current screen, the section between + * the mark and the bottom line displayed is piped. + * If the mark is on the current screen, or if the mark is ".", + * the whole current screen is piped. + */ +int +pipe_mark(int c, char *cmd) +{ + off_t mpos, tpos, bpos; + + /* + * mpos = the marked position. + * tpos = top of screen. + * bpos = bottom of screen. + */ + mpos = markpos(c); + if (mpos == -1) + return (-1); + tpos = position(TOP); + if (tpos == -1) + tpos = ch_zero(); + bpos = position(BOTTOM); + + if (c == '.') + return (pipe_data(cmd, tpos, bpos)); + else if (mpos <= tpos) + return (pipe_data(cmd, mpos, bpos)); + else if (bpos == -1) + return (pipe_data(cmd, tpos, bpos)); + else + return (pipe_data(cmd, tpos, mpos)); +} + +/* + * Create a pipe to the given shell command. + * Feed it the file contents between the positions spos and epos. + */ +static int +pipe_data(char *cmd, off_t spos, off_t epos) +{ + FILE *f; + int c; + + /* + * This is structured much like lsystem(). + * Since we're running a shell program, we must be careful + * to perform the necessary deinitialization before running + * the command, and reinitialization after it. + */ + if (ch_seek(spos) != 0) { + error("Cannot seek to start position", NULL); + return (-1); + } + + if ((f = popen(cmd, "w")) == NULL) { + error("Cannot create pipe", NULL); + return (-1); + } + clear_bot(); + putstr("!"); + putstr(cmd); + putstr("\n"); + + deinit(); + flush(0); + raw_mode(0); + init_signals(0); + lsignal(SIGPIPE, SIG_IGN); + + c = EOI; + while (epos == -1 || spos++ <= epos) { + /* + * Read a character from the file and give it to the pipe. + */ + c = ch_forw_get(); + if (c == EOI) + break; + if (putc(c, f) == EOF) + break; + } + + /* + * Finish up the last line. + */ + while (c != '\n' && c != EOI) { + c = ch_forw_get(); + if (c == EOI) + break; + if (putc(c, f) == EOF) + break; + } + + (void) pclose(f); + + lsignal(SIGPIPE, SIG_DFL); + init_signals(1); + raw_mode(1); + init(); + screen_trashed = 1; + /* {{ Probably don't need this here. }} */ + sigwinch(0); + return (0); +} diff --git a/bin/less/main.c b/bin/less/main.c new file mode 100644 index 0000000000..6a06cd0679 --- /dev/null +++ b/bin/less/main.c @@ -0,0 +1,398 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Entry point, initialization, miscellaneous routines. + */ + +#include + +#include +#include + +#include "less.h" + +char *every_first_cmd = NULL; +int new_file; +int is_tty; +IFILE curr_ifile = NULL; +IFILE old_ifile = NULL; +struct scrpos initial_scrpos; +int any_display = FALSE; +off_t start_attnpos = -1; +off_t end_attnpos = -1; +int wscroll; + +static char *progname; + +int quitting; +int secure; +int dohelp; + +int logfile = -1; +int force_logfile = FALSE; +char *namelogfile = NULL; +char *editor; +char *editproto; + +extern char *tags; +extern char *tagoption; +extern int jump_sline; +extern int less_is_more; +extern int missing_cap; +extern int know_dumb; +extern int quit_if_one_screen; +extern int quit_at_eof; +extern int pr_type; +extern int hilite_search; +extern int use_lessopen; +extern int no_init; +extern int top_scroll; +extern int errmsgs; + + +/* + * Entry point. + */ +int +main(int argc, char *argv[]) +{ + IFILE ifile; + char *s; + + progname = basename(argv[0]); + argv++; + argc--; + + /* + * If the name of the executable program is "more", + * act like LESS_IS_MORE is set. We have to set this as early + * as possible for POSIX. + */ + if (strcmp(progname, "more") == 0) + less_is_more = 1; + else { + s = lgetenv("LESS_IS_MORE"); + if (s != NULL && *s != '\0') + less_is_more = 1; + } + + secure = 0; + s = lgetenv("LESSSECURE"); + if (s != NULL && *s != '\0') + secure = 1; + + if (secure) { + if (pledge("stdio rpath wpath tty", NULL) == -1) { + perror("pledge"); + exit(1); + } + } else { + if (pledge("stdio rpath wpath cpath fattr proc exec tty", NULL) == -1) { + perror("pledge"); + exit(1); + } + } + + /* + * Process command line arguments and LESS environment arguments. + * Command line arguments override environment arguments. + */ + is_tty = isatty(1); + get_term(); + init_cmds(); + init_charset(); + init_line(); + init_cmdhist(); + init_option(); + init_search(); + + + init_prompt(); + + if (less_is_more) { + /* this is specified by XPG */ + quit_at_eof = OPT_ON; + + /* more users don't like the warning */ + know_dumb = OPT_ON; + + /* default prompt is medium */ + pr_type = OPT_ON; + + /* do not hilight search terms */ + hilite_search = OPT_OFF; + + /* do not use LESSOPEN */ + use_lessopen = OPT_OFF; + + /* do not set init strings to terminal */ + no_init = OPT_ON; + + /* repaint from top of screen */ + top_scroll = OPT_OFF; + } + + s = lgetenv(less_is_more ? "MORE" : "LESS"); + if (s != NULL) + scan_option(estrdup(s)); + +#define isoptstring(s) (((s)[0] == '-' || (s)[0] == '+') && (s)[1] != '\0') + while (argc > 0 && (isoptstring(*argv) || isoptpending())) { + s = *argv++; + argc--; + if (strcmp(s, "--") == 0) + break; + scan_option(s); + } +#undef isoptstring + + if (isoptpending()) { + /* + * Last command line option was a flag requiring a + * following string, but there was no following string. + */ + nopendopt(); + quit(QUIT_OK); + } + + if (errmsgs) { + quit(QUIT_ERROR); + } + if (less_is_more && quit_at_eof == OPT_ONPLUS) { + extern int no_init; + no_init = OPT_ON; + } + if (less_is_more && pr_type == OPT_ONPLUS) { + extern int quiet; + quiet = VERY_QUIET; + } + + editor = lgetenv("VISUAL"); + if (editor == NULL || *editor == '\0') { + editor = lgetenv("EDITOR"); + if (editor == NULL || *editor == '\0') + editor = EDIT_PGM; + } + editproto = lgetenv("LESSEDIT"); + if (editproto == NULL || *editproto == '\0') + editproto = "%E ?lm+%lm. %f"; + + /* + * Call get_ifile with all the command line filenames + * to "register" them with the ifile system. + */ + ifile = NULL; + if (dohelp) + ifile = get_ifile(helpfile(), ifile); + while (argc-- > 0) { + char *filename; + filename = shell_quote(*argv); + if (filename == NULL) + filename = *argv; + argv++; + (void) get_ifile(filename, ifile); + ifile = prev_ifile(NULL); + free(filename); + } + /* + * Set up terminal, etc. + */ + if (!is_tty) { + /* + * Output is not a tty. + * Just copy the input file(s) to output. + */ + if (nifile() == 0) { + if (edit_stdin() == 0) + cat_file(); + } else if (edit_first() == 0) { + do { + cat_file(); + } while (edit_next(1) == 0); + } + quit(QUIT_OK); + } + + if (missing_cap && !know_dumb) + error("WARNING: terminal is not fully functional", NULL); + init_mark(); + open_getchr(); + + if (secure) + if (pledge("stdio rpath tty", NULL) == -1) { + perror("pledge"); + exit(1); + } + + raw_mode(1); + init_signals(1); + + /* + * Select the first file to examine. + */ + if (tagoption != NULL || strcmp(tags, "-") == 0) { + /* + * A -t option was given. + * Verify that no filenames were also given. + * Edit the file selected by the "tags" search, + * and search for the proper line in the file. + */ + if (nifile() > 0) { + error("No filenames allowed with -t option", NULL); + quit(QUIT_ERROR); + } + findtag(tagoption); + if (edit_tagfile()) /* Edit file which contains the tag */ + quit(QUIT_ERROR); + /* + * Search for the line which contains the tag. + * Set up initial_scrpos so we display that line. + */ + initial_scrpos.pos = tagsearch(); + if (initial_scrpos.pos == -1) + quit(QUIT_ERROR); + initial_scrpos.ln = jump_sline; + } else if (nifile() == 0) { + if (edit_stdin()) /* Edit standard input */ + quit(QUIT_ERROR); + } else { + if (edit_first()) /* Edit first valid file in cmd line */ + quit(QUIT_ERROR); + } + + init(); + commands(); + quit(QUIT_OK); + return (0); +} + +/* + * Allocate memory. + * Like calloc(), but never returns an error (NULL). + */ +void * +ecalloc(int count, unsigned int size) +{ + void *p; + + p = calloc(count, size); + if (p != NULL) + return (p); + error("Cannot allocate memory", NULL); + quit(QUIT_ERROR); + return (NULL); +} + +char * +easprintf(const char *fmt, ...) +{ + char *p = NULL; + int rv; + va_list ap; + + va_start(ap, fmt); + rv = vasprintf(&p, fmt, ap); + va_end(ap); + + if (p == NULL || rv < 0) { + error("Cannot allocate memory", NULL); + quit(QUIT_ERROR); + } + return (p); +} + +char * +estrdup(const char *str) +{ + char *n; + + n = strdup(str); + if (n == NULL) { + error("Cannot allocate memory", NULL); + quit(QUIT_ERROR); + } + return (n); +} + +/* + * Skip leading spaces in a string. + */ +char * +skipsp(char *s) +{ + while (*s == ' ' || *s == '\t') + s++; + return (s); +} + +/* + * See how many characters of two strings are identical. + * If uppercase is true, the first string must begin with an uppercase + * character; the remainder of the first string may be either case. + */ +int +sprefix(char *ps, char *s, int uppercase) +{ + int c; + int sc; + int len = 0; + + for (; *s != '\0'; s++, ps++) { + c = *ps; + if (uppercase) { + if (len == 0 && islower(c)) + return (-1); + c = tolower(c); + } + sc = *s; + if (len > 0) + sc = tolower(sc); + if (c != sc) + break; + len++; + } + return (len); +} + +/* + * Exit the program. + */ +void +quit(int status) +{ + static int save_status; + + /* + * Put cursor at bottom left corner, clear the line, + * reset the terminal modes, and exit. + */ + if (status < 0) + status = save_status; + else + save_status = status; + quitting = 1; + edit(NULL); + if (!secure) + save_cmdhist(); + if (any_display && is_tty) + clear_bot(); + deinit(); + flush(1); + raw_mode(0); + exit(status); +} + +char * +helpfile(void) +{ + return (less_is_more ? HELPDIR "/more.help" : HELPDIR "/less.help"); +} diff --git a/bin/less/mark.c b/bin/less/mark.c new file mode 100644 index 0000000000..044f8c456e --- /dev/null +++ b/bin/less/mark.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#include "less.h" + +extern IFILE curr_ifile; +extern int sc_height; +extern int jump_sline; + +/* + * A mark is an ifile (input file) plus a position within the file. + */ +struct mark { + IFILE m_ifile; + struct scrpos m_scrpos; +}; + +/* + * The table of marks. + * Each mark is identified by a lowercase or uppercase letter. + * The final one is lmark, for the "last mark"; addressed by the apostrophe. + */ +#define NMARKS ((2*26)+1) /* a-z, A-Z, lastmark */ +#define LASTMARK (NMARKS-1) +static struct mark marks[NMARKS]; + +/* + * Initialize the mark table to show no marks are set. + */ +void +init_mark(void) +{ + int i; + + for (i = 0; i < NMARKS; i++) + marks[i].m_scrpos.pos = -1; +} + +/* + * See if a mark letter is valid (between a and z). + */ +static struct mark * +getumark(int c) +{ + if (c >= 'a' && c <= 'z') + return (&marks[c-'a']); + + if (c >= 'A' && c <= 'Z') + return (&marks[c-'A'+26]); + + error("Invalid mark letter", NULL); + return (NULL); +} + +/* + * Get the mark structure identified by a character. + * The mark struct may come either from the mark table + * or may be constructed on the fly for certain characters like ^, $. + */ +static struct mark * +getmark(int c) +{ + struct mark *m; + static struct mark sm; + + switch (c) { + case '^': + /* + * Beginning of the current file. + */ + m = &sm; + m->m_scrpos.pos = ch_zero(); + m->m_scrpos.ln = 0; + m->m_ifile = curr_ifile; + break; + case '$': + /* + * End of the current file. + */ + if (ch_end_seek()) { + error("Cannot seek to end of file", NULL); + return (NULL); + } + m = &sm; + m->m_scrpos.pos = ch_tell(); + m->m_scrpos.ln = sc_height-1; + m->m_ifile = curr_ifile; + break; + case '.': + /* + * Current position in the current file. + */ + m = &sm; + get_scrpos(&m->m_scrpos); + m->m_ifile = curr_ifile; + break; + case '\'': + /* + * The "last mark". + */ + m = &marks[LASTMARK]; + break; + default: + /* + * Must be a user-defined mark. + */ + m = getumark(c); + if (m == NULL) + break; + if (m->m_scrpos.pos == -1) { + error("Mark not set", NULL); + return (NULL); + } + break; + } + return (m); +} + +/* + * Is a mark letter is invalid? + */ +int +badmark(int c) +{ + return (getmark(c) == NULL); +} + +/* + * Set a user-defined mark. + */ +void +setmark(int c) +{ + struct mark *m; + struct scrpos scrpos; + + m = getumark(c); + if (m == NULL) + return; + get_scrpos(&scrpos); + m->m_scrpos = scrpos; + m->m_ifile = curr_ifile; +} + +/* + * Set lmark (the mark named by the apostrophe). + */ +void +lastmark(void) +{ + struct scrpos scrpos; + + if (ch_getflags() & CH_HELPFILE) + return; + get_scrpos(&scrpos); + if (scrpos.pos == -1) + return; + marks[LASTMARK].m_scrpos = scrpos; + marks[LASTMARK].m_ifile = curr_ifile; +} + +/* + * Go to a mark. + */ +void +gomark(int c) +{ + struct mark *m; + struct scrpos scrpos; + + m = getmark(c); + if (m == NULL) + return; + + /* + * If we're trying to go to the lastmark and + * it has not been set to anything yet, + * set it to the beginning of the current file. + */ + if (m == &marks[LASTMARK] && m->m_scrpos.pos == -1) { + m->m_ifile = curr_ifile; + m->m_scrpos.pos = ch_zero(); + m->m_scrpos.ln = jump_sline; + } + + /* + * If we're using lmark, we must save the screen position now, + * because if we call edit_ifile() below, lmark will change. + * (We save the screen position even if we're not using lmark.) + */ + scrpos = m->m_scrpos; + if (m->m_ifile != curr_ifile) { + /* + * Not in the current file; edit the correct file. + */ + if (edit_ifile(m->m_ifile)) + return; + } + + jump_loc(scrpos.pos, scrpos.ln); +} + +/* + * Return the position associated with a given mark letter. + * + * We don't return which screen line the position + * is associated with, but this doesn't matter much, + * because it's always the first non-blank line on the screen. + */ +off_t +markpos(int c) +{ + struct mark *m; + + m = getmark(c); + if (m == NULL) + return (-1); + + if (m->m_ifile != curr_ifile) { + error("Mark not in current file", NULL); + return (-1); + } + return (m->m_scrpos.pos); +} + +/* + * Clear the marks associated with a specified ifile. + */ +void +unmark(IFILE ifile) +{ + int i; + + for (i = 0; i < NMARKS; i++) + if (marks[i].m_ifile == ifile) + marks[i].m_scrpos.pos = -1; +} diff --git a/bin/less/more.hlp b/bin/less/more.hlp new file mode 100644 index 0000000000..8bf59056e4 --- /dev/null +++ b/bin/less/more.hlp @@ -0,0 +1,75 @@ + + SSUUMMMMAARRYY OOFF CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h Display this help. + q :q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + j CR * Forward one line (or _N lines). + k * Backward one line (or _N lines). + f ^F SPACE * Forward one window (or _N lines). + b ^B * Backward one window (or _N lines). + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + r ^L Repaint screen. + R Repaint screen, discarding buffered input. + :t Go to supplied tag. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N Repeat previous search in reverse direction. + --------------------------------------------------- + A search pattern may be preceded by one or more of: + ! Search for NON-matching lines. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g * Go to first line in file (or line _N). + G * Go to last line in file (or line _N). + --------------------------------------------------- + + m_<_l_e_t_t_e_r_> Mark the current position with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + --------------------------------------------------- + A mark is any lower-case letter. + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + = ^G Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + v Edit the current file with $VISUAL or vi(1). + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + -c Repaint by clearing rather than scrolling. + -e Quit at end of file. + -i Ignore case in searches. + -n _n_u_m_b_e_r Page number of lines per screenful. + -p _c_o_m_m_a_n_d Execute the specified commands. + -s Squeeze multiple blank lines. + -t _t_a_g Examine the file containing tag. + -u Change handling of backspaces. + diff --git a/bin/less/optfunc.c b/bin/less/optfunc.c new file mode 100644 index 0000000000..ddd3712320 --- /dev/null +++ b/bin/less/optfunc.c @@ -0,0 +1,547 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Handling functions for command line options. + * + * Most options are handled by the generic code in option.c. + * But all string options, and a few non-string options, require + * special handling specific to the particular option. + * This special processing is done by the "handling functions" in this file. + * + * Each handling function is passed a "type" and, if it is a string + * option, the string which should be "assigned" to the option. + * The type may be one of: + * INIT The option is being initialized from the command line. + * TOGGLE The option is being changed from within the program. + * QUERY The setting of the option is merely being queried. + */ + +#include "less.h" +#include "option.h" + +extern int bufspace; +extern int pr_type; +extern int plusoption; +extern int swindow; +extern int sc_width; +extern int sc_height; +extern int secure; +extern int dohelp; +extern int any_display; +extern char openquote; +extern char closequote; +extern char *prproto[]; +extern char *eqproto; +extern char *hproto; +extern char *wproto; +extern IFILE curr_ifile; +extern char version[]; +extern int jump_sline; +extern int jump_sline_fraction; +extern int less_is_more; +extern char *namelogfile; +extern int force_logfile; +extern int logfile; +char *tagoption = NULL; +extern char *tags; + +int shift_count; /* Number of positions to shift horizontally */ +static int shift_count_fraction = -1; + +/* + * Handler for -o option. + */ +void +opt_o(int type, char *s) +{ + PARG parg; + + if (secure) { + error("log file support is not available", NULL); + return; + } + switch (type) { + case INIT: + namelogfile = s; + break; + case TOGGLE: + if (ch_getflags() & CH_CANSEEK) { + error("Input is not a pipe", NULL); + return; + } + if (logfile >= 0) { + error("Log file is already in use", NULL); + return; + } + s = skipsp(s); + namelogfile = lglob(s); + use_logfile(namelogfile); + sync_logfile(); + break; + case QUERY: + if (logfile < 0) { + error("No log file", NULL); + } else { + parg.p_string = namelogfile; + error("Log file \"%s\"", &parg); + } + break; + } +} + +/* + * Handler for -O option. + */ +void +opt__O(int type, char *s) +{ + force_logfile = TRUE; + opt_o(type, s); +} + +/* + * Handlers for -j option. + */ +void +opt_j(int type, char *s) +{ + PARG parg; + char buf[16]; + int len; + int err; + + switch (type) { + case INIT: + case TOGGLE: + if (*s == '.') { + s++; + jump_sline_fraction = getfraction(&s, "j", &err); + if (err) + error("Invalid line fraction", NULL); + else + calc_jump_sline(); + } else { + int sline = getnum(&s, "j", &err); + if (err) { + error("Invalid line number", NULL); + } else { + jump_sline = sline; + jump_sline_fraction = -1; + } + } + break; + case QUERY: + if (jump_sline_fraction < 0) { + parg.p_int = jump_sline; + error("Position target at screen line %d", &parg); + } else { + (void) snprintf(buf, sizeof (buf), ".%06d", + jump_sline_fraction); + len = strlen(buf); + while (len > 2 && buf[len-1] == '0') + len--; + buf[len] = '\0'; + parg.p_string = buf; + error("Position target at screen position %s", &parg); + } + break; + } +} + +void +calc_jump_sline(void) +{ + if (jump_sline_fraction < 0) + return; + jump_sline = sc_height * jump_sline_fraction / NUM_FRAC_DENOM; +} + +/* + * Handlers for -# option. + */ +void +opt_shift(int type, char *s) +{ + PARG parg; + char buf[16]; + int len; + int err; + + switch (type) { + case INIT: + case TOGGLE: + if (*s == '.') { + s++; + shift_count_fraction = getfraction(&s, "#", &err); + if (err) + error("Invalid column fraction", NULL); + else + calc_shift_count(); + } else { + int hs = getnum(&s, "#", &err); + if (err) { + error("Invalid column number", NULL); + } else { + shift_count = hs; + shift_count_fraction = -1; + } + } + break; + case QUERY: + if (shift_count_fraction < 0) { + parg.p_int = shift_count; + error("Horizontal shift %d columns", &parg); + } else { + + (void) snprintf(buf, sizeof (buf), ".%06d", + shift_count_fraction); + len = strlen(buf); + while (len > 2 && buf[len-1] == '0') + len--; + buf[len] = '\0'; + parg.p_string = buf; + error("Horizontal shift %s of screen width", &parg); + } + break; + } +} + +void +calc_shift_count(void) +{ + if (shift_count_fraction < 0) + return; + shift_count = sc_width * shift_count_fraction / NUM_FRAC_DENOM; +} + +void +opt_k(int type, char *s) +{ + PARG parg; + + switch (type) { + case INIT: + if (lesskey(s, 0)) { + parg.p_string = s; + error("Cannot use lesskey file \"%s\"", &parg); + } + break; + } +} + +/* + * Handler for -t option. + */ +void +opt_t(int type, char *s) +{ + IFILE save_ifile; + off_t pos; + + switch (type) { + case INIT: + tagoption = s; + /* Do the rest in main() */ + break; + case TOGGLE: + if (secure) { + error("tags support is not available", NULL); + break; + } + findtag(skipsp(s)); + save_ifile = save_curr_ifile(); + /* + * Try to open the file containing the tag + * and search for the tag in that file. + */ + if (edit_tagfile() || (pos = tagsearch()) == -1) { + /* Failed: reopen the old file. */ + reedit_ifile(save_ifile); + break; + } + unsave_ifile(save_ifile); + jump_loc(pos, jump_sline); + break; + } +} + +/* + * Handler for -T option. + */ +void +opt__T(int type, char *s) +{ + PARG parg; + + switch (type) { + case INIT: + tags = s; + break; + case TOGGLE: + s = skipsp(s); + tags = lglob(s); + break; + case QUERY: + parg.p_string = tags; + error("Tags file \"%s\"", &parg); + break; + } +} + +/* + * Handler for -p option. + */ +void +opt_p(int type, char *s) +{ + switch (type) { + case INIT: + /* + * Unget a search command for the specified string. + * {{ This won't work if the "/" command is + * changed or invalidated by a .lesskey file. }} + */ + plusoption = TRUE; + ungetsc(s); + /* + * In "more" mode, the -p argument is a command, + * not a search string, so we don't need a slash. + */ + if (!less_is_more) + ungetsc("/"); + break; + } +} + +/* + * Handler for -P option. + */ +void +opt__P(int type, char *s) +{ + char **proto; + PARG parg; + + switch (type) { + case INIT: + case TOGGLE: + /* + * Figure out which prototype string should be changed. + */ + switch (*s) { + case 's': proto = &prproto[PR_SHORT]; s++; break; + case 'm': proto = &prproto[PR_MEDIUM]; s++; break; + case 'M': proto = &prproto[PR_LONG]; s++; break; + case '=': proto = &eqproto; s++; break; + case 'h': proto = &hproto; s++; break; + case 'w': proto = &wproto; s++; break; + default: proto = &prproto[PR_SHORT]; break; + } + free(*proto); + *proto = estrdup(s); + break; + case QUERY: + parg.p_string = prproto[pr_type]; + error("%s", &parg); + break; + } +} + +/* + * Handler for the -b option. + */ +void +opt_b(int type, char *s) +{ + switch (type) { + case INIT: + case TOGGLE: + /* + * Set the new number of buffers. + */ + ch_setbufspace(bufspace); + break; + case QUERY: + break; + } +} + +/* + * Handler for the -i option. + */ +void +opt_i(int type, char *s) +{ + switch (type) { + case TOGGLE: + chg_caseless(); + break; + case QUERY: + case INIT: + break; + } +} + +/* + * Handler for the -V option. + */ +void +opt__V(int type, char *s) +{ + switch (type) { + case TOGGLE: + case QUERY: + dispversion(); + break; + case INIT: + /* + * Force output to stdout per GNU standard for --version output. + */ + any_display = 1; + putstr("less "); + putstr(version); + putstr(" ("); + putstr("POSIX "); + putstr("regular expressions)\n"); + putstr("Copyright (C) 1984-2012 Mark Nudelman\n"); + putstr("Modified for use with illumos by Garrett D'Amore.\n"); + putstr("Copyright 2014 Garrett D'Amore\n\n"); + putstr("less comes with NO WARRANTY, "); + putstr("to the extent permitted by law.\n"); + putstr("For information about the terms of redistribution,\n"); + putstr("see the file named README in the less distribution.\n"); + putstr("Homepage: http://www.greenwoodsoftware.com/less\n"); + putstr("\n"); + quit(QUIT_OK); + break; + } +} + +/* + * Handler for the -x option. + */ +void +opt_x(int type, char *s) +{ + extern int tabstops[]; + extern int ntabstops; + extern int tabdefault; + char tabs[60+(4*TABSTOP_MAX)]; + int i; + PARG p; + + switch (type) { + case INIT: + case TOGGLE: + /* Start at 1 because tabstops[0] is always zero. */ + for (i = 1; i < TABSTOP_MAX; ) { + int n = 0; + s = skipsp(s); + while (*s >= '0' && *s <= '9') + n = (10 * n) + (*s++ - '0'); + if (n > tabstops[i-1]) + tabstops[i++] = n; + s = skipsp(s); + if (*s++ != ',') + break; + } + if (i < 2) + return; + ntabstops = i; + tabdefault = tabstops[ntabstops-1] - tabstops[ntabstops-2]; + break; + case QUERY: + (void) strlcpy(tabs, "Tab stops ", sizeof(tabs)); + if (ntabstops > 2) { + for (i = 1; i < ntabstops; i++) { + if (i > 1) + strlcat(tabs, ",", sizeof(tabs)); + (void) snprintf(tabs+strlen(tabs), + sizeof(tabs)-strlen(tabs), + "%d", tabstops[i]); + } + (void) snprintf(tabs+strlen(tabs), + sizeof(tabs)-strlen(tabs), " and then "); + } + (void) snprintf(tabs+strlen(tabs), sizeof(tabs)-strlen(tabs), + "every %d spaces", tabdefault); + p.p_string = tabs; + error("%s", &p); + break; + } +} + + +/* + * Handler for the -" option. + */ +void +opt_quote(int type, char *s) +{ + char buf[3]; + PARG parg; + + switch (type) { + case INIT: + case TOGGLE: + if (s[0] == '\0') { + openquote = closequote = '\0'; + break; + } + if (s[1] != '\0' && s[2] != '\0') { + error("-\" must be followed by 1 or 2 chars", + NULL); + return; + } + openquote = s[0]; + if (s[1] == '\0') + closequote = openquote; + else + closequote = s[1]; + break; + case QUERY: + buf[0] = openquote; + buf[1] = closequote; + buf[2] = '\0'; + parg.p_string = buf; + error("quotes %s", &parg); + break; + } +} + +/* + * "-?" means display a help message. + * If from the command line, exit immediately. + */ +void +opt_query(int type, char *s) +{ + switch (type) { + case QUERY: + case TOGGLE: + error("Use \"h\" for help", NULL); + break; + case INIT: + dohelp = 1; + } +} + +/* + * Get the "screen window" size. + */ +int +get_swindow(void) +{ + if (swindow > 0) + return (swindow); + return (sc_height + swindow); +} diff --git a/bin/less/option.c b/bin/less/option.c new file mode 100644 index 0000000000..83615f377d --- /dev/null +++ b/bin/less/option.c @@ -0,0 +1,683 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Process command line options. + * + * Each option is a single letter which controls a program variable. + * The options have defaults which may be changed via + * the command line option, toggled via the "-" command, + * or queried via the "_" command. + */ + +#include "less.h" +#include "option.h" + +static struct loption *pendopt; +int plusoption = FALSE; + +static char *optstring(char *, char **, char *, char *); +static int flip_triple(int, int); + +extern int screen_trashed; +extern int less_is_more; +extern int quit_at_eof; +extern char *every_first_cmd; +extern int opt_use_backslash; + +/* + * Return a printable description of an option. + */ +static char * +opt_desc(struct loption *o) +{ + static char buf[OPTNAME_MAX + 10]; + if (o->oletter == OLETTER_NONE) + (void) snprintf(buf, sizeof (buf), "--%s", o->onames->oname); + else + (void) snprintf(buf, sizeof (buf), "-%c (--%s)", + o->oletter, o->onames->oname); + return (buf); +} + +/* + * Return a string suitable for printing as the "name" of an option. + * For example, if the option letter is 'x', just return "-x". + */ +char * +propt(int c) +{ + static char buf[8]; + + (void) snprintf(buf, sizeof (buf), "-%s", prchar(c)); + return (buf); +} + +/* + * Scan an argument (either from the command line or from the + * LESS environment variable) and process it. + */ +void +scan_option(char *s) +{ + struct loption *o; + int optc; + char *optname; + char *printopt; + char *str; + int set_default; + int lc; + int err; + int moreopt; + PARG parg; + + if (s == NULL) + return; + + /* + * If we have a pending option which requires an argument, + * handle it now. + * This happens if the previous option was, for example, "-P" + * without a following string. In that case, the current + * option is simply the argument for the previous option. + */ + if (pendopt != NULL) { + switch (pendopt->otype & OTYPE) { + case STRING: + (*pendopt->ofunc)(INIT, s); + break; + case NUMBER: + printopt = opt_desc(pendopt); + *(pendopt->ovar) = getnum(&s, printopt, NULL); + break; + } + pendopt = NULL; + return; + } + + set_default = FALSE; + optname = NULL; + moreopt = 0; + o = NULL; + + while (*s != '\0') { + /* + * Check some special cases first. + */ + switch (optc = *s++) { + case ' ': + case '\t': + case END_OPTION_STRING: + continue; + case '-': + /* + * "--" indicates an option name instead of a letter. + */ + if (*s == '-') { + if (!less_is_more) { + optname = ++s; + } + break; + } + /* + * "-+" means set these options back to their defaults. + * (They may have been set otherwise by previous + * options.) + */ + if (!less_is_more) { + set_default = (*s == '+'); + if (set_default) + s++; + } + continue; + case '+': + /* + * An option prefixed by a "+" is ungotten, so + * that it is interpreted as less commands + * processed at the start of the first input file. + * "++" means process the commands at the start of + * EVERY input file. + */ + plusoption = TRUE; + s = optstring(s, &str, propt('+'), NULL); + if (s == NULL) + return; + if (*str == '+') + every_first_cmd = estrdup(str+1); + else + ungetsc(str); + free(str); + continue; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* + * Special "more" compatibility form "-" + * instead of -z to set the scrolling + * window size. + */ + s--; + optc = 'z'; + moreopt = 1; + break; + case 'n': + if (less_is_more) { + moreopt = 1; + optc = 'z'; + } + break; + case 'i': + if (less_is_more) { + moreopt = 1; + optc = 'I'; + } + break; + case 'u': + if (less_is_more) { + moreopt = 1; + optc = 'U'; + } + break; + case 'e': + if (less_is_more) { + moreopt = 1; + optc = 'E'; + } + break; + case 'h': + if (less_is_more) { + moreopt = 1; + optc = '?'; + } + break; + case 'd': + if (less_is_more) { + moreopt = 1; + optc = 'M'; + } + break; + } + + /* + * Not a special case. + * Look up the option letter in the option table. + */ + err = 0; + if (optname == NULL) { + printopt = propt(optc); + lc = islower(optc); + o = findopt(optc); + if (less_is_more && (!moreopt) && (o != NULL) && + ((o->otype & MORE_OK) == 0)) { + o = NULL; + } + } else { + printopt = optname; + lc = islower(optname[0]); + o = findopt_name(&optname, NULL, &err); + s = optname; + optname = NULL; + switch (*s) { + case ' ': /* name matches exactly */ + case '\0': + break; + + case '=': /* name followed by "=value" */ + if (o != NULL && + (o->otype & OTYPE) != STRING && + (o->otype & OTYPE) != NUMBER) { + parg.p_string = printopt; + error("The %s option should not be " + "followed by =", &parg); + return; + } + s++; + break; + default: /* name longer than option, bad */ + o = NULL; + } + } + if (o == NULL) { + parg.p_string = printopt; + if (less_is_more) { + error("Illegal option %s (more -h for help)", + &parg); + } else if (err == OPT_AMBIG) { + error("%s is an ambiguous abbreviation " + "(\"less --help\" for help)", &parg); + } else { + error("There is no %s option " + "(\"less --help\" for help)", &parg); + } + return; + } + + str = NULL; + switch (o->otype & OTYPE) { + case BOOL: + if (set_default) + *(o->ovar) = o->odefault; + else + *(o->ovar) = ! o->odefault; + break; + case TRIPLE: + if (set_default) + *(o->ovar) = o->odefault; + else + *(o->ovar) = flip_triple(o->odefault, lc); + break; + case STRING: + if (*s == '\0') { + /* + * Set pendopt and return. + * We will get the string next time + * scan_option is called. + */ + pendopt = o; + return; + } + /* + * Don't do anything here. + * All processing of STRING options is done by + * the handling function. + */ + while (*s == ' ') + s++; + s = optstring(s, &str, printopt, o->odesc[1]); + if (s == NULL) + return; + break; + case NUMBER: + if (*s == '\0') { + pendopt = o; + return; + } + *(o->ovar) = getnum(&s, printopt, NULL); + break; + } + /* + * If the option has a handling function, call it. + */ + if (o->ofunc != NULL) + (*o->ofunc)(INIT, str); + free(str); + } +} + +/* + * Toggle command line flags from within the program. + * Used by the "-" and "_" commands. + * how_toggle may be: + * OPT_NO_TOGGLE just report the current setting, without changing it. + * OPT_TOGGLE invert the current setting + * OPT_UNSET set to the default value + * OPT_SET set to the inverse of the default value + */ +void +toggle_option(struct loption *o, int lower, char *s, int how_toggle) +{ + int num; + int no_prompt; + int err; + PARG parg; + + no_prompt = (how_toggle & OPT_NO_PROMPT); + how_toggle &= ~OPT_NO_PROMPT; + + if (o == NULL) { + error("No such option", NULL); + return; + } + + if (how_toggle == OPT_TOGGLE && (o->otype & NO_TOGGLE)) { + parg.p_string = opt_desc(o); + error("Cannot change the %s option", &parg); + return; + } + + if (how_toggle == OPT_NO_TOGGLE && (o->otype & NO_QUERY)) { + parg.p_string = opt_desc(o); + error("Cannot query the %s option", &parg); + return; + } + + /* + * Check for something which appears to be a do_toggle + * (because the "-" command was used), but really is not. + * This could be a string option with no string, or + * a number option with no number. + */ + switch (o->otype & OTYPE) { + case STRING: + case NUMBER: + if (how_toggle == OPT_TOGGLE && *s == '\0') + how_toggle = OPT_NO_TOGGLE; + break; + } + + if (how_toggle != OPT_NO_TOGGLE && (o->otype & HL_REPAINT)) + repaint_hilite(0); + + /* + * Now actually toggle (change) the variable. + */ + if (how_toggle != OPT_NO_TOGGLE) { + switch (o->otype & OTYPE) { + case BOOL: + /* + * Boolean. + */ + switch (how_toggle) { + case OPT_TOGGLE: + *(o->ovar) = ! *(o->ovar); + break; + case OPT_UNSET: + *(o->ovar) = o->odefault; + break; + case OPT_SET: + *(o->ovar) = ! o->odefault; + break; + } + break; + case TRIPLE: + /* + * Triple: + * If user gave the lower case letter, then switch + * to 1 unless already 1, in which case make it 0. + * If user gave the upper case letter, then switch + * to 2 unless already 2, in which case make it 0. + */ + switch (how_toggle) { + case OPT_TOGGLE: + *(o->ovar) = flip_triple(*(o->ovar), lower); + break; + case OPT_UNSET: + *(o->ovar) = o->odefault; + break; + case OPT_SET: + *(o->ovar) = flip_triple(o->odefault, lower); + break; + } + break; + case STRING: + /* + * String: don't do anything here. + * The handling function will do everything. + */ + switch (how_toggle) { + case OPT_SET: + case OPT_UNSET: + error("Cannot use \"-+\" or \"--\" " + "for a string option", NULL); + return; + } + break; + case NUMBER: + /* + * Number: set the variable to the given number. + */ + switch (how_toggle) { + case OPT_TOGGLE: + num = getnum(&s, NULL, &err); + if (!err) + *(o->ovar) = num; + break; + case OPT_UNSET: + *(o->ovar) = o->odefault; + break; + case OPT_SET: + error("Can't use \"-!\" for a numeric option", + NULL); + return; + } + break; + } + } + + /* + * Call the handling function for any special action + * specific to this option. + */ + if (o->ofunc != NULL) + (*o->ofunc)((how_toggle == OPT_NO_TOGGLE) ? QUERY : TOGGLE, s); + + if (how_toggle != OPT_NO_TOGGLE && (o->otype & HL_REPAINT)) + chg_hilite(); + + if (!no_prompt) { + /* + * Print a message describing the new setting. + */ + switch (o->otype & OTYPE) { + case BOOL: + case TRIPLE: + /* + * Print the odesc message. + */ + error(o->odesc[*(o->ovar)], NULL); + break; + case NUMBER: + /* + * The message is in odesc[1] and has a %d for + * the value of the variable. + */ + parg.p_int = *(o->ovar); + error(o->odesc[1], &parg); + break; + case STRING: + /* + * Message was already printed by the handling function. + */ + break; + } + } + + if (how_toggle != OPT_NO_TOGGLE && (o->otype & REPAINT)) + screen_trashed = TRUE; +} + +/* + * "Toggle" a triple-valued option. + */ +static int +flip_triple(int val, int lc) +{ + if (lc) + return ((val == OPT_ON) ? OPT_OFF : OPT_ON); + else + return ((val == OPT_ONPLUS) ? OPT_OFF : OPT_ONPLUS); +} + +/* + * Determine if an option takes a parameter. + */ +int +opt_has_param(struct loption *o) +{ + if (o == NULL) + return (0); + if (o->otype & (BOOL|TRIPLE|NOVAR|NO_TOGGLE)) + return (0); + return (1); +} + +/* + * Return the prompt to be used for a given option letter. + * Only string and number valued options have prompts. + */ +char * +opt_prompt(struct loption *o) +{ + if (o == NULL || (o->otype & (STRING|NUMBER)) == 0) + return ("?"); + return (o->odesc[0]); +} + +/* + * Return whether or not there is a string option pending; + * that is, if the previous option was a string-valued option letter + * (like -P) without a following string. + * In that case, the current option is taken to be the string for + * the previous option. + */ +int +isoptpending(void) +{ + return (pendopt != NULL); +} + +/* + * Print error message about missing string. + */ +static void +nostring(char *printopt) +{ + PARG parg; + parg.p_string = printopt; + error("Value is required after %s", &parg); +} + +/* + * Print error message if a STRING type option is not followed by a string. + */ +void +nopendopt(void) +{ + nostring(opt_desc(pendopt)); +} + +/* + * Scan to end of string or to an END_OPTION_STRING character. + * In the latter case, replace the char with a null char. + * Return a pointer to the remainder of the string, if any. + */ +static char * +optstring(char *s, char **p_str, char *printopt, char *validchars) +{ + char *p; + char *out; + + if (*s == '\0') { + nostring(printopt); + return (NULL); + } + /* Alloc could be more than needed, but not worth trimming. */ + *p_str = ecalloc(strlen(s)+1, sizeof (char)); + out = *p_str; + + for (p = s; *p != '\0'; p++) { + if (opt_use_backslash && *p == '\\' && p[1] != '\0') { + /* Take next char literally. */ + ++p; + } else { + if (*p == END_OPTION_STRING || + (validchars != NULL && + strchr(validchars, *p) == NULL)) + /* End of option string. */ + break; + } + *out++ = *p; + } + *out = '\0'; + return (p); +} + +/* + */ +static int +num_error(char *printopt, int *errp) +{ + PARG parg; + + if (errp != NULL) { + *errp = TRUE; + return (-1); + } + if (printopt != NULL) { + parg.p_string = printopt; + error("Number is required after %s", &parg); + } + return (-1); +} + +/* + * Translate a string into a number. + * Like atoi(), but takes a pointer to a char *, and updates + * the char * to point after the translated number. + */ +int +getnum(char **sp, char *printopt, int *errp) +{ + char *s; + int n; + int neg; + + s = skipsp(*sp); + neg = FALSE; + if (*s == '-') { + neg = TRUE; + s++; + } + if (*s < '0' || *s > '9') + return (num_error(printopt, errp)); + + n = 0; + while (*s >= '0' && *s <= '9') + n = 10 * n + *s++ - '0'; + *sp = s; + if (errp != NULL) + *errp = FALSE; + if (neg) + n = -n; + return (n); +} + +/* + * Translate a string into a fraction, represented by the part of a + * number which would follow a decimal point. + * The value of the fraction is returned as parts per NUM_FRAC_DENOM. + * That is, if "n" is returned, the fraction intended is n/NUM_FRAC_DENOM. + */ +long +getfraction(char **sp, char *printopt, int *errp) +{ + char *s; + long frac = 0; + int fraclen = 0; + + s = skipsp(*sp); + if (*s < '0' || *s > '9') + return (num_error(printopt, errp)); + + for (; *s >= '0' && *s <= '9'; s++) { + frac = (frac * 10) + (*s - '0'); + fraclen++; + } + if (fraclen > NUM_LOG_FRAC_DENOM) + while (fraclen-- > NUM_LOG_FRAC_DENOM) + frac /= 10; + else + while (fraclen++ < NUM_LOG_FRAC_DENOM) + frac *= 10; + *sp = s; + if (errp != NULL) + *errp = FALSE; + return (frac); +} + + +/* + * Get the value of the -e flag. + */ +int +get_quit_at_eof(void) +{ + return (quit_at_eof); +} diff --git a/bin/less/option.h b/bin/less/option.h new file mode 100644 index 0000000000..a0505df31c --- /dev/null +++ b/bin/less/option.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#define END_OPTION_STRING ('$') + +/* + * Types of options. + */ +#define BOOL 01 /* Boolean option: 0 or 1 */ +#define TRIPLE 02 /* Triple-valued option: 0, 1 or 2 */ +#define NUMBER 04 /* Numeric option */ +#define STRING 010 /* String-valued option */ +#define NOVAR 020 /* No associated variable */ +#define REPAINT 040 /* Repaint screen after toggling option */ +#define NO_TOGGLE 0100 /* Option cannot be toggled with "-" cmd */ +#define HL_REPAINT 0200 /* Repaint hilites after toggling option */ +#define NO_QUERY 0400 /* Option cannot be queried with "_" cmd */ +#define INIT_HANDLER 01000 /* Call option handler function at startup */ +#define MORE_OK 02000 /* Allow when less_is_more */ + +#define OTYPE (BOOL|TRIPLE|NUMBER|STRING|NOVAR) + +#define OLETTER_NONE '\1' /* Invalid option letter */ + +/* + * Argument to a handling function tells what type of activity: + */ +#define INIT 0 /* Initialization (from command line) */ +#define QUERY 1 /* Query (from _ or - command) */ +#define TOGGLE 2 /* Change value (from - command) */ + +/* Flag to toggle_option to specify how to "toggle" */ +#define OPT_NO_TOGGLE 0 +#define OPT_TOGGLE 1 +#define OPT_UNSET 2 +#define OPT_SET 3 +#define OPT_NO_PROMPT 0100 + +/* Error code from findopt_name */ +#define OPT_AMBIG 1 + +struct optname { + char *oname; /* Long (GNU-style) option name */ + struct optname *onext; /* List of synonymous option names */ +}; + +#define OPTNAME_MAX 32 /* Max length of long option name */ + +struct loption { + char oletter; /* The controlling letter (a-z) */ + struct optname *onames; /* Long (GNU-style) option name */ + int otype; /* Type of the option */ + int odefault; /* Default value */ + int *ovar; /* Pointer to the associated variable */ + void (*ofunc)(int, char *); /* Pointer to special handling function */ + char *odesc[3]; /* Description of each value */ +}; diff --git a/bin/less/opttbl.c b/bin/less/opttbl.c new file mode 100644 index 0000000000..483f4c1669 --- /dev/null +++ b/bin/less/opttbl.c @@ -0,0 +1,556 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * The option table. + */ + +#include "less.h" +#include "option.h" + +/* + * Variables controlled by command line options. + */ +int quiet; /* Should we suppress the audible bell? */ +int how_search; /* Where should forward searches start? */ +int top_scroll; /* Repaint screen from top? (vs scroll from bottom) */ +int pr_type; /* Type of prompt (short, medium, long) */ +int bs_mode; /* How to process backspaces */ +int know_dumb; /* Don't complain about dumb terminals */ +int quit_at_eof; /* Quit after hitting end of file twice */ +int quit_if_one_screen; /* Quit if EOF on first screen */ +int squeeze; /* Squeeze multiple blank lines into one */ +int back_scroll; /* Repaint screen on backwards movement */ +int forw_scroll; /* Repaint screen on forward movement */ +int caseless; /* Do "caseless" searches */ +int linenums; /* Use line numbers */ +int autobuf; /* Automatically allocate buffers as needed */ +int bufspace; /* Max buffer space per file (K) */ +int ctldisp; /* Send control chars to screen untranslated */ +int force_open; /* Open the file even if not regular file */ +int swindow; /* Size of scrolling window */ +int jump_sline; /* Screen line of "jump target" */ +long jump_sline_fraction = -1; +int chopline; /* Truncate displayed lines at screen width */ +int no_init; /* Disable sending ti/te termcap strings */ +int no_keypad; /* Disable sending ks/ke termcap strings */ +int twiddle; /* Show tildes after EOF */ +int show_attn; /* Hilite first unread line */ +int status_col; /* Display a status column */ +int use_lessopen; /* Use the LESSOPEN filter */ +int quit_on_intr; /* Quit on interrupt */ +int follow_mode; /* F cmd Follows file desc or file name? */ +int oldbot; /* Old bottom of screen behavior {{REMOVE}} */ +int opt_use_backslash; /* Use backslash escaping in option parsing */ +int hilite_search; /* Highlight matched search patterns? */ + +int less_is_more = 0; /* Make compatible with POSIX more */ + +/* + * Long option names. + */ +static struct optname a_optname = { "search-skip-screen", NULL }; +static struct optname b_optname = { "buffers", NULL }; +static struct optname B__optname = { "auto-buffers", NULL }; +static struct optname c_optname = { "clear-screen", NULL }; +static struct optname d_optname = { "dumb", NULL }; +static struct optname e_optname = { "quit-at-eof", NULL }; +static struct optname f_optname = { "force", NULL }; +static struct optname F__optname = { "quit-if-one-screen", NULL }; +static struct optname g_optname = { "hilite-search", NULL }; +static struct optname h_optname = { "max-back-scroll", NULL }; +static struct optname i_optname = { "ignore-case", NULL }; +static struct optname j_optname = { "jump-target", NULL }; +static struct optname J__optname = { "status-column", NULL }; +static struct optname k_optname = { "lesskey-file", NULL }; +static struct optname K__optname = { "quit-on-intr", NULL }; +static struct optname L__optname = { "no-lessopen", NULL }; +static struct optname m_optname = { "long-prompt", NULL }; +static struct optname n_optname = { "line-numbers", NULL }; +static struct optname o_optname = { "log-file", NULL }; +static struct optname O__optname = { "LOG-FILE", NULL }; +static struct optname p_optname = { "pattern", NULL }; +static struct optname P__optname = { "prompt", NULL }; +static struct optname q2_optname = { "silent", NULL }; +static struct optname q_optname = { "quiet", &q2_optname }; +static struct optname r_optname = { "raw-control-chars", NULL }; +static struct optname s_optname = { "squeeze-blank-lines", NULL }; +static struct optname S__optname = { "chop-long-lines", NULL }; +static struct optname t_optname = { "tag", NULL }; +static struct optname T__optname = { "tag-file", NULL }; +static struct optname u_optname = { "underline-special", NULL }; +static struct optname V__optname = { "version", NULL }; +static struct optname w_optname = { "hilite-unread", NULL }; +static struct optname x_optname = { "tabs", NULL }; +static struct optname X__optname = { "no-init", NULL }; +static struct optname y_optname = { "max-forw-scroll", NULL }; +static struct optname z_optname = { "window", NULL }; +static struct optname quote_optname = { "quotes", NULL }; +static struct optname tilde_optname = { "tilde", NULL }; +static struct optname query_optname = { "help", NULL }; +static struct optname pound_optname = { "shift", NULL }; +static struct optname keypad_optname = { "no-keypad", NULL }; +static struct optname oldbot_optname = { "old-bot", NULL }; +static struct optname follow_optname = { "follow-name", NULL }; +static struct optname use_backslash_optname = { "use-backslash", NULL }; + + +/* + * Table of all options and their semantics. + * + * For BOOL and TRIPLE options, odesc[0], odesc[1], odesc[2] are + * the description of the option when set to 0, 1 or 2, respectively. + * For NUMBER options, odesc[0] is the prompt to use when entering + * a new value, and odesc[1] is the description, which should contain + * one %d which is replaced by the value of the number. + * For STRING options, odesc[0] is the prompt to use when entering + * a new value, and odesc[1], if not NULL, is the set of characters + * that are valid in the string. + */ +static struct loption option[] = { + { 'a', &a_optname, + TRIPLE, OPT_ONPLUS, &how_search, NULL, + { + "Search includes displayed screen", + "Search skips displayed screen", + "Search includes all of displayed screen" + } + }, + + { 'b', &b_optname, + NUMBER|INIT_HANDLER, 64, &bufspace, opt_b, + { + "Max buffer space per file (K): ", + "Max buffer space per file: %dK", + NULL + } + }, + { 'B', &B__optname, + BOOL, OPT_ON, &autobuf, NULL, + { + "Don't automatically allocate buffers", + "Automatically allocate buffers when needed", + NULL + } + }, + { 'c', &c_optname, + TRIPLE|MORE_OK, OPT_ON, &top_scroll, NULL, + { + "Repaint by scrolling from bottom of screen", + "Repaint by painting from top of screen", + "Repaint by painting from top of screen" + } + }, + { 'd', &d_optname, + BOOL|MORE_OK|NO_TOGGLE, OPT_OFF, &know_dumb, NULL, + { + "Assume intelligent terminal", + "Assume dumb terminal", + NULL + } + }, + { 'e', &e_optname, + TRIPLE, OPT_OFF, &quit_at_eof, NULL, + { + "Don't quit at end-of-file", + "Quit at end-of-file", + "Quit immediately at end-of-file" + } + }, + { 'f', &f_optname, + BOOL, OPT_OFF, &force_open, NULL, + { + "Open only regular files", + "Open even non-regular files", + NULL + } + }, + { 'F', &F__optname, + BOOL, OPT_OFF, &quit_if_one_screen, NULL, + { + "Don't quit if end-of-file on first screen", + "Quit if end-of-file on first screen", + NULL + } + }, + { 'g', &g_optname, + TRIPLE|HL_REPAINT, OPT_ONPLUS, &hilite_search, NULL, + { + "Don't highlight search matches", + "Highlight matches for previous search only", + "Highlight all matches for previous search pattern", + } + }, + { 'h', &h_optname, + NUMBER, -1, &back_scroll, NULL, + { + "Backwards scroll limit: ", + "Backwards scroll limit is %d lines", + NULL + } + }, + { 'i', &i_optname, + TRIPLE|HL_REPAINT, OPT_OFF, &caseless, opt_i, + { + "Case is significant in searches", + "Ignore case in searches", + "Ignore case in searches and in patterns" + } + }, + { 'j', &j_optname, + STRING, 0, NULL, opt_j, + { + "Target line: ", + "0123456789.-", + NULL + } + }, + { 'J', &J__optname, + BOOL|REPAINT, OPT_OFF, &status_col, NULL, + { + "Don't display a status column", + "Display a status column", + NULL + } + }, + { 'k', &k_optname, + STRING|NO_TOGGLE|NO_QUERY, 0, NULL, opt_k, + { NULL, NULL, NULL } + }, + { 'K', &K__optname, + BOOL, OPT_OFF, &quit_on_intr, NULL, + { + "Interrupt (ctrl-C) returns to prompt", + "Interrupt (ctrl-C) exits less", + NULL + } + }, + { 'L', &L__optname, + BOOL, OPT_ON, &use_lessopen, NULL, + { + "Don't use the LESSOPEN filter", + "Use the LESSOPEN filter", + NULL + } + }, + { 'm', &m_optname, + TRIPLE, OPT_OFF, &pr_type, NULL, + { + "Short prompt", + "Medium prompt", + "Long prompt" + } + }, + { 'n', &n_optname, + TRIPLE|REPAINT, OPT_ON, &linenums, NULL, + { + "Don't use line numbers", + "Use line numbers", + "Constantly display line numbers" + } + }, + { 'o', &o_optname, + STRING, 0, NULL, opt_o, + { "log file: ", NULL, NULL } + }, + { 'O', &O__optname, + STRING, 0, NULL, opt__O, + { "Log file: ", NULL, NULL } + }, + { 'p', &p_optname, + STRING|NO_TOGGLE|NO_QUERY|MORE_OK, 0, NULL, opt_p, + { NULL, NULL, NULL } + }, + { 'P', &P__optname, + STRING, 0, NULL, opt__P, + { "prompt: ", NULL, NULL } + }, + { 'q', &q_optname, + TRIPLE, OPT_OFF, &quiet, NULL, + { + "Ring the bell for errors AND at eof/bof", + "Ring the bell for errors but not at eof/bof", + "Never ring the bell" + } + }, + { 'r', &r_optname, + TRIPLE|REPAINT, OPT_OFF, &ctldisp, NULL, + { + "Display control characters as ^X", + "Display control characters directly", + "Display control characters directly, " + "processing ANSI sequences" + } + }, + { 's', &s_optname, + BOOL|REPAINT|MORE_OK, OPT_OFF, &squeeze, NULL, + { + "Display all blank lines", + "Squeeze multiple blank lines", + NULL + } + }, + { 'S', &S__optname, + BOOL|REPAINT, OPT_OFF, &chopline, NULL, + { + "Fold long lines", + "Chop long lines", + NULL + } + }, + { 't', &t_optname, + STRING|NO_QUERY|MORE_OK, 0, NULL, opt_t, + { "tag: ", NULL, NULL } + }, + { 'T', &T__optname, + STRING|MORE_OK, 0, NULL, opt__T, + { "tags file: ", NULL, NULL } + }, + { 'u', &u_optname, + TRIPLE|REPAINT, OPT_OFF, &bs_mode, NULL, + { + "Display underlined text in underline mode", + "Backspaces cause overstrike", + "Print backspace as ^H" + } + }, + { 'V', &V__optname, + NOVAR, 0, NULL, opt__V, + { NULL, NULL, NULL } + }, + { 'w', &w_optname, + TRIPLE|REPAINT, OPT_OFF, &show_attn, NULL, + { + "Don't highlight first unread line", + "Highlight first unread line after forward-screen", + "Highlight first unread line after any " + "forward movement", + } + }, + { 'x', &x_optname, + STRING|REPAINT, 0, NULL, opt_x, + { + "Tab stops: ", + "0123456789,", + NULL + } + }, + { 'X', &X__optname, + BOOL|NO_TOGGLE, OPT_OFF, &no_init, NULL, + { + "Send init/deinit strings to terminal", + "Don't use init/deinit strings", + NULL + } + }, + { 'y', &y_optname, + NUMBER, -1, &forw_scroll, NULL, + { + "Forward scroll limit: ", + "Forward scroll limit is %d lines", + NULL + } + }, + { 'z', &z_optname, + NUMBER, -1, &swindow, NULL, + { + "Scroll window size: ", + "Scroll window size is %d lines", + NULL + } + }, + { '"', "e_optname, + STRING, 0, NULL, opt_quote, + { "quotes: ", NULL, NULL } + }, + { '~', &tilde_optname, + BOOL|REPAINT, OPT_ON, &twiddle, NULL, + { + "Don't show tildes after end of file", + "Show tildes after end of file", + NULL + } + }, + { '?', &query_optname, + NOVAR, 0, NULL, opt_query, + { NULL, NULL, NULL } + }, + { '#', £_optname, + STRING, 0, NULL, opt_shift, + { + "Horizontal shift: ", + "0123456789.", + NULL + } + }, + { OLETTER_NONE, &keypad_optname, + BOOL|NO_TOGGLE, OPT_OFF, &no_keypad, NULL, + { + "Use keypad mode", + "Don't use keypad mode", + NULL + } + }, + { OLETTER_NONE, &oldbot_optname, + BOOL, OPT_OFF, &oldbot, NULL, + { + "Use new bottom of screen behavior", + "Use old bottom of screen behavior", + NULL + } + }, + { OLETTER_NONE, &follow_optname, + BOOL, FOLLOW_DESC, &follow_mode, NULL, + { + "F command follows file descriptor", + "F command follows file name", + NULL + } + }, + { OLETTER_NONE, &use_backslash_optname, + BOOL, OPT_OFF, &opt_use_backslash, NULL, + { + "Use backslash escaping in command line parameters", + "Don't use backslash escaping in command line " + "parameters", + NULL + } + }, + { '\0', NULL, NOVAR, 0, NULL, NULL, { NULL, NULL, NULL } } +}; + + +/* + * Initialize each option to its default value. + */ +void +init_option(void) +{ + struct loption *o; + + for (o = option; o->oletter != '\0'; o++) { + /* + * Set each variable to its default. + */ + if (o->ovar != NULL) + *(o->ovar) = o->odefault; + if (o->otype & INIT_HANDLER) + (*(o->ofunc))(INIT, NULL); + } +} + +/* + * Find an option in the option table, given its option letter. + */ +struct loption * +findopt(int c) +{ + struct loption *o; + + for (o = option; o->oletter != '\0'; o++) { + if (o->oletter == c) + return (o); + if ((o->otype & TRIPLE) && + (toupper((unsigned char)o->oletter) == c)) + return (o); + } + return (NULL); +} + +/* + * + */ +static int +is_optchar(unsigned char c) +{ + if (isupper(c) || islower(c) || c == '-') + return (1); + else + return (0); +} + +/* + * Find an option in the option table, given its option name. + * p_optname is the (possibly partial) name to look for, and + * is updated to point after the matched name. + * p_oname if non-NULL is set to point to the full option name. + */ +struct loption * +findopt_name(char **p_optname, char **p_oname, int *p_err) +{ + char *optname = *p_optname; + struct loption *o; + struct optname *oname; + int len; + int uppercase; + struct loption *maxo = NULL; + struct optname *maxoname = NULL; + int maxlen = 0; + int ambig = 0; + int exact = 0; + + /* + * Check all options. + */ + for (o = option; o->oletter != '\0'; o++) { + /* + * Check all names for this option. + */ + for (oname = o->onames; oname != NULL; oname = oname->onext) { + /* + * Try normal match first (uppercase == 0), + * then, then if it's a TRIPLE option, + * try uppercase match (uppercase == 1). + */ + for (uppercase = 0; uppercase <= 1; uppercase++) { + len = sprefix(optname, oname->oname, uppercase); + if (len <= 0 || is_optchar(optname[len])) { + /* + * We didn't use all of the option name. + */ + continue; + } + if (!exact && len == maxlen) { + /* + * Already had a partial match, + * and now there's another one that + * matches the same length. + */ + ambig = 1; + } else if (len > maxlen) { + /* + * Found a better match than + * the one we had. + */ + maxo = o; + maxoname = oname; + maxlen = len; + ambig = 0; + exact = (len == strlen(oname->oname)); + } + if (!(o->otype & TRIPLE)) + break; + } + } + } + if (ambig) { + /* + * Name matched more than one option. + */ + if (p_err != NULL) + *p_err = OPT_AMBIG; + return (NULL); + } + *p_optname = optname + maxlen; + if (p_oname != NULL) + *p_oname = maxoname == NULL ? NULL : maxoname->oname; + return (maxo); +} diff --git a/bin/less/os.c b/bin/less/os.c new file mode 100644 index 0000000000..bb8b483a2d --- /dev/null +++ b/bin/less/os.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Operating system dependent routines. + * + * Most of the stuff in here is based on Unix, but an attempt + * has been made to make things work on other operating systems. + * This will sometimes result in a loss of functionality, unless + * someone rewrites code specifically for the new operating system. + * + * The makefile provides defines to decide whether various + * Unix features are present. + */ + +#include +#include +#include + +#include "less.h" + +extern volatile sig_atomic_t sigs; + +/* + * Like read() system call, but is deliberately interruptible. + */ +int +iread(int fd, unsigned char *buf, unsigned int len) +{ + int n; + +start: + flush(0); + n = read(fd, buf, len); + if (n < 0) { + /* + * Certain values of errno indicate we should just retry the + * read. + */ + if (errno == EINTR) + return (READ_INTR); + if (errno == EAGAIN) + goto start; + return (-1); + } + return (n); +} + +/* + * errno_message: Return an error message based on the value of "errno". + */ +char * +errno_message(char *filename) +{ + return (easprintf("%s: %s", filename, strerror(errno))); +} + +static off_t +muldiv(off_t val, off_t num, off_t den) +{ + double v = (((double)val) * num) / den; + return ((off_t)(v + 0.5)); +} + +/* + * Return the ratio of two off_t, as a percentage. + * {{ Assumes a off_t is a long int. }} + */ +int +percentage(off_t num, off_t den) +{ + return ((int)muldiv(num, (off_t)100, den)); +} + +/* + * Return the specified percentage of a off_t. + */ +off_t +percent_pos(off_t pos, int percent, long fraction) +{ + /* + * Change percent (parts per 100) to perden + * (parts per NUM_FRAC_DENOM). + */ + off_t perden = (percent * (NUM_FRAC_DENOM / 100)) + (fraction / 100); + + if (perden == 0) + return (0); + return (muldiv(pos, perden, (off_t)NUM_FRAC_DENOM)); +} diff --git a/bin/less/output.c b/bin/less/output.c new file mode 100644 index 0000000000..bafe48b814 --- /dev/null +++ b/bin/less/output.c @@ -0,0 +1,330 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * High level routines dealing with the output to the screen. + */ + +#include "less.h" + +int errmsgs; /* Count of messages displayed by error() */ + +extern volatile sig_atomic_t sigs; +extern int sc_width; +extern int so_s_width, so_e_width; +extern int screen_trashed; +extern int any_display; +extern int is_tty; +extern int oldbot; + +static int need_clr; + +/* + * Display the line which is in the line buffer. + */ +void +put_line(void) +{ + int c; + int i; + int a; + + if (ABORT_SIGS()) { + /* + * Don't output if a signal is pending. + */ + screen_trashed = 1; + return; + } + + for (i = 0; (c = gline(i, &a)) != '\0'; i++) { + at_switch(a); + if (c == '\b') + putbs(); + else + (void) putchr(c); + } + + at_exit(); +} + +static char obuf[OUTBUF_SIZE]; +static char *ob = obuf; + +/* + * Flush buffered output. + * + * If we haven't displayed any file data yet, + * output messages on error output (file descriptor 2), + * otherwise output on standard output (file descriptor 1). + * + * This has the desirable effect of producing all + * error messages on error output if standard output + * is directed to a file. It also does the same if + * we never produce any real output; for example, if + * the input file(s) cannot be opened. If we do + * eventually produce output, code in edit() makes + * sure these messages can be seen before they are + * overwritten or scrolled away. + */ +void +flush(int ignore_errors) +{ + int n; + int fd; + ssize_t nwritten; + + n = (intptr_t)ob - (intptr_t)obuf; + if (n == 0) + return; + + fd = (any_display) ? STDOUT_FILENO : STDERR_FILENO; + nwritten = write(fd, obuf, n); + if (nwritten != n) { + if (nwritten == -1 && !ignore_errors) + quit(QUIT_ERROR); + screen_trashed = 1; + } + ob = obuf; +} + +/* + * Output a character. + */ +int +putchr(int c) +{ + if (need_clr) { + need_clr = 0; + clear_bot(); + } + /* + * Some versions of flush() write to *ob, so we must flush + * when we are still one char from the end of obuf. + */ + if (ob >= &obuf[sizeof (obuf)-1]) + flush(0); + *ob++ = (char)c; + return (c); +} + +/* + * Output a string. + */ +void +putstr(const char *s) +{ + while (*s != '\0') + (void) putchr(*s++); +} + + +/* + * Convert an integral type to a string. + */ +#define TYPE_TO_A_FUNC(funcname, type) \ +void \ +funcname(type num, char *buf, size_t len) \ +{ \ + int neg = (num < 0); \ + char tbuf[23]; \ + char *s = tbuf + sizeof (tbuf); \ + if (neg) \ + num = -num; \ + *--s = '\0'; \ + do { \ + *--s = (num % 10) + '0'; \ + } while ((num /= 10) != 0); \ + if (neg) \ + *--s = '-'; \ + (void) strlcpy(buf, s, len); \ +} + +TYPE_TO_A_FUNC(postoa, off_t) +TYPE_TO_A_FUNC(inttoa, int) + +/* + * Output an integer in a given radix. + */ +static int +iprint_int(int num) +{ + char buf[11]; + + inttoa(num, buf, sizeof (buf)); + putstr(buf); + return (strlen(buf)); +} + +/* + * Output a line number in a given radix. + */ +static int +iprint_linenum(off_t num) +{ + char buf[21]; + + postoa(num, buf, sizeof(buf)); + putstr(buf); + return (strlen(buf)); +} + +/* + * This function implements printf-like functionality + * using a more portable argument list mechanism than printf's. + */ +static int +less_printf(const char *fmt, PARG *parg) +{ + char *s; + int col; + + col = 0; + while (*fmt != '\0') { + if (*fmt != '%') { + (void) putchr(*fmt++); + col++; + } else { + ++fmt; + switch (*fmt++) { + case 's': + s = parg->p_string; + parg++; + while (*s != '\0') { + (void) putchr(*s++); + col++; + } + break; + case 'd': + col += iprint_int(parg->p_int); + parg++; + break; + case 'n': + col += iprint_linenum(parg->p_linenum); + parg++; + break; + } + } + } + return (col); +} + +/* + * Get a RETURN. + * If some other non-trivial char is pressed, unget it, so it will + * become the next command. + */ +void +get_return(void) +{ + int c; + + c = getchr(); + if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR) + ungetcc(c); +} + +/* + * Output a message in the lower left corner of the screen + * and wait for carriage return. + */ +void +error(const char *fmt, PARG *parg) +{ + int col = 0; + static char return_to_continue[] = " (press RETURN)"; + + errmsgs++; + + if (any_display && is_tty) { + if (!oldbot) + squish_check(); + at_exit(); + clear_bot(); + at_enter(AT_STANDOUT); + col += so_s_width; + } + + col += less_printf(fmt, parg); + + if (!(any_display && is_tty)) { + (void) putchr('\n'); + return; + } + + putstr(return_to_continue); + at_exit(); + col += sizeof (return_to_continue) + so_e_width; + + get_return(); + lower_left(); + clear_eol(); + + if (col >= sc_width) + /* + * Printing the message has probably scrolled the screen. + * {{ Unless the terminal doesn't have auto margins, + * in which case we just hammered on the right margin. }} + */ + screen_trashed = 1; + + flush(0); +} + +static char intr_to_abort[] = "... (interrupt to abort)"; + +/* + * Output a message in the lower left corner of the screen + * and don't wait for carriage return. + * Usually used to warn that we are beginning a potentially + * time-consuming operation. + */ +void +ierror(const char *fmt, PARG *parg) +{ + at_exit(); + clear_bot(); + at_enter(AT_STANDOUT); + (void) less_printf(fmt, parg); + putstr(intr_to_abort); + at_exit(); + flush(0); + need_clr = 1; +} + +/* + * Output a message in the lower left corner of the screen + * and return a single-character response. + */ +int +query(const char *fmt, PARG *parg) +{ + int c; + int col = 0; + + if (any_display && is_tty) + clear_bot(); + + (void) less_printf(fmt, parg); + c = getchr(); + + if (!(any_display && is_tty)) { + (void) putchr('\n'); + return (c); + } + + lower_left(); + if (col >= sc_width) + screen_trashed = 1; + flush(0); + + return (c); +} diff --git a/bin/less/pattern.c b/bin/less/pattern.c new file mode 100644 index 0000000000..6e98ff28b0 --- /dev/null +++ b/bin/less/pattern.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to do pattern matching. + */ + +#include "less.h" +#include "pattern.h" + +extern int caseless; +extern int less_is_more; + +/* + * Compile a search pattern, for future use by match_pattern. + */ +static int +compile_pattern2(char *pattern, int search_type, regex_t **comp_pattern) +{ + regex_t *comp; + + if (search_type & SRCH_NO_REGEX) + return (0); + comp = ecalloc(1, sizeof (regex_t)); + if (regcomp(comp, pattern, less_is_more ? 0 : REGCOMP_FLAG)) { + free(comp); + error("Invalid pattern", NULL); + return (-1); + } + if (*comp_pattern != NULL) + regfree(*comp_pattern); + *comp_pattern = comp; + return (0); +} + +/* + * Like compile_pattern2, but convert the pattern to lowercase if necessary. + */ +int +compile_pattern(char *pattern, int search_type, regex_t **comp_pattern) +{ + char *cvt_pattern; + int result; + + if (caseless != OPT_ONPLUS) { + cvt_pattern = pattern; + } else { + cvt_pattern = ecalloc(1, cvt_length(strlen(pattern))); + cvt_text(cvt_pattern, pattern, NULL, NULL, CVT_TO_LC); + } + result = compile_pattern2(cvt_pattern, search_type, comp_pattern); + if (cvt_pattern != pattern) + free(cvt_pattern); + return (result); +} + +/* + * Forget that we have a compiled pattern. + */ +void +uncompile_pattern(regex_t **pattern) +{ + if (*pattern != NULL) + regfree(*pattern); + *pattern = NULL; +} + +/* + * Simple pattern matching function. + * It supports no metacharacters like *, etc. + */ +static int +match(char *pattern, int pattern_len, char *buf, int buf_len, + char **pfound, char **pend) +{ + char *pp, *lp; + char *pattern_end = pattern + pattern_len; + char *buf_end = buf + buf_len; + + for (; buf < buf_end; buf++) { + for (pp = pattern, lp = buf; *pp == *lp; pp++, lp++) + if (pp == pattern_end || lp == buf_end) + break; + if (pp == pattern_end) { + if (pfound != NULL) + *pfound = buf; + if (pend != NULL) + *pend = lp; + return (1); + } + } + return (0); +} + +/* + * Perform a pattern match with the previously compiled pattern. + * Set sp and ep to the start and end of the matched string. + */ +int +match_pattern(void *pattern, char *tpattern, char *line, int line_len, + char **sp, char **ep, int notbol, int search_type) +{ + int matched; + regex_t *spattern = (regex_t *)pattern; + + if (search_type & SRCH_NO_REGEX) { + matched = match(tpattern, strlen(tpattern), line, line_len, + sp, ep); + } else { + regmatch_t rm; + int flags = (notbol) ? REG_NOTBOL : 0; +#ifdef REG_STARTEND + flags |= REG_STARTEND; + rm.rm_so = 0; + rm.rm_eo = line_len; +#endif + *sp = NULL; + *ep = NULL; + matched = !regexec(spattern, line, 1, &rm, flags); + if (matched) { + *sp = line + rm.rm_so; + *ep = line + rm.rm_eo; + } + } + matched = (!(search_type & SRCH_NO_MATCH) && matched) || + ((search_type & SRCH_NO_MATCH) && !matched); + return (matched); +} diff --git a/bin/less/pattern.h b/bin/less/pattern.h new file mode 100644 index 0000000000..7e8c327a9c --- /dev/null +++ b/bin/less/pattern.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#include +#define REGCOMP_FLAG REG_EXTENDED +#define DEFINE_PATTERN(name) regex_t *name +#define CLEAR_PATTERN(name) name = NULL diff --git a/bin/less/position.c b/bin/less/position.c new file mode 100644 index 0000000000..7a7b5c5e7d --- /dev/null +++ b/bin/less/position.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines dealing with the "position" table. + * This is a table which tells the position (in the input file) of the + * first char on each currently displayed line. + * + * {{ The position table is scrolled by moving all the entries. + * Would be better to have a circular table + * and just change a couple of pointers. }} + */ + +#include "less.h" +#include "position.h" + +static off_t *table = NULL; /* The position table */ +static int table_size; + +extern int sc_width, sc_height; + +/* + * Return the starting file position of a line displayed on the screen. + * The line may be specified as a line number relative to the top + * of the screen, but is usually one of these special cases: + * the top (first) line on the screen + * the second line on the screen + * the bottom line on the screen + * the line after the bottom line on the screen + */ +off_t +position(int where) +{ + switch (where) { + case BOTTOM: + where = sc_height - 2; + break; + case BOTTOM_PLUS_ONE: + where = sc_height - 1; + break; + case MIDDLE: + where = (sc_height - 1) / 2; + } + return (table[where]); +} + +/* + * Add a new file position to the bottom of the position table. + */ +void +add_forw_pos(off_t pos) +{ + int i; + + /* + * Scroll the position table up. + */ + for (i = 1; i < sc_height; i++) + table[i-1] = table[i]; + table[sc_height - 1] = pos; +} + +/* + * Add a new file position to the top of the position table. + */ +void +add_back_pos(off_t pos) +{ + int i; + + /* + * Scroll the position table down. + */ + for (i = sc_height - 1; i > 0; i--) + table[i] = table[i-1]; + table[0] = pos; +} + +/* + * Initialize the position table, done whenever we clear the screen. + */ +void +pos_clear(void) +{ + int i; + + for (i = 0; i < sc_height; i++) + table[i] = -1; +} + +/* + * Allocate or reallocate the position table. + */ +void +pos_init(void) +{ + struct scrpos scrpos; + + if (sc_height <= table_size) + return; + /* + * If we already have a table, remember the first line in it + * before we free it, so we can copy that line to the new table. + */ + if (table != NULL) { + get_scrpos(&scrpos); + free(table); + } else { + scrpos.pos = -1; + } + table = ecalloc(sc_height, sizeof (off_t)); + table_size = sc_height; + pos_clear(); + if (scrpos.pos != -1) + table[scrpos.ln-1] = scrpos.pos; +} + +/* + * See if the byte at a specified position is currently on the screen. + * Check the position table to see if the position falls within its range. + * Return the position table entry if found, -1 if not. + */ +int +onscreen(off_t pos) +{ + int i; + + if (pos < table[0]) + return (-1); + for (i = 1; i < sc_height; i++) + if (pos < table[i]) + return (i-1); + return (-1); +} + +/* + * See if the entire screen is empty. + */ +int +empty_screen(void) +{ + return (empty_lines(0, sc_height-1)); +} + +int +empty_lines(int s, int e) +{ + int i; + + for (i = s; i <= e; i++) + if (table[i] != -1 && table[i] != 0) + return (0); + return (1); +} + +/* + * Get the current screen position. + * The screen position consists of both a file position and + * a screen line number where the file position is placed on the screen. + * Normally the screen line number is 0, but if we are positioned + * such that the top few lines are empty, we may have to set + * the screen line to a number > 0. + */ +void +get_scrpos(struct scrpos *scrpos) +{ + int i; + + /* + * Find the first line on the screen which has something on it, + * and return the screen line number and the file position. + */ + for (i = 0; i < sc_height; i++) + if (table[i] != -1) { + scrpos->ln = i+1; + scrpos->pos = table[i]; + return; + } + /* + * The screen is empty. + */ + scrpos->pos = -1; +} + +/* + * Adjust a screen line number to be a simple positive integer + * in the range { 0 .. sc_height-2 }. + * (The bottom line, sc_height-1, is reserved for prompts, etc.) + * The given "sline" may be in the range { 1 .. sc_height-1 } + * to refer to lines relative to the top of the screen (starting from 1), + * or it may be in { -1 .. -(sc_height-1) } to refer to lines + * relative to the bottom of the screen. + */ +int +adjsline(int sline) +{ + /* + * Negative screen line number means + * relative to the bottom of the screen. + */ + if (sline < 0) + sline += sc_height; + /* + * Can't be less than 1 or greater than sc_height-1. + */ + if (sline <= 0) + sline = 1; + if (sline >= sc_height) + sline = sc_height - 1; + /* + * Return zero-based line number, not one-based. + */ + return (sline-1); +} diff --git a/bin/less/position.h b/bin/less/position.h new file mode 100644 index 0000000000..37967ffc2c --- /dev/null +++ b/bin/less/position.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Include file for interfacing to position.c modules. + */ +#define TOP (0) +#define TOP_PLUS_ONE (1) +#define BOTTOM (-1) +#define BOTTOM_PLUS_ONE (-2) +#define MIDDLE (-3) diff --git a/bin/less/prompt.c b/bin/less/prompt.c new file mode 100644 index 0000000000..be343ba6d8 --- /dev/null +++ b/bin/less/prompt.c @@ -0,0 +1,528 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Prompting and other messages. + * There are three flavors of prompts, SHORT, MEDIUM and LONG, + * selected by the -m/-M options. + * There is also the "equals message", printed by the = command. + * A prompt is a message composed of various pieces, such as the + * name of the file being viewed, the percentage into the file, etc. + */ + +#include "less.h" +#include "position.h" + +extern int pr_type; +extern int new_file; +extern int sc_width; +extern int so_s_width, so_e_width; +extern int linenums; +extern int hshift; +extern int sc_height; +extern int jump_sline; +extern int less_is_more; +extern IFILE curr_ifile; +extern char *editor; +extern char *editproto; + +/* + * Prototypes for the three flavors of prompts. + * These strings are expanded by pr_expand(). + */ +static const char s_proto[] = + "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t"; +static const char m_proto[] = + "?n?f%f .?m(%T %i of %m) ..?e(END) " + "?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; +static const char M_proto[] = + "?f%f .?n?m(%T %i of %m) ..?" + "ltlines %lt-%lb?L/%L. :byte %bB?s/%s. .?e(END)" + " ?x- Next\\: %x.:?pB%pB\\%..%t"; +static const char e_proto[] = + "?f%f .?m(%T %i of %m) .?ltlines " + "%lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t"; +static const char h_proto[] = + "HELP -- ?eEND -- Press g to see it again:" + "Press RETURN for more., or q when done"; +static const char w_proto[] = + "Waiting for data"; +static const char more_proto[] = + "%f (?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)"; +static const char more_M_proto[] = + "%f (?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)" + "[Press space to continue, q to quit, h for help]"; + +char *prproto[3]; +char const *eqproto = e_proto; +char const *hproto = h_proto; +char const *wproto = w_proto; + +static char message[PROMPT_SIZE]; +static char *mp; + +/* + * Initialize the prompt prototype strings. + */ +void +init_prompt(void) +{ + prproto[0] = estrdup(s_proto); + prproto[1] = estrdup(less_is_more ? more_proto : m_proto); + prproto[2] = estrdup(less_is_more ? more_M_proto : M_proto); + eqproto = estrdup(e_proto); + hproto = estrdup(h_proto); + wproto = estrdup(w_proto); +} + +/* + * Append a string to the end of the message. + */ +static void +ap_str(char *s) +{ + int len; + + len = strlen(s); + if (mp + len >= message + PROMPT_SIZE) + len = message + PROMPT_SIZE - mp - 1; + (void) strncpy(mp, s, len); + mp += len; + *mp = '\0'; +} + +/* + * Append a character to the end of the message. + */ +static void +ap_char(char c) +{ + char buf[2]; + + buf[0] = c; + buf[1] = '\0'; + ap_str(buf); +} + +/* + * Append a off_t (as a decimal integer) to the end of the message. + */ +static void +ap_pos(off_t pos) +{ + char buf[23]; + + postoa(pos, buf, sizeof(buf)); + ap_str(buf); +} + +/* + * Append an integer to the end of the message. + */ +static void +ap_int(int num) +{ + char buf[13]; + + inttoa(num, buf, sizeof buf); + ap_str(buf); +} + +/* + * Append a question mark to the end of the message. + */ +static void +ap_quest(void) +{ + ap_str("?"); +} + +/* + * Return the "current" byte offset in the file. + */ +static off_t +curr_byte(int where) +{ + off_t pos; + + pos = position(where); + while (pos == -1 && where >= 0 && where < sc_height-1) + pos = position(++where); + if (pos == -1) + pos = ch_length(); + return (pos); +} + +/* + * Return the value of a prototype conditional. + * A prototype string may include conditionals which consist of a + * question mark followed by a single letter. + * Here we decode that letter and return the appropriate boolean value. + */ +static int +cond(char c, int where) +{ + off_t len; + + switch (c) { + case 'a': /* Anything in the message yet? */ + return (*message != '\0'); + case 'b': /* Current byte offset known? */ + return (curr_byte(where) != -1); + case 'c': + return (hshift != 0); + case 'e': /* At end of file? */ + return (eof_displayed()); + case 'f': /* Filename known? */ + return (strcmp(get_filename(curr_ifile), "-") != 0); + case 'l': /* Line number known? */ + case 'd': /* Same as l */ + return (linenums); + case 'L': /* Final line number known? */ + case 'D': /* Final page number known? */ + return (linenums && ch_length() != -1); + case 'm': /* More than one file? */ + return (ntags() ? (ntags() > 1) : (nifile() > 1)); + case 'n': /* First prompt in a new file? */ + return (ntags() ? 1 : new_file); + case 'p': /* Percent into file (bytes) known? */ + return (curr_byte(where) != -1 && ch_length() > 0); + case 'P': /* Percent into file (lines) known? */ + return (currline(where) != 0 && + (len = ch_length()) > 0 && find_linenum(len) != 0); + case 's': /* Size of file known? */ + case 'B': + return (ch_length() != -1); + case 'x': /* Is there a "next" file? */ + if (ntags()) + return (0); + return (next_ifile(curr_ifile) != NULL); + } + return (0); +} + +/* + * Decode a "percent" prototype character. + * A prototype string may include various "percent" escapes; + * that is, a percent sign followed by a single letter. + * Here we decode that letter and take the appropriate action, + * usually by appending something to the message being built. + */ +static void +protochar(int c, int where) +{ + off_t pos; + off_t len; + int n; + off_t linenum; + off_t last_linenum; + IFILE h; + +#undef PAGE_NUM +#define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - 1)) + 1) + + switch (c) { + case 'b': /* Current byte offset */ + pos = curr_byte(where); + if (pos != -1) + ap_pos(pos); + else + ap_quest(); + break; + case 'c': + ap_int(hshift); + break; + case 'd': /* Current page number */ + linenum = currline(where); + if (linenum > 0 && sc_height > 1) + ap_pos(PAGE_NUM(linenum)); + else + ap_quest(); + break; + case 'D': /* Final page number */ + /* Find the page number of the last byte in the file (len-1). */ + len = ch_length(); + if (len == -1) { + ap_quest(); + } else if (len == 0) { + /* An empty file has no pages. */ + ap_pos(0); + } else { + linenum = find_linenum(len - 1); + if (linenum <= 0) + ap_quest(); + else + ap_pos(PAGE_NUM(linenum)); + } + break; + case 'E': /* Editor name */ + ap_str(editor); + break; + case 'f': /* File name */ + ap_str(get_filename(curr_ifile)); + break; + case 'F': /* Last component of file name */ + ap_str(last_component(get_filename(curr_ifile))); + break; + case 'i': /* Index into list of files */ + if (ntags()) + ap_int(curr_tag()); + else + ap_int(get_index(curr_ifile)); + break; + case 'l': /* Current line number */ + linenum = currline(where); + if (linenum != 0) + ap_pos(linenum); + else + ap_quest(); + break; + case 'L': /* Final line number */ + len = ch_length(); + if (len == -1 || len == ch_zero() || + (linenum = find_linenum(len)) <= 0) + ap_quest(); + else + ap_pos(linenum-1); + break; + case 'm': /* Number of files */ + n = ntags(); + if (n) + ap_int(n); + else + ap_int(nifile()); + break; + case 'p': /* Percent into file (bytes) */ + pos = curr_byte(where); + len = ch_length(); + if (pos != -1 && len > 0) + ap_int(percentage(pos, len)); + else + ap_quest(); + break; + case 'P': /* Percent into file (lines) */ + linenum = currline(where); + if (linenum == 0 || + (len = ch_length()) == -1 || len == ch_zero() || + (last_linenum = find_linenum(len)) <= 0) + ap_quest(); + else + ap_int(percentage(linenum, last_linenum)); + break; + case 's': /* Size of file */ + case 'B': + len = ch_length(); + if (len != -1) + ap_pos(len); + else + ap_quest(); + break; + case 't': /* Truncate trailing spaces in the message */ + while (mp > message && mp[-1] == ' ') + mp--; + *mp = '\0'; + break; + case 'T': /* Type of list */ + if (ntags()) + ap_str("tag"); + else + ap_str("file"); + break; + case 'x': /* Name of next file */ + h = next_ifile(curr_ifile); + if (h != NULL) + ap_str(get_filename(h)); + else + ap_quest(); + break; + } +} + +/* + * Skip a false conditional. + * When a false condition is found (either a false IF or the ELSE part + * of a true IF), this routine scans the prototype string to decide + * where to resume parsing the string. + * We must keep track of nested IFs and skip them properly. + */ +static const char * +skipcond(const char *p) +{ + int iflevel; + + /* + * We came in here after processing a ? or :, + * so we start nested one level deep. + */ + iflevel = 1; + + for (;;) { + switch (*++p) { + case '?': + /* + * Start of a nested IF. + */ + iflevel++; + break; + case ':': + /* + * Else. + * If this matches the IF we came in here with, + * then we're done. + */ + if (iflevel == 1) + return (p); + break; + case '.': + /* + * Endif. + * If this matches the IF we came in here with, + * then we're done. + */ + if (--iflevel == 0) + return (p); + break; + case '\\': + /* + * Backslash escapes the next character. + */ + ++p; + break; + case '\0': + /* + * Whoops. Hit end of string. + * This is a malformed conditional, but just treat it + * as if all active conditionals ends here. + */ + return (p-1); + } + } +} + +/* + * Decode a char that represents a position on the screen. + */ +static const char * +wherechar(const char *p, int *wp) +{ + switch (*p) { + case 'b': case 'd': case 'l': case 'p': case 'P': + switch (*++p) { + case 't': *wp = TOP; break; + case 'm': *wp = MIDDLE; break; + case 'b': *wp = BOTTOM; break; + case 'B': *wp = BOTTOM_PLUS_ONE; break; + case 'j': *wp = adjsline(jump_sline); break; + default: *wp = TOP; p--; break; + } + } + return (p); +} + +/* + * Construct a message based on a prototype string. + */ +char * +pr_expand(const char *proto, int maxwidth) +{ + const char *p; + int c; + int where; + + mp = message; + + if (*proto == '\0') + return (""); + + for (p = proto; *p != '\0'; p++) { + switch (*p) { + default: /* Just put the character in the message */ + ap_char(*p); + break; + case '\\': /* Backslash escapes the next character */ + p++; + ap_char(*p); + break; + case '?': /* Conditional (IF) */ + if ((c = *++p) == '\0') { + --p; + } else { + where = 0; + p = wherechar(p, &where); + if (!cond(c, where)) + p = skipcond(p); + } + break; + case ':': /* ELSE */ + p = skipcond(p); + break; + case '.': /* ENDIF */ + break; + case '%': /* Percent escape */ + if ((c = *++p) == '\0') { + --p; + } else { + where = 0; + p = wherechar(p, &where); + protochar(c, where); + } + break; + } + } + + if (*message == '\0') + return (""); + if (maxwidth > 0 && mp >= message + maxwidth) { + /* + * Message is too long. + * Return just the final portion of it. + */ + return (mp - maxwidth); + } + return (message); +} + +/* + * Return a message suitable for printing by the "=" command. + */ +char * +eq_message(void) +{ + return (pr_expand(eqproto, 0)); +} + +/* + * Return a prompt. + * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. + * If we can't come up with an appropriate prompt, return NULL + * and the caller will prompt with a colon. + */ +char * +prompt_string(void) +{ + char *prompt; + int type; + + type = pr_type; + prompt = pr_expand((ch_getflags() & CH_HELPFILE) ? + hproto : prproto[type], sc_width-so_s_width-so_e_width-2); + new_file = 0; + return (prompt); +} + +/* + * Return a message suitable for printing while waiting in the F command. + */ +char * +wait_message(void) +{ + return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2)); +} diff --git a/bin/less/screen.c b/bin/less/screen.c new file mode 100644 index 0000000000..a794afc680 --- /dev/null +++ b/bin/less/screen.c @@ -0,0 +1,780 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines which deal with the characteristics of the terminal. + * Uses termcap to be as terminal-independent as possible. + */ + +#include + +#include +#include +#include + +#include "cmd.h" +#include "less.h" + +#define DEFAULT_TERM "unknown" + +/* + * Strings passed to tputs() to do various terminal functions. + */ +static char + *sc_home, /* Cursor home */ + *sc_addline, /* Add line, scroll down following lines */ + *sc_lower_left, /* Cursor to last line, first column */ + *sc_return, /* Cursor to beginning of current line */ + *sc_move, /* General cursor positioning */ + *sc_clear, /* Clear screen */ + *sc_eol_clear, /* Clear to end of line */ + *sc_eos_clear, /* Clear to end of screen */ + *sc_s_in, /* Enter standout (highlighted) mode */ + *sc_s_out, /* Exit standout mode */ + *sc_u_in, /* Enter underline mode */ + *sc_u_out, /* Exit underline mode */ + *sc_b_in, /* Enter bold mode */ + *sc_b_out, /* Exit bold mode */ + *sc_bl_in, /* Enter blink mode */ + *sc_bl_out, /* Exit blink mode */ + *sc_visual_bell, /* Visual bell (flash screen) sequence */ + *sc_backspace, /* Backspace cursor */ + *sc_s_keypad, /* Start keypad mode */ + *sc_e_keypad, /* End keypad mode */ + *sc_init, /* Startup terminal initialization */ + *sc_deinit; /* Exit terminal de-initialization */ + +static int init_done = 0; + +int auto_wrap; /* Terminal does \r\n when write past margin */ +int ignaw; /* Terminal ignores \n immediately after wrap */ +int erase_char; /* The user's erase char */ +int erase2_char; /* The user's other erase char */ +int kill_char; /* The user's line-kill char */ +int werase_char; /* The user's word-erase char */ +int sc_width, sc_height; /* Height & width of screen */ +int bo_s_width, bo_e_width; /* Printing width of boldface seq */ +int ul_s_width, ul_e_width; /* Printing width of underline seq */ +int so_s_width, so_e_width; /* Printing width of standout seq */ +int bl_s_width, bl_e_width; /* Printing width of blink seq */ +int can_goto_line; /* Can move cursor to any line */ +int missing_cap = 0; /* Some capability is missing */ +static int above_mem; /* Memory retained above screen */ +static int below_mem; /* Memory retained below screen */ + +static int attrmode = AT_NORMAL; +extern int binattr; + +static char *cheaper(char *, char *, char *); +static void tmodes(char *, char *, char **, char **, char *, char *); + +extern int quiet; /* If VERY_QUIET, use visual bell for bell */ +extern int no_back_scroll; +extern int swindow; +extern int no_init; +extern int no_keypad; +extern volatile sig_atomic_t sigs; +extern int wscroll; +extern int screen_trashed; +extern int tty; +extern int top_scroll; +extern int oldbot; +extern int hilite_search; + +/* + * Change terminal to "raw mode", or restore to "normal" mode. + * "Raw mode" means + * 1. An outstanding read will complete on receipt of a single keystroke. + * 2. Input is not echoed. + * 3. On output, \n is mapped to \r\n. + * 4. \t is NOT expanded into spaces. + * 5. Signal-causing characters such as ctrl-C (interrupt), + * etc. are NOT disabled. + * It doesn't matter whether an input \n is mapped to \r, or vice versa. + */ +void +raw_mode(int on) +{ + static int curr_on = 0; + struct termios s; + static struct termios save_term; + static int saved_term = 0; + + if (on == curr_on) + return; + erase2_char = '\b'; /* in case OS doesn't know about erase2 */ + + if (on) { + /* + * Get terminal modes. + */ + (void) tcgetattr(tty, &s); + + /* + * Save modes and set certain variables dependent on modes. + */ + if (!saved_term) { + save_term = s; + saved_term = 1; + } + + erase_char = s.c_cc[VERASE]; +#ifdef VERASE2 + erase2_char = s.c_cc[VERASE2]; +#endif + kill_char = s.c_cc[VKILL]; +#ifdef VWERASE + werase_char = s.c_cc[VWERASE]; +#endif + + /* + * Set the modes to the way we want them. + */ + s.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL); + +#ifndef TAB3 +#define TAB3 0 /* Its a lie, but TAB3 isn't defined by POSIX. */ +#endif + s.c_oflag |= (TAB3 | OPOST | ONLCR); + s.c_oflag &= ~(OCRNL | ONOCR | ONLRET); + s.c_cc[VMIN] = 1; + s.c_cc[VTIME] = 0; +#ifdef VLNEXT + s.c_cc[VLNEXT] = 0; +#endif +#ifdef VDSUSP + s.c_cc[VDSUSP] = 0; +#endif + } else { + /* + * Restore saved modes. + */ + s = save_term; + } + (void) tcsetattr(tty, TCSASOFT | TCSADRAIN, &s); + curr_on = on; +} + +/* + * Some glue to prevent calling termcap functions if tgetent() failed. + */ +static int hardcopy; + +/* + * Get size of the output screen. + */ +static void +scrsize(void) +{ + int sys_height = 0, sys_width = 0, n; + struct winsize w; + char *s; + +#define DEF_SC_WIDTH 80 +#define DEF_SC_HEIGHT 24 + + if (ioctl(2, TIOCGWINSZ, &w) == 0) { + if (w.ws_row > 0) + sys_height = w.ws_row; + if (w.ws_col > 0) + sys_width = w.ws_col; + } + + if (sys_height > 0) + sc_height = sys_height; + else if ((s = lgetenv("LINES")) != NULL) + sc_height = atoi(s); + else if (!hardcopy && (n = lines) > 0) + sc_height = n; + if (sc_height <= 0) + sc_height = DEF_SC_HEIGHT; + + if (sys_width > 0) + sc_width = sys_width; + else if ((s = lgetenv("COLUMNS")) != NULL) + sc_width = atoi(s); + else if (!hardcopy && (n = columns) > 0) + sc_width = n; + if (sc_width <= 0) + sc_width = DEF_SC_WIDTH; +} + +/* + * Return the characters actually input by a "special" key. + */ +char * +special_key_str(int key) +{ + char *s; + static char ctrlk[] = { CONTROL('K'), 0 }; + + if (hardcopy) + return (NULL); + + switch (key) { + case SK_RIGHT_ARROW: + s = key_right; + break; + case SK_LEFT_ARROW: + s = key_left; + break; + case SK_UP_ARROW: + s = key_up; + break; + case SK_DOWN_ARROW: + s = key_down; + break; + case SK_PAGE_UP: + s = key_ppage; + break; + case SK_PAGE_DOWN: + s = key_npage; + break; + case SK_HOME: + s = key_home; + break; + case SK_END: + s = key_end; + break; + case SK_DELETE: + s = key_dc; + if (s == NULL) { + s = "\177\0"; + } + break; + case SK_CONTROL_K: + s = ctrlk; + break; + default: + return (NULL); + } + return (s); +} + +/* + * Get terminal capabilities via termcap. + */ +void +get_term(void) +{ + char *t1, *t2; + char *term; + int err; + + /* + * Find out what kind of terminal this is. + */ + if ((term = lgetenv("TERM")) == NULL) + term = DEFAULT_TERM; + hardcopy = 0; + + if (setupterm(term, 1, &err) < 0) { + if (err == 1) + hardcopy = 1; + else + errx(1, "%s: unknown terminal type", term); + } + if (hard_copy == 1) + hardcopy = 1; + + /* + * Get size of the screen. + */ + scrsize(); + pos_init(); + + auto_wrap = hardcopy ? 0 : auto_right_margin; + ignaw = hardcopy ? 0 : eat_newline_glitch; + above_mem = hardcopy ? 0 : memory_above; + below_mem = hardcopy ? 0 : memory_below; + + /* + * Assumes termcap variable "sg" is the printing width of: + * the standout sequence, the end standout sequence, + * the underline sequence, the end underline sequence, + * the boldface sequence, and the end boldface sequence. + */ + if (hardcopy || (so_s_width = magic_cookie_glitch) < 0) + so_s_width = 0; + so_e_width = so_s_width; + + bo_s_width = bo_e_width = so_s_width; + ul_s_width = ul_e_width = so_s_width; + bl_s_width = bl_e_width = so_s_width; + + if (so_s_width > 0 || so_e_width > 0) + /* + * Disable highlighting by default on magic cookie terminals. + * Turning on highlighting might change the displayed width + * of a line, causing the display to get messed up. + * The user can turn it back on with -g, + * but she won't like the results. + */ + hilite_search = 0; + + /* + * Get various string-valued capabilities. + */ + + sc_s_keypad = keypad_xmit; + if (hardcopy || sc_s_keypad == NULL) + sc_s_keypad = ""; + sc_e_keypad = keypad_local; + if (hardcopy || sc_e_keypad == NULL) + sc_e_keypad = ""; + + sc_init = enter_ca_mode; + if (hardcopy || sc_init == NULL) + sc_init = ""; + + sc_deinit = exit_ca_mode; + if (hardcopy || sc_deinit == NULL) + sc_deinit = ""; + + sc_eol_clear = clr_eol; + if (hardcopy || sc_eol_clear == NULL || *sc_eol_clear == '\0') { + missing_cap = 1; + sc_eol_clear = ""; + } + + sc_eos_clear = clr_eos; + if (below_mem && + (hardcopy || sc_eos_clear == NULL || *sc_eos_clear == '\0')) { + missing_cap = 1; + sc_eos_clear = ""; + } + + sc_clear = clear_screen; + if (hardcopy || sc_clear == NULL || *sc_clear == '\0') { + missing_cap = 1; + sc_clear = "\n\n"; + } + + sc_move = cursor_address; + if (hardcopy || sc_move == NULL || *sc_move == '\0') { + /* + * This is not an error here, because we don't + * always need sc_move. + * We need it only if we don't have home or lower-left. + */ + sc_move = ""; + can_goto_line = 0; + } else { + can_goto_line = 1; + } + + tmodes(enter_standout_mode, exit_standout_mode, &sc_s_in, &sc_s_out, + "", ""); + tmodes(enter_underline_mode, exit_underline_mode, &sc_u_in, &sc_u_out, + sc_s_in, sc_s_out); + tmodes(enter_bold_mode, exit_attribute_mode, &sc_b_in, &sc_b_out, + sc_s_in, sc_s_out); + tmodes(enter_blink_mode, exit_attribute_mode, &sc_bl_in, &sc_bl_out, + sc_s_in, sc_s_out); + + sc_visual_bell = flash_screen; + if (hardcopy || sc_visual_bell == NULL) + sc_visual_bell = ""; + + sc_backspace = "\b"; + + /* + * Choose between using "ho" and "cm" ("home" and "cursor move") + * to move the cursor to the upper left corner of the screen. + */ + t1 = cursor_home; + if (hardcopy || t1 == NULL) + t1 = ""; + if (*sc_move == '\0') { + t2 = ""; + } else { + t2 = estrdup(tparm(sc_move, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + } + sc_home = cheaper(t1, t2, "|\b^"); + + /* + * Choose between using "ll" and "cm" ("lower left" and "cursor move") + * to move the cursor to the lower left corner of the screen. + */ + t1 = cursor_to_ll; + if (hardcopy || t1 == NULL) + t1 = ""; + if (*sc_move == '\0') { + t2 = ""; + } else { + t2 = estrdup(tparm(sc_move, sc_height-1, + 0, 0, 0, 0, 0, 0, 0, 0)); + } + sc_lower_left = cheaper(t1, t2, "\r"); + + /* + * Get carriage return string. + */ + sc_return = carriage_return; + if (hardcopy || sc_return == NULL) + sc_return = "\r"; + + /* + * Choose between using insert_line or scroll_reverse + * to add a line at the top of the screen. + */ + t1 = insert_line; + if (hardcopy || t1 == NULL) + t1 = ""; + t2 = scroll_reverse; + if (hardcopy || t2 == NULL) + t2 = ""; + if (above_mem) + sc_addline = t1; + else + sc_addline = cheaper(t1, t2, ""); + if (*sc_addline == '\0') { + /* + * Force repaint on any backward movement. + */ + no_back_scroll = 1; + } +} + +/* + * Return the cost of displaying a termcap string. + * We use the trick of calling tputs, but as a char printing function + * we give it inc_costcount, which just increments "costcount". + * This tells us how many chars would be printed by using this string. + * {{ Couldn't we just use strlen? }} + */ +static int costcount; + +static int +inc_costcount(int c) +{ + costcount++; + return (c); +} + +static int +cost(char *t) +{ + costcount = 0; + (void) tputs(t, sc_height, inc_costcount); + return (costcount); +} + +/* + * Return the "best" of the two given termcap strings. + * The best, if both exist, is the one with the lower + * cost (see cost() function). + */ +static char * +cheaper(char *t1, char *t2, char *def) +{ + if (*t1 == '\0' && *t2 == '\0') { + missing_cap = 1; + return (def); + } + if (*t1 == '\0') + return (t2); + if (*t2 == '\0') + return (t1); + if (cost(t1) < cost(t2)) + return (t1); + return (t2); +} + +static void +tmodes(char *incap, char *outcap, char **instr, char **outstr, + char *def_instr, char *def_outstr) +{ + if (hardcopy) { + *instr = ""; + *outstr = ""; + return; + } + + *instr = incap; + *outstr = outcap; + + if (*instr == NULL) { + /* Use defaults. */ + *instr = def_instr; + *outstr = def_outstr; + return; + } + + if (*outstr == NULL) + /* No specific out capability; use exit_attribute_mode. */ + *outstr = exit_attribute_mode; + if (*outstr == NULL) + /* Don't even have that, use an empty string */ + *outstr = ""; +} + +/* + * Below are the functions which perform all the + * terminal-specific screen manipulation. + */ + +/* + * Initialize terminal + */ +void +init(void) +{ + if (!no_init) + (void) tputs(sc_init, sc_height, putchr); + if (!no_keypad) + (void) tputs(sc_s_keypad, sc_height, putchr); + if (top_scroll) { + int i; + + /* + * This is nice to terminals with no alternate screen, + * but with saved scrolled-off-the-top lines. This way, + * no previous line is lost, but we start with a whole + * screen to ourself. + */ + for (i = 1; i < sc_height; i++) + (void) putchr('\n'); + } else + line_left(); + init_done = 1; +} + +/* + * Deinitialize terminal + */ +void +deinit(void) +{ + if (!init_done) + return; + if (!no_keypad) + (void) tputs(sc_e_keypad, sc_height, putchr); + if (!no_init) + (void) tputs(sc_deinit, sc_height, putchr); + init_done = 0; +} + +/* + * Home cursor (move to upper left corner of screen). + */ +void +home(void) +{ + (void) tputs(sc_home, 1, putchr); +} + +/* + * Add a blank line (called with cursor at home). + * Should scroll the display down. + */ +void +add_line(void) +{ + (void) tputs(sc_addline, sc_height, putchr); +} + +/* + * Move cursor to lower left corner of screen. + */ +void +lower_left(void) +{ + (void) tputs(sc_lower_left, 1, putchr); +} + +/* + * Move cursor to left position of current line. + */ +void +line_left(void) +{ + (void) tputs(sc_return, 1, putchr); +} + +/* + * Goto a specific line on the screen. + */ +void +goto_line(int slinenum) +{ + (void) tputs(tparm(sc_move, slinenum, 0, 0, 0, 0, 0, 0, 0, 0), 1, + putchr); +} + +/* + * Output the "visual bell", if there is one. + */ +void +vbell(void) +{ + if (*sc_visual_bell == '\0') + return; + (void) tputs(sc_visual_bell, sc_height, putchr); +} + +/* + * Make a noise. + */ +static void +beep(void) +{ + (void) putchr(CONTROL('G')); +} + +/* + * Ring the terminal bell. + */ +void +ring_bell(void) +{ + if (quiet == VERY_QUIET) + vbell(); + else + beep(); +} + +/* + * Clear the screen. + */ +void +do_clear(void) +{ + (void) tputs(sc_clear, sc_height, putchr); +} + +/* + * Clear from the cursor to the end of the cursor's line. + * {{ This must not move the cursor. }} + */ +void +clear_eol(void) +{ + (void) tputs(sc_eol_clear, 1, putchr); +} + +/* + * Clear the current line. + * Clear the screen if there's off-screen memory below the display. + */ +static void +clear_eol_bot(void) +{ + if (below_mem) + (void) tputs(sc_eos_clear, 1, putchr); + else + (void) tputs(sc_eol_clear, 1, putchr); +} + +/* + * Clear the bottom line of the display. + * Leave the cursor at the beginning of the bottom line. + */ +void +clear_bot(void) +{ + /* + * If we're in a non-normal attribute mode, temporarily exit + * the mode while we do the clear. Some terminals fill the + * cleared area with the current attribute. + */ + if (oldbot) + lower_left(); + else + line_left(); + + if (attrmode == AT_NORMAL) + clear_eol_bot(); + else + { + int saved_attrmode = attrmode; + + at_exit(); + clear_eol_bot(); + at_enter(saved_attrmode); + } +} + +void +at_enter(int attr) +{ + attr = apply_at_specials(attr); + + /* The one with the most priority is last. */ + if (attr & AT_UNDERLINE) + (void) tputs(sc_u_in, 1, putchr); + if (attr & AT_BOLD) + (void) tputs(sc_b_in, 1, putchr); + if (attr & AT_BLINK) + (void) tputs(sc_bl_in, 1, putchr); + if (attr & AT_STANDOUT) + (void) tputs(sc_s_in, 1, putchr); + + attrmode = attr; +} + +void +at_exit(void) +{ + /* Undo things in the reverse order we did them. */ + if (attrmode & AT_STANDOUT) + (void) tputs(sc_s_out, 1, putchr); + if (attrmode & AT_BLINK) + (void) tputs(sc_bl_out, 1, putchr); + if (attrmode & AT_BOLD) + (void) tputs(sc_b_out, 1, putchr); + if (attrmode & AT_UNDERLINE) + (void) tputs(sc_u_out, 1, putchr); + + attrmode = AT_NORMAL; +} + +void +at_switch(int attr) +{ + int new_attrmode = apply_at_specials(attr); + int ignore_modes = AT_ANSI; + + if ((new_attrmode & ~ignore_modes) != (attrmode & ~ignore_modes)) { + at_exit(); + at_enter(attr); + } +} + +int +is_at_equiv(int attr1, int attr2) +{ + attr1 = apply_at_specials(attr1); + attr2 = apply_at_specials(attr2); + + return (attr1 == attr2); +} + +int +apply_at_specials(int attr) +{ + if (attr & AT_BINARY) + attr |= binattr; + if (attr & AT_HILITE) + attr |= AT_STANDOUT; + attr &= ~(AT_BINARY|AT_HILITE); + + return (attr); +} + +/* + * Output a plain backspace, without erasing the previous char. + */ +void +putbs(void) +{ + (void) tputs(sc_backspace, 1, putchr); +} diff --git a/bin/less/screen.d b/bin/less/screen.d new file mode 100644 index 0000000000..9e692fb1fb --- /dev/null +++ b/bin/less/screen.d @@ -0,0 +1,113 @@ +screen.o: /home/lauri/bsd/src/usr.bin/less/less/../screen.c \ + /usr/include/sys/ioctl.h /usr/include/sys/ttycom.h \ + /usr/include/sys/ioccom.h /usr/include/sys/filio.h \ + /usr/include/sys/sockio.h /usr/include/sys/cdefs.h \ + /usr/include/machine/cdefs.h /usr/include/err.h \ + /usr/include/machine/_types.h /usr/include/term.h \ + /usr/include/termios.h /usr/include/sys/_types.h \ + /usr/include/sys/ttydefaults.h \ + /home/lauri/bsd/src/usr.bin/less/less/../cmd.h \ + /home/lauri/bsd/src/usr.bin/less/less/../less.h \ + /home/lauri/bsd/src/usr.bin/less/less/../defines.h \ + /usr/include/sys/types.h /usr/include/sys/endian.h \ + /usr/include/sys/_endian.h /usr/include/machine/endian.h \ + /usr/include/ctype.h /usr/include/fcntl.h /usr/include/libgen.h \ + /usr/include/limits.h /usr/include/sys/limits.h \ + /usr/include/machine/limits.h /usr/include/sys/syslimits.h \ + /usr/include/signal.h /usr/include/sys/signal.h \ + /usr/include/machine/signal.h /usr/include/sys/siginfo.h \ + /usr/include/sys/time.h /usr/include/sys/select.h /usr/include/time.h \ + /usr/include/sys/_null.h /usr/include/sys/_time.h /usr/include/stdio.h \ + /usr/include/stdlib.h /usr/include/string.h /usr/include/strings.h \ + /usr/include/unistd.h /usr/include/sys/unistd.h /usr/include/wctype.h \ + /home/lauri/bsd/src/usr.bin/less/less/../funcs.h /usr/include/regex.h + +/usr/include/sys/ioctl.h: + +/usr/include/sys/ttycom.h: + +/usr/include/sys/ioccom.h: + +/usr/include/sys/filio.h: + +/usr/include/sys/sockio.h: + +/usr/include/sys/cdefs.h: + +/usr/include/machine/cdefs.h: + +/usr/include/err.h: + +/usr/include/machine/_types.h: + +/usr/include/term.h: + +/usr/include/termios.h: + +/usr/include/sys/_types.h: + +/usr/include/sys/ttydefaults.h: + +/home/lauri/bsd/src/usr.bin/less/less/../cmd.h: + +/home/lauri/bsd/src/usr.bin/less/less/../less.h: + +/home/lauri/bsd/src/usr.bin/less/less/../defines.h: + +/usr/include/sys/types.h: + +/usr/include/sys/endian.h: + +/usr/include/sys/_endian.h: + +/usr/include/machine/endian.h: + +/usr/include/ctype.h: + +/usr/include/fcntl.h: + +/usr/include/libgen.h: + +/usr/include/limits.h: + +/usr/include/sys/limits.h: + +/usr/include/machine/limits.h: + +/usr/include/sys/syslimits.h: + +/usr/include/signal.h: + +/usr/include/sys/signal.h: + +/usr/include/machine/signal.h: + +/usr/include/sys/siginfo.h: + +/usr/include/sys/time.h: + +/usr/include/sys/select.h: + +/usr/include/time.h: + +/usr/include/sys/_null.h: + +/usr/include/sys/_time.h: + +/usr/include/stdio.h: + +/usr/include/stdlib.h: + +/usr/include/string.h: + +/usr/include/strings.h: + +/usr/include/unistd.h: + +/usr/include/sys/unistd.h: + +/usr/include/wctype.h: + +/home/lauri/bsd/src/usr.bin/less/less/../funcs.h: + +/usr/include/regex.h: diff --git a/bin/less/search.c b/bin/less/search.c new file mode 100644 index 0000000000..48e5314cbf --- /dev/null +++ b/bin/less/search.c @@ -0,0 +1,1101 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines to search a file for a pattern. + */ + +#include "charset.h" +#include "less.h" +#include "pattern.h" +#include "position.h" + +#define MINPOS(a, b) (((a) < (b)) ? (a) : (b)) +#define MAXPOS(a, b) (((a) > (b)) ? (a) : (b)) + +extern volatile sig_atomic_t sigs; +extern int how_search; +extern int caseless; +extern int linenums; +extern int sc_height; +extern int jump_sline; +extern int bs_mode; +extern int ctldisp; +extern int status_col; +extern void *const ml_search; +extern off_t start_attnpos; +extern off_t end_attnpos; +extern int utf_mode; +extern int screen_trashed; +extern int hilite_search; +extern int size_linebuf; +extern int squished; +extern int can_goto_line; +static int hide_hilite; +static off_t prep_startpos; +static off_t prep_endpos; +static int is_caseless; +static int is_ucase_pattern; + +struct hilite { + struct hilite *hl_next; + off_t hl_startpos; + off_t hl_endpos; +}; +static struct hilite hilite_anchor = { NULL, -1, -1 }; +static struct hilite filter_anchor = { NULL, -1, -1 }; +#define hl_first hl_next + +/* + * These are the static variables that represent the "remembered" + * search pattern and filter pattern. + */ +struct pattern_info { + regex_t *compiled; + char *text; + int search_type; +}; + +#define info_compiled(info) ((info)->compiled) + +static struct pattern_info search_info; +static struct pattern_info filter_info; + +/* + * Are there any uppercase letters in this string? + */ +static int +is_ucase(char *str) +{ + char *str_end = str + strlen(str); + LWCHAR ch; + + while (str < str_end) { + ch = step_char(&str, +1, str_end); + if (isupper(ch)) + return (1); + } + return (0); +} + +/* + * Compile and save a search pattern. + */ +static int +set_pattern(struct pattern_info *info, char *pattern, int search_type) +{ + if (pattern == NULL) + info->compiled = NULL; + else if (compile_pattern(pattern, search_type, &info->compiled) < 0) + return (-1); + /* Pattern compiled successfully; save the text too. */ + free(info->text); + info->text = NULL; + if (pattern != NULL) + info->text = estrdup(pattern); + info->search_type = search_type; + + /* + * Ignore case if -I is set OR + * -i is set AND the pattern is all lowercase. + */ + is_ucase_pattern = is_ucase(pattern); + if (is_ucase_pattern && caseless != OPT_ONPLUS) + is_caseless = 0; + else + is_caseless = caseless; + return (0); +} + +/* + * Discard a saved pattern. + */ +static void +clear_pattern(struct pattern_info *info) +{ + free(info->text); + info->text = NULL; + uncompile_pattern(&info->compiled); +} + +/* + * Initialize saved pattern to nothing. + */ +static void +init_pattern(struct pattern_info *info) +{ + info->compiled = NULL; + info->text = NULL; + info->search_type = 0; +} + +/* + * Initialize search variables. + */ +void +init_search(void) +{ + init_pattern(&search_info); + init_pattern(&filter_info); +} + +/* + * Determine which text conversions to perform before pattern matching. + */ +static int +get_cvt_ops(void) +{ + int ops = 0; + if (is_caseless || bs_mode == BS_SPECIAL) { + if (is_caseless) + ops |= CVT_TO_LC; + if (bs_mode == BS_SPECIAL) + ops |= CVT_BS; + if (bs_mode != BS_CONTROL) + ops |= CVT_CRLF; + } else if (bs_mode != BS_CONTROL) { + ops |= CVT_CRLF; + } + if (ctldisp == OPT_ONPLUS) + ops |= CVT_ANSI; + return (ops); +} + +/* + * Is there a previous (remembered) search pattern? + */ +static int +prev_pattern(struct pattern_info *info) +{ + if ((info->search_type & SRCH_NO_REGEX) == 0) + return (info->compiled != NULL); + return (info->text != NULL); +} + +/* + * Repaint the hilites currently displayed on the screen. + * Repaint each line which contains highlighted text. + * If on==0, force all hilites off. + */ +void +repaint_hilite(int on) +{ + int slinenum; + off_t pos; + int save_hide_hilite; + + if (squished) + repaint(); + + save_hide_hilite = hide_hilite; + if (!on) { + if (hide_hilite) + return; + hide_hilite = 1; + } + + if (!can_goto_line) { + repaint(); + hide_hilite = save_hide_hilite; + return; + } + + for (slinenum = TOP; slinenum < TOP + sc_height-1; slinenum++) { + pos = position(slinenum); + if (pos == -1) + continue; + (void) forw_line(pos); + goto_line(slinenum); + put_line(); + } + lower_left(); + hide_hilite = save_hide_hilite; +} + +/* + * Clear the attn hilite. + */ +void +clear_attn(void) +{ + int slinenum; + off_t old_start_attnpos; + off_t old_end_attnpos; + off_t pos; + off_t epos; + int moved = 0; + + if (start_attnpos == -1) + return; + old_start_attnpos = start_attnpos; + old_end_attnpos = end_attnpos; + start_attnpos = end_attnpos = -1; + + if (!can_goto_line) { + repaint(); + return; + } + if (squished) + repaint(); + + for (slinenum = TOP; slinenum < TOP + sc_height-1; slinenum++) { + pos = position(slinenum); + if (pos == -1) + continue; + epos = position(slinenum+1); + if (pos < old_end_attnpos && + (epos == -1 || epos > old_start_attnpos)) { + (void) forw_line(pos); + goto_line(slinenum); + put_line(); + moved = 1; + } + } + if (moved) + lower_left(); +} + +/* + * Hide search string highlighting. + */ +void +undo_search(void) +{ + if (!prev_pattern(&search_info)) { + error("No previous regular expression", NULL); + return; + } + hide_hilite = !hide_hilite; + repaint_hilite(1); +} + +/* + * Clear the hilite list. + */ +static void +clr_hlist(struct hilite *anchor) +{ + struct hilite *hl; + struct hilite *nexthl; + + for (hl = anchor->hl_first; hl != NULL; hl = nexthl) { + nexthl = hl->hl_next; + free(hl); + } + anchor->hl_first = NULL; + prep_startpos = prep_endpos = -1; +} + +void +clr_hilite(void) +{ + clr_hlist(&hilite_anchor); +} + +static void +clr_filter(void) +{ + clr_hlist(&filter_anchor); +} + +/* + * Should any characters in a specified range be highlighted? + */ + static int +is_hilited_range(off_t pos, off_t epos) +{ + struct hilite *hl; + + /* + * Look at each highlight and see if any part of it falls in the range. + */ + for (hl = hilite_anchor.hl_first; hl != NULL; hl = hl->hl_next) { + if (hl->hl_endpos > pos && + (epos == -1 || epos > hl->hl_startpos)) + return (1); + } + return (0); +} + +/* + * Is a line "filtered" -- that is, should it be hidden? + */ +int +is_filtered(off_t pos) +{ + struct hilite *hl; + + if (ch_getflags() & CH_HELPFILE) + return (0); + + /* + * Look at each filter and see if the start position + * equals the start position of the line. + */ + for (hl = filter_anchor.hl_first; hl != NULL; hl = hl->hl_next) { + if (hl->hl_startpos == pos) + return (1); + } + return (0); +} + +/* + * Should any characters in a specified range be highlighted? + * If nohide is nonzero, don't consider hide_hilite. + */ +int +is_hilited(off_t pos, off_t epos, int nohide, int *p_matches) +{ + int match; + + if (p_matches != NULL) + *p_matches = 0; + + if (!status_col && + start_attnpos != -1 && + pos < end_attnpos && + (epos == -1 || epos > start_attnpos)) + /* + * The attn line overlaps this range. + */ + return (1); + + match = is_hilited_range(pos, epos); + if (!match) + return (0); + + if (p_matches != NULL) + /* + * Report matches, even if we're hiding highlights. + */ + *p_matches = 1; + + if (hilite_search == 0) + /* + * Not doing highlighting. + */ + return (0); + + if (!nohide && hide_hilite) + /* + * Highlighting is hidden. + */ + return (0); + + return (1); +} + +/* + * Add a new hilite to a hilite list. + */ +static void +add_hilite(struct hilite *anchor, struct hilite *hl) +{ + struct hilite *ihl; + + /* + * Hilites are sorted in the list; find where new one belongs. + * Insert new one after ihl. + */ + for (ihl = anchor; ihl->hl_next != NULL; ihl = ihl->hl_next) + { + if (ihl->hl_next->hl_startpos > hl->hl_startpos) + break; + } + + /* + * Truncate hilite so it doesn't overlap any existing ones + * above and below it. + */ + if (ihl != anchor) + hl->hl_startpos = MAXPOS(hl->hl_startpos, ihl->hl_endpos); + if (ihl->hl_next != NULL) + hl->hl_endpos = MINPOS(hl->hl_endpos, + ihl->hl_next->hl_startpos); + if (hl->hl_startpos >= hl->hl_endpos) { + /* + * Hilite was truncated out of existence. + */ + free(hl); + return; + } + hl->hl_next = ihl->hl_next; + ihl->hl_next = hl; +} + +/* + * Hilight every character in a range of displayed characters. + */ +static void +create_hilites(off_t linepos, int start_index, int end_index, int *chpos) +{ + struct hilite *hl; + int i; + + /* Start the first hilite. */ + hl = ecalloc(1, sizeof (struct hilite)); + hl->hl_startpos = linepos + chpos[start_index]; + + /* + * Step through the displayed chars. + * If the source position (before cvt) of the char is one more + * than the source pos of the previous char (the usual case), + * just increase the size of the current hilite by one. + * Otherwise (there are backspaces or something involved), + * finish the current hilite and start a new one. + */ + for (i = start_index+1; i <= end_index; i++) { + if (chpos[i] != chpos[i-1] + 1 || i == end_index) { + hl->hl_endpos = linepos + chpos[i-1] + 1; + add_hilite(&hilite_anchor, hl); + /* Start new hilite unless this is the last char. */ + if (i < end_index) { + hl = ecalloc(1, sizeof (struct hilite)); + hl->hl_startpos = linepos + chpos[i]; + } + } + } +} + +/* + * Make a hilite for each string in a physical line which matches + * the current pattern. + * sp,ep delimit the first match already found. + */ +static void +hilite_line(off_t linepos, char *line, int line_len, int *chpos, + char *sp, char *ep) +{ + char *searchp; + char *line_end = line + line_len; + + /* + * sp and ep delimit the first match in the line. + * Mark the corresponding file positions, then + * look for further matches and mark them. + * {{ This technique, of calling match_pattern on subsequent + * substrings of the line, may mark more than is correct + * if the pattern starts with "^". This bug is fixed + * for those regex functions that accept a notbol parameter + * (currently POSIX, PCRE and V8-with-regexec2). }} + */ + searchp = line; + do { + if (sp == NULL || ep == NULL) + return; + + create_hilites(linepos, (intptr_t)sp - (intptr_t)line, + (intptr_t)ep - (intptr_t)line, chpos); + /* + * If we matched more than zero characters, + * move to the first char after the string we matched. + * If we matched zero, just move to the next char. + */ + if (ep > searchp) + searchp = ep; + else if (searchp != line_end) + searchp++; + else /* end of line */ + break; + } while (match_pattern(info_compiled(&search_info), search_info.text, + searchp, (intptr_t)line_end - (intptr_t)searchp, &sp, &ep, 1, + search_info.search_type)); +} + +/* + * Change the caseless-ness of searches. + * Updates the internal search state to reflect a change in the -i flag. + */ +void +chg_caseless(void) +{ + if (!is_ucase_pattern) + /* + * Pattern did not have uppercase. + * Just set the search caselessness to the global caselessness. + */ + is_caseless = caseless; + else + /* + * Pattern did have uppercase. + * Discard the pattern; we can't change search caselessness now. + */ + clear_pattern(&search_info); +} + +/* + * Find matching text which is currently on screen and highlight it. + */ +static void +hilite_screen(void) +{ + struct scrpos scrpos; + + get_scrpos(&scrpos); + if (scrpos.pos == -1) + return; + prep_hilite(scrpos.pos, position(BOTTOM_PLUS_ONE), -1); + repaint_hilite(1); +} + +/* + * Change highlighting parameters. + */ +void +chg_hilite(void) +{ + /* + * Erase any highlights currently on screen. + */ + clr_hilite(); + hide_hilite = 0; + + if (hilite_search == OPT_ONPLUS) + /* + * Display highlights. + */ + hilite_screen(); +} + +/* + * Figure out where to start a search. + */ +static off_t +search_pos(int search_type) +{ + off_t pos; + int linenum; + + if (empty_screen()) { + /* + * Start at the beginning (or end) of the file. + * The empty_screen() case is mainly for + * command line initiated searches; + * for example, "+/xyz" on the command line. + * Also for multi-file (SRCH_PAST_EOF) searches. + */ + if (search_type & SRCH_FORW) { + pos = ch_zero(); + } else { + pos = ch_length(); + if (pos == -1) { + (void) ch_end_seek(); + pos = ch_length(); + } + } + linenum = 0; + } else { + int add_one = 0; + + if (how_search == OPT_ON) { + /* + * Search does not include current screen. + */ + if (search_type & SRCH_FORW) + linenum = BOTTOM_PLUS_ONE; + else + linenum = TOP; + } else if (how_search == OPT_ONPLUS && + !(search_type & SRCH_AFTER_TARGET)) { + /* + * Search includes all of displayed screen. + */ + if (search_type & SRCH_FORW) + linenum = TOP; + else + linenum = BOTTOM_PLUS_ONE; + } else { + /* + * Search includes the part of current screen beyond + * the jump target. + * It starts at the jump target (if searching + * backwards), or at the jump target plus one + * (if forwards). + */ + linenum = jump_sline; + if (search_type & SRCH_FORW) + add_one = 1; + } + linenum = adjsline(linenum); + pos = position(linenum); + if (add_one) + pos = forw_raw_line(pos, NULL, NULL); + } + + /* + * If the line is empty, look around for a plausible starting place. + */ + if (search_type & SRCH_FORW) { + while (pos == -1) { + if (++linenum >= sc_height) + break; + pos = position(linenum); + } + } else { + while (pos == -1) { + if (--linenum < 0) + break; + pos = position(linenum); + } + } + return (pos); +} + +/* + * Search a subset of the file, specified by start/end position. + */ +static int +search_range(off_t pos, off_t endpos, int search_type, int matches, + int maxlines, off_t *plinepos, off_t *pendpos) +{ + char *line; + char *cline; + int line_len; + off_t linenum; + char *sp, *ep; + int line_match; + int cvt_ops; + int cvt_len; + int *chpos; + off_t linepos, oldpos; + + linenum = find_linenum(pos); + oldpos = pos; + for (;;) { + /* + * Get lines until we find a matching one or until + * we hit end-of-file (or beginning-of-file if we're + * going backwards), or until we hit the end position. + */ + if (ABORT_SIGS()) { + /* + * A signal aborts the search. + */ + return (-1); + } + + if ((endpos != -1 && pos >= endpos) || + maxlines == 0) { + /* + * Reached end position without a match. + */ + if (pendpos != NULL) + *pendpos = pos; + return (matches); + } + if (maxlines > 0) + maxlines--; + + if (search_type & SRCH_FORW) { + /* + * Read the next line, and save the + * starting position of that line in linepos. + */ + linepos = pos; + pos = forw_raw_line(pos, &line, &line_len); + if (linenum != 0) + linenum++; + } else { + /* + * Read the previous line and save the + * starting position of that line in linepos. + */ + pos = back_raw_line(pos, &line, &line_len); + linepos = pos; + if (linenum != 0) + linenum--; + } + + if (pos == -1) { + /* + * Reached EOF/BOF without a match. + */ + if (pendpos != NULL) + *pendpos = oldpos; + return (matches); + } + + /* + * If we're using line numbers, we might as well + * remember the information we have now (the position + * and line number of the current line). + * Don't do it for every line because it slows down + * the search. Remember the line number only if + * we're "far" from the last place we remembered it. + */ + if (linenums && abs((int)(pos - oldpos)) > 2048) + add_lnum(linenum, pos); + oldpos = pos; + + if (is_filtered(linepos)) + continue; + + /* + * If it's a caseless search, convert the line to lowercase. + * If we're doing backspace processing, delete backspaces. + */ + cvt_ops = get_cvt_ops(); + cvt_len = cvt_length(line_len); + cline = ecalloc(1, cvt_len); + chpos = cvt_alloc_chpos(cvt_len); + cvt_text(cline, line, chpos, &line_len, cvt_ops); + + /* + * Check to see if the line matches the filter pattern. + * If so, add an entry to the filter list. + */ + if ((search_type & SRCH_FIND_ALL) && + prev_pattern(&filter_info)) { + int line_filter = + match_pattern(info_compiled(&filter_info), + filter_info.text, cline, line_len, &sp, &ep, 0, + filter_info.search_type); + if (line_filter) { + struct hilite *hl = + ecalloc(1, sizeof (struct hilite)); + hl->hl_startpos = linepos; + hl->hl_endpos = pos; + add_hilite(&filter_anchor, hl); + } + } + + /* + * Test the next line to see if we have a match. + * We are successful if we either want a match and got one, + * or if we want a non-match and got one. + */ + if (prev_pattern(&search_info)) { + line_match = match_pattern(info_compiled(&search_info), + search_info.text, cline, line_len, &sp, &ep, 0, + search_type); + if (line_match) { + /* + * Got a match. + */ + if (search_type & SRCH_FIND_ALL) { + /* + * We are supposed to find all matches + * in the range. + * Just add the matches in this line + * to the hilite list and keep + * searching. + */ + hilite_line(linepos, cline, line_len, + chpos, sp, ep); + } else if (--matches <= 0) { + /* + * Found the one match we're looking + * for. Return it. + */ + if (hilite_search == OPT_ON) { + /* + * Clear the hilite list and + * add only + * the matches in this one line. + */ + clr_hilite(); + hilite_line(linepos, cline, + line_len, chpos, sp, ep); + } + free(cline); + free(chpos); + if (plinepos != NULL) + *plinepos = linepos; + return (0); + } + } + } + free(cline); + free(chpos); + } +} + +/* + * search for a pattern in history. If found, compile that pattern. + */ +static int +hist_pattern(int search_type) +{ + char *pattern; + + set_mlist(ml_search, 0); + pattern = cmd_lastpattern(); + if (pattern == NULL) + return (0); + + if (set_pattern(&search_info, pattern, search_type) < 0) + return (0); + + if (hilite_search == OPT_ONPLUS && !hide_hilite) + hilite_screen(); + + return (1); +} + +/* + * Search for the n-th occurrence of a specified pattern, + * either forward or backward. + * Return the number of matches not yet found in this file + * (that is, n minus the number of matches found). + * Return -1 if the search should be aborted. + * Caller may continue the search in another file + * if less than n matches are found in this file. + */ +int +search(int search_type, char *pattern, int n) +{ + off_t pos; + + if (pattern == NULL || *pattern == '\0') { + /* + * A null pattern means use the previously compiled pattern. + */ + search_type |= SRCH_AFTER_TARGET; + if (!prev_pattern(&search_info) && !hist_pattern(search_type)) { + error("No previous regular expression", NULL); + return (-1); + } + if ((search_type & SRCH_NO_REGEX) != + (search_info.search_type & SRCH_NO_REGEX)) { + error("Please re-enter search pattern", NULL); + return (-1); + } + if (hilite_search == OPT_ON) { + /* + * Erase the highlights currently on screen. + * If the search fails, we'll redisplay them later. + */ + repaint_hilite(0); + } + if (hilite_search == OPT_ONPLUS && hide_hilite) { + /* + * Highlight any matches currently on screen, + * before we actually start the search. + */ + hide_hilite = 0; + hilite_screen(); + } + hide_hilite = 0; + } else { + /* + * Compile the pattern. + */ + if (set_pattern(&search_info, pattern, search_type) < 0) + return (-1); + if (hilite_search) { + /* + * Erase the highlights currently on screen. + * Also permanently delete them from the hilite list. + */ + repaint_hilite(0); + hide_hilite = 0; + clr_hilite(); + } + if (hilite_search == OPT_ONPLUS) { + /* + * Highlight any matches currently on screen, + * before we actually start the search. + */ + hilite_screen(); + } + } + + /* + * Figure out where to start the search. + */ + pos = search_pos(search_type); + if (pos == -1) { + /* + * Can't find anyplace to start searching from. + */ + if (search_type & SRCH_PAST_EOF) + return (n); + /* repaint(); -- why was this here? */ + error("Nothing to search", NULL); + return (-1); + } + + n = search_range(pos, -1, search_type, n, -1, &pos, NULL); + if (n != 0) { + /* + * Search was unsuccessful. + */ + if (hilite_search == OPT_ON && n > 0) + /* + * Redisplay old hilites. + */ + repaint_hilite(1); + return (n); + } + + if (!(search_type & SRCH_NO_MOVE)) { + /* + * Go to the matching line. + */ + jump_loc(pos, jump_sline); + } + + if (hilite_search == OPT_ON) + /* + * Display new hilites in the matching line. + */ + repaint_hilite(1); + return (0); +} + + +/* + * Prepare hilites in a given range of the file. + * + * The pair (prep_startpos,prep_endpos) delimits a contiguous region + * of the file that has been "prepared"; that is, scanned for matches for + * the current search pattern, and hilites have been created for such matches. + * If prep_startpos == -1, the prep region is empty. + * If prep_endpos == -1, the prep region extends to EOF. + * prep_hilite asks that the range (spos,epos) be covered by the prep region. + */ +void +prep_hilite(off_t spos, off_t epos, int maxlines) +{ + off_t nprep_startpos = prep_startpos; + off_t nprep_endpos = prep_endpos; + off_t new_epos; + off_t max_epos; + int result; + int i; + +/* + * Search beyond where we're asked to search, so the prep region covers + * more than we need. Do one big search instead of a bunch of small ones. + */ +#define SEARCH_MORE (3*size_linebuf) + + if (!prev_pattern(&search_info) && !is_filtering()) + return; + + /* + * If we're limited to a max number of lines, figure out the + * file position we should stop at. + */ + if (maxlines < 0) { + max_epos = -1; + } else { + max_epos = spos; + for (i = 0; i < maxlines; i++) + max_epos = forw_raw_line(max_epos, NULL, NULL); + } + + /* + * Find two ranges: + * The range that we need to search (spos,epos); and the range that + * the "prep" region will then cover (nprep_startpos,nprep_endpos). + */ + + if (prep_startpos == -1 || + (epos != -1 && epos < prep_startpos) || + spos > prep_endpos) { + /* + * New range is not contiguous with old prep region. + * Discard the old prep region and start a new one. + */ + clr_hilite(); + clr_filter(); + if (epos != -1) + epos += SEARCH_MORE; + nprep_startpos = spos; + } else { + /* + * New range partially or completely overlaps old prep region. + */ + if (epos != -1) { + if (epos > prep_endpos) { + /* + * New range ends after old prep region. + * Extend prep region to end at end of new + * range. + */ + epos += SEARCH_MORE; + + } else { + /* + * New range ends within old prep region. + * Truncate search to end at start of old prep + * region. + */ + epos = prep_startpos; + } + } + + if (spos < prep_startpos) { + /* + * New range starts before old prep region. + * Extend old prep region backwards to start at + * start of new range. + */ + if (spos < SEARCH_MORE) + spos = 0; + else + spos -= SEARCH_MORE; + nprep_startpos = spos; + } else { /* (spos >= prep_startpos) */ + /* + * New range starts within or after old prep region. + * Trim search to start at end of old prep region. + */ + spos = prep_endpos; + } + } + + if (epos != -1 && max_epos != -1 && + epos > max_epos) + /* + * Don't go past the max position we're allowed. + */ + epos = max_epos; + + if (epos == -1 || epos > spos) { + int search_type = SRCH_FORW | SRCH_FIND_ALL; + search_type |= (search_info.search_type & SRCH_NO_REGEX); + result = search_range(spos, epos, search_type, 0, + maxlines, NULL, &new_epos); + if (result < 0) + return; + if (prep_endpos == -1 || new_epos > prep_endpos) + nprep_endpos = new_epos; + } + prep_startpos = nprep_startpos; + prep_endpos = nprep_endpos; +} + +/* + * Set the pattern to be used for line filtering. + */ +void +set_filter_pattern(char *pattern, int search_type) +{ + clr_filter(); + if (pattern == NULL || *pattern == '\0') + clear_pattern(&filter_info); + else + (void) set_pattern(&filter_info, pattern, search_type); + screen_trashed = 1; +} + +/* + * Is there a line filter in effect? + */ +int +is_filtering(void) +{ + if (ch_getflags() & CH_HELPFILE) + return (0); + return (prev_pattern(&filter_info)); +} diff --git a/bin/less/signal.c b/bin/less/signal.c new file mode 100644 index 0000000000..5bf672c06b --- /dev/null +++ b/bin/less/signal.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2015 Garrett D'Amore + * + * 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. + */ + +/* + * Routines dealing with signals. + * + * A signal usually merely causes a bit to be set in the "signals" word. + * At some convenient time, the mainline code checks to see if any + * signals need processing by calling psignal(). + */ + +#include + +#include "less.h" + +/* + * "sigs" contains bits indicating signals which need to be processed. + */ +volatile sig_atomic_t sigs; + +extern int sc_width, sc_height; +extern int screen_trashed; +extern int linenums; +extern int wscroll; +extern int quit_on_intr; +extern long jump_sline_fraction; + +/* + * Interrupt signal handler. + */ +static void +u_interrupt(int type) +{ + sigs |= S_INTERRUPT; +} + +/* + * "Stop" (^Z) signal handler. + */ +static void +stop(int type) +{ + sigs |= S_STOP; +} + +/* + * "Window" change handler + */ +void +sigwinch(int type) +{ + sigs |= S_WINCH; +} + +/* + * Set up the signal handlers. + */ +void +init_signals(int on) +{ + if (on) { + /* + * Set signal handlers. + */ + (void) lsignal(SIGINT, u_interrupt); + (void) lsignal(SIGTSTP, stop); + (void) lsignal(SIGWINCH, sigwinch); + (void) lsignal(SIGQUIT, SIG_IGN); + } else { + /* + * Restore signals to defaults. + */ + (void) lsignal(SIGINT, SIG_DFL); + (void) lsignal(SIGTSTP, SIG_DFL); + (void) lsignal(SIGWINCH, SIG_IGN); + (void) lsignal(SIGQUIT, SIG_DFL); + } +} + +/* + * Process any signals we have received. + * A received signal cause a bit to be set in "sigs". + */ +void +psignals(void) +{ + int tsignals; + + if ((tsignals = sigs) == 0) + return; + sigs = 0; + + if (tsignals & S_STOP) { + /* + * Clean up the terminal. + */ + lsignal(SIGTTOU, SIG_IGN); + clear_bot(); + deinit(); + flush(0); + raw_mode(0); + lsignal(SIGTTOU, SIG_DFL); + lsignal(SIGTSTP, SIG_DFL); + kill(getpid(), SIGTSTP); + /* + * ... Bye bye. ... + * Hopefully we'll be back later and resume here... + * Reset the terminal and arrange to repaint the + * screen when we get back to the main command loop. + */ + lsignal(SIGTSTP, stop); + raw_mode(1); + init(); + screen_trashed = 1; + tsignals |= S_WINCH; + } + if (tsignals & S_WINCH) { + int old_width, old_height; + /* + * Re-execute scrsize() to read the new window size. + */ + old_width = sc_width; + old_height = sc_height; + get_term(); + if (sc_width != old_width || sc_height != old_height) { + wscroll = (sc_height + 1) / 2; + calc_jump_sline(); + calc_shift_count(); + screen_trashed = 1; + } + } + if (tsignals & S_INTERRUPT) { + ring_bell(); + if (quit_on_intr) + quit(QUIT_INTERRUPT); + } +} + +/* + * Custom version of signal() that causes syscalls to be interrupted. + */ +void * +lsignal(int s, void (*a)(int)) +{ + struct sigaction sa, osa; + + sa.sa_handler = a; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* don't restart system calls */ + if (sigaction(s, &sa, &osa) != 0) + return (SIG_ERR); + return (osa.sa_handler); +} diff --git a/bin/less/tags.c b/bin/less/tags.c new file mode 100644 index 0000000000..9c7cba9688 --- /dev/null +++ b/bin/less/tags.c @@ -0,0 +1,437 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +#include "less.h" + +#define WHITESP(c) ((c) == ' ' || (c) == '\t') + +char *tags = "tags"; + +static int total; +static int curseq; + +extern int linenums; +extern volatile sig_atomic_t sigs; + +enum tag_result { + TAG_FOUND, + TAG_NOFILE, + TAG_NOTAG, + TAG_NOTYPE, + TAG_INTR +}; + +static enum tag_result findctag(char *); +static char *nextctag(void); +static char *prevctag(void); +static off_t ctagsearch(void); + +/* + * The list of tags generated by the last findctag() call. + */ +struct taglist { + struct tag *tl_first; + struct tag *tl_last; +}; +#define TAG_END ((struct tag *)&taglist) +static struct taglist taglist = { TAG_END, TAG_END }; +struct tag { + struct tag *next, *prev; /* List links */ + char *tag_file; /* Source file containing the tag */ + off_t tag_linenum; /* Appropriate line number in source file */ + char *tag_pattern; /* Pattern used to find the tag */ + int tag_endline; /* True if the pattern includes '$' */ +}; +static struct tag *curtag; + +#define TAG_INS(tp) \ + (tp)->next = TAG_END; \ + (tp)->prev = taglist.tl_last; \ + taglist.tl_last->next = (tp); \ + taglist.tl_last = (tp); + +#define TAG_RM(tp) \ + (tp)->next->prev = (tp)->prev; \ + (tp)->prev->next = (tp)->next; + +/* + * Delete tag structures. + */ +void +cleantags(void) +{ + struct tag *tp; + + /* + * Delete any existing tag list. + * {{ Ideally, we wouldn't do this until after we know that we + * can load some other tag information. }} + */ + while ((tp = taglist.tl_first) != TAG_END) { + TAG_RM(tp); + free(tp->tag_file); + free(tp->tag_pattern); + free(tp); + } + curtag = NULL; + total = curseq = 0; +} + +/* + * Create a new tag entry. + */ +static struct tag * +maketagent(char *file, off_t linenum, char *pattern, int endline) +{ + struct tag *tp; + + tp = ecalloc(sizeof (struct tag), 1); + tp->tag_file = estrdup(file); + tp->tag_linenum = linenum; + tp->tag_endline = endline; + if (pattern == NULL) + tp->tag_pattern = NULL; + else + tp->tag_pattern = estrdup(pattern); + return (tp); +} + +/* + * Find tags in tag file. + */ +void +findtag(char *tag) +{ + enum tag_result result; + + result = findctag(tag); + switch (result) { + case TAG_FOUND: + case TAG_INTR: + break; + case TAG_NOFILE: + error("No tags file", NULL); + break; + case TAG_NOTAG: + error("No such tag in tags file", NULL); + break; + case TAG_NOTYPE: + error("unknown tag type", NULL); + break; + } +} + +/* + * Search for a tag. + */ +off_t +tagsearch(void) +{ + if (curtag == NULL) + return (-1); /* No tags loaded! */ + if (curtag->tag_linenum != 0) + return (find_pos(curtag->tag_linenum)); + return (ctagsearch()); +} + +/* + * Go to the next tag. + */ +char * +nexttag(int n) +{ + char *tagfile = NULL; + + while (n-- > 0) + tagfile = nextctag(); + return (tagfile); +} + +/* + * Go to the previous tag. + */ +char * +prevtag(int n) +{ + char *tagfile = NULL; + + while (n-- > 0) + tagfile = prevctag(); + return (tagfile); +} + +/* + * Return the total number of tags. + */ +int +ntags(void) +{ + return (total); +} + +/* + * Return the sequence number of current tag. + */ +int +curr_tag(void) +{ + return (curseq); +} + +/* + * Find tags in the "tags" file. + * Sets curtag to the first tag entry. + */ +static enum tag_result +findctag(char *tag) +{ + char *p; + FILE *f; + int taglen; + off_t taglinenum; + char *tagfile; + char *tagpattern; + int tagendline; + int search_char; + int err; + char tline[TAGLINE_SIZE]; + struct tag *tp; + + p = shell_unquote(tags); + f = fopen(p, "r"); + free(p); + if (f == NULL) + return (TAG_NOFILE); + + cleantags(); + total = 0; + taglen = strlen(tag); + + /* + * Search the tags file for the desired tag. + */ + while (fgets(tline, sizeof (tline), f) != NULL) { + if (tline[0] == '!') + /* Skip header of extended format. */ + continue; + if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) + continue; + + /* + * Found it. + * The line contains the tag, the filename and the + * location in the file, separated by white space. + * The location is either a decimal line number, + * or a search pattern surrounded by a pair of delimiters. + * Parse the line and extract these parts. + */ + tagpattern = NULL; + + /* + * Skip over the whitespace after the tag name. + */ + p = skipsp(tline+taglen); + if (*p == '\0') + /* File name is missing! */ + continue; + + /* + * Save the file name. + * Skip over the whitespace after the file name. + */ + tagfile = p; + while (!WHITESP(*p) && *p != '\0') + p++; + *p++ = '\0'; + p = skipsp(p); + if (*p == '\0') + /* Pattern is missing! */ + continue; + + /* + * First see if it is a line number. + */ + tagendline = 0; + taglinenum = getnum(&p, 0, &err); + if (err) { + /* + * No, it must be a pattern. + * Delete the initial "^" (if present) and + * the final "$" from the pattern. + * Delete any backslash in the pattern. + */ + taglinenum = 0; + search_char = *p++; + if (*p == '^') + p++; + tagpattern = p; + while (*p != search_char && *p != '\0') { + if (*p == '\\') + p++; + p++; + } + tagendline = (p[-1] == '$'); + if (tagendline) + p--; + *p = '\0'; + } + tp = maketagent(tagfile, taglinenum, tagpattern, tagendline); + TAG_INS(tp); + total++; + } + fclose(f); + if (total == 0) + return (TAG_NOTAG); + curtag = taglist.tl_first; + curseq = 1; + return (TAG_FOUND); +} + +/* + * Edit current tagged file. + */ +int +edit_tagfile(void) +{ + if (curtag == NULL) + return (1); + return (edit(curtag->tag_file)); +} + +/* + * Search for a tag. + * This is a stripped-down version of search(). + * We don't use search() for several reasons: + * - We don't want to blow away any search string we may have saved. + * - The various regular-expression functions (from different systems: + * regcmp vs. re_comp) behave differently in the presence of + * parentheses (which are almost always found in a tag). + */ +static off_t +ctagsearch(void) +{ + off_t pos, linepos; + off_t linenum; + int len; + char *line; + + pos = ch_zero(); + linenum = find_linenum(pos); + + for (;;) { + /* + * Get lines until we find a matching one or + * until we hit end-of-file. + */ + if (ABORT_SIGS()) + return (-1); + + /* + * Read the next line, and save the + * starting position of that line in linepos. + */ + linepos = pos; + pos = forw_raw_line(pos, &line, (int *)NULL); + if (linenum != 0) + linenum++; + + if (pos == -1) { + /* + * We hit EOF without a match. + */ + error("Tag not found", NULL); + return (-1); + } + + /* + * If we're using line numbers, we might as well + * remember the information we have now (the position + * and line number of the current line). + */ + if (linenums) + add_lnum(linenum, pos); + + /* + * Test the line to see if we have a match. + * Use strncmp because the pattern may be + * truncated (in the tags file) if it is too long. + * If tagendline is set, make sure we match all + * the way to end of line (no extra chars after the match). + */ + len = strlen(curtag->tag_pattern); + if (strncmp(curtag->tag_pattern, line, len) == 0 && + (!curtag->tag_endline || line[len] == '\0' || + line[len] == '\r')) { + curtag->tag_linenum = find_linenum(linepos); + break; + } + } + + return (linepos); +} + +static int circular = 0; /* 1: circular tag structure */ + +/* + * Return the filename required for the next tag in the queue that was setup + * by findctag(). The next call to ctagsearch() will try to position at the + * appropriate tag. + */ +static char * +nextctag(void) +{ + struct tag *tp; + + if (curtag == NULL) + /* No tag loaded */ + return (NULL); + + tp = curtag->next; + if (tp == TAG_END) { + if (!circular) + return (NULL); + /* Wrapped around to the head of the queue */ + curtag = taglist.tl_first; + curseq = 1; + } else { + curtag = tp; + curseq++; + } + return (curtag->tag_file); +} + +/* + * Return the filename required for the previous ctag in the queue that was + * setup by findctag(). The next call to ctagsearch() will try to position + * at the appropriate tag. + */ +static char * +prevctag(void) +{ + struct tag *tp; + + if (curtag == NULL) + /* No tag loaded */ + return (NULL); + + tp = curtag->prev; + if (tp == TAG_END) { + if (!circular) + return (NULL); + /* Wrapped around to the tail of the queue */ + curtag = taglist.tl_last; + curseq = total; + } else { + curtag = tp; + curseq--; + } + return (curtag->tag_file); +} diff --git a/bin/less/ttyin.c b/bin/less/ttyin.c new file mode 100644 index 0000000000..71aac24201 --- /dev/null +++ b/bin/less/ttyin.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * Routines dealing with getting input from the keyboard (i.e. from the user). + */ + +#include "less.h" + +int tty; +extern volatile sig_atomic_t sigs; +extern int utf_mode; + +/* + * Open keyboard for input. + */ +void +open_getchr(void) +{ + /* + * Try /dev/tty. + * If that doesn't work, use file descriptor 2, + * which in Unix is usually attached to the screen, + * but also usually lets you read from the keyboard. + */ + tty = open("/dev/tty", O_RDONLY); + if (tty < 0) + tty = STDERR_FILENO; +} + +/* + * Get a character from the keyboard. + */ +int +getchr(void) +{ + unsigned char c; + int result; + + do { + result = iread(tty, &c, sizeof (char)); + if (result == READ_INTR) + return (READ_INTR); + if (result < 0) { + /* + * Don't call error() here, + * because error calls getchr! + */ + quit(QUIT_ERROR); + } + /* + * Various parts of the program cannot handle + * an input character of '\0'. + * If a '\0' was actually typed, convert it to '\340' here. + */ + if (c == '\0') + c = 0340; + } while (result != 1); + + return (c & 0xFF); +} diff --git a/bin/less/version.c b/bin/less/version.c new file mode 100644 index 0000000000..ea14afe648 --- /dev/null +++ b/bin/less/version.c @@ -0,0 +1,15 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * Modified for use with illumos by Garrett D'Amore. + * Copyright 2014 Garrett D'Amore + * + * 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. + */ + +/* + * This records the base version, plus our own details. + */ +char version[] = "(OpenBSD)"; diff --git a/share/man/man1/Makefile b/share/man/man1/Makefile index 01cd16049b..e7477696a5 100644 --- a/share/man/man1/Makefile +++ b/share/man/man1/Makefile @@ -167,7 +167,6 @@ MAN = acctcom.1 \ mkmsgs.1 \ mktemp.1 \ moe.1 \ - more.1 \ mpss.so.1.1 \ msgcc.1 \ msgcpp.1 \ diff --git a/share/man/man1/more.1 b/share/man/man1/more.1 deleted file mode 100644 index 68a2d8d184..0000000000 --- a/share/man/man1/more.1 +++ /dev/null @@ -1,508 +0,0 @@ -'\" te -.\" Copyright 1989 AT&T Copyright (c) 2005, Sun Microsystems, Inc. All Rights Reserved Portions Copyright (c) 1992, X/Open Company Limited All Rights Reserved -.\" Sun Microsystems, Inc. gratefully acknowledges The Open Group for permission to reproduce portions of its copyrighted documentation. Original documentation from The Open Group can be obtained online at -.\" http://www.opengroup.org/bookstore/. -.\" The Institute of Electrical and Electronics Engineers and The Open Group, have given us permission to reprint portions of their documentation. In the following statement, the phrase "this text" refers to portions of the system documentation. Portions of this text are reprinted and reproduced in electronic form in the Sun OS Reference Manual, from IEEE Std 1003.1, 2004 Edition, Standard for Information Technology -- Portable Operating System Interface (POSIX), The Open Group Base Specifications Issue 6, Copyright (C) 2001-2004 by the Institute of Electrical and Electronics Engineers, Inc and The Open Group. In the event of any discrepancy between these versions and the original IEEE and The Open Group Standard, the original IEEE and The Open Group Standard is the referee document. The original Standard can be obtained online at http://www.opengroup.org/unix/online.html. -.\" This notice shall appear on any product containing this material. -.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License. -.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License. -.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner] -.TH MORE 1 "Nov 4, 2005" -.SH NAME -more, page \- browse or page through a text file -.SH SYNOPSIS -.LP -.nf -\fBmore\fR [\fB-cdflrsuw\fR] [\fB-lines\fR] [+ \fIlinenumber\fR] - [+/ \fIpattern\fR] [\fIfile\fR]... -.fi - -.LP -.nf -\fBpage\fR [\fB-cdflrsuw\fR] [\fB-lines\fR] [+ \fIlinenumber\fR] - [+/ \fIpattern\fR] [\fIfile\fR]... -.fi - -.SH DESCRIPTION -.sp -.LP -The \fBmore\fR utility is a filter that displays the contents of a text file on -the terminal, one screenful at a time. It normally pauses after each -screenful. \fBmore\fR then prints \fB--More--\fR -at the bottom of the screen. If -\fBmore\fR is reading from a file rather than a pipe, the percentage of -characters displayed so far is also shown. -.sp -.LP -The \fBmore\fR utility scrolls up to display one more line in response to a -\fBRETURN\fR character. \fBmore\fR displays another screenful in response to a -\fBSPACE\fR character. Other commands are listed below. -.sp -.LP -The \fBpage\fR utility clears the screen before displaying the next screenful -of text. \fBpage\fR only provides a one-line overlap between screens. -.sp -.LP -The \fBmore\fR utility sets the terminal to \fBNOECHO\fR mode, so that the -output can be continuous. Commands that you type do not normally show up on -your terminal, except for the \fB/\fR and \fB!\fR commands. -.sp -.LP -The \fBmore\fR utility exits after displaying the last specified file. -.sp -.LP -If the standard output is not a terminal, \fBmore\fR acts just like -\fBcat\fR(1), except that a header is printed before each file in a series. -.SH OPTIONS -.sp -.LP -The following options are supported: -.sp -.ne 2 -.na -\fB\fB-c\fR \fR -.ad -.RS 7n -Clears before displaying. Redraws the screen instead of scrolling for faster -displays. This option is ignored if the terminal does not have the ability to -clear to the end of a line. -.RE - -.sp -.ne 2 -.na -\fB\fB-d\fR \fR -.ad -.RS 7n -Displays error messages rather than ringing the terminal bell if an -unrecognized command is used. This is helpful for inexperienced users. -.RE - -.sp -.ne 2 -.na -\fB\fB-s\fR \fR -.ad -.RS 7n -Squeeze. Replaces multiple blank lines with a single blank line. This is -helpful when viewing \fBnroff\fR(1) output on the screen. -.RE - -.sp -.ne 2 -.na -\fB\fB-f\fR \fR -.ad -.RS 7n -Does not fold long lines. This is useful when lines contain nonprinting -characters or escape sequences, such as those generated when \fBnroff\fR(1) -output is piped through \fBul\fR(1). -.RE - -.sp -.ne 2 -.na -\fB\fB-l\fR \fR -.ad -.RS 7n -Does not treat \fBFORMFEED\fR characters (Control-l) as page breaks. If -\fB-l\fR is not used, \fBmore\fR pauses to accept commands after any line -containing a \fB^L\fR character (Control-l). Also, if a file begins with a -\fBFORMFEED\fR, the screen is cleared before the file is printed. -.RE - -.sp -.ne 2 -.na -\fB\fB-r\fR \fR -.ad -.RS 7n -Normally, \fBmore\fR ignores control characters that it does not interpret in -some way. The \fB-r\fR option causes these to be displayed as \fB^\fR\fIC\fR -where \fIC\fR stands for any such control character. -.RE - -.sp -.ne 2 -.na -\fB\fB-u\fR \fR -.ad -.RS 7n -Suppresses generation of underlining escape sequences. Normally, \fBmore\fR -handles underlining, such as that produced by \fBnroff\fR(1), in a manner -appropriate to the terminal. If the terminal can perform underlining or has a -stand-out mode, \fBmore\fR supplies appropriate escape sequences as called for -in the text file. -.RE - -.sp -.ne 2 -.na -\fB\fB-w\fR \fR -.ad -.RS 7n -Normally, \fBmore\fR exits when it comes to the end of its input. With -\fB-w\fR, however, \fBmore\fR prompts and waits for any key to be struck before -exiting. -.RE - -.sp -.ne 2 -.na -\fB\fB-\fR\fIlines\fR\fR -.ad -.RS 7n -Displays the indicated number of \fIlines\fR in each screenful, rather than the -default (the number of lines in the terminal screen less two). -.RE - -.sp -.ne 2 -.na -\fB\fB+\fR\fIlinenumber\fR\fR -.ad -.RS 7n -Start up at \fIlinenumber\fR. -.RE - -.sp -.ne 2 -.na -\fB\fB+/\fR\fIpattern\fR\fR -.ad -.RS 7n -Start up two lines above the line containing the regular expression -\fIpattern\fR. \fBNote:\fR Unlike editors, this construct should \fInot\fR end -with a `\fB/\fR.' If it does, then the trailing slash is taken as a character -in the search pattern. -.RE - -.SH USAGE -.SS "Environment" -.sp -.LP -\fBmore\fR uses the terminal's \fBterminfo\fR(4) entry to determine its display -characteristics. -.sp -.LP -\fBmore\fR looks in the environment variable \fBMORE\fR for any preset options. -For instance, to page through files using the \fB-c\fR mode by default, set the -value of this variable to \fB-c\fR. (Normally, the command sequence to set up -this environment variable is placed in the \fB\&.login\fR or \fB\&.profile\fR -file). -.SS "Commands" -.sp -.LP -The commands take effect immediately. It is not necessary to type a carriage -return unless the command requires a \fIfile\fR, \fIcommand\fR, -\fItagstring\fR, or \fIpattern\fR. Up to the time when the command character -itself is given, the user may type the line kill character to cancel the -numerical argument being formed. In addition, the user may type the erase -character to redisplay the `\fB--More--(\fR\fIxx\fR%)' or \fIfile\fR message. -.sp -.LP -In the following commands, \fIi\fR is a numerical argument (\fB1\fR by -default). -.sp -.ne 2 -.na -\fB\fIi\fRSPACE \fR -.ad -.RS 13n -Display another screenful, or \fIi\fR more lines if \fIi\fR is specified. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fRRETURN \fR -.ad -.RS 13n -Display another line, or \fIi\fR more lines, if specified. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fBb\fR\fR -.ad -.br -.na -\fB\fIi\fR\fB^B\fR\fR -.ad -.RS 13n -(Control-b) Skip back \fIi\fR screenfuls and then print a screenful. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fBd\fR\fR -.ad -.br -.na -\fB\fIi\fR\fB^D\fR\fR -.ad -.RS 13n -(Control-d) Scroll forward one half screenful or \fIi\fR more lines. If \fIi\fR -is specified, the count becomes the default for subsequent \fBd\fR and \fBu\fR -commands. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fBf\fR\fR -.ad -.RS 13n -Skip \fIi\fR screens full and then print a screenful. -.RE - -.sp -.ne 2 -.na -\fB\fBh\fR\fR -.ad -.RS 13n -Help. Give a description of all the \fBmore\fR commands. -.RE - -.sp -.ne 2 -.na -\fB\fB^L\fR \fR -.ad -.RS 13n -(Control-l) Refresh. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fBn\fR\fR -.ad -.RS 13n -Search for the \fIi\|\fRth occurrence of the last \fIpattern\fR entered. -.RE - -.sp -.ne 2 -.na -\fB\fBq\fR \fR -.ad -.br -.na -\fB\fBQ\fR \fR -.ad -.RS 13n -Exit from \fBmore\fR. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fBs\fR\fR -.ad -.RS 13n -Skip \fIi\fR lines and then print a screenful. -.RE - -.sp -.ne 2 -.na -\fB\fBv\fR\fR -.ad -.RS 13n -Drop into the \fBvi\fR editor at the current line of the current file. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fBz\fR\fR -.ad -.RS 13n -Same as SPACE, except that \fIi\fR, if present, becomes the new default number -of lines per screenful. -.RE - -.sp -.ne 2 -.na -\fB\fB=\fR \fR -.ad -.RS 13n -Display the current line number. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fB/\fR\fIpattern\fR\fR -.ad -.RS 13n -Search forward for the \fIi\|\fRth occurrence of the regular expression -\fIpattern\fR. Display the screenful starting two lines before the line that -contains the \fIi\|\fRth match for the regular expression \fIpattern\fR, or the -end of a pipe, whichever comes first. If \fBmore\fR is displaying a file and -there is no match, its position in the file remains unchanged. Regular -expressions can be edited using erase and kill characters. Erasing back past -the first column cancels the search command. -.RE - -.sp -.ne 2 -.na -\fB\fB!\fR\fIcommand\fR\fR -.ad -.RS 13n -Invoke a shell to execute \fIcommand\|\fR. The characters \fB%\fR and \fB!\fR, -when used within \fIcommand\fR are replaced with the current filename and the -previous shell command, respectively. If there is no current filename, \fB%\fR -is not expanded. Prepend a backslash to these characters to escape expansion. -.RE - -.sp -.ne 2 -.na -\fB\fB:f\fR\fR -.ad -.RS 13n -Display the current filename and line number. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fB:n\fR\fR -.ad -.RS 13n -Skip to the \fIi\|\fRth next filename given in the command line, or to the last -filename in the list if \fIi\fR is out of range. -.RE - -.sp -.ne 2 -.na -\fB\fIi\fR\fB:p\fR\fR -.ad -.RS 13n -Skip to the \fIi\|\fRth previous filename given in the command line, or to the -first filename if \fIi\fR is out of range. If given while \fBmore\fR is -positioned within a file, go to the beginning of the file. If \fBmore\fR is -reading from a pipe, \fBmore\fR simply rings the terminal bell. -.RE - -.sp -.ne 2 -.na -\fB\fB:q\fR\fR -.ad -.br -.na -\fB\fB:Q\fR\fR -.ad -.RS 13n -Exit from \fBmore\fR (same as \fBq\fR or \fBQ\fR). -.RE - -.sp -.ne 2 -.na -\fB\fB\&'\fR\fR -.ad -.RS 9n -Single quote. Go to the point from which the last search started. If no search -has been performed in the current file, go to the beginning of the file. -.RE - -.sp -.ne 2 -.na -\fB\fB\&.\fR\fR -.ad -.RS 9n -Dot. Repeat the previous command. -.RE - -.sp -.ne 2 -.na -\fB\fB^\|\e\fR\fR -.ad -.RS 9n -Halt a partial display of text. \fBmore\fR stops sending output, and displays -the usual \fB--More--\fR prompt. Some output is lost as a result. -.RE - -.SS "Large File Behavior" -.sp -.LP -See \fBlargefile\fR(5) for the description of the behavior of \fBmore\fR and -\fBpage\fR when encountering files greater than or equal to 2 Gbyte ( 2^31 -bytes). -.SH ENVIRONMENT VARIABLES -.sp -.LP -See \fBenviron\fR(5) for descriptions of the following environment variables -that affect the execution of \fBmore\fR: \fBLANG\fR, \fBLC_ALL\fR, -\fBLC_CTYPE\fR, -\fBLC_MESSAGES\fR, \fBNLSPATH\fR, and \fBTERM\fR. - -.SH EXIT STATUS -.sp -.LP -The following exit values are returned: -.sp -.ne 2 -.na -\fB\fB0\fR \fR -.ad -.RS 7n -Successful completion. -.RE - -.sp -.ne 2 -.na -\fB\fB>0\fR \fR -.ad -.RS 7n -An error occurred. -.RE - -.SH FILES -.sp -.ne 2 -.na -\fB\fB/usr/lib/more.help\fR\fR -.ad -.RS 22n -help file for \fBmore\fR and \fBpage\fR. -.RE - -.SH ATTRIBUTES -.sp -.LP -See \fBattributes\fR(5) for descriptions of the following attributes: - -.sp -.TS -box; -c | c -l | l . -ATTRIBUTE TYPE ATTRIBUTE VALUE -_ -CSI Not enabled -.TE - -.SH SEE ALSO -.sp -.LP -\fBcat\fR(1), \fBcsh\fR(1), \fBctags\fR(1), \fBman\fR(1), \fBnroff\fR(1), -\fBscript\fR(1), \fBsh\fR(1), \fBul\fR(1), \fBterminfo\fR(4), -\fBattributes\fR(5), \fBenviron\fR(5), \fBlargefile\fR(5), \fBstandards\fR(5), -\fBregcomp(3C) -.SH NOTES -.LP -Skipping backwards is too slow on large files. diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs index c7b5fadb09..7fbaa327fc 100644 --- a/usr/src/Targetdirs +++ b/usr/src/Targetdirs @@ -278,6 +278,7 @@ DIRS= \ /usr/share/lib/xml \ /usr/share/lib/xml/dtd \ /usr/share/man \ + /usr/share/misc \ /var \ /var/adm \ /var/adm/exacct \ diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index 16ad822480..a3d5a2e50a 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -232,7 +232,6 @@ COMMON_SUBDIRS= \ mkpwdict \ mktemp \ modload \ - more \ mpathadm \ msgfmt \ msgid \ @@ -540,7 +539,6 @@ MSGSUBDIRS= \ mkdir \ mkpwdict \ mktemp \ - more \ mpathadm \ msgfmt \ mv \ diff --git a/usr/src/cmd/more/Makefile b/usr/src/cmd/more/Makefile deleted file mode 100644 index b33ec448d1..0000000000 --- a/usr/src/cmd/more/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -# -# CDDL HEADER START -# -# The contents of this file are subject to the terms of the -# Common Development and Distribution License, Version 1.0 only -# (the "License"). You may not use this file except in compliance -# with the License. -# -# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE -# or http://www.opensolaris.org/os/licensing. -# See the License for the specific language governing permissions -# and limitations under the License. -# -# When distributing Covered Code, include this CDDL HEADER in each -# file and include the License file at usr/src/OPENSOLARIS.LICENSE. -# If applicable, add the following below this CDDL HEADER, with the -# fields enclosed by brackets "[]" replaced with your own identifying -# information: Portions Copyright [yyyy] [name of copyright owner] -# -# CDDL HEADER END -# -# -# Copyright 2004 Sun Microsystems, Inc. All rights reserved. -# Use is subject to license terms. -# - -PROG= more -DATA= more.help - -include ../Makefile.cmd - -LDLIBS += -lncurses - -ROOTLIBDATA= $(DATA:%=$(ROOTLIB)/%) -CPPFLAGS += -D_FILE_OFFSET_BITS=64 - -$(ROOTLIBDATA) := FILEMODE = 0644 - -CERRWARN += -Wno-parentheses -CERRWARN += -Wno-uninitialized -CERRWARN += -Wno-clobbered - -.KEEP_STATE: - -all: $(PROG) $(DATA) - -install: all $(ROOTPROG) $(ROOTLIBDATA) - $(RM) $(ROOTBIN)/page - $(LN) $(ROOTPROG) $(ROOTBIN)/page - -clean: - - -include ../Makefile.targ diff --git a/usr/src/cmd/more/more.c b/usr/src/cmd/more/more.c deleted file mode 100644 index 14fdd76ff1..0000000000 --- a/usr/src/cmd/more/more.c +++ /dev/null @@ -1,1803 +0,0 @@ -/* - * CDDL HEADER START - * - * The contents of this file are subject to the terms of the - * Common Development and Distribution License (the "License"). - * You may not use this file except in compliance with the License. - * - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or http://www.opensolaris.org/os/licensing. - * See the License for the specific language governing permissions - * and limitations under the License. - * - * When distributing Covered Code, include this CDDL HEADER in each - * file and include the License file at usr/src/OPENSOLARIS.LICENSE. - * If applicable, add the following below this CDDL HEADER, with the - * fields enclosed by brackets "[]" replaced with your own identifying - * information: Portions Copyright [yyyy] [name of copyright owner] - * - * CDDL HEADER END - */ - -/* - * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ -/* All Rights Reserved */ - -/* Copyright (c) 1987, 1988 Microsoft Corporation */ -/* All Rights Reserved */ - -/* - * University Copyright- Copyright (c) 1982, 1986, 1988 - * The Regents of the University of California - * All Rights Reserved - * - * University Acknowledgment- Portions of this document are derived from - * software developed by the University of California, Berkeley, and its - * contributors. - */ - -/* - * @(#) more.c 1.1 88/03/29 more:more.c - */ - -/* -** more.c - General purpose tty output filter and file perusal program -** -** by Eric Shienbrood, UC Berkeley -** -** modified by Geoff Peck, UCB to add underlining, single spacing -** modified by John Foderaro, UCB to add -c and MORE environment variable -** modified by Hans Spiller, Microsoft to handle \r better July 23, 82 -** added ? help command, and -w -** -** vwh 11 Jan 83 M001 -** modified to handle x.out magic number and magic number -** byte ordering OTHER than the vax and pdp11. -** JJD 19 Jan 83 M002 -** - fix distributed on USENET -** From decvax!ucbvax!dist2 Sun Dec 6 02:58:31 1981 -** Subject: FIXED: bug in src/more/more.c -** - fixed bug on terminal with "magic cookie" standout -** sequences. -** JJD 14 Feb 83 M003 -** - fix exit status of more -** - Made first letter of "no more" message uppercase -** andyp 03 Aug 83 M004 3.0 upgrade -** - moved to cmd/include. -** - use UCB, rather than XENIX, stty(2). -** andyp 30 Nov 83 M005 -** - (thanks to reubenb). Changed frame variable to static, it is -** used as a global buffer. We never saw the bug before because -** of the depth of the stack. -** barrys 03 Jul 84 M006 -** - Updated the usage message to include the 's' and 'w' options -** and to make the 'n' option a separate entry (uncommented). -** ericc 26 Dec 84 M007 -** - Replaced the constant 0x7fffffffffffffffL with MAXLONG. -** ericc 25 Jul 85 M008 -** - made "-r" option display control characters as '^x', as documented. -** - fixed processing of '\b' so that more doesn't terminate when -** the sequence "\b\n" is encountered. -** - changed "Hit Rubout ..." to "Hit Del ...", for ibm keyboards. -** davidby 9 March 1988 Unmarked -** - replaced all locally defined functions with library equivalents, -** - changed from termcap to terminfo -** - included for MAXLONG value -** - removed most ifdef code for V6, V7, and BSD -** - added /etc/magic support for file type checking -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -eucwidth_t wp; -int cw[4]; -int scw[4]; -#include - -/* Help file will eventually go in libpath(more.help) on all systems */ - -#ifdef INGRES -#define VI "/usr/bin/vi" -#define HELPFILE "/mntp/doucette/more/more.help" -#define LOCAL_HELP "/usr/lib/locale/%s/LC_MESSAGES/more.help" -#endif - -#ifndef INGRES -#ifndef HELPFILE -#define HELPFILE "/usr/lib/more.help" -#define LOCAL_HELP "/usr/lib/locale/%s/LC_MESSAGES/more.help" -#endif -#define VI "vi" -#endif - -#define Fopen(s,m) (Currline = 0,file_pos=0,fopen(s,m)) -#define Ftell(f) file_pos -#define Fseek(f,off) (file_pos=off,fseeko(f,off,0)) -#define Getc(f) (++file_pos, getc(f)) -#define Ungetc(c,f) (--file_pos, ungetc(c,f)) - -#define pr(s1) fputs(s1, stdout) -#define clreos() putp(clr_eos) -#define cleareol() putp(clr_eol) -#define home() putp(cursor_home) - -#define LINSIZ 512 -#define ctrl(letter) ((letter) & 077) -#define RUBOUT '\177' -#define ESC '\033' -#define QUIT '\034' - -struct termio otty; /* old tty modes */ -struct termio ntty; /* new tty modes */ -off_t file_pos, file_size; -int fnum, no_intty, no_tty; -int dum_opt; -off_t dlines; -void end_it(int sig); -void onquit(int sig); -void chgwinsz(int sig); -#ifdef SIGTSTP -void onsusp(int sig); -#endif -int nscroll = 11; /* Number of lines scrolled by 'd' */ -int fold_opt = 1; /* Fold long lines */ -int stop_opt = 1; /* Stop after form feeds */ -int ssp_opt = 0; /* Suppress white space */ -int ul_opt = 1; /* Underline as best we can */ -int cr_opt = 0; /* show ctrl characters as '^c' */ -int wait_opt = 0; /* prompt for exit at eof */ -int promptlen; -off_t Currline; /* Line we are currently at */ -int startup = 1; -int firstf = 1; -int notell = 1; -int inwait, Pause, errors; -int within; /* true if we are within a file, - false if we are between files */ -int hard, dumb, noscroll, hardtabs, clreol; -int catch_susp; /* We should catch the SIGTSTP signal */ -char **fnames; /* The list of file names */ -int nfiles; /* Number of files left to process */ -char *shell; /* The name of the shell to use */ -int shellp; /* A previous shell command exists */ -char ch; -jmp_buf restore; -char obuf[BUFSIZ]; /* stdout buffer */ -char Line[LINSIZ]; /* Line buffer */ -int Lpp = 24; /* lines per page */ -char *ULenter, *ULexit; /* enter and exit underline mode */ -int Mcol = 80; /* number of columns */ -int Wrap = 1; /* set if automargins */ -int fseeko(); -struct { - off_t chrctr, line; -} context, screen_start; -int exitstat = 0; /* status to use when exiting more */ /*M003*/ - -static void execute(char *filename, char *cmd, ...); -static void error(char *mess); -static void wait_eof(void); -static void prompt(char *filename); -static void argscan(char *s); -static void copy_file(register FILE *f); -static void initterm(void); -static void do_shell(char *filename); -static FILE *checkf(register char *fs, int *clearfirst); -static void screen(register FILE *f, register off_t num_lines); -static void skiplns(register off_t n, register FILE *f); -static void skipf(register int nskip); -static int readch(void); -static void prmpt_erase(register int col); -static void kill_line(void); -static void prbuf(register char *s, register int n); -static void search(char buf[], FILE *file, register off_t n); -static void doclear(void); -static void ttyin(char buf[], register int nmax, char pchar); -static int expand(char *outbuf, char *inbuf); -static void show(register char ch); -static void set_tty(void); -static void reset_tty(void); -static void rdline(register FILE *f); -static off_t command(char *filename, register FILE *f); -static int getaline(register FILE *f, int *length); -static int number(char *cmd); -static int colon(char *filename, int cmd, off_t nlines); - -int -main(int argc, char *argv[]) -{ - register FILE *f; - register char *s; - register char *p; - register int ch; - register off_t left; - int prnames = 0; - int initopt = 0; - int srchopt = 0; - int clearit = 0; - off_t initline; - char initbuf[80]; - - setlocale( LC_ALL, "" ); - getwidth(&wp); - cw[0] = 1; - cw[1] = wp._eucw1; - cw[2] = wp._eucw2+1; - cw[3] = wp._eucw3+1; - scw[0] = 1; - scw[1] = wp._scrw1; - scw[2] = wp._scrw2; - scw[3] = wp._scrw3; - - nfiles = argc; - fnames = argv; - - (void) setlocale(LC_ALL,""); -#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ -#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ -#endif - (void) textdomain(TEXT_DOMAIN); - - initterm (); - if(s = getenv("MORE")) argscan(s); - while (--nfiles > 0) { - if ((ch = (*++fnames)[0]) == '-') { - argscan(*fnames+1); - } - else if (ch == '+') { - s = *fnames; - if (*++s == '/') { - srchopt++; - for (++s, p = initbuf; p < initbuf + 79 && *s != '\0';) - *p++ = *s++; - *p = '\0'; - } - else { - initopt++; - for (initline = 0; *s != '\0'; s++) - if (isdigit (*s)) - initline = initline*10 + *s -'0'; - --initline; - } - } - else break; - } - /* allow clreol only if cursor_home and clr_eol and clr_eos strings are - * defined, and in that case, make sure we are in noscroll mode - */ - if(clreol) - { - if (!cursor_home || !clr_eol || !clr_eos) { - clreol = 0; - } - else noscroll = 1; - } - - if (dlines == 0) - dlines =(off_t) (Lpp - (noscroll ? 1 : 2)); - left = dlines; - if (nfiles > 1) - prnames++; - if (!no_intty && nfiles == 0) { - fprintf(stderr, gettext("Usage: %s\ - [-cdflrsuw] [-lines] [+linenumber] [+/pattern] [filename ...].\n") - , argv[0]); - exit(1); - } - else - f = stdin; - if (!no_tty) { - signal(SIGQUIT, onquit); - signal(SIGINT, end_it); - signal(SIGWINCH, chgwinsz); -#ifdef SIGTSTP - if (signal (SIGTSTP, SIG_IGN) == SIG_DFL) { - signal(SIGTSTP, onsusp); - catch_susp++; - } -#endif - set_tty(); - } - if (no_intty) { - if (no_tty) - copy_file (stdin); - else { - if ((ch = Getc (f)) == '\f') - doclear(); - else { - Ungetc (ch, f); - if (noscroll && (ch != EOF)) { - if (clreol) - home (); - else - doclear (); - } - } - if (!setjmp(restore)) { - if (srchopt) { - search (initbuf, stdin,(off_t) 1); - if (noscroll) - left--; - } - else if (initopt) - skiplns (initline, stdin); - } - else - left = command(NULL, f); - screen (stdin, left); - } - no_intty = 0; - prnames++; - firstf = 0; - } - - while (fnum < nfiles) { - if ((f = checkf (fnames[fnum], &clearit)) != NULL) { - context.line = context.chrctr = 0; - Currline = 0; - if (firstf) setjmp (restore); - if (firstf) { - firstf = 0; - if (srchopt) - { - search (initbuf, f,(off_t) 1); - if (noscroll) - left--; - } - else if (initopt) - skiplns (initline, f); - } - else if (fnum < nfiles && !no_tty) { - setjmp (restore); - left = command (fnames[fnum], f); - } - if (left != 0) { - if ((noscroll || clearit) && (file_size != LLONG_MAX)) - if (clreol) - home (); - else - doclear (); - if (prnames) { - if (ceol_standout_glitch) - prmpt_erase (0); - if (clreol) - cleareol (); - pr("::::::::::::::"); - if (promptlen > 14) - prmpt_erase (14); - printf ("\n"); - if(clreol) cleareol(); - printf("%s\n", fnames[fnum]); - if(clreol) cleareol(); - pr("::::::::::::::\n"); - if (left > (off_t)(Lpp - 4)) - left =(off_t)(Lpp - 4); - } - if (no_tty) - copy_file (f); - else { - within++; - screen(f, left); - within = 0; - } - } - setjmp (restore); - fflush(stdout); - fclose(f); - screen_start.line = screen_start.chrctr = 0LL; - context.line = context.chrctr = 0LL; - } else - exitstat |= 1; /*M003*/ - fnum++; - firstf = 0; - } - if (wait_opt) wait_eof(); - reset_tty (); - return (exitstat); /*M003*/ -} - -static void -argscan(char *s) -{ - for (dlines = 0; *s != '\0'; s++) - if (isdigit(*s)) - dlines = dlines*10 + *s - '0'; - else if (*s == 'd') - dum_opt = 1; - else if (*s == 'l') - stop_opt = 0; - else if (*s == 'f') - fold_opt = 0; - else if (*s == 'p') - noscroll++; - else if (*s == 'c') - clreol++; - else if (*s == 's') - ssp_opt = 1; - else if (*s == 'u') - ul_opt = 0; - else if (*s == 'r') - cr_opt = 1; - else if (*s == 'w') - wait_opt = 1; -} - - -/* -** Check whether the file named by fs is a file which the user may -** access. If it is, return the opened file. Otherwise return NULL. -*/ - -static FILE * -checkf(register char *fs, int *clearfirst) -{ - struct stat stbuf; - register FILE *f; - int c; - - if (stat (fs, &stbuf) == -1) { - fflush(stdout); - if (clreol) - cleareol (); - perror(fs); - return (NULL); - } - if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { - printf(gettext("\n*** %s: directory ***\n\n"), fs); - return (NULL); - } - if ((f=Fopen(fs, "r")) == NULL) { - fflush(stdout); - perror(fs); - return (NULL); - } - - if ((c = Getc(f)) == '\f') /* end M001 */ - *clearfirst = 1; - else { - *clearfirst = 0; - Ungetc (c, f); - } - if ((file_size = (off_t)stbuf.st_size) == 0) - file_size = LLONG_MAX; - return (f); -} - -/* -** Print out the contents of the file f, one screenful at a time. -*/ - -#define STOP -10 - -static void -screen(register FILE *f, register off_t num_lines) -{ - register int c; - register int nchars; - int length; /* length of current line */ - static int prev_len = 1; /* length of previous line */ - - for (;;) { - while (num_lines > 0 && !Pause) { - if ((nchars = getaline (f, &length)) == EOF) - { - if (clreol) clreos(); - return; - } - if (ssp_opt && length == 0 && prev_len == 0) - continue; - prev_len = length; - if (ceol_standout_glitch || - (enter_standout_mode && *enter_standout_mode == ' ') - && promptlen > 0) - prmpt_erase (0); - /* must clear before drawing line since tabs on some terminals - * do not erase what they tab over. - */ - if (clreol) - cleareol (); - prbuf (Line, length); - if (nchars < promptlen) - prmpt_erase (nchars); /* prmpt_erase () sets promptlen to 0 */ - else promptlen = 0; - /* is this needed? - * if (clreol) - * cleareol(); */ /* must clear again in case we wrapped */ - - if (nchars < Mcol || !fold_opt) - putchar('\n'); - if (nchars == STOP) - break; - num_lines--; - } - fflush(stdout); - if ((c = Getc(f)) == EOF) - { - if (clreol) clreos (); - return; - } - - if (Pause && clreol) - clreos (); - Ungetc (c, f); - setjmp (restore); - Pause = 0; startup = 0; - if ((num_lines = command (NULL, f)) == 0) - return; - if (hard && promptlen > 0) - prmpt_erase (0); - if (noscroll && num_lines == dlines) { - if (clreol) - home(); - else - doclear (); - } - screen_start.line = Currline; - screen_start.chrctr = Ftell (f); - } -} - -/* -** Come here if a quit signal is received -*/ -/* - * sig is put in as a dummy arg to have the compiler not to complain - */ - -/* ARGSUSED */ -void -onquit(int sig) -{ - signal(SIGQUIT, SIG_IGN); - if (!inwait) { - putchar ('\n'); - if (!startup) { - signal(SIGQUIT, onquit); - longjmp (restore, 1); - } - else - Pause++; - } - else if (!dum_opt && notell) { - write (2, gettext("[Use q or Q to quit]"), 20); - promptlen += 20; - notell = 0; - } - signal(SIGQUIT, onquit); -} - -/* -** Come here if a signal for a window size change is received -*/ -/*ARGSUSED*/ -void -chgwinsz(int sig) -{ - struct winsize win; - - (void) signal(SIGWINCH, SIG_IGN); - if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) { - if (win.ws_row != 0) { - Lpp = win.ws_row; - nscroll = Lpp/2 - 1; - if (nscroll <= 0) - nscroll = 1; - dlines = (off_t)(Lpp - (noscroll ? 1 : 2)); - } - if (win.ws_col != 0) - Mcol = win.ws_col; - } - (void) signal(SIGWINCH, chgwinsz); -} - -/* -** Clean up terminal state and exit. Also come here if interrupt signal received -*/ - -/* - * sig is put in as a dummy arg to have the compiler not to complain - */ - -/* ARGSUSED */ -void -end_it(int sig) -{ - - reset_tty (); - if (clreol) { - putchar ('\r'); - clreos (); - fflush (stdout); - } - else if (!clreol && (promptlen > 0)) { - kill_line (); - fflush (stdout); - } - else - write (2, "\n", 1); - _exit(exitstat); /*M003*/ -} - -static void -copy_file(register FILE *f) -{ - register int c; - - while ((c = getc(f)) != EOF) - putchar(c); -} - -static char Bell = ctrl('G'); - - -/* See whether the last component of the path name "path" is equal to the -** string "string" -*/ - -int -tailequ(char *path, char *string) -{ - return (!strcmp(basename(path), string)); -} - -static void -prompt(char *filename) -{ - if (clreol) - cleareol (); - else if (promptlen > 0) - kill_line (); - if (!hard) { - promptlen = 8; - if (enter_standout_mode && exit_standout_mode) - putp (enter_standout_mode); - if (clreol) - cleareol (); - pr(gettext("--More--")); - if (filename != NULL) { - promptlen += printf (gettext("(Next file: %s)"), filename); - } - else if (!no_intty) { - promptlen += printf ("(%d%%)", (int)((file_pos * 100) / file_size)); - } - if (dum_opt) { - promptlen += pr(gettext("[Hit space to continue, Del to abort]")); - } - if (enter_standout_mode && exit_standout_mode) - putp (exit_standout_mode); - if (clreol) clreos (); - fflush(stdout); - } - else - write (2, &Bell, 1); - inwait++; -} - -/* - * when run from another program or a shell script, it is - * sometimes useful to prevent the next program from scrolling - * us off the screen before we get a chance to read this page. - * -Hans, July 24, 1982 - */ -static void -wait_eof(void) -{ - if (enter_standout_mode && exit_standout_mode) - putp (enter_standout_mode); - promptlen = pr(gettext("--No more--")); /*M003*/ - if (dum_opt) - promptlen += pr(gettext("[Hit any key to continue]")); - if (enter_standout_mode && exit_standout_mode) - putp(exit_standout_mode); - if (clreol) clreos(); - fflush(stdout); - readch(); - prmpt_erase (0); - fflush(stdout); -} - -/* -** Get a logical line -*/ - -static int -getaline(register FILE *f, int *length) -{ - register int c; - register char *p; - register int column; - static int colflg; - register int oldcolumn; - int csno; - - p = Line; - column = 0; - oldcolumn = 0; - c = Getc (f); - if (colflg && c == '\n') { - Currline++; - c = Getc (f); - } - while (p < &Line[LINSIZ - 1]) { - csno = csetno(c); - if (c == EOF) { - if (p > Line) { - *p = '\0'; - *length = p - Line; - return (column); - } - *length = p - Line; - return (EOF); - } - if (!csno) { - if (c == '\n') { - /* detect \r\n. -Hans */ - if (p>Line && p[-1] == '\r') { - column = oldcolumn; - p--; - } - Currline++; - break; - } - *p++ = c; - if (c == '\t') - if (hardtabs && column < promptlen && !hard) { - if (clr_eol && !dumb) { - column = 1 + (column | 7); - putp (clr_eol); - promptlen = 0; - } - else { - for (--p; column & 7 && p < &Line[LINSIZ - 1]; column++) { - *p++ = ' '; - } - if (column >= promptlen) promptlen = 0; - } - } - else - column = 1 + (column | 7); - else if ((c == '\b') && (ul_opt || !cr_opt) && (column > 0)) /* M008 */ - column--; - - /* this is sort of strange. what was here before was that - \r always set column to zero, and the hack above to - detect \r\n didnt exist. the net effect is to make - the current line be overwritten by the prompt if it - had a \r at the end, and the line start after the \r - otherwise. I suppose this is useful for overstriking - on hard copy terminals, but not on anything glass - -Hans */ - - else if ((c == '\r') && !cr_opt) { - oldcolumn = column; - column = 0; - } - else if (c == '\f' && stop_opt) { - p[-1] = '^'; - *p++ = 'L'; - column += 2; - Pause++; - } - else if (c == EOF) { - *length = p - Line; - return (column); - } - else if (c < ' ' && cr_opt){ /* M008 begin */ - p[-1] = '^'; - *p++ = c | ('A' - 1); - column += 2; - } /* M008 end */ - else if (c >= ' ' && c != RUBOUT) - column++; - } /* end of code set 0 */ - else { - column += scw[csno]; - if ( column > Mcol && fold_opt ) { - column -= scw[csno]; - while ( column < Mcol ) { - column++; - *p++ = ' '; - } - column = Mcol; - Ungetc(c,f); - } else { - int i; - *p++ = c; - for(i=1; i= Mcol && fold_opt) break; - c = Getc (f); - } - if (column >= Mcol && Mcol > 0) { - if (!Wrap) { - *p++ = '\n'; - } - } - colflg = column == Mcol && fold_opt; - if (colflg && eat_newline_glitch && Wrap) { - *p++ = '\n'; /* simulate normal wrap */ - } - *length = p - Line; - *p = 0; - return (column); -} - -/* -** Erase the rest of the prompt, assuming we are starting at column col. -*/ - -static void -prmpt_erase(register int col) -{ - - if (promptlen == 0) - return; - if (hard) { - putchar ('\n'); - } - else { - if (col == 0) - putchar ('\r'); - if (!dumb && clr_eol) - putp (clr_eol); - else - for (col = promptlen - col; col > 0; col--) - putchar (' '); - } - promptlen = 0; -} - -/* -** Erase the current line entirely -*/ - -static void -kill_line(void) -{ - prmpt_erase (0); - if (!clr_eol || dumb) putchar ('\r'); -} - -/* Print a buffer of n characters */ - -static void -prbuf(register char *s, register int n) -{ - char c; /* next ouput character */ - register int state = 0; /* next output char's UL state */ - static int pstate = 0; /* current terminal UL state (off) */ - - while (--n >= 0) - if (!ul_opt) - putchar (*s++); - else { - if (n >= 2 && s[0] == '_' && s[1] == '\b') { - n -= 2; - s += 2; - c = *s++; - state = 1; - } else if (n >= 2 && s[1] == '\b' && s[2] == '_') { - n -= 2; - c = *s++; - s += 2; - state = 1; - } else { - c = *s++; - state = 0; - } - if (state != pstate) - putp(state ? ULenter : ULexit); - pstate = state; - putchar(c); - if (state && underline_char) { - putp(cursor_left); - putp(underline_char); - } - } - /* - * M002 - * You don't want to stay in standout mode at the end of the line; - * on some terminals, this will leave all of the remaining blank - * space on the line in standout mode. - */ - if (state && !underline_char) { /*M002*/ - putp(ULexit); /*M002*/ - pstate = 0; /*M002*/ - } /*M002*/ -} - -/* -** Clear the screen -*/ - -static void -doclear(void) -{ - if (clear_screen && !hard) { - putp(clear_screen); - - /* Put out carriage return so that system doesn't - ** get confused by escape sequences when expanding tabs - */ - putchar ('\r'); - promptlen = 0; - } -} - - -static int lastcmd, lastp; -static off_t lastarg; -static int lastcolon; -char shell_line[PATH_MAX]; - -/* -** Read a command and do it. A command consists of an optional integer -** argument followed by the command character. Return the number of lines -** to display in the next screenful. If there is nothing more to display -** in the current file, zero is returned. -*/ - -static off_t -command(char *filename, register FILE *f) -{ - register off_t nlines; - register off_t retval; - register int c; - char colonch; - FILE *helpf; - int done; - char comchar, cmdbuf[80]; - char filebuf[128]; - char *loc; - -#define ret(val) retval=val;done++;break - - done = 0; - if (!errors) - prompt (filename); - else - errors = 0; - for (;;) { - nlines = number (&comchar); - lastp = colonch = 0; - if (comchar == '.') { /* Repeat last command */ - lastp++; - comchar = lastcmd; - nlines = lastarg; - if (lastcmd == ':') - colonch = lastcolon; - } - lastcmd = comchar; - lastarg = nlines; - if((comchar != RUBOUT) && !dum_opt) { - if (comchar == otty.c_cc[VERASE]) { - kill_line (); - prompt (filename); - continue; - } - } - switch (comchar) { - case ':': - retval = colon (filename, colonch, nlines); - if (retval >= 0) - done++; - break; - case 'b': - case ctrl('B'): - { - register off_t initline; - - if (no_intty) { - write(2, &bell, 1); - return (-1); - } - - if (nlines == 0) nlines++; - - putchar ('\r'); - prmpt_erase (0); - printf ("\n"); - if (clreol) - cleareol (); - printf (gettext("...back %lld page"), nlines); - if (nlines > 1) - pr ("s\n"); - else - pr ("\n"); - - if (clreol) - cleareol (); - pr ("\n"); - - initline = Currline - dlines * (nlines + 1); - if (! noscroll) - --initline; - if (initline < 0) initline = 0; - Fseek(f, 0LL); - Currline = 0; /* skiplns() will make Currline correct */ - skiplns(initline, f); - if (! noscroll) { - ret(dlines + 1); - } - else { - ret(dlines); - } - } - case ' ': - case 'z': - if (nlines == 0) nlines = dlines; - else if (comchar == 'z') dlines = nlines; - ret (nlines); - case 'd': - case ctrl('D'): - if (nlines != 0) nscroll = nlines; - ret (nscroll); - case RUBOUT: - case 'q': - case 'Q': - end_it(0); - /*NOTREACHED*/ - case 's': - case 'f': - if (nlines == 0) nlines++; - if (comchar == 'f') - nlines *= dlines; - putchar ('\r'); - prmpt_erase (0); - printf ("\n"); - if (clreol) - cleareol (); - printf (gettext("...skipping %lld line"), nlines); - if (nlines > 1) - pr ("s\n"); - else - pr ("\n"); - - if (clreol) - cleareol (); - pr ("\n"); - - while (nlines > 0) { - while ((c = Getc (f)) != '\n') - if (c == EOF) { - retval = 0; - done++; - goto endsw; - } - Currline++; - nlines--; - } - ret (dlines); - case '\n': - if (nlines != 0) - dlines = nlines; - else - nlines = 1; - ret (nlines); - case '\f': - if (!no_intty) { - doclear (); - Fseek (f, screen_start.chrctr); - Currline = screen_start.line; - ret (dlines); - } - else { - write (2, &Bell, 1); - break; - } - case '\'': - if (!no_intty) { - kill_line (); - pr (gettext("\n***Back***\n\n")); - Fseek (f, context.chrctr); - Currline = context.line; - ret (dlines); - } - else { - write (2, &Bell, 1); - break; - } - case '=': - kill_line (); - promptlen = printf ("%lld", Currline); - fflush (stdout); - break; - case 'n': - lastp++; - /*FALLTHROUGH*/ - case '/': - if (nlines == 0) nlines++; - kill_line (); - pr ("/"); - promptlen = 1; - fflush (stdout); - if (lastp) { - write (2,"\r", 1); - search (NULL, f, nlines); /* Use previous r.e. */ - } - else { - ttyin (cmdbuf, 78, '/'); - write (2, "\r", 1); - search (cmdbuf, f, nlines); - } - ret (dlines-1); - case '!': - do_shell (filename); - break; - case 'h': - case '?': - /* - * First get local help file. - */ - loc = setlocale(LC_MESSAGES, 0); - sprintf(filebuf, LOCAL_HELP, loc); - - if ((strcmp(loc, "C") == 0) || (helpf = fopen (filebuf, "r")) == NULL) { - if ((helpf = fopen (HELPFILE, "r")) == NULL) - error (gettext("Can't open help file")); - } - if (noscroll) doclear (); - copy_file (helpf); - fclose (helpf); - prompt (filename); - break; - case 'v': /* This case should go right before default */ - if (!no_intty) { - kill_line (); - cmdbuf[0] = '+'; - sprintf(&cmdbuf[1], "%lld", Currline); - pr ("vi "); pr (cmdbuf); putchar (' '); pr (fnames[fnum]); - execute (filename, VI, "vi", cmdbuf, fnames[fnum], 0); - break; - } - default: - if (dum_opt) { - kill_line (); - promptlen = pr(gettext("[Press 'h' for instructions.]")); - fflush (stdout); - } - else - write (2, &Bell, 1); - break; - } - if (done) break; - } - putchar ('\r'); -endsw: - inwait = 0; - notell++; - return (retval); -} - -char ch; - -/* - * Execute a colon-prefixed command. - * Returns <0 if not a command that should cause - * more of the file to be printed. - */ - -static int -colon(char *filename, int cmd, off_t nlines) -{ - if (cmd == 0) - ch = readch (); - else - ch = cmd; - lastcolon = ch; - switch (ch) { - case 'f': - kill_line (); - if (!no_intty) - promptlen = printf (gettext("\"%s\" line %lld"), - fnames[fnum], Currline); - else - promptlen = printf( - gettext("[Not a file] line %lld"), Currline); - fflush (stdout); - return (-1); - case 'n': - if (nlines == 0) { - if (fnum >= nfiles - 1) - end_it(0); - nlines++; - } - putchar ('\r'); - prmpt_erase (0); - skipf ((int)nlines); - return (0); - case 'p': - if (no_intty) { - write (2, &Bell, 1); - return (-1); - } - putchar ('\r'); - prmpt_erase (0); - if (nlines == 0) - nlines++; - skipf ((int)-nlines); - return (0); - case '!': - do_shell (filename); - return (-1); - case 'q': - case 'Q': - end_it(0); - default: - write (2, &Bell, 1); - return (-1); - } -} - -/* -** Read a decimal number from the terminal. Set cmd to the non-digit which -** terminates the number. -*/ - -static int -number(char *cmd) -{ - register int i; - - i = 0; ch = otty.c_cc[VKILL]; - for (;;) { - ch = readch (); - if (ch >= '0' && ch <= '9') { - i = i*10 + ch - '0'; - } else if (ch == RUBOUT) { - i = 0; - *cmd = ch; - break; - } else if (ch == otty.c_cc[VKILL]) { - i = 0; - } else { - *cmd = ch; - break; - } - } - return (i); -} - -static void -do_shell(char *filename) -{ - char cmdbuf[80]; - - kill_line (); - pr ("!"); - fflush (stdout); - promptlen = 1; - if (lastp) - pr (shell_line); - else { - ttyin (cmdbuf, 78, '!'); - if (expand (shell_line, cmdbuf)) { - kill_line (); - promptlen = printf ("!%s", shell_line); - } - } - fflush (stdout); - write (2, "\n", 1); - promptlen = 0; - shellp = 1; - execute (filename, shell, shell, "-c", shell_line, 0); -} - -/* -** Search for nth ocurrence of regular expression contained in buf in the file -*/ - -static void -search(char buf[], FILE *file, register off_t n) -{ - off_t startline = Ftell (file); - register off_t line1 = startline; - register off_t line2 = startline; - register off_t line3 = startline; - register off_t lncount; - off_t saveln; - static char *s = NULL; - static char lastbuf[80]; - - if (buf != NULL) { - free(s); - if (*buf != '\0') { - if ((s = regcmp(buf, (char *) NULL)) == NULL) - error(gettext("Regular expression botch")); - else - strcpy(lastbuf, buf); - } else { - if ((s = regcmp(lastbuf, (char *) NULL)) == NULL) - error(gettext("No previous regular expression")); - } - } else { - if (s == NULL) - error(gettext("No previous regular expression")); - } - context.line = saveln = Currline; - context.chrctr = startline; - lncount = 0; - while (!feof (file)) { - line3 = line2; - line2 = line1; - line1 = Ftell (file); - rdline (file); - lncount++; - if (regex(s, Line) != NULL) - if (--n == 0) { - if (lncount > 3 || (lncount > 1 && no_intty)) - { - pr ("\n"); - if (clreol) - cleareol (); - pr(gettext("...skipping\n")); - } - if (!no_intty) { - Currline -= (lncount >= 3 ? 3 : lncount); - Fseek (file, line3); - if (noscroll) - if (clreol) { - home (); - cleareol (); - } - else - doclear (); - } - else { - kill_line (); - if (noscroll) - if (clreol) { - home (); - cleareol (); - } else - doclear (); - pr (Line); - putchar ('\n'); - } - break; - } - } - if (feof (file)) { - if (!no_intty) { - Currline = saveln; - Fseek (file, startline); - } - else { - pr (gettext("\nPattern not found\n")); - end_it (0); - } - error (gettext("Pattern not found")); - } -} - -#define MAXARGS 10 /* enough for 9 args. We are only passed 4 now */ - -static void -execute (char *filename, char *cmd, ...) -{ - pid_t id; - va_list ap; - char *argp[MAXARGS]; - int count; - - fflush (stdout); - reset_tty (); - while ((id = fork ()) < 0) - sleep (5); - if (id == 0) { - if (no_intty) { /*M002*/ - close(0); /*M002*/ - dup(2); /*M002*/ - } /*M002*/ - va_start(ap, cmd); - count = 0; - do { - argp[count] = va_arg(ap, char *); - count++; - if (count > MAXARGS) - error (gettext("Too many arguments in execute()\n")); - } while (argp[count - 1] != NULL); - va_end(ap); - execvp(cmd, argp); - write (2, "exec failed\n", 12); - exit (1); - } - signal (SIGINT, SIG_IGN); - signal (SIGQUIT, SIG_IGN); - signal (SIGWINCH, SIG_IGN); -#ifdef SIGTSTP - if (catch_susp) - signal(SIGTSTP, SIG_DFL); -#endif - wait ((pid_t)0); - signal (SIGINT, end_it); - signal (SIGQUIT, onquit); - signal (SIGWINCH, chgwinsz); -#ifdef SIGTSTP - if (catch_susp) - signal(SIGTSTP, onsusp); -#endif - /* - * Since we were ignoring window change signals while we executed - * the command, we must assume the window changed. - */ - (void) chgwinsz(0); - set_tty (); - - pr ("------------------------\n"); - prompt (filename); -} -/* -** Skip n lines in the file f -*/ - -static void -skiplns(register off_t n, register FILE *f) -{ - register int c; - - while (n > 0) { - while ((c = Getc (f)) != '\n') - if (c == EOF) - return; - n--; - Currline++; - } -} - -/* -** Skip nskip files in the file list (from the command line). Nskip may be -** negative. -*/ - -static void -skipf(register int nskip) -{ - if (nskip == 0) return; - if (nskip > 0) { - if (fnum + nskip > nfiles - 1) - nskip = nfiles - fnum - 1; - } - else if (within) - ++fnum; - fnum += nskip; - if (fnum < 0) - fnum = 0; - pr (gettext("\n...Skipping ")); - pr ("\n"); - if (clreol) - cleareol (); - if (nskip > 0) - printf(gettext("...Skipping to file %s\n"), fnames[fnum]); - else - printf(gettext("...Skipping back to file %s\n"), fnames[fnum]); - if (clreol) - cleareol (); - pr ("\n"); - --fnum; -} - -/*----------------------------- Terminal I/O -------------------------------*/ - -static void -initterm(void) -{ - int erret = 0; - - setbuf(stdout, obuf); - if (!(no_tty = ioctl(1, TCGETA, &otty))) { - if (setupterm(NULL, 1, &erret) != OK) { - dumb++; ul_opt = 0; - } - else { - reset_shell_mode(); - if (((Lpp = lines) < 0) || hard_copy) { - hard++; /* Hard copy terminal */ - Lpp = 24; - } - if (tailequ(fnames[0], "page") || !hard && (scroll_forward == NULL)) - noscroll++; - if ((Mcol = columns) < 0) - Mcol = 80; - Wrap = tigetflag("am"); - /* - * Set up for underlining: some terminals don't need it; - * others have start/stop sequences, still others have an - * underline char sequence which is assumed to move the - * cursor forward one character. If underline sequence - * isn't available, settle for standout sequence. - */ - - if (transparent_underline || over_strike) - ul_opt = 0; - if ((ULenter = tigetstr("smul")) == NULL && - (!underline_char) && (ULenter = tigetstr("smso")) == NULL) - ULenter = ""; - if ((ULexit = tigetstr("rmul")) == NULL && - (!underline_char) && (ULexit = tigetstr("rmso")) == NULL) - ULexit = ""; - } - if ((shell = getenv("SHELL")) == NULL) - shell = "/usr/bin/sh"; - } - no_intty = ioctl(0, TCGETA, &otty); - ioctl(2, TCGETA, &otty); - hardtabs = !(otty.c_oflag & TAB3); -} - -static int -readch(void) -{ - char ch; - extern int errno; - - if (read (2, &ch, 1) <= 0) - if (errno != EINTR) - end_it(0); /* clean up before exiting */ - else - ch = otty.c_cc[VKILL]; - return (ch); -} - -static char BS = '\b'; -static char CARAT = '^'; - -static void -ttyin(char buf[], register int nmax, char pchar) -{ - register char *sptr; - register unsigned char ch; - int LengthBuffer[80]; - int *BufferPointer; - int csno; - register int slash = 0; - int maxlen; - char cbuf; - - BufferPointer = LengthBuffer; - sptr = buf; - maxlen = 0; - while (sptr - buf < nmax) { - if (promptlen > maxlen) - maxlen = promptlen; - ch = readch (); - csno = csetno(ch); - if (!csno) { - if (ch == '\\') { - slash++; - } else if ((ch == otty.c_cc[VERASE]) && !slash) { - if (sptr > buf) { - --promptlen; - write (2, &BS, 1); - sptr -= (*--BufferPointer); - if ((*sptr < ' ' && *sptr != '\n') || *sptr == RUBOUT) { - --promptlen; - write (2, &BS, 1); - } - continue; - } else { - if (!clr_eol) - promptlen = maxlen; - longjmp (restore, 1); - } - } else if ((ch == otty.c_cc[VKILL]) && !slash) { - if (hard) { - show(ch); - putchar ('\n'); - putchar (pchar); - } else { - putchar ('\r'); - putchar (pchar); - if (clr_eol) - prmpt_erase (1); - promptlen = 1; - } - sptr = buf; - fflush (stdout); - continue; - } - if (slash && (ch == otty.c_cc[VKILL] || ch == otty.c_cc[VERASE])) { - write (2, &BS, 1); - sptr -= (*--BufferPointer); - } - if (ch != '\\') - slash = 0; - *BufferPointer++ = 1; - *sptr++ = ch; - if ((ch < ' ' && ch != '\n' && ch != ESC) || ch == RUBOUT) { - ch += ch == RUBOUT ? -0100 : 0100; - write (2, &CARAT, 1); - promptlen++; - } - cbuf = ch; - if (ch != '\n' && ch != ESC) { - write (2, &cbuf, 1); - promptlen++; - } else - break; - /* end of code set 0 */ - } else { - int i; - u_char buffer[5]; - - *BufferPointer++ = cw[csno]; - buffer[0] = *sptr++ = ch; - for(i=1; i= nmax - 1) - error (gettext("Line too long")); -} - -static int -expand(char *outbuf, char *inbuf) -{ - char *in_str; - char *out_str; - char ch; - char temp[PATH_MAX]; - int changed = 0; - - in_str = inbuf; - out_str = temp; - while ((ch = *in_str++) != '\0') - switch (ch) { - case '%': - if (!no_intty) { - if (strlcpy(out_str, fnames[fnum], sizeof (temp)) - >= sizeof (temp)) - error(gettext("Command too long")); - out_str += strlen (fnames[fnum]); - changed++; - } - else - *out_str++ = ch; - break; - case '!': - if (!shellp) - error (gettext("No previous command to substitute for")); - if (strlcpy(out_str, shell_line, sizeof (temp)) >= sizeof (temp)) - error(gettext("Command too long")); - out_str += strlen (shell_line); - changed++; - break; - case '\\': - if (*in_str == '%' || *in_str == '!') { - *out_str++ = *in_str++; - break; - } - default: - *out_str++ = ch; - } - *out_str++ = '\0'; - if (strlcpy(outbuf, temp, sizeof (shell_line)) >= sizeof (shell_line)) - error(gettext("Command too long")); - return (changed); -} - -static void -show(register char ch) -{ - char cbuf; - - if ((ch < ' ' && ch != '\n' && ch != ESC) || ch == RUBOUT) { - ch += ch == RUBOUT ? -0100 : 0100; - write (2, &CARAT, 1); - promptlen++; - } - cbuf = ch; - write (2, &cbuf, 1); - promptlen++; -} - -static void -error (char *mess) -{ - if (clreol) - cleareol (); - else - kill_line (); - promptlen += strlen (mess); - if (enter_standout_mode && exit_standout_mode) { - putp (enter_standout_mode); - pr(mess); - putp (exit_standout_mode); - } - else - pr (mess); - fflush(stdout); - errors++; - longjmp (restore, 1); -} - - -static void -set_tty(void) -{ - ioctl(2, TCGETA, &otty); /* save old tty modes */ - ioctl(2, TCGETA, &ntty); - ntty.c_lflag &= ~ECHO & ~ICANON; - ntty.c_cc[VMIN] = (char)1; - ntty.c_cc[VTIME] = (char)0; - ioctl (2, TCSETAF, &ntty); /* set new tty modes */ -} - -static void -reset_tty(void) -{ - ioctl (2, TCSETAF, &otty); /* reset tty modes */ -} - -static void -rdline(register FILE *f) -{ - register int c; - register char *p; - - p = Line; - while ((c = Getc (f)) != '\n' && c != EOF && p - Line < LINSIZ - 1) - *p++ = c; - if (c == '\n') - Currline++; - *p = '\0'; -} - -/* Come here when we get a suspend signal from the terminal */ - -/* - * sig is put in as a dummy arg to have the compiler not to complain - */ -#ifdef SIGTSTP -/* ARGSUSED */ -void -onsusp(int sig) -{ - /* ignore SIGTTOU so we don't get stopped if csh grabs the tty */ - signal(SIGTTOU, SIG_IGN); - reset_tty (); - fflush (stdout); - signal(SIGTTOU, SIG_DFL); - - /* Send the TSTP signal to suspend our process group */ - kill (0, SIGTSTP); - /* Pause for station break */ - - /* We're back */ - signal (SIGTSTP, onsusp); - set_tty (); - if (inwait) - longjmp (restore, 1); -} -#endif diff --git a/usr/src/cmd/more/more.help b/usr/src/cmd/more/more.help deleted file mode 100644 index 060a996e73..0000000000 --- a/usr/src/cmd/more/more.help +++ /dev/null @@ -1,25 +0,0 @@ -#ident "%Z%%M% %I% %E% SMI" -Most commands optionally preceded by integer argument k. Defaults in brackets. -Star (*) indicates argument becomes new default. -------------------------------------------------------------------------------- - Display next k lines of text [current screen size] -z Display next k lines of text [current screen size]* - Display next k lines of text [1]* -d or ctrl-D Scroll k lines [current scroll size, initially 11]* -q or Q or Exit from more -s Skip forward k lines of text [1] -f Skip forward k screenfuls of text [1] -b or ctrl-B Skip backwards k screenfuls of text [1] -' Go to place where previous search started -= Display current line number -/ Search for kth occurrence of regular expression [1] -n Search for kth occurrence of last r.e [1] -! or :! Execute in a subshell -v Start up vi at current line -h Display this message -ctrl-L Redraw screen -:n Go to kth next file [1] -:p Go to kth previous file [1] -:f Display current file name and line number -. Repeat previous command -------------------------------------------------------------------------------- diff --git a/usr/src/msg/Makefile b/usr/src/msg/Makefile index 9c398da3bd..a6bf34d5bc 100644 --- a/usr/src/msg/Makefile +++ b/usr/src/msg/Makefile @@ -48,7 +48,6 @@ MSGDIRFILES_LOCAL= \ # MSGDIRFILES_REMOTE= \ mailx.help \ - more.help \ priv_names MSGDIRFILES= $(MSGDIRFILES_LOCAL) $(MSGDIRFILES_REMOTE) @@ -83,8 +82,6 @@ priv_names: $(ROOT)/etc/security/priv_names mailx.help: $(SRC)/cmd/mailx/misc/mailx.help -more.help: $(SRC)/cmd/more/more.help - $(MSGDIRFILES_REMOTE): $(RM) $@; $(CP) $? $@ diff --git a/usr/src/pkg/manifests/SUNWcs.man1.inc b/usr/src/pkg/manifests/SUNWcs.man1.inc index 2dcf6c4e37..c542bdd0b1 100644 --- a/usr/src/pkg/manifests/SUNWcs.man1.inc +++ b/usr/src/pkg/manifests/SUNWcs.man1.inc @@ -92,6 +92,8 @@ file path=usr/share/man/man1/kill.1 file path=usr/share/man/man1/kmfcfg.1 file path=usr/share/man/man1/kvmstat.1 file path=usr/share/man/man1/ld.so.1.1 +file path=usr/share/man/man1/less.1 +file path=usr/share/man/man1/lesskey.1 file path=usr/share/man/man1/let.1 file path=usr/share/man/man1/limit.1 file path=usr/share/man/man1/line.1 diff --git a/usr/src/pkg/manifests/consolidation-osnet-osnet-message-files.mf b/usr/src/pkg/manifests/consolidation-osnet-osnet-message-files.mf index 0b47a3c080..4fcc97e6aa 100644 --- a/usr/src/pkg/manifests/consolidation-osnet-osnet-message-files.mf +++ b/usr/src/pkg/manifests/consolidation-osnet-osnet-message-files.mf @@ -286,7 +286,6 @@ file path=usr/lib/locale/C/LC_MESSAGES/libdll group=sys mode=0644 file path=usr/lib/locale/C/LC_MESSAGES/libshell group=sys mode=0644 file path=usr/lib/locale/C/LC_MESSAGES/libsum group=sys mode=0644 file path=usr/lib/locale/C/LC_MESSAGES/mailx.help group=sys mode=0644 -file path=usr/lib/locale/C/LC_MESSAGES/more.help group=sys mode=0644 file path=usr/lib/locale/C/LC_MESSAGES/priv_names group=sys mode=0644 file path=usr/lib/locale/C/LC_MESSAGES/uxlibc.src group=sys mode=0644 file path=usr/lib/locale/C/LC_TIME/SUNW_OST_OSCMD.po group=sys mode=0644 diff --git a/usr/src/pkg/manifests/system-core-os.mf b/usr/src/pkg/manifests/system-core-os.mf index 49022360bb..cc51faedb4 100644 --- a/usr/src/pkg/manifests/system-core-os.mf +++ b/usr/src/pkg/manifests/system-core-os.mf @@ -214,6 +214,7 @@ dir path=usr/share/man/man5 dir path=usr/share/man/man7d dir path=usr/share/man/man7fs dir path=usr/share/man/man8 +dir path=usr/share/misc dir path=var group=sys dir path=var/adm group=sys mode=0775 dir path=var/adm/exacct group=adm owner=adm @@ -675,6 +676,8 @@ file path=usr/bin/keylogin mode=0555 file path=usr/bin/keylogout mode=0555 file path=usr/bin/kmfcfg mode=0555 file path=usr/bin/kvmstat mode=0555 +file path=usr/bin/less mode=0555 +file path=usr/bin/lesskey mode=0555 file path=usr/bin/line mode=0555 file path=usr/bin/listdgrp mode=0555 file path=usr/bin/loadkeys mode=0555 @@ -690,7 +693,6 @@ file path=usr/bin/mkdir mode=0555 file path=usr/bin/mkpwdict mode=0555 file path=usr/bin/mktemp mode=0555 file path=usr/bin/moe mode=0555 -file path=usr/bin/more mode=0555 file path=usr/bin/mpstat mode=0555 file path=usr/bin/mt mode=0555 file path=usr/bin/netstat mode=0555 @@ -1020,7 +1022,6 @@ file path=usr/lib/inet/inetd mode=0555 file path=usr/lib/intrd mode=0555 file path=usr/lib/isaexec mode=0555 file path=usr/lib/libshare.so.1 -file path=usr/lib/more.help file path=usr/lib/newsyslog group=sys mode=0555 file path=usr/lib/passmgmt group=sys mode=0555 file path=usr/lib/pci/pcidr mode=0555 @@ -1201,6 +1202,8 @@ file path=usr/share/lib/xml/dtd/adt_record.dtd.1 file path=usr/share/lib/xml/dtd/kmfpolicy.dtd file path=usr/share/lib/xml/dtd/service_bundle.dtd.1 group=sys file path=usr/share/lib/xml/style/adt_record.xsl.1 +file path=usr/share/misc/less.help +file path=usr/share/misc/more.help file path=var/adm/aculog mode=0600 owner=uucp preserve=true file path=var/adm/spellhist mode=0666 preserve=true file path=var/adm/utmpx preserve=true @@ -1232,10 +1235,10 @@ hardlink path=usr/bin/lzegrep target=../../usr/bin/grep hardlink path=usr/bin/lzfgrep target=../../usr/bin/grep hardlink path=usr/bin/lzgrep target=../../usr/bin/grep hardlink path=usr/bin/mac target=../../usr/lib/isaexec +hardlink path=usr/bin/more target=less hardlink path=usr/bin/mv target=../../usr/bin/cp hardlink path=usr/bin/newtask target=../../usr/lib/isaexec hardlink path=usr/bin/nohup target=../../usr/lib/isaexec -hardlink path=usr/bin/page target=../../usr/bin/more hardlink path=usr/bin/pfbash target=../../usr/bin/pfexec hardlink path=usr/bin/pfcsh target=../../usr/bin/pfexec hardlink path=usr/bin/pfsh target=../../usr/bin/pfexec diff --git a/usr/src/pkg/manifests/text-less.mf b/usr/src/pkg/manifests/text-less.mf new file mode 100644 index 0000000000..e418dedaf0 --- /dev/null +++ b/usr/src/pkg/manifests/text-less.mf @@ -0,0 +1,3 @@ +set name=pkg.fmri value=pkg:/text/less@481.1,$(PKGVERS_BUILTON)$(PKGVERS_BRANCH) +set name=pkg.renamed value=true +depend fmri=pkg:/system/core-os@$(PKGVERS) type=require -- 2.11.4.GIT