Garrett D'Amore  committed db22b54

This is a first go at an import of less; this is a major port of less,
making it lint and cstyle clean, making it speak terminfo instead of
termcap, use native glob routines, etc. All the portability stuff is
removed from it. It is a fairly clean POSIX program now (although admittedly
its not *that* clean; some of the code is pretty ugly still, but its
vastly better than it was.) I've fixed a number of bugs (the -e problem
when acting as more was particularly nasty, that's resovled now.)

This integration also attempts to replace /usr/bin/more and /usr/xpg4/bin/more
with links to this version of less. Casual use and verfication against POSIX
standards seems to hold up so far.

The legacy more options may not be supported. The more(1) man page still
needs an edit.

It would be nice to do some other cleanups here and improvements. This
program is not i18n compliant (really?!?) and still has legacy crap like
its own replacement for getopt (MS-DOS sucks, fortunately we don't have to
worry about it.)

Much more detail is in README.illumos.

This commit has not been through a full nightly build yet.

  • Participants
  • Parent commits 65d909c
  • Branches less

Comments (0)

Files changed (65)

File exception_lists/closed-bins Modified

View file
  • Ignore whitespace
  • Hide word diff
 ./usr/xpg4/bin/hash
 ./usr/xpg4/bin/jobs
 ./usr/xpg4/bin/kill
+./usr/xpg4/bin/more
 ./usr/xpg4/bin/od
 ./usr/xpg4/bin/read
 ./usr/xpg4/bin/sed

File usr/src/Makefile.lint Modified

View file
  • Ignore whitespace
  • Hide word diff
 	cmd/last \
 	cmd/lastcomm \
 	cmd/ldapcachemgr \
+	cmd/less \
 	cmd/line \
 	cmd/link \
 	cmd/locator \

File usr/src/cmd/Makefile Modified

View file
  • Ignore whitespace
  • Hide word diff
 	line		\
 	link		\
 	dlmgmtd		\
+	less		\
 	listen		\
 	loadkeys	\
 	locale		\
 	mkpwdict	\
 	mktemp		\
 	modload		\
-	more		\
 	mpathadm	\
 	msgfmt		\
 	msgid		\
 	mkdir		\
 	mkpwdict	\
 	mktemp		\
-	more		\
 	mpathadm	\
 	msgfmt		\
 	mv		\

File usr/src/cmd/less/LICENSE Added

View file
  • Ignore whitespace
  • Hide word diff
+
+illumos chooses to distribute less under the following Less License terms.
+The illumos modifications to less are provided under these terms.
+
+                          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.
+

File usr/src/cmd/less/Makefile Added

View file
  • Ignore whitespace
  • Hide word diff
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+PROGS=		less lesskey
+XPG4PROG =	more
+LINKS =		more
+
+
+OBJS=		$(less_OBJS) $(lesskey_OBJS)
+LINTS=		$(less_LNS) $(lesskey_LNS)
+CLOBBERFILES +=	$(PROGS)
+
+include		$(SRC)/cmd/Makefile.cmd
+include		$(SRC)/cmd/less/Makefile.common
+
+ROOTLINKS =	$(LINKS:%=$(ROOTBIN)/%)
+ROOTPROGS =	$(PROGS:%=$(ROOTBIN)/%) $(ROOTLINKS) $(ROOTXPG4PROG)
+$(ROOTLINKS) 	:= INSLINKTARGET = $(ROOTBIN)/less
+$(ROOTXPG4PROG)	:= INSLINKTARGET = ../../bin/$(XPG4PROG)
+
+.KEEP_STATE:
+
+all:		$(PROGS)
+
+less:		$(less_OBJS)
+		$(LINK.c) $(less_OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+lesskey:	$(lesskey_OBJS)
+		$(LINK.c) $(lesskey_OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+clean:
+		$(RM) $(OBJS) $(LINTS)
+
+install:	all $(ROOTPROGS)
+
+lint:		less_LINT lesskey_lint
+
+less_LINT:	$(less_LNS)
+		$(LINT.c) $(less_LNS) $(LDLIBS)
+
+lesskey_lint:	$(lesskey_LNS)
+		$(LINT.c) $(lesskey_LNS) $(LDLIBS)
+
+$(ROOTLINKS):	$(ROOTBIN)/less
+		$(INS.link)
+
+$(ROOTXPG4PROG):
+		$(INS.symlink)
+		
+include		$(SRC)/cmd/Makefile.targ

File usr/src/cmd/less/Makefile.common Added

View file
  • Ignore whitespace
  • Hide word diff
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+PROGS=		less lesskey
+
+less_OBJS =	main.o screen.o brac.o ch.o charset.o cmdbuf.o		\
+		command.o cvt.o decode.o edit.o filename.o forwback.o	\
+		help.o ifile.o input.o jump.o line.o linenum.o		\
+		lsystem.o mark.o optfunc.o option.o opttbl.o os.o	\
+		output.o pattern.o position.o prompt.o search.o		\
+		signal.o tags.o ttyin.o version.o
+
+lesskey_OBJS =	lesskey.o version.o
+
+less_LNS =	$(less_OBJS:%.o=%.ln)
+
+lesskey_LNS =	$(lesskey_OBJS:%.o=%.ln)
+
+CFLAGS +=	-D_FILE_OFFSET_BITS=64
+CFLAGS +=	-I$(ROOT)/usr/xpg4/include -I . -DSYSDIR=\"/etc\" $(CC_VERBOSE)
+
+LINTFLAGS +=	-D_FILE_OFFSET_BITS=64
+LINTFLAGS +=	-I$(ROOT)/usr/xpg4/include -I . -DSYSDIR=\"/etc\" $(CC_VERBOSE)
+
+LDLIBS +=	-L$(ROOT)/usr/xpg4/lib -R /usr/xpg4/lib -lcurses

File usr/src/cmd/less/NEWS Added

View file
  • Ignore whitespace
  • Hide word diff
+
+                     NEWS about less
+
+======================================================================
+
+  For the latest news about less, see the "less" Web page:
+      http://www.greenwoodsoftware.com/less
+  You can also download the latest version of less from there.
+
+  To report bugs, suggestions or comments, send email to bug-less@gnu.org.
+
+======================================================================
+
+	Major changes between "less" versions 451 and 458
+
+* Allow backslash escaping of metacharacters in LESS environment variable
+  after the --use-backslash option.
+
+* Don't quit if syntax errors are found in command line options.
+
+* Increase sizes of some internal buffers.
+
+* Fix configure bug with --with-regex=none.
+
+* Fix crash with "stty rows 0".
+
+* Fix Win32 attribute display bug.
+
+* Fix display bug when using up/down arrow on the command line.
+
+======================================================================
+
+	Major changes between "less" versions 444 and 451
+
+* Add ESC-F command to keep reading data until a pattern is found.
+
+* Use exit code of LESSOPEN script if LESSOPEN starts with "||".
+
+* When up/down arrow is used on the command line immediately after
+  typing text, the next command starting with that text is found.
+
+* Add support for GNU regex.
+
+* Add configure option --with-regex=none and fix compile errors
+  when compiling with no regex library.
+
+* Fix bugs handling SGR sequences in Win32.
+
+* Fix possible crashes caused by malformed LESSOPEN or 
+  LESSCLOSE variables.
+
+* Fix bug highlighting text which is discontiguous in the file 
+  due to backspace processing.
+
+* Fix bug in displaying status column when scrolling backwards 
+  with -J and -S in effect.
+
+======================================================================
+
+	Major changes between "less" versions 443 and 444
+
+* Fix bug in unget handling that can cause strange effects on the
+  command line.
+
+* Remove vestiges of obsolete -l option that can cause a crash.
+
+======================================================================
+
+	Major changes between "less" versions 436 and 443
+
+* Change search behavior such that when a search is given an explicit 
+  pattern, the entire displayed screen is included in the search and 
+  not just the portion after the target line.
+
+* Add -A option to change search behavior to the old way: only
+  the portion of the screen after the target line is searched.
+
+* Add %F formatting to prompt strings, replaced by the last component
+  of the input file.
+
+* Control-G while editing a command exits the command.
+
+* Less now exits with status 2 if control-C is pressed and -K is in effect.
+
+* Fix "ungetc overflow" when passing long commands via the -p option.
+
+* Fix bug in using line filtering via the & command 
+  in combination with -i and -I.
+
+* Fix bug in handling negative arguments to the -j option.
+
+* Fix bug in handling %t in prompt strings.
+
+* Improve handling of long option names.
+
+* Improve percentage calculation for very large files.
+
+======================================================================
+
+	Major changes between "less" versions 429 and 436
+
+* Don't pass "-" to non-pipe LESSOPEN unless it starts with "-".
+
+* Allow a fraction as the argument to the -# (--shift) option.
+
+* Fix highlight bug when underlined/overstruck text matches at end of line.
+
+* Fix non-regex searches with ctrl-R.
+
+======================================================================
+
+	Major changes between "less" versions 424 and 429
+
+* LESSOPEN pipe will now be used on standard input, if the LESSOPEN
+  environment variable begins with "|-".
+
+* The -D option with one number now means use the normal background color.
+
+* Don't change permissions on history file if it is not a regular file.
+
+* Fix non-ANSI-compliant code that caused problems with some compilers.
+
+* Fix binary file detection in UTF-8 mode.
+
+* Fix display problems with long lines on "ignaw" terminals.
+
+* Fix problem interrupting the line number calculation for initial prompt.
+
+* Fix SGR emulation when dealing with multiple attributes (eg. bold+underline).
+
+* Fix highlight bug when searching for underlined/overstruck text.
+
+======================================================================
+
+	Major changes between "less" versions 418 and 424
+
+* New "&" command allows filtering of lines based on a pattern.
+
+* Status column now displays a search match, even if the matched
+  string is scrolled off screen because -S is in effect.
+
+* Improve behavior of -F option.
+
+* Allow CSI character (0x9B) to work in UTF-8 mode.
+
+* Output carriage return at startup in case terminal doesn't default
+  to column 1.
+
+* Fix bug in '' (quote, quote) command after G command.
+
+======================================================================
+
+	Major changes between "less" versions 416 and 418
+
+* Color escape sequences are now supported in WIN32 build.
+
+* Makefile now uses EXEEXT feature of autoconf.
+
+* Fix search bug when using -R and text contains ANSI color escape sequences.
+
+* Fix crash when using -r with UTF-8 text containing 0x9B bytes.
+
+* Fix display bug when using ' command to move less than one page forward.
+
+* Update GPL to version 3.
+
+======================================================================
+
+	Major changes between "less" versions 409 and 416
+
+* New --follow-name option makes F command follow the name of a file
+  rather than the file descriptor if an open file is renamed.
+
+* Make searching with -i/-I work correctly with non-ASCII text.
+
+* Fix DJGPP build.
+
+======================================================================
+
+	Major changes between "less" versions 406 and 409
+
+* Support CSI escape sequences, like SGR escape sequences.
+
+* Fix bug which caused screen to fail to repaint when window is resized.
+
+* Fix bug in using -i and -I flags with non-ASCII text.
+
+* Fix configure bug on systems which don't support langinfo.h.
+
+* Fix crash when searching text containing certain invalid UTF-8 sequences.
+
+======================================================================
+
+	Major changes between "less" versions 394 and 406
+
+* Allow decimal point in number for % (percent) command.
+
+* Allow decimal point in number for -j option (fraction of screen height).
+
+* Make n command fetch previous pattern from history file on first search.
+
+* Don't rewrite history file if it has not changed.
+
+* Don't move to bottom of screen on first page.
+
+* Don't output extraneous newlines, so copy & pasting lines from the
+  output works better.
+
+* The -c option has been made identical with the -C option.
+
+* Allow "/dev/null" as synomym for "-" in LESSHISTFILE to indicate
+  that no history file should be used.
+
+* Search can now find text which follows a null byte, if the PCRE
+  library is used, or if no-regex searching (ctrl-R) is used.
+
+* Better compatibility with POSIX more specification.
+
+* Make -f work for directories.
+
+* Make "t" cmd traverse tags in the correct order.
+
+* Allow a few binary characters in the input file before warning
+  that the file is binary.
+
+* Don't warn that file is binary if it merely contains ANSI color sequences
+  and -R is in effect.
+
+* Update Unicode character tables.
+
+* Support DESTDIR in Makefile.
+
+* Fix bug when filename contains certain shell metacharacters such as "$".
+
+* Fix bug when resizing the window while waiting for input from a pipe.
+
+* Fix configure bugs.
+
+======================================================================
+
+	Major changes between "less" versions 382 and 394
+
+* Add history file to save search and shell command history between
+  invocations of less.
+
+* Improve behavior of history list for search and shell commands.
+
+* Add -K (or --quit-on-intr) option to make less exit immediately on ctrl-C.
+
+* Improve handling of UTF-8 files and commands, including better
+  line wrapping and handling double-width chars.
+
+* Added LESSUTFBINFMT environment variable to control display of
+  non-printable characters in a UTF-8 file.
+
+* Add --with-secure option to configure, to make it easier to
+  build a secure version of less.
+
+* Show search matches in the status column even if search highlights
+  are disabled via the -G option or the ESC-u command.
+
+* Improve performance when the file contains very long lines.
+
+* Add "windows" charset.
+
+* Add man page for lessecho.
+
+* Add support for erase2 character, treated same as erase.
+
+* Use ASCII lowercase/uppercase logic when operating on the command line.
+
+* Update makefile for Borland C++ 5.5.1.
+
+* Fix bug in calculating number of pages for %D prompt.
+
+* Fix bug in handling tag file error.
+
+* Fix obscure bug if input file is deleted while viewing help.
+
+* Fix bug handling filenames which include square brackets.
+
+* Fix possible buffer overflow in "global" tag search.
+
+* Fix possible buffer overflow in usage of LESSOPEN and LESSCLOSE.
+
+* Fix buffer overflow in reverse search.
+
+======================================================================
+
+	Major changes between "less" versions 381 and 382
+
+* Removed some old copyrighted code.
+  This probably breaks OS/9 support.
+
+======================================================================
+
+	Major changes between "less" versions 378 and 381
+
+* New -L option to disable LESSOPEN processing.
+
+* Further support for large (64 bit) file addressing.
+  Large file support is now set up by the configure script.
+
+* Use autoconf 2.54.
+  Replace configure.in, acconfig.h, defines.h.top with configure.ac.
+
+* Overstriking underscore with underscore is now bold or underlined 
+  depending on context.
+
+* Use only 7 spaces for line numbers in -N mode, if possible.
+
+* Fix some bugs in handling overstriking in UTF-8 files.
+
+* Fix some nroff issues in the man page.
+
+======================================================================
+
+	Major changes between "less" versions 376 and 378
+
+* Bug fixes:
+  Default buffer space is now 64K as documented.
+  Search highlighting works properly when used with -R.
+  Windows version works properly when input file contains carriage returns.
+  Clean up some compiler warnings.
+
+======================================================================
+
+	Major changes between "less" versions 358 and 376
+
+* -x option can now specify multiple variable-width tab stops.
+
+* -X option no longer disables keypad initialization.
+  New option --no-keypad disables keypad initialization.
+
+* New commands t and T step through multiple tag matches.
+  Added support for "global(1)" tags
+  (see http://www.gnu.org/software/global/global.html).
+
+* New prompt style set by option -Pw defines the message printed 
+  while waiting for data in the F command.
+
+* System-wide lesskey file now defaults to sysless in etc directory 
+  instead of .sysless in bin directory.
+  Use "configure --sysconfdir=..." to change it.
+  (For backwards compatibility, .sysless in bin is still recognized.)
+
+* Pressing RightArrow or LeftArrow while entering a number now shifts
+  the display N columns rather than editing the number itself.
+
+* Status column (enabled with -J) now shows search results.
+
+* Windows version sets window title.
+
+* Default LESSCHARSET for MS-DOS versions is now "dos".
+
+* Searching works better with ANSI (SGR) escape sequences.
+  ANSI color escape sequences are now supported in the MS-DOS (DJGPP) version.
+
+* Improved performance in reading very large pipes.
+
+* Eliminated some dependencies on file offets being 32 bits.
+
+* Fixed problems when viewing files with very long lines.
+
+* Fixed overstriking in UTF-8 mode, and overstriking tabs.
+
+* Improved horizontal shifting of text using -R option with ANSI color.
+
+* Improved handling of filenames containing shell metacharacters.
+
+* Some fixes for EBCDIC systems.
+
+* Some fixes for OS/2 systems.
+
+======================================================================
+
+	Major changes between "less" versions 354 and 358
+
+* Add -J (--status-column) option to display a status column.
+
+* Add -# (--shift) option to set default horizontal shift distance.
+  Default horizontal shift distance is now one-half screen width.
+
+* Horizontal shifting does not shift line numbers if -N is in effect.
+
+* Horizontal shifting acts as though -S were set, to avoid confusion.
+
+======================================================================
+
+
+	Major changes between "less" versions 352 and 354
+
+* Allow space after numeric-valued command line options.
+
+* Fix problem with configuring terminal libraries on some systems.
+
+* Add support for PCRE regular expression library.
+
+* Add --with-regex option to configure to allow manually selecting
+  a regular expression library.
+
+* Fix bug compiling with SECURE = 1.
+
+======================================================================
+
+
+	Major changes between "less" versions 346 and 352
+
+* Enable UTF-8 if "UTF-8" appears in locale-related environment variables.
+
+* Add --with-editor option to configure script.
+
+* The -M prompt and = message now show the top and bottom line number.
+
+* Fix bug in running the editor on a file whose name contains quotes, etc.
+
+* Fix bug in horizontal scrolling of long lines.
+
+* Fix bug in doing :d on a file which contains marks.
+
+* Fix bug causing cleared lines to sometimes be filled with standout, 
+  bold, underline, etc. on certain terminals.
+
+* Fixes for MS-DOS (DJGPP) version.
+
+======================================================================
+
+
+	Major changes between "less" versions 340 and 346
+
+* The UTF-8 character set is now supported.
+
+* The default character set is now latin1 rather than ascii.
+
+* New option -R (--RAW-CONTROL-CHARS) is like -r but handles 
+  long (wrapped) lines correctly, as long as the input contains only 
+  normal text and ANSI color escape sequences.
+
+* New option -F (--quit-if-one-screen) quits if the text fits on
+  the first screen.
+
+* The -w option now highlights the target line of a g or p command.
+
+* A system-wide lesskey file is supported (LESSKEY_SYSTEM).
+
+* New escape for prompt strings: %c is replaced by column number.
+
+* New escape for prompt strings: %P is replaced by percentage into
+  file, based on line number rather than byte offset.
+
+* HOME and END keys now jump to beginning of file or end of file.
+
+======================================================================
+
+
+	Major changes between "less" versions 337 and 340
+
+* Command line options for less may now be given in either the old 
+  single-letter form, or a new long name form (--option-name).
+  See the less man page or "less --help" for the list of long option names.
+
+* Command line options for lesskey may now be given in a new long name
+  form.  See the lesskey man page for the list of long option names.
+
+* New command -- toggles an option using the long option name.
+
+* New command __ queries an option using the long option name.
+
+* The old -- command is renamed as -!.
+
+* If a ^P is entered between the dash and the option letter of the -
+  command, the message describing the new setting is suppressed.
+
+* Lesskey files may now contain \k escape sequences to represent the
+  "special" keys (arrows, PAGE-UP/PAGE-DOWN, HOME, END, INSERT, DELETE).
+
+* New command :d removes the current file from the list of files.
+
+* New option -~ (like -w before version 335)
+  suppresses tildes after end-of-file.
+
+* Less is now released under the GNU General Public License.
+
+======================================================================
+
+
+	Major changes between "less" versions 335 and 337
+
+* Fixed bugs in "make install".
+
+======================================================================
+
+
+	Major changes between "less" versions 332 and 335
+
+* The old -w flag (suppress tildes after end-of-file) has been removed.
+
+* New -w flag highlights the first new line after a forward-screen.
+
+* New -W flag highlights the first new line after any forward movement.
+
+* Window resize works even if LINES and/or COLUMNS environment 
+  variables are incorrect.
+
+* New percent escapes for prompt strings:
+  %d is replaced by the page number, and
+  %D is replaced by the number of pages in the file.
+
+* Added charsets "iso8859" and "ebcdic".
+
+* In Windows version, uses HOMEDRIVE and HOMEPATH if HOME is not defined.
+
+* Fixed some bugs causing incorrect display on DOS/Windows.
+
+======================================================================
+
+
+	Major changes between "less" versions 330 and 332
+
+* Filenames from the command line are entered into the command history,
+  so UPARROW/DOWNARROW can be used to retrieve them from the :e command.
+
+* Now works correctly on Windows when using a scrolling terminal
+  window (buffer larger than display window).
+
+* On Windows, now restores the console screen on exit.  
+  Use -X to get the old behavior.
+
+* Fixed bug on Windows when CAPS-LOCK or NUM-LOCK is pressed.
+
+* Fixed bug on Windows when piping output of an interactive program.
+
+* Fixed bug in tags file processing when tags file has DOS-style
+  line terminators (CR/LF).
+
+* Fixed compilation problem on OS/2.
+
+======================================================================
+
+
+	Major changes between "less" versions 321 and 330
+
+* Now supports filenames containing spaces (in double quotes).
+  New option -" can be used to change the quoting characters.
+
+* In filename completion, a slash is appended to a directory name.
+  If the environment variable LESSSEPARATOR is set, the value of
+  that variable, rather than a slash, is appended.
+
+* LeftArrow and RightArrow are same as ESC-[ and ESC-].
+
+* Added commands ESC-( and ESC-), same as ESC-[ and ESC-].
+
+* A "quit" command defined in a lesskey file may now have an "extra" 
+  string, which is used to return an exit code from less when it quits.
+
+* New environment variables LESSMETACHARS and LESSMETAESCAPE provide
+  more control over how less interfaces to the shell.
+
+* Ported to Microsoft Visual C compiler for Windows.
+
+* Ported to DJGPP compiler for MS-DOS.
+
+* Bug fixes.
+
+======================================================================
+
+
+	Major changes between "less" versions 291 and 321
+
+* Command line at bottom of screen now scrolls, so it can be longer 
+  than the screen width.
+
+* New commands ESC-] and ESC-[ scroll the display horizontally.
+
+* New command ESC-SPACE scrolls forward a full screen, even if it
+  hits end-of-file.
+
+* Alternate modifiers for search commands: ^N is same as !,
+  ^F is same as @, and ^E is same as *.
+
+* New modifier for search commands: ^K means highlight the matches
+  currently on-screen, but don't move to the first match.
+
+* New modifier for search commands: ^R means don't use regular
+  expressions in the search.
+
+* Environment variable LESSKEY gives name of default lesskey file.
+
+* Environment variable LESSSECURE will force less to run in
+  "secure" mode.
+
+* Command line argument "--" signals that the rest of the arguments
+  are files (not option flags).
+
+* Help file (less.hlp) is no longer installed.  Help text is now 
+  embedded in the less executable itself.
+
+* Added -Ph to change the prompt for the help text.
+  Added -Ps to change the default short prompt (same as plain -P).
+
+* Ported to the Borland C compiler for MS-DOS.
+
+* Ported to Windows 95 & Windows NT.
+
+* Ported to OS-9.
+
+* Ported to GNU Hurd.
+
+======================================================================
+
+
+	Major changes between "less" versions 290 and 291
+
+* Less environment variables can be specified in lesskey files.
+
+* Fixed MS-DOS build.
+
+======================================================================
+
+
+	Major changes between "less" versions 278 and 290
+
+* Accepts GNU-style options "--help" and "--version".
+
+* OS/2 version looks for less.ini in $HOME before $INIT and $PATH.
+
+* Bug fixes
+
+======================================================================
+
+
+	Major changes between "less" versions 252 and 278
+
+* A LESSOPEN preprocessor may now pipe the converted file data to less,
+  rather than writing it to a temporary file.
+
+* Search pattern highlighting has been fixed.  It now highlights 
+  reliably, even if a string is split across two screen lines,
+  contains TABs, etc.
+
+* The -F flag (which suppress search highlighting) has been changed 
+  to -G.  A new flag, -g, changes search highlighting to highlight 
+  only the string found by the last search command, instead of all 
+  strings which match the last search command.
+
+* New flag -I acts like -i, but ignores case even if the search 
+  pattern contains uppercase letters.
+
+* Less now checks for the environment variable VISUAL before EDITOR.
+
+* Ported to OS/2.
+
+======================================================================
+
+
+	Major changes between "less" versions 237 and 252
+
+* Changes in line-editing keys:
+  The literal key is now ^V or ^A rather than \ (backslash).
+  Filename completion commands (TAB and ^L) are disabled 
+  when typing a search pattern.
+
+* Line-editing command keys can be redefined using lesskey.
+
+* Lesskey with no input file defaults to $HOME/.lesskey
+  rather than standard input.
+
+* New option -V displays version number of less.
+
+* New option -V displays version number of lesskey.
+
+* Help file less.hlp is now installed by default in /usr/local/share 
+  rather than /usr/local/lib.
+
+
+======================================================================
+
+
+	Major changes between "less" versions 170 and 237
+
+* By popular demand, text which matches the current search pattern
+  is highlighted.  New -F flag disables this feature.
+
+* Henry Spencer's regexp.c is now included, for systems which do not
+  have a regular expression library.
+  regexp.c is Copyright (c) 1986 by University of Toronto.
+
+* New line-editing keys, including command history (arrow keys) and 
+  filename completion (TAB).
+
+* Input preprocessor allows modification of input files (e.g. uncompress)
+  via LESSOPEN/LESSCLOSE environment variables.
+
+* New -X flag disables sending termcap "ti" and "te" (initialize and
+  deinitialize) strings to the terminal. 
+
+* Changing -i from within less now correctly affects a subsequent
+  repeated search.  
+
+* Searching for underlined or overstruck text now works when the -u
+  flag is in effect, rather than the -i flag.
+
+* Use setlocale (LANG and LC_CTYPE environment variables) to determine
+  the character set if LESSCHARSET/LESSCHARDEF are not set.
+
+* The default format for displaying binary characters is now standout
+  (reverse video) rather than blinking.  This can still be changed by
+  setting the LESSBINFMT environment variable.
+
+* Use autoconf installation technology.
+
+* Ported to MS-DOS.
+
+        ********************************
+          Things that may surprise you
+        ********************************
+
+* When you enter text at the bottom of the screen (search string, 
+  filename, etc.), some keys act different than previously.  
+  Specifically, \ (backslash), ESC, TAB, BACKTAB, and control-L 
+  now have line editing functions.
+
+* Some previous unofficial versions of less were able to display
+  compressed files.  The new LESSOPEN/LESSCLOSE feature now provides
+  this functionality in a different way.
+
+* Some previous unofficial versions of less provided a -Z flag to 
+  set the number of lines of text to retain between full screen scrolls.
+  The -z-n flag (that is, -z with a negative number) provides this 
+  functionality.
+
+
+======================================================================
+
+
+	Major changes between "less" versions 123 and 170
+
+* New option -j allows target lines to be positioned anywhere on screen.
+
+* New option -S truncates displayed line at the screen width,
+  rather than wrapping onto the next line.
+
+* New option -y limits amount of forward scroll.
+
+* New option -T specifies a "tags" file.
+
+* Non-printable, non-control characters are displayed in octal.
+  Such characters, as well as control characters, are displayed 
+  in blinking mode.
+
+* New command -+ sets an option to its default.
+* New command -- sets an option to the opposite of its default.
+
+* Lesskey file may have a string appended to a key's action,
+  which acts as though typed in after the command.
+
+* New commands ESC-^F and ESC-^B match arbitrary types of brackets.
+
+* New command F monitors a growing file (like "tail -f").
+
+* New command | pipes a section of the input file into a shell command.
+
+* New command :x directly jumps to a file in the command line list.
+
+* Search commands have been enhanced and reorganized:
+	n	Repeat search, same direction.
+	N	Repeat search, opposite direction.
+	ESC-/	Search forward thru file boundaries
+	ESC-?	Search backward thru file boundaries
+	ESC-n	Repeat search thru file boundaries, same direction.
+	ESC-N	Repeat search thru file boundaries, opposite direction.
+  Special character * causes search to search thru file boundaries.
+  Special character @ causes search to begin at start/end of file list.
+
+* Examining a new file adds it to the command line list.
+  A list of files, or an expression which matches more than one file,
+  may be examined; all of them are added to the command line list.
+
+* Environment variables LESSCHARSET and LESSCHARDEF can define
+  a non-ASCII character set.
+
+* Partial support for MSDOS, including options -R for repainting screen
+  on quit, -v/-V to select video mode, and -W to change window size.
+
+
+======================================================================
+
+
+	Major changes between "less" versions 97 and 123
+
+* New option (-N) causes line numbers to be displayed in the
+  text of the file (like vi "set nu").
+
+* New option (-?) prints help message immediately.
+
+* New option (-r) displays "raw" control characters, without
+  mapping them to ^X notation.
+
+* New option (-f) forces less to open non-regular files
+  (directories, etc).
+
+* New option (-k) can be used to specify lesskey files by name.
+
+* New option (-y) can be used to set a forward scroll limit
+  (like -h sets a backward scroll limit).
+
+* File marks (set by the m command) are now preserved when a new
+  file is edited.  The ' command can thus be used to switch files.
+
+* New command ESC-/ searches all files (on the command line) 
+  for a pattern.
+
+* New command ESC-n repeats previous search, spanning files.
+
+* The N command has been changed to repeat the previous search
+  in the reverse direction.  The old N command is still available 
+  via :n.
+
+* New command ESC-N repeats previous search in the reverse
+  direction and spanning files.
+
+* 8 bit characters are now supported.  A new option (-g) can be 
+  used to strip off the eighth bit (the previous behavior).
+
+* Options which take a following string (like -t) may now
+  optionally have a space between the option letter and the string.
+
+* Six new commands { } ( ) [ and ] can be used to match
+  brackets of specific types, similar to vi % command.
+
+* New commands z and w move forward/backward one window and
+  simultaneously set the window size.
+
+* Prompt string expansion now has %L for line number of the last
+  line in the file, and %E for the name of the editor.
+  Also, % escapes which refer to a line (b=bottom, t=top, etc.)
+  can use j for the jump target line.
+
+* New environment variable LESSEDIT can be used to tailor the
+  command string passed to the editor by the v command.
+
+* Examining a file which was previously examined will return
+  to the same position in the file.
+
+* A "%" is expanded to the current filename and a "#" to the 
+  previous filename, in both shell commands and the E command.
+  (Previously % worked only in shell commands and # worked 
+  only in the E command.)
+
+* New command ":ta" is equivalent to "-t".
+
+* New command "s" is equivalent to "-l".
+
+* The - command may be followed by "+X" to revert to the default
+  for option X, or "-X" to get the opposite of the default.
+
+* Lesskey files may now include characters after the action as
+  extra input to be parsed after the action; for example:
+  "toggle-option X" to toggle a specific option X.
+
+
+
+
+

File usr/src/cmd/less/README Added

View file
  • Ignore whitespace
  • Hide word diff
+
+                            Less, version 458
+
+    This is the distribution of less, version 458, released 04 Apr 2013.
+    This program is part of the GNU project (http://www.gnu.org).
+
+    This program is free software.  You may redistribute it and/or
+    modify it under the terms of either:
+
+    1. The GNU General Public License, as published by the Free
+       Software Foundation; either version 3, or (at your option) any
+       later version.  A copy of this license is in the file COPYING.
+    or
+    2. The Less License, in the file LICENSE.
+
+    Please report any problems to bug-less@gnu.org.
+    See http://www.greenwoodsoftware.com/less for the latest info.
+
+=========================================================================
+
+This is the distribution of "less", a paginator similar to "more" or "pg".
+
+The formatted manual page is in less.man.
+The manual page nroff source is in less.nro.
+Major changes made since the last posted version are in NEWS.
+
+=======================================================================
+INSTALLATION (Unix systems only):
+
+1. Move the distributed source to its own directory and unpack it,
+   if you have not already done so.  
+
+2. Type "sh configure".
+   This will generate a Makefile and a defines.h.
+   Warning: if you have a GNU sed, make sure it is version 2.05 or later.
+
+   The file INSTALL describes the usage of the configure program in
+   general.  In addition, these options to configure are supported:
+
+   --with-editor=program
+     Specifies the default editor program used by the "v" command.
+     The default is "vi".
+
+   --with-regex=lib
+     Specifies the regular expression library used by less for pattern
+     matching.  The default is "auto", which means the configure program 
+     finds a regular expression library automatically.  Other values are:
+        posix          Use the POSIX-compatible regcomp.
+        pcre           Use the PCRE library.
+        regcmp         Use the regcmp library.
+        re_comp        Use the re_comp library.
+        regcomp        Use the V8-compatible regcomp.
+        regcomp-local  Use Henry Spencer's V8-compatible regcomp
+                       (source is supplied with less).
+        none           No regular expressions, only simple string matching.
+   --with-secure
+     Builds a "secure" version of less, with some features disabled
+     to prevent users from viewing other files, accessing shell
+     commands, etc.
+
+
+3. It is a good idea to look over the generated Makefile and defines.h
+   and make sure they look ok.  If you know of any peculiarities of
+   your system that configure might not have detected, you may fix the
+   Makefile now.  Take particular notice of the list of "terminal" 
+   libraries in the LIBS definition in the Makefile; these may need 
+   to be edited.  The terminal libraries will be some subset of
+       -lncurses  -lcurses  -ltermcap  -ltermlib
+
+   If you wish, you may edit defines.h to remove some optional features.
+   If you choose not to include some features in your version, you may
+   wish to edit the manual page "less.nro" and the help page "less.hlp" 
+   to remove the descriptions of the features which you are removing.
+   If you edit less.hlp, you should run "make -f Makefile.aut help.c".
+
+4. Type "make" and watch the fun.
+
+5. If the make succeeds, it will generate the programs "less",
+   "lesskey" and "lessecho" in your current directory.  Test the 
+   generated programs.
+
+6. When satisfied that it works, if you wish to install it
+   in a public place, type "make install".
+
+   The default install destinations are:
+        Executables (less, lesskey, lessecho) in /usr/local/bin
+        Documentation (less.nro, lesskey.nro) in /usr/local/man/man1
+   If you want to install any of these files elsewhere, define
+   bindir and/or mandir to the appropriate directories.
+
+If you have any problems building or running "less", suggestions, 
+complaints, etc., you may mail to bug-less@gnu.org.
+
+Note to hackers: comments noting possible improvements are enclosed
+in double curly brackets {{ like this }}.
+
+(Note that the above note was originally written at a time when 
+"hackers" most commonly meant "enthusiastic and dedicated computer 
+programmers", not "persons who attempt to circumvent computer security".)
+
+
+
+=======================================================================
+INSTALLATION (MS-DOS systems only,
+              with Microsoft C, Borland C, or DJGPP)
+
+1. Move the distributed source to its own directory.
+   Depending on your compiler, you may need to convert the source 
+   to have CR-LF rather than LF as line terminators.
+
+2. If you are using Microsoft C, rename MAKEFILE.DSU to MAKEFILE.
+   If you are using Borland C, rename MAKEFILE.DSB to MAKEFILE.
+   If you are using DJGPP, rename MAKEFILE.DSG to MAKEFILE.
+
+3. Look at MAKEFILE to make sure that the definitions for CC and LIBDIR
+   are correct.  CC should be the name of your C compiler and
+   LIBDIR should be the directory where the C libraries reside (for
+   Microsoft C only).  If these definitions need to be changed, you can
+   either modify the definitions directly in MAKEFILE, or set your
+   environment variables CC and/or LIBDIR to override the definitions
+   in MAKEFILE.
+
+4. If you wish, you may edit DEFINES.DS to remove some optional features.
+   If you choose not to include some features in your version, you may
+   wish to edit the manual page LESS.MAN and the help page HELP.C
+   to remove the descriptions of the features which you are removing.
+
+5. Run your "make" program and watch the fun.
+   If your "make" requires a flag to import environment variables,
+   you should use that flag.
+   If your compiler runs out of memory, try running "make -n >cmds.bat" 
+   and then run cmds.bat.
+
+6. If the make succeeds, it will generate the programs "LESS.EXE" and
+   "LESSKEY.EXE" in your current directory.  Test the generated programs.
+
+7. When satisfied that it works, you may wish to install LESS.EXE and
+   LESSKEY.EXE in a directory which is included in your PATH.
+
+
+
+=======================================================================
+INSTALLATION (Windows-95, Windows-98 and Windows-NT systems only,
+              with Borland C or Microsoft Visual C++)
+
+1. Move the distributed source to its own directory.
+
+2. If you are using Borland C, rename Makefile.wnb to Makefile.
+   If you are using Microsoft Visual C++, rename Makefile.wnm to Makefile.
+
+3. Check the Makefile to make sure the definitions look ok.
+
+4. If you wish, you may edit defines.wn to remove some optional features.
+   If you choose not to include some features in your version, you may
+   wish to edit the manual page less.man and the help page help.c
+   to remove the descriptions of the features which you are removing.
+
+5. Type "make" and watch the fun.
+
+6. If the make succeeds, it will generate the programs "less.exe" and
+   "lesskey.exe" in your current directory.  Test the generated programs.
+
+7. When satisfied that it works, if you wish to install it
+   in a public place, type "make install".
+   See step 6 of the Unix installation instructions for details
+   on how to change the default installation directories.
+
+
+
+=======================================================================
+INSTALLATION (OS/2 systems only,
+              with EMX C)
+
+1. Move the distributed source to its own directory.
+
+2. Rename Makefile.o2e to Makefile.
+
+3. Check the Makefile to make sure the definitions look ok.
+
+4. If you wish, you may edit defines.o2 to remove some optional features.
+   If you choose not to include some features in your version, you may
+   wish to edit the manual page less.man and the help page help.c
+   to remove the descriptions of the features which you are removing.
+
+5. Type "make" and watch the fun.
+
+6. If the make succeeds, it will generate the programs "less.exe" and
+   "lesskey.exe" in your current directory.  Test the generated programs.
+
+7. Make sure you have the emx runtime installed. You need the emx DLLs
+   emx.dll and emxlibcs.dll and also the termcap database, termcap.dat.
+   Make sure you have termcap.dat either in the default location or
+   somewhere in a directory listed in the PATH or INIT environment 
+   variables.
+
+8. When satisfied that it works, you may wish to install less.exe,
+   lesskey.exe and scrsize.exe in a directory which is included in 
+   your PATH.  scrsize.exe is required only if you use a terminal
+   emulator such as xterm or rxvt.
+
+
+
+=======================================================================
+INSTALLATION (OS-9 systems only,
+              with Microware C or Ultra C)
+
+1. Move the distributed source to its own directory.
+
+2. If you are using Microware C, rename Makefile.o9c to Makefile.
+   If you are using Ultra C, rename Makefile.o9u to Makefile.
+
+3. Check the Makefile to make sure the definitions look ok.
+
+4. If you wish, you may edit defines.o9 to remove some optional features.
+   If you choose not to include some features in your version, you may
+   wish to edit the manual page less.man and the help page help.c
+   to remove the descriptions of the features which you are removing.
+
+5. Type "dmake" and watch the fun.
+   The standard OS-9 "make" will probably not work.  If you don't
+   have dmake, you can get a copy from os9archive.rtsi.com.
+
+6. If the make succeeds, it will generate the programs "less" and
+   "lesskey" in your current directory.  Test the generated programs.
+
+7. When satisfied that it works, if you wish to install it
+   in a public place, type "dmake install".
+   See step 6 of the Unix installation instructions for details
+   on how to change the default installation directories.
+
+=======================================================================
+ACKNOWLEDGMENTS:
+  Some versions of the less distribution are packaged using 
+  Info-ZIP's compression utility.
+  Info-ZIP's software is free and can be obtained as source 
+  code or executables from various anonymous-ftp sites,
+  including ftp.uu.net:/pub/archiving/zip.

File usr/src/cmd/less/README.illumos Added

View file
  • Ignore whitespace
  • Hide word diff
+
+I have substantially "ported" less, starting with version 458.  I've made
+changes in the following way:
+
+ * COPYING file removed to and LICENSE file updated reflect that illumos
+   redistributes under the LESS license rather than GPLv3.  (FreeBSD uses
+   a similar precedent.)  Only the first line was added to LICENSE.  All
+   of my changes are distributed exclusively under the LESS License.
+
+ * configure was run to generate the defines.h file, which should be used.
+   The options given were ./configure --prefix=/usr --with-regex=posix
+
+ * lglob() was rewritten to use POSIX glob(3C), instead of using popen()
+   and shell expansion.  (There was at least one minor overrun bug in the
+   existing code -- single byte overrun.)
+
+ * lessecho is removed, since we no longer need it (it was used for the
+   popen() glob hack.  Just calling glob() is *so* much cleaner.
+
+ * calls to tgetstr() were replaced with terminfo tigetstr() (which doesn't
+   require a caller-supplied buffer).  There were bugs here.
+
+ * all of the code supporting non-POSIX systems (OS2, many Microsoft platforms,
+   and OSK) was removed.  This cut huge swathes of code that I couldn't
+   follow.
+ 
+ * all #if 0 stuff is gone.
+
+ * code is made C-style and ANSI C conformant.
+
+ * scrsize.c is removed.  It is not used on UNIX systems.
+
+ * SECURE mode (limited features) is removed - this version of less is always
+   fully featured.  (It was a compile-time option.)  Download official less
+   if you need restricted mode. (Hint: zones or chroot are better solutions!)
+
+ *  The following man pages were copied from OpenBSD into
+    usr/src/man/man1:  less.1, lesskey.1, they were subsequently customized
+    for illumos.
+
+ * mkhelp.c was rewhacked to emit more readable fixed strings instead of
+   character arrays.
+
+ * the handling of quit-at-eof was fixed when acting as "more" -- it now
+   properly acts according to the spec -- that is it does automatically
+   trigger -F.  Instead more behaves like less with -e, and when -e is added
+   to more, it behaves like less -E.
+
+ * also, when acting as more, if -e is supplied then we do not init/deinit
+   the terminal, since doing so may cause the use of alternate screens, which
+   is really terrible -- your short file will just go to the alternate screen,
+   and the program will exit back; the user will not actually see any output!
+   arguably this would be a good thing to do for less -E and -F as well.  But
+   most less users probably don't use those options.
+
+ * locale stuff changed to recognize "646" and "C" locales as ASCII.
+
+ * numerous files not needed were removed -- separate regex implementation,
+   pckeys.h, autoconfiguration glue, etc. etc.
+
+---- >> TODO << ----
+
+ 1. it seems silly that less reinvents getopt, consider rewhacking this to
+    use standard getopts; we don't have to worry about supporting MS-DOS.
+
+ 2. The multibyte character handling for unicode is a local hack.  It should
+    just use the current locale, character classification macros, and also
+    the wcwidth(), mbstowcs(), mblen(), and such.  This will make it portable
+    to other encodings than just UTF-8.
+
+ 3. Internationalize the program using gettext().
+

File usr/src/cmd/less/THIRDPARTYLICENSE Added

View file
  • Ignore whitespace
  • Hide word diff
+
+illumos chooses to distribute less under the following Less License terms.
+The illumos modifications to less are provided under these terms.
+
+                          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.
+

File usr/src/cmd/less/brac.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * 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;
+	POSITION pos;
+	int (*chget)();
+
+	extern int ch_forw_get(), ch_back_get();
+
+	/*
+	 * 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 == NULL_POSITION || ch_seek(pos)) {
+		if (forwdir)
+			error("Nothing in top line", NULL_PARG);
+		else
+			error("Nothing in bottom line", NULL_PARG);
+		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_PARG);
+			else
+				error("No bracket in bottom line", NULL_PARG);
+			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_PARG);
+}

File usr/src/cmd/less/ch.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * 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 "less.h"
+
+#include <sys/stat.h>
+extern dev_t curr_dev;
+extern ino_t curr_ino;
+
+typedef POSITION 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;
+	POSITION fpos;
+	int nbufs;
+	BLOCKNUM block;
+	unsigned int offset;
+	POSITION 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 int sigs;
+extern int secure;
+extern int screen_trashed;
+extern int follow_mode;
+extern const char helpdata[];
+extern const int size_helpdata;
+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;
+	POSITION pos;
+	POSITION 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()) != NULL_POSITION && 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) == BAD_LSEEK) {
+			error("seek error", NULL_PARG);
+			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] = ch_ungotchar;
+		n = 1;
+		ch_ungotchar = -1;
+	} else if (ch_flags & CH_HELPFILE) {
+		bp->data[bp->datasize] = helpdata[ch_fpos];
+		n = 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_PARG);
+		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_PARG);
+	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 == NULL_POSITION) {
+		tried = TRUE;
+		ierror("Finishing logfile", NULL_PARG);
+		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_PARG);
+			warned = TRUE;
+		}
+	}
+}
+
+/*
+ * Determine if a specific block is currently in one of the buffers.
+ */
+static int
+buffered(BLOCKNUM block)
+{
+	register struct buf *bp;
+	register struct bufnode *bn;
+	register 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(POSITION pos)
+{
+	BLOCKNUM new_block;
+	POSITION len;
+
+	if (thisfile == NULL)
+		return (0);
+
+	len = ch_length();
+	if (pos < ch_zero() || (len != NULL_POSITION && 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)
+{
+	POSITION len;
+
+	if (thisfile == NULL)
+		return (0);
+
+	if (ch_flags & CH_CANSEEK)
+		ch_fsize = filesize(ch_file);
+
+	len = ch_length();
+	if (len != NULL_POSITION)
+		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.
+ */
+POSITION
+ch_length(void)
+{
+	if (thisfile == NULL)
+		return (NULL_POSITION);
+	if (ignore_eoi)
+		return (NULL_POSITION);
+	if (ch_flags & CH_HELPFILE)
+		return (size_helpdata);
+	if (ch_flags & CH_NODATA)
+		return (0);
+	return (ch_fsize);
+}
+
+/*
+ * Return the current position in the file.
+ */
+POSITION
+ch_tell(void)
+{
+	if (thisfile == NULL)
+		return (NULL_POSITION);
+	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 = NULL_POSITION;
+		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 = NULL_POSITION;
+		ch_flags &= ~CH_CANSEEK;
+	}
+#endif
+
+	if (lseek(ch_file, (off_t)0, SEEK_SET) == BAD_LSEEK) {
+		/*
+		 * 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_PARG);
+	}
+}
+
+/*
+ * 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) != BAD_LSEEK);
+}
+
+/*
+ * 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 = NULL_POSITION;
+		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|CH_HELPFILE)))
+			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);
+}

File usr/src/cmd/less/charset.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * Functions to define the character set
+ * and do things specific to the character set.
+ */
+
+#include "less.h"
+#include <locale.h>
+#include <ctype.h>
+#include <langinfo.h>
+
+#include "charset.h"
+
+int utf_mode = 0;
+
+/*
+ * Predefined character sets,
+ * selected by the LESSCHARSET environment variable.
+ */
+struct charset {
+	char *name;
+	int *p_flag;
+	char *desc;
+} charsets[] = {
+	/* BEGIN CSTYLED */
+	{ "ascii",		NULL,	"8bcccbcc18b95.b" },
+	{ "utf-8",		&utf_mode,	 "8bcccbcc18b95.b126.bb" },
+	{ "iso8859",		NULL,	"8bcccbcc18b95.33b." },
+	{ "latin3",		NULL,	"8bcccbcc18b95.33b5.b8.b15.b4.b12.b18.b12.b." },
+	{ "arabic",		NULL,	"8bcccbcc18b95.33b.3b.7b2.13b.3b.b26.5b19.b" },
+	{ "greek",		NULL,	"8bcccbcc18b95.33b4.2b4.b3.b35.b44.b" },
+	{ "greek2005",		NULL,	"8bcccbcc18b95.33b14.b35.b44.b" },
+	{ "hebrew",		NULL,	"8bcccbcc18b95.33b.b29.32b28.2b2.b" },
+	{ "koi8-r",		NULL,	"8bcccbcc18b95.b." },
+	{ "KOI8-T",		NULL,	"8bcccbcc18b95.b8.b6.b8.b.b.5b7.3b4.b4.b3.b.b.3b." },
+	{ "georgianps",		NULL,	"8bcccbcc18b95.3b11.4b12.2b." },
+	{ "tcvn",		NULL,	"b..b...bcccbccbbb7.8b95.b48.5b." },
+	{ "TIS-620",		NULL,	"8bcccbcc18b95.b.4b.11b7.8b." },
+	{ "next",		NULL,	"8bcccbcc18b95.bb125.bb" },
+	{ "dos",		NULL,	"8bcccbcc12bc5b95.b." },
+	{ "windows-1251",	NULL,	"8bcccbcc12bc5b95.b24.b." },
+	{ "windows-1252",	NULL,	"8bcccbcc12bc5b95.b.b11.b.2b12.b." },
+	{ "windows-1255",	NULL,	"8bcccbcc12bc5b95.b.b8.b.5b9.b.4b." },
+	{ "ebcdic",		NULL,	"5bc6bcc7bcc41b.9b7.9b5.b..8b6.10b6.b9.7b9.8b8.17b3.3b9.7b9.8b8.6b10.b.b.b." },
+	{ "IBM-1047",		NULL,	"4cbcbc3b9cbccbccbb4c6bcc5b3cbbc4bc4bccbc191.b" },
+	{ NULL, NULL, NULL }
+	/* END CSTYLED */
+};
+
+/*
+ * Support "locale charmap"/nl_langinfo(CODESET) values, as well as others.
+ */
+struct cs_alias {
+	char *name;
+	char *oname;
+} cs_aliases[] = {
+	{ "UTF-8",		"utf-8" },
+	{ "ANSI_X3.4-1968",	"ascii" },
+	{ "US-ASCII",		"ascii" },
+	{ "646",		"ascii" },
+	{ "C",			"ascii" },
+	{ "latin1",		"iso8859" },
+	{ "ISO-8859-1",		"iso8859" },
+	{ "latin9",		"iso8859" },
+	{ "ISO-8859-15",	"iso8859" },
+	{ "latin2",		"iso8859" },
+	{ "ISO-8859-2",		"iso8859" },
+	{ "ISO-8859-3",		"latin3" },
+	{ "latin4",		"iso8859" },
+	{ "ISO-8859-4",		"iso8859" },
+	{ "cyrillic",		"iso8859" },
+	{ "ISO-8859-5",		"iso8859" },
+	{ "ISO-8859-6",		"arabic" },
+	{ "ISO-8859-7",		"greek" },
+	{ "IBM9005",		"greek2005" },
+	{ "ISO-8859-8",		"hebrew" },
+	{ "latin5",		"iso8859" },
+	{ "ISO-8859-9",		"iso8859" },
+	{ "latin6",		"iso8859" },
+	{ "ISO-8859-10",	"iso8859" },
+	{ "latin7",		"iso8859" },
+	{ "ISO-8859-13",	"iso8859" },
+	{ "latin8",		"iso8859" },
+	{ "ISO-8859-14",	"iso8859" },
+	{ "latin10",		"iso8859" },
+	{ "ISO-8859-16",	"iso8859" },
+	{ "IBM437",		"dos" },
+	{ "EBCDIC-US",		"ebcdic" },
+	{ "IBM1047",		"IBM-1047" },
+	{ "KOI8-R",		"koi8-r" },
+	{ "KOI8-U",		"koi8-r" },
+	{ "GEORGIAN-PS",	"georgianps" },
+	{ "TCVN5712-1", 	"tcvn" },
+	{ "NEXTSTEP",		"next" },
+	{ "windows",		"windows-1252" }, /* backward compatibility */
+	{ "CP1251",		"windows-1251" },
+	{ "CP1252",		"windows-1252" },
+	{ "CP1255",		"windows-1255" },
+	{ NULL, NULL }
+};
+
+#define	IS_BINARY_CHAR	01
+#define	IS_CONTROL_CHAR	02
+
+static char chardef[256];
+static char *binfmt = NULL;
+static char *utfbinfmt = NULL;
+int binattr = AT_STANDOUT;
+
+
+/*
+ * Define a charset, given a description string.
+ * The string consists of 256 letters,
+ * one for each character in the charset.
+ * If the string is shorter than 256 letters, missing letters
+ * are taken to be identical to the last one.
+ * A decimal number followed by a letter is taken to be a
+ * repetition of the letter.
+ *
+ * Each letter is one of:
+ *	. normal character
+ *	b binary character
+ *	c control character
+ */
+static void
+ichardef(char *s)
+{
+	char *cp;
+	int n;
+	char v;
+
+	n = 0;
+	v = 0;
+	cp = chardef;
+	while (*s != '\0') {
+		switch (*s++) {
+		case '.':
+			v = 0;
+			break;
+		case 'c':
+			v = IS_CONTROL_CHAR;
+			break;
+		case 'b':
+			v = IS_BINARY_CHAR|IS_CONTROL_CHAR;
+			break;
+
+		case '0': case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+			n = (10 * n) + (s[-1] - '0');
+			continue;
+
+		default:
+			error("invalid chardef", NULL_PARG);
+			quit(QUIT_ERROR);
+			/*NOTREACHED*/
+		}
+
+		do {
+			if (cp >= chardef + sizeof (chardef)) {
+				error("chardef longer than 256", NULL_PARG);
+				quit(QUIT_ERROR);
+				/*NOTREACHED*/
+			}
+			*cp++ = v;
+		} while (--n > 0);
+		n = 0;
+	}
+
+	while (cp < chardef + sizeof (chardef))
+		*cp++ = v;
+}
+
+/*
+ * Define a charset, given a charset name.
+ * The valid charset names are listed in the "charsets" array.
+ */
+static int
+icharset(char *name, int no_error)
+{
+	struct charset *p;
+	struct cs_alias *a;
+
+	if (name == NULL || *name == '\0')
+		return (0);
+
+	/* First see if the name is an alias. */
+	for (a = cs_aliases;  a->name != NULL;  a++) {
+		if (strcmp(name, a->name) == 0) {
+			name = a->oname;
+			break;
+		}
+	}
+
+	for (p = charsets;  p->name != NULL;  p++) {
+		if (strcmp(name, p->name) == 0) {
+			ichardef(p->desc);
+			if (p->p_flag != NULL)
+				*(p->p_flag) = 1;
+			return (1);
+		}
+	}
+
+	if (!no_error) {
+		error("invalid charset name", NULL_PARG);
+		quit(QUIT_ERROR);
+	}
+	return (0);
+}
+
+/*
+ * Define a charset, given a locale name.
+ */
+static void
+ilocale(void)
+{
+	int c;
+
+	for (c = 0; c < sizeof (chardef); c++) {
+		if (isprint(c))
+			chardef[c] = 0;
+		else if (iscntrl(c))
+			chardef[c] = IS_CONTROL_CHAR;
+		else
+			chardef[c] = IS_BINARY_CHAR|IS_CONTROL_CHAR;
+	}
+}
+
+/*
+ * Define the printing format for control (or binary utf) chars.
+ */
+static void
+setbinfmt(char *s, char **fmtvarptr, char *default_fmt)
+{
+	if (s && utf_mode) {
+		/* It would be too hard to account for width otherwise. */
+		char *t = s;
+		while (*t) {
+			if (*t < ' ' || *t > '~') {
+				s = default_fmt;
+				goto attr;
+			}
+			t++;
+		}
+	}
+
+	/* %n is evil */
+	if (s == NULL || *s == '\0' ||
+	    (*s == '*' &&
+	    (s[1] == '\0' || s[2] == '\0' || strchr(s + 2, 'n'))) ||
+	    (*s != '*' && strchr(s, 'n')))
+		s = default_fmt;
+
+	/*
+	 * 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;
+}
+
+/*
+ *
+ */
+static void
+set_charset(void)
+{
+	char *s;
+
+	/*
+	 * See if environment variable LESSCHARSET is defined.
+	 */
+	s = lgetenv("LESSCHARSET");
+	if (icharset(s, 0))
+		return;
+
+	/*
+	 * LESSCHARSET is not defined: try LESSCHARDEF.
+	 */
+	s = lgetenv("LESSCHARDEF");
+	if (s != NULL && *s != '\0') {
+		ichardef(s);
+		return;
+	}
+
+	/*
+	 * Try using the codeset name as the charset name.
+	 */
+	s = nl_langinfo(CODESET);
+	if (icharset(s, 1))
+		return;
+
+	/*
+	 * Get character definitions from locale functions,
+	 * rather than from predefined charset entry.
+	 */
+	ilocale();
+}
+
+/*
+ * Initialize charset data structures.
+ */
+void
+init_charset(void)
+{
+	char *s;
+
+	setlocale(LC_ALL, "");
+
+	set_charset();
+
+	s = lgetenv("LESSBINFMT");
+	setbinfmt(s, &binfmt, "*s<%02X>");
+
+	s = lgetenv("LESSUTFBINFMT");
+	setbinfmt(s, &utfbinfmt, "<U+%04lX>");
+}
+
+/*
+ * Is a given character a "binary" character?
+ */
+int
+binary_char(LWCHAR c)
+{
+	if (utf_mode)
+		return (is_ubin_char(c));
+	c &= 0377;
+	return (chardef[c] & IS_BINARY_CHAR);
+}
+
+/*
+ * Is a given character a "control" character?
+ */
+int
+control_char(LWCHAR c)
+{
+	c &= 0377;
+	return (chardef[c] & IS_CONTROL_CHAR);
+}
+
+/*
+ * 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)
+		strcpy(buf, "ESC");
+	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) strcpy(buf, "ESC");
+	} 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(unsigned 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 (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
+ *	<http://www.unicode.org/Public/5.0.0/ucd/UnicodeData-5.0.0d7.txt>
+ *	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
+ *	<http://www.unicode.org/Public/5.0.0/ucd/UnicodeData-5.0.0d7.txt>
+ *	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
+ *	<http://www.unicode.org/Public/5.0.0/ucd/EastAsianWidth-5.0.0d2.txt>
+ *	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);
+}

File usr/src/cmd/less/charset.h Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+#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))

File usr/src/cmd/less/cmd.h Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+#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
+#define	A_SHELL			27
+#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_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

File usr/src/cmd/less/cmdbuf.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * Functions which manipulate the command buffer.
+ * Used only by command() and related functions.
+ */
+
+#include "less.h"
+#include "cmd.h"
+#include "charset.h"
+#include <sys/stat.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 len = strlen(pr);
+			if (pwidth != NULL)
+				*pwidth = len;
+			if (bswidth != NULL)
+				*bswidth = len;
+		}
+	} else {
+		pr = prutfchar(ch);
+		if (pwidth != NULL || bswidth != NULL) {
+			if (is_composing_char(ch)) {
+				if (pwidth != NULL)
+					*pwidth = 0;
+				if (bswidth != NULL)
+					*bswidth = 0;
+			} else if (is_ubin_char(ch)) {
+				int len = strlen(pr);
+				if (pwidth != NULL)
+					*pwidth = len;
+				if (bswidth != NULL)
+					*bswidth = len;
+			} else {
+				LWCHAR prev_ch = step_char(&p, -1, cmdbuf);
+				if (is_combining_char(prev_ch, ch)) {
+					if (pwidth != NULL)
+						*pwidth = 0;
+					if (bswidth != NULL)
+						*bswidth = 0;
+				} else {
+					if (pwidth != NULL)
+						*pwidth	= is_wide_char(ch)
+						    ? 2 : 1;
+					if (bswidth != NULL)
+						*bswidth = 1;
+				}
+			}
+		}
+	}
+
+	return (pr);
+}
+
+/*
+ * Step a pointer one character right in the command buffer.
+ */
+static char *
+cmd_step_right(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();
+			strcpy(cmdbuf, s);
+			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 = save(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();
+	} else if (cp > cmdbuf && cp[-1] != ' ') {
+		/*
+		 * Cursor is on a space, and char to the left is a nonspace.
+		 * We're already at the end of the word.
+		 */
+		;
+	}
+	/*
+	 * 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;
+
+	/*
+	 * Get rid of any previous tk_text.
+	 */
+	if (tk_text != NULL) {
+		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
+	 */
+	if (tk_original != NULL)
+		free(tk_original);
+	tk_original = ecalloc(cp-word+1, sizeof (char));
+	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;
+		len = 1;
+	} else {
+		/* Perform strict validation in all possible cases.  */
+		if (cmd_mbc_buf_len == 0) {
+retry:
+			cmd_mbc_buf_index = 1;
+			*cmd_mbc_buf = c;
+			if (IS_ASCII_OCTET(c))
+				cmd_mbc_buf_len = 1;
+			else if (IS_UTF8_LEAD(c)) {
+				cmd_mbc_buf_len = utf_len(c);
+				return (CC_OK);
+			} else {
+				/* UTF8_INVALID or stray UTF8_TRAIL */
+				ring_bell();
+				return (CC_ERROR);
+			}
+		} else if (IS_UTF8_TRAIL(c)) {
+			cmd_mbc_buf[cmd_mbc_buf_index++] = c;
+			if (cmd_mbc_buf_index < cmd_mbc_buf_len)
+				return (CC_OK);
+			if (!is_utf8_well_formed((uchar_t *)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.
+ */
+LINENUM
+cmd_int(long *frac)
+{
+	char *p;
+	LINENUM 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;
+	int len;
+
+	/* See if filename is explicitly specified by $LESSHISTFILE. */
+	name = lgetenv("LESSHISTFILE");
+	if (name != NULL && *name != '\0') {
+		if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
+			/* $LESSHISTFILE == "-" means don't use history file */
+			return (NULL);
+		return (save(name));
+	}
+
+	/* Otherwise, file is in $HOME. */
+	home = lgetenv("HOME");
+	if (home == NULL || *home == '\0') {
+		return (NULL);
+	}
+	len = strlen(home) + strlen(LESSHISTFILE) + 2;
+	name = ecalloc(len, sizeof (char));
+	(void) snprintf(name, len, "%s/%s", home, LESSHISTFILE);
+	return (name);
+}
+
+/*
+ * 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)
+		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);
+}

File usr/src/cmd/less/command.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * User-level command processor.
+ */
+
+#include "less.h"
+#include "position.h"
+#include "option.h"
+#include "cmd.h"
+
+extern int erase_char, erase2_char, kill_char;
+extern int sigs;
+extern int quit_if_one_screen;
+extern int 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 POSITION 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 char *shellcmd = NULL;	/* For holding last shell command for "!!" */
+static int mca;			/* The multicharacter command (action) */
+static int search_type;		/* The previous type of search */
+static LINENUM number;		/* The number typed by the user */
+static long fraction;		/* The fractional part of the number */
+static struct loption *curropt;
+static int opt_lower;
+static int optflag;
+static int optgetname;
+static POSITION bottompos;
+static int save_hshift;
+static char pipec;
+
+struct ungot {
+	struct ungot *ug_next;
+	char ug_char;
+};
+static struct ungot *ungot = NULL;
+static int unget_end = 0;
+
+static void multi_search();
+
+/*
+ * 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();
+}
+
+/*
+ * 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()
+{
+	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++;
+		if (every_first_cmd != NULL)
+			free(every_first_cmd);
+		if (*cbuf == '\0')
+			every_first_cmd = NULL;
+		else
+			every_first_cmd = save(cbuf);
+		break;
+	case A_OPT_TOGGLE:
+		toggle_option(curropt, opt_lower, cbuf, optflag);
+		curropt = NULL;
+		break;
+	case A_F_BRACKET:
+		match_brac(cbuf[0], cbuf[1], 1, (int)number);
+		break;
+	case A_B_BRACKET:
+		match_brac(cbuf[1], cbuf[0], 0, (int)number);
+		break;
+	case A_EXAMINE:
+		if (secure)
+			break;
+		edit_list(cbuf);
+		/* If tag structure is loaded then clean it up. */
+		cleantags();
+		break;
+	case A_SHELL:
+		/*
+		 * !! just uses whatever is in shellcmd.
+		 * Otherwise, copy cmdbuf to shellcmd,
+		 * expanding any special characters ("%" or "#").
+		 */
+		if (*cbuf != '!') {
+			if (shellcmd != NULL)
+				free(shellcmd);
+			shellcmd = fexpand(cbuf);
+		}
+
+		if (secure)
+			break;
+		if (shellcmd == NULL)
+			lsystem("", "!done");
+		else
+			lsystem(shellcmd, "!done");
+		break;
+	case A_PIPE:
+		if (secure)
+			break;
+		(void) pipe_mark(pipec, cbuf);
+		error("|done", NULL_PARG);
+		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 == NULL_POSITION)
+			/*
+			 * {{ 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_IFILE)
+		quit(QUIT_OK);
+
+	/*
+	 * If the entire file is displayed and the -F flag is set, quit.
+	 */
+	if (quit_if_one_screen &&
+	    entire_file_displayed() && !(ch_getflags() & CH_HELPFILE) &&
+	    next_ifile(curr_ifile) == NULL_IFILE)
+		quit(QUIT_OK);
+
+	/*
+	 * Select the proper prompt and display it.
+	 */
+	/*
+	 * If the previous action was a forward movement,
+	 * don't clear the bottom line of the display;
+	 * just print the prompt since the forward movement guarantees
+	 * that we're in the right position to display the prompt.
+	 * Clearing the line could cause a problem: for example, if the last
+	 * line displayed ended at the right screen edge without a newline,
+	 * then clearing would clear the last displayed line rather than
+	 * the prompt line.
+	 */
+	if (!forw_prompt)
+		clear_bot();
+	clear_cmd();
+	forw_prompt = 0;
+	p = pr_string();
+	if (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;
+		char 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)
+{
+	register 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_PARG);
+
+	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)
+{
+	POSITION curr_len;
+
+	if (ch_getflags() & CH_HELPFILE)
+		return (A_NOACTION);
+
+	cmd_exec();
+	jump_forw();
+	curr_len = ch_length();
+	highest_hilite = until_hilite ? curr_len : NULL_POSITION;
+	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] = 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_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((POSITION) 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_IFILE &&
+			    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;
+			cmd_exec();
+			save_hshift = hshift;
+			hshift = 0;
+			(void) edit(FAKE_HELPFILE);
+			break;
+
+		case A_EXAMINE:
+			/*
+			 * Edit a new file.  Get the filename.
+			 */
+			if (secure) {
+				error("Command not available", NULL_PARG);
+				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_PARG);
+				break;
+			}
+			if (ch_getflags() & CH_HELPFILE)
+				break;
+			if (strcmp(get_filename(curr_ifile), "-") == 0) {
+				error("Cannot edit standard input", NULL_PARG);
+				break;
+			}
+			if (curr_altfilename != NULL) {
+				error("WARNING: This file was viewed via "
+				    "LESSOPEN", NULL_PARG);
+			}
+			start_mca(A_SHELL, "!", ml_shell, 0);
+			/*
+			 * 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_PARG);
+				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_PARG);
+				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;
+			tagfile = nexttag((int)number);
+			if (tagfile == NULL) {
+				error("No next tag", NULL_PARG);
+				break;
+			}
+			if (edit(tagfile) == 0) {
+				POSITION pos = tagsearch();
+				if (pos != NULL_POSITION)
+					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_PARG);
+				break;
+			}
+			if (edit(tagfile) == 0) {
+				POSITION pos = tagsearch();
+				if (pos != NULL_POSITION)
+					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_PARG);
+			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_IFILE) {
+				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_SHELL:
+			/*
+			 * Shell escape.
+			 */
+			if (secure) {
+				error("Command not available", NULL_PARG);
+				break;
+			}
+			start_mca(A_SHELL, "!", ml_shell, 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_PARG);
+				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;
+		}
+	}
+}

File usr/src/cmd/less/cvt.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * Routines to convert text in various ways.  Used by search.
+ */
+
+#include "less.h"
+#include "charset.h"
+
+extern int utf_mode;
+
+/*
+ * Get the length of a buffer needed to convert a string.
+ */
+int
+cvt_length(int len, int ops)
+{
+	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) && IS_UPPER(ch))
+				ch = TO_LOWER(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;
+}

File usr/src/cmd/less/decode.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * 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:
+ *	<c1><c2>...<cN><0><action>
+ * 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 "less.h"
+#include "cmd.h"
+#include "lesskey.h"
+
+extern int erase_char, erase2_char, kill_char;
+extern int secure;
+
+#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,
+	'-', 0,				A_OPT_TOGGLE,
+	':', 't', 0,			A_OPT_TOGGLE|A_EXTRA,	't', 0,
+	's', 0,				A_OPT_TOGGLE|A_EXTRA,	'o', 0,
+	'_', 0,				A_DISP_OPTION,
+	'|', 0,				A_PIPE,
+	'v', 0,				A_VISUAL,
+	'!', 0,				A_SHELL,
+	'+', 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 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));
+	/*
+	 * 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_PARG);
+}
+
+/*
+ * 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_PARG);
+}
+
+/*
+ * 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_PARG);
+}
+
+/*
+ * Search a single command table for the command string in cmd.
+ */
+static int
+cmd_search(char *cmd, char *table, char *endtable, char **sp)
+{
+	char *p;
+	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, 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(char *cmd, char **sp)
+{
+	return (cmd_decode(list_fcmd_tables, cmd, sp));
+}
+
+/*
+ * Decode a command from the edittables list.
+ */
+int
+ecmd_decode(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;
+
+	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;
+	POSITION len;
+	long n;
+	int f;
+
+	if (secure)
+		return (1);
+	/*
+	 * Try to open the lesskey file.
+	 */
+	filename = shell_unquote(filename);
+	f = open(filename, OPEN_READ);
+	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 == NULL_POSITION || 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) == BAD_LSEEK) {
+		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 = save(filename);
+	else if (sysvar)
+		filename = save(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] = 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);
+}

File usr/src/cmd/less/defines.h Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ *
+ * 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		".lesshst"
+#define	TGETENT_OK  1		/* "OK" from curses.h */
+
+/*
+ * Default shell metacharacters and meta-escape character.
+ */
+#define	DEF_METACHARS	"; *?\t\n'\"()<>[]|&^`#\\$%=~"
+#define	DEF_METAESCAPE	"\\"
+
+#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 */

File usr/src/cmd/less/edit.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+#include "less.h"
+#include <sys/stat.h>
+
+int fd0 = 0;
+
+extern int new_file;
+extern int errmsgs;
+extern int cbufs;
+extern char *every_first_cmd;
+extern int any_display;
+extern int force_open;
+extern int is_tty;
+extern int 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 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_IFILE)
+		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 != NULL_POSITION) {
+		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_IFILE;
+	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_IFILE));
+	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_IFILE) {
+		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_IFILE) {
+		/*
+		 * 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 = save(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 (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 (strcmp(open_filename, FAKE_HELPFILE) == 0) {
+		f = -1;
+		chflags |= CH_HELPFILE;
+	} 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_IFILE) {
+		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();
+	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_IFILE;
+	return (edit_next(1));
+}
+
+/*
+ * Edit the last file in the command line (ifile) list.
+ */
+int
+edit_last(void)
+{
+	curr_ifile = NULL_IFILE;
+	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_IFILE) {
+			/*
+			 * 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_IFILE;
+	do {
+		if ((h = next_ifile(h)) == NULL_IFILE) {
+			/*
+			 * 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_IFILE)
+		hold_ifile(curr_ifile, 1);
+	return (curr_ifile);
+}
+
+void
+unsave_ifile(IFILE save_ifile)
+{
+	if (save_ifile != NULL_IFILE)
+		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_IFILE && edit_inext(next, 0) == 0)
+		return;
+	/*
+	 * If can't open THAT one, open the previous input file in the list.
+	 */
+	if (prev != NULL_IFILE && 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)) {
+		error("Missing filename (\"less --help\" for help)", NULL_PARG);
+		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();
+}
+
+/*
+ * 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, OPEN_READ);
+	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 = creat(filename, 0644);
+		break;
+	case 'A': case 'a':
+		/*
+		 * Append: open the file and seek to the end.
+		 */
+		logfile = open(filename, OPEN_APPEND);
+		if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK) {
+			close(logfile);
+			logfile = -1;
+		}
+		break;
+	case 'D': case 'd':
+		/*
+		 * Don't do anything.
+		 */
+		free(filename);
+		return;
+	case 'q':
+		quit(QUIT_OK);
+		/*NOTREACHED*/
+	default:
+		/*
+		 * Eh?
+		 */
+		answer = query("Overwrite, Append, or Don't log? "
+		    "(Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
+		goto loop;
+	}
+
+	if (logfile < 0) {
+		/*
+		 * Error in opening logfile.
+		 */
+		parg.p_string = filename;
+		error("Cannot write to \"%s\"", &parg);
+		free(filename);
+		return;
+	}
+	free(filename);
+}

File usr/src/cmd/less/filename.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * 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 "less.h"
+
+#include <sys/stat.h>
+#include <glob.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 = DEF_METAESCAPE;
+	return (s);
+}
+
+/*
+ * Get the characters which the shell considers to be "metacharacters".
+ */
+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(char *s)
+{
+	char *p;
+	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;
+			}
+		}
+	}
+	if (use_quotes) {
+		if (have_quotes)
+			/*
+			 * We can't quote a string that contains quotes.
+			 */
+			return (NULL);
+		len = strlen(s) + 3;
+	}
+	/*
+	 * Allocate and construct the new string.
+	 */
+	newstr = p = ecalloc(len, sizeof (char));
+	if (use_quotes) {
+		(void) snprintf(newstr, len, "%c%s%c", openquote, s,
+		    closequote);
+	} else {
+		while (*s != '\0') {
+			if (metachar(*s)) {
+				/*
+				 * Add the escape char.
+				 */
+				strcpy(p, esc);
+				p += esclen;
+			}
+			*p++ = *s++;
+		}
+		*p = '\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(char *dirname, char *filename)
+{
+	char *pathname;
+	char *qpathname;
+	int len;
+	int f;
+
+	if (dirname == NULL || *dirname == '\0')
+		return (NULL);
+	/*
+	 * Construct the full pathname.
+	 */
+	len = strlen(dirname) + strlen(filename) + 2;
+	pathname = calloc(len, sizeof (char));
+	if (pathname == NULL)
+		return (NULL);
+	(void) snprintf(pathname, len, "%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 {
+		close(f);
+	}
+	free(qpathname);
+	return (pathname);
+}
+
+/*
+ * Return the full pathname of the given file in the "home directory".
+ */
+char *
+homefile(char *filename)
+{
+	char *pathname;
+
+	/*
+	 * Try $HOME/filename.
+	 */
+	pathname = dirfile(lgetenv("HOME"), filename);
+	if (pathname != NULL)
+		return (pathname);
+	return (NULL);
+}
+
+/*
+ * 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_IFILE)
+
+	/*
+	 * 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_IFILE)
+					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_IFILE) {
+					*to++ = *fr;
+				} else {
+					(void) strcpy(to, get_filename(ifile));
+					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;
+	int len;
+
+	if (secure)
+		return (NULL);
+	/*
+	 * Complete the filename "s" by globbing "s*".
+	 */
+	len = strlen(s) + 2;
+	fpat =  ecalloc(len, sizeof (char));
+	(void) snprintf(fpat, len, "%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) == BAD_LSEEK)
+		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);
+}
+
+/*
+ * Try to determine the size of a file by seeking to the end.
+ */
+static POSITION
+seek_filesize(int f)
+{
+	off_t spos;
+
+	spos = lseek(f, (off_t)0, SEEK_END);
+	if (spos == BAD_LSEEK)
+		return (NULL_POSITION);
+	return ((POSITION) spos);
+}
+
+/*
+ * 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));
+			strcpy(p, buf);
+			free(buf);
+			buf = p;
+			p = buf + strlen(buf);
+		}
+		*p = 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 {
+			int len = strlen(shell) + strlen(esccmd) + 5;
+			scmd = ecalloc(len, sizeof (char));
+			(void) snprintf(scmd, len, "%s %s %s",
+			    shell, shell_coption(), 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.
+	 */
+
+	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);
+}
+
+/*
+ * Return number of %s escapes in a string.
+ * Return a large number if there are any other % escapes besides %s.
+ */
+static int
+num_pct_s(char *lessopen)
+{
+	int num;
+
+	for (num = 0; ; num++) {
+		lessopen = strchr(lessopen, '%');
+		if (lessopen == NULL)
+			break;
+		if (*++lessopen != 's')
+			return (999);
+	}
+	return (num);
+}
+
+/*
+ * 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;
+	int len;
+	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 (num_pct_s(lessopen) > 1) {
+		error("Invalid LESSOPEN variable", NULL_PARG);
+		return (NULL);
+	}
+
+	len = strlen(lessopen) + strlen(filename) + 2;
+	cmd = ecalloc(len, sizeof (char));
+	(void) snprintf(cmd, len, lessopen, filename);
+	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 (save(FAKE_EMPTYFILE));
+			}
+			return (NULL);
+		}
+		ch_ungetchar(c);
+		*pfd = (void *) fd;
+		*pf = f;
+		return (save("-"));
+	}
+	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;
+	int len;
+
+	if (secure)
+		return;
+	if (pipefd != NULL) {
+		pclose((FILE *)pipefd);
+	}
+	if ((lessclose = lgetenv("LESSCLOSE")) == NULL)
+		return;
+	if (num_pct_s(lessclose) > 2) {
+		error("Invalid LESSCLOSE variable", NULL_PARG);
+		return;
+	}
+	len = strlen(lessclose) + strlen(filename) + strlen(altfilename) + 2;
+	cmd = ecalloc(len, sizeof (char));
+	(void) snprintf(cmd, len, lessclose, filename, altfilename);
+	fd = shellcmd(cmd);
+	free(cmd);
+	if (fd != NULL)
+		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)) {
+		static char is_a_dir[] = " is a directory";
+
+		m = ecalloc(strlen(filename) + sizeof (is_a_dir),
+		    sizeof (char));
+		strcpy(m, filename);
+		strcat(m, is_a_dir);
+	} 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)) {
+			static char not_reg[] =
+			    " is not a regular file (use -f to see it)";
+			m = ecalloc(strlen(filename) + sizeof (not_reg),
+			    sizeof (char));
+			(void) strcpy(m, filename);
+			(void) strcat(m, not_reg);
+		}
+	}
+	free(filename);
+	return (m);
+}
+
+/*
+ * Return the size of a file, as cheaply as possible.
+ * In Unix, we can stat the file.
+ */
+POSITION
+filesize(int f)
+{
+	struct stat statbuf;
+
+	if (fstat(f, &statbuf) >= 0)
+		return ((POSITION) statbuf.st_size);
+	return (seek_filesize(f));
+}
+
+/*
+ *
+ */
+char *
+shell_coption(void)
+{
+	return ("-c");
+}
+
+/*
+ * 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);
+}

File usr/src/cmd/less/forwback.c Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright (C) 1984-2012  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information, see the README file.
+ */
+/*
+ * Modified for use with illumos.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * 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 int 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)
+{
+	POSITION pos;
+
+	if (ignore_eoi)
+		return (0);
+
+	if (ch_length() == NULL_POSITION)
+		/*
+		 * 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 == NULL_POSITION || pos == ch_length());
+}
+
+/*
+ * Check to see if the entire file is currently displayed.
+ */
+int
+entire_file_displayed(void)
+{
+	POSITION 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 == NULL_POSITION || 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 NULL_POSITION.
+ *   The first real line after the blanks will start at ch_zero().
+ */
+void
+forw(int n, POSITION pos, int force, int only_last, int nblank)
+{
+	int eof = 0;
+	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 == NULL_POSITION) {
+				/*
+				 * 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.
+				 */
+				eof = 1;
+				if (!force && position(TOP) != NULL_POSITION)
+					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 == NULL_POSITION && !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, POSITION 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 == NULL_POSITION) {
+			/*
+			 * 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)
+{
+	POSITION 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 == NULL_POSITION &&
+	    (!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 == NULL_POSITION);
+			}
+		} 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)
+{
+	POSITION pos;
+
+	pos = position(TOP);
+	if (pos == NULL_POSITION && (!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 */
+}

File usr/src/cmd/less/funcs.h Added

View file
  • Ignore whitespace
  • Hide word diff
+/*
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ *
+ * This file is made available under the terms of the Less License.
+ */
+
+#include <regex.h>
+
+struct mlist;
+struct loption;
+
+extern char *save(const char *);
+extern void *ecalloc(int, unsigned int);
+extern char *skipsp(char *);
+extern int sprefix(char *, char *, int);
+extern void quit(int);
+extern void raw_mode(int);
+extern	void scrsize(void);
+extern	char *special_key_str(int);
+extern	void get_term(void);
+extern	void init(void);
+extern	void deinit(void);
+extern	void home(void);
+extern	void add_line(void);
+extern	void remove_top(void);
+extern	void lower_left(void);
+extern	void line_left(void);
+extern	void goto_line(int);
+extern	void vbell(void);
+extern	void ring_bell(void);
+extern	void do_clear(void);
+extern	void clear_eol(void);
+extern	void clear_bot(void);
+extern	void at_enter(int);
+extern	void at_exit(void);
+extern	void at_switch(int);
+extern	int is_at_equiv(int, int);
+extern	int apply_at_specials(int);
+extern	void putbs(void);
+extern	void match_brac(int, int, int, int);
+extern	void ch_ungetchar(int);
+extern	void end_logfile(void);
+extern	void sync_logfile(void);
+extern	int ch_seek(POSITION);
+extern	int ch_end_seek(void);
+extern	int ch_beg_seek(void);
+extern	POSITION ch_length(void);
+extern	POSITION ch_tell(void);
+extern	int ch_forw_get(void);
+extern	int ch_back_get(void);
+extern	void ch_setbufspace(int);
+extern	void ch_flush(void);
+extern	int seekable(int);
+extern	void ch_set_eof(void);
+extern	void ch_init(int, int);
+extern	void ch_close(void);
+extern	int ch_getflags(void);
+extern	void init_charset(void);
+extern	int binary_char(LWCHAR);
+extern	int control_char(LWCHAR);
+extern	char *prchar(LWCHAR);
+extern	char *prutfchar(LWCHAR);
+extern	int utf_len(char);
+extern	int is_utf8_well_formed(unsigned char *);
+extern	LWCHAR get_wchar(const char *);
+extern	void put_wchar(char **, LWCHAR);
+extern	LWCHAR step_char(char **, int, char *);
+extern	int is_composing_char(LWCHAR);
+extern	int is_ubin_char(LWCHAR);
+extern	int is_wide_char(LWCHAR);
+extern	int is_combining_char(LWCHAR, LWCHAR);
+extern	void cmd_reset(void);
+extern	void clear_cmd(void);
+extern	void cmd_putstr(char *);
+extern	int len_cmdbuf(void);
+extern	void set_mlist(void *, int);
+extern	void cmd_addhist(struct mlist *, const char *);
+extern	void cmd_accept(void);
+extern	int cmd_char(int);
+extern	LINENUM cmd_int(long *);
+extern	char *get_cmdbuf(void);
+extern	char *cmd_lastpattern(void);
+extern	void init_cmdhist(void);
+extern	void save_cmdhist(void);
+extern	int in_mca(void);
+extern	void dispversion(void);
+extern	int getcc(void);
+extern	void ungetcc(int);
+extern	void ungetsc(char *);
+extern	void commands(void);
+extern	int cvt_length(int, int);
+extern	int *cvt_alloc_chpos(int);
+extern	void cvt_text(char *, char *, int *, int *, int);
+extern	void init_cmds(void);
+extern	void add_fcmd_table(char *, int);
+extern	void add_ecmd_table(char *, int);
+extern	int fcmd_decode(char *, char **);
+extern	int ecmd_decode(char *, char **);
+extern	char *lgetenv(char *);
+extern	int lesskey(char *, int);
+extern	void add_hometable(char *, char *, int);
+extern	int editchar(int, int);
+extern	void init_textlist(struct textlist *, char *);
+extern	char *forw_textlist(struct textlist *, char *);
+extern	char *back_textlist(struct textlist *, char *);
+extern	int edit(char *);
+extern	int edit_ifile(IFILE);
+extern	int edit_list(char *);
+extern	int edit_first(void);
+extern	int edit_last(void);
+extern	int edit_next(int);
+extern	int edit_prev(int);
+extern	int edit_index(int);
+extern	IFILE save_curr_ifile(void);
+extern	void unsave_ifile(IFILE);
+extern	void reedit_ifile(IFILE);
+extern	void reopen_curr_ifile(void);
+extern	int edit_stdin(void);
+extern	void cat_file(void);
+extern	void use_logfile(char *);
+extern	char *shell_unquote(char *);
+extern	char *get_meta_escape(void);
+extern	char *shell_quote(char *);
+extern	char *homefile(char *);
+extern	char *fexpand(char *);
+extern	char *fcomplete(char *);
+extern	int bin_file(int f);
+extern	char *lglob(char *);
+extern	char *open_altfile(char *, int *, void **);
+extern	void close_altfile(char *, char *, void *);
+extern	int is_dir(char *);
+extern	char *bad_file(char *);
+extern	POSITION filesize(int);
+extern	char *shell_coption(void);
+extern	char *last_component(char *);
+extern	int eof_displayed(void);
+extern	int entire_file_displayed(void);
+extern	void squish_check(void);
+extern	void forw(int, POSITION, int, int, int);
+extern	void back(int, POSITION, int, int);
+extern	void forward(int, int, int);
+extern	void backward(int, int, int);
+extern	int get_back_scroll(void);
+extern	void del_ifile(IFILE);
+extern	IFILE next_ifile(IFILE);
+extern	IFILE prev_ifile(IFILE);
+extern	IFILE getoff_ifile(IFILE);
+extern	int nifile(void);
+extern	IFILE get_ifile(char *, IFILE);
+extern	char *get_filename(IFILE);
+extern	int get_index(IFILE);
+extern	void store_pos(IFILE, struct scrpos *);
+extern	void get_pos(IFILE, struct scrpos *);
+extern	int opened(IFILE);
+extern	void hold_ifile(IFILE, int);
+extern	int held_ifile(IFILE);
+extern	void set_open(IFILE);
+extern	void *get_filestate(IFILE);
+extern	void set_filestate(IFILE, void *);
+extern	POSITION forw_line(POSITION);
+extern	POSITION back_line(POSITION);
+extern	void set_attnpos(POSITION);
+extern	void jump_forw(void);
+extern	void jump_back(LINENUM);
+extern	void repaint(void);
+extern	void jump_percent(int, long);
+extern	void jump_line_loc(POSITION, int);
+extern	void jump_loc(POSITION, int);
+extern	void init_line(void);
+extern	int is_ascii_char(LWCHAR);
+extern	void prewind(void);
+extern	void plinenum(LINENUM);
+extern	void pshift_all(void);
+extern	int is_ansi_end(LWCHAR);
+extern	int is_ansi_middle(LWCHAR);
+extern	int pappend(char, POSITION);
+extern	int pflushmbc(void);
+extern	void pdone(int, int);
+extern	void set_status_col(char);
+extern	int gline(int, int *);
+extern	void null_line(void);
+extern	POSITION forw_raw_line(POSITION, char **, int *);
+extern	POSITION back_raw_line(POSITION, char **, int *);
+extern	void clr_linenum(void);
+extern	void add_lnum(LINENUM, POSITION);
+extern	LINENUM find_linenum(POSITION);
+extern	POSITION find_pos(LINENUM);
+extern	LINENUM currline(int);
+extern	void lsystem(char *, char *);
+extern	int pipe_mark(int, char *);
+extern	int pipe_data(char *, POSITION, POSITION);
+extern	void init_mark(void);
+extern	int badmark(int);
+extern	void setmark(int);
+extern	void lastmark(void);
+extern	void gomark(int);
+extern	POSITION markpos(int);
+extern	void unmark(IFILE);
+extern	void opt_o(int, char *);
+extern	void opt__O(int, char *);
+extern	void opt_j(int, char *);
+extern	void calc_jump_sline(void);
+extern	void opt_shift(int, char *);
+extern	void calc_shift_count(void);
+extern	void opt_k(int, char *);
+extern	void opt_t(int, char *);
+extern	void opt__T(int, char *);
+extern	void opt_p(int, char *);
+extern	void opt__P(int, char *);
+extern	void opt_b(int, char *);
+extern	void opt_i(int, char *);
+extern	void opt_e(int, char *);
+extern	void opt__V(int, char *);
+extern	void opt_D(int, char *);
+extern	void opt_x(int, char *);
+extern	void opt_quote(int, char *);
+extern	void opt_query(int, char *);
+extern	int get_swindow(void);
+extern	char *propt(int);
+extern	void scan_option(char *);
+extern	void toggle_option(struct loption *, int, char *, int);
+extern	int opt_has_param(struct loption *);
+extern	char *opt_prompt(struct loption *);
+extern	int isoptpending(void);
+extern	void nopendopt(void);
+extern	int getnum(char **, char *, int *);
+extern	long getfraction(char **, char *, int *);
+extern	int get_quit_at_eof(void);
+extern	void init_option(void);
+extern	struct loption *findopt(int);
+extern	struct loption *findopt_name(char **, char **, int *);
+extern	int iread(int, unsigned char *, unsigned int);
+extern	void intread(void);
+extern	long get_time(void);
+extern	char *errno_message(char *);
+extern	int percentage(POSITION, POSITION);
+extern	POSITION percent_pos(POSITION, int, long);
+extern	void put_line(void);
+extern	void flush(void);
+extern	int putchr(int);
+extern	void putstr(const char *);
+extern	void get_return(void);
+extern	void error(const char *, PARG *);
+extern	void ierror(const char *, PARG *);
+extern	int query(const char *, PARG *);
+extern	int compile_pattern(char *, int, regex_t **);
+extern	void uncompile_pattern(regex_t **);
+extern	int is_null_pattern(void *);
+extern	int match_pattern(void *, char *, char *, int, char **, char **,
+    int, int);
+extern	POSITION position(int);
+extern	void add_forw_pos(POSITION);
+extern	void add_back_pos(POSITION);
+extern	void pos_clear(void);
+extern	void pos_init(void);
+extern	int onscreen(POSITION);
+extern	int empty_screen(void);
+extern	int empty_lines(int, int);
+extern	void get_scrpos(struct scrpos *);
+extern	int adjsline(int);
+extern	void init_prompt(void);
+extern	char *pr_expand(const char *, int);
+extern	char *eq_message(void);
+extern	char *pr_string(void);
+extern	char *wait_message(void);
+extern	void init_search(void);
+extern	void repaint_hilite(int);
+extern	void clear_attn(void);
+extern	void undo_search(void);
+extern	void clr_hilite(void);
+extern	void clr_filter(void);
+extern	int is_filtered(POSITION);
+extern	int is_hilited(POSITION, POSITION, int, int *);
+extern	void chg_caseless(void);
+extern	void chg_hilite(void);
+extern	int search(int, char *, int);
+extern	void prep_hilite(POSITION, POSITION, int);
+extern	void set_filter_pattern(char *, int);
+extern	int is_filtering(void);
+extern	void winch(int);
+extern	void init_signals(int);
+extern	void psignals(void);
+extern	void cleantags(void);
+extern	int gettagtype(void);
+extern	void findtag(char *);
+extern	POSITION tagsearch(void);
+extern	char *nexttag(int);
+extern	char *prevtag(int);
+extern	int ntags(void);
+extern	int curr_tag(void);
+extern	int edit_tagfile(void);
+extern	void open_getchr(void);
+extern	void close_getchr(void);
+extern	int getchr(void);