[
  {
    "path": ".gitattributes",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n*.txt       linguist-documentation\n*.ms        linguist-documentation\n*.1         linguist-documentation\n*.8         linguist-documentation\n*.md        linguist-documentation\n*.rm        linguist-documentation\n*.advanced  linguist-documentation\n*.beginner  linguist-documentation\n*.summary   linguist-documentation\n*.vindex    linguist-documentation\n*.chars     linguist-documentation\n*.refs      linguist-documentation\n*.roff      linguist-documentation\n./docs/*    linguist-documentation\n*.in        linguist-vendored\n*.csh       linguist-vendored\n*.script    linguist-vendored\n"
  },
  {
    "path": ".gitignore",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n\n# Prerequisites\n*.d\n\n# Timing\n*.t\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n*.dll\n*.exe\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Debug files\n*.dSYM/\n*.su\n*.idb\n*.pdb\n\n# Kernel Module Compile Results\n*.mod*\n*.cmd\n.tmp_versions/\nmodules.order\nModule.symvers\nMkfile.old\ndkms.conf\n\n# Output\ncommon/options_def.h\nex/ex_def.h\nbin/*\n\n# Patch\n*.rej\n*.orig\n*.diff\n\n# Junk\ntypescript\n.DS_Store\n.cproject\n.project\n.settings/\n.vscode/\n.nvimlog\n.vimlog\n*.*.swp\ntest.txt\n\n# Tags\ntags\n.tags\nctags\n.ctags\netags\n.etags\n.TAGS\nTAGS\nGRTAGS\nGTAGS\nGPATH\n\n# Cores\ncore\n*.core\n\n# Local\n.autoenv\n.envrc\n\n# Bad Targets\nall\nclean\ninstall\ndistclean\n\n# Profiling\n*.gcda\n*.gcov\n*.gcno\n*.profdata\n*.profout\n\n# Tidy\ncompile_commands.json\n"
  },
  {
    "path": "BSDmakefile",
    "content": "###############################################################################\n#                               -  O p e n V i  -                             #\n###############################################################################\n# vim: filetype=make:tabstop=8:tw=79:noexpandtab:colorcolumn=79\n# SPDX-License-Identifier: BSD-3-Clause\n###############################################################################\n\n###############################################################################\n#\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n#\n# Copying and distribution of this file, with or without modification,\n# are permitted in any medium without royalty provided the copyright\n# notice and this notice are preserved.  This file is offered \"AS-IS\",\n# without any warranty.\n#\n###############################################################################\n\n###############################################################################\n# Configuration\n\n.SHELL: name=sh\n.MAIN: all\n.MAKE.JOBS ?= 1\n.NOTPARALLEL: _FAIL all _GMAKE $(.TARGETS)\n.PHONY: _FAIL all _GMAKE $(.TARGETS)\n$(.TARGETS): _GMAKE\n\n###############################################################################\n# Wrapper\n\n_GMAKE:\n\t@command -v gmake > /dev/null 2>&1 ||\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tprintf '\\rError: %s\\n' \"GNU Make is required.\" 1>&2;\t\\\n\t\texit 1;\t\t\t\t\t\t\t\\\n\t} &&\t\t\t\t\t\t\t\t\\\n\tcommand gmake \t\t\t\t\t\t\t\\\n\t\t$$(printf '%s' \"$(MAKEFLAGS)\"  2> /dev/null |\t\t\\\n\t\t\tsed -e 's/-J .* //' \t\t\t\t\\\n\t\t\t\t-e 's/-J.* //' 2> /dev/null)\t\t\\\n\t\t$(.TARGETS)\n\n###############################################################################\n"
  },
  {
    "path": "ChangeLog",
    "content": "OpenVi 7.7.32 -> OpenVi 7.8.33-dev: Wed Dec 24 15:06:57 2025\n        + Update README to mention optional `pkg-config` prerequisite\n        + Do not include termio.h on Linux systems\n        + Update email address (johnsonjh.dev@gmail.com)\n\nOpenVi 7.6.31 -> OpenVi 7.7.32: Thu Oct 2 03:49:26 2025\n        + Try to query `pkg-config` for ncurses flags and libs on some systems\n        + Fix filename completion using `D_NAMLEN` as defined in the compat\n          `include/compat.h` header; patch from @jerryfletcher21; closes #22.\n        + Fix `p` command when used with a count\n        + Fix crash with expandtab and running external commands; fix from\n          Jerry Fletcher, OK job@\n        + Update docs to add percentage to ruler after recent changes\n\nOpenVi 7.6.30 -> OpenVi 7.6.31: Sun Apr 6 15:25:04 2025\n        + Don't require xinstall to be built for `install`, `strip`, and\n          `sstrip` targets to complete successfully.\n        + In xinstall's create_tempfile() pass pointer to full pathname to\n          strlcat(); fixes a potential buffer overrun; also check strlcpy()\n          and strlcat() return value to detect truncations; based on a diff\n          from naddy@; ok naddy@ tb@ deraadt@\n        + Support building for Managarm (xbstrap cross-compilation only)\n\nOpenVi 7.5.29 -> OpenVi 7.6.30: Mon Oct 7 08:45:39 2024\n        + Bump for OpenBSD 7.6 release\n\nOpenVi 7.5.28 -> OpenVi 7.5.29: Tue May 21 05:26:38 2024\n        + Support building natively on OS/400; verified using PASE for IBM i\n          7.5 with GCC 10.5 (gcc10) and ncurses 6.0 (ncurses, ncurses-devel)\n        + Add a new output level, M_XINFO, which outputs an informational\n          message and ignores the state of the verbose and silent flags\n        + Show version command output even in ex silent mode\n        + Update the usage help text to document the new `-C` option\n        + Minor changes to xinstall.c to appease Oracle Lint warnings\n        + Consistently use .Dq for double-quoting in man pages\n        + Fix various spelling errors and typos, mostly in comments\n        + Increase buf size\n\nOpenVi 7.4.27 -> OpenVi 7.5.28: Sun Apr 7 19:06:02 2024\n        + Add `-C cmd` startup option; similar to `-c` but always runs `cmd`\n        + Remove duplicate include statements from xinstall.c\n        + Add showfilename set option to display file name; OK millert@ otto@\n        + Avoid use after free of frp and frp->tname; found by smatch,\n          ok miod@ millert@\n        + fix fd leaks in error paths; ok miod@\n\nOpenVi 7.4.26 -> OpenVi 7.4.27: Wed Nov 22 19:30:41 2023\n        + Update README file with additional links and packaging information\n        + Always suppress db_err output when !lno; closes GitHub Issue #34\n        + Update minpwcache.c to OpenBSD pwcache.c v1.16; contains spelling\n          fixes from Paul Tagliamonte; only comments, no user-facing change\n        + Use openbsd_strlcpy for strlcpy in ptym_open(); fixes compilation\n          on AIX and some other System V-derived systems\n\nOpenVi 7.4.25 -> OpenVi 7.4.26: Fri Oct 28 00:15:39 2023\n        + Add a fallback path for `altnotation` mode; fixes a crash on macOS\n        + Bump MAX_CHARACTER_COLUMNS to 6 for future usage\n        + Eliminate strcpy usage in ptym_open function\n\nOpenVi 7.4.24 -> OpenVi 7.4.25: Fri Oct 27 11:44:45 2023\n        + Add a new option, `altnotation` (abbreviation `an`), inspired\n          by the Nvi2 2.2.1 option of the same name; if set, most control\n          characters less than 0x20 will be displayed in <C-char> notation,\n          and carriage feed, escape, and delete will be displayed as <Ret>,\n          <Esc>, and <Del>, respectively\n\nOpenVi 7.4.23 -> OpenVi 7.4.24: Tue Oct 10 19:47:07 2023\n        + Silence a warning when building with recent Clang compilers\n        + Treat consecutive paragraph indicators as different paragraphs;\n          Consecutive empty lines count toward the same state, so there're\n          2x states (to get in and out). `^L` and `.PP` are counted as text,\n          hitting those in the text should be treated as getting out of a\n          paragraph and then getting in. From Walter Alejandro Iglesias and\n          Zhihao Yuan in nvi2; ok bluhm@\n        + Fix typo in last ChangeLog entry\n\nOpenVi 7.3.22 -> OpenVi 7.4.23: Sat Jun 24 03:12:41 2023\n        + Bump OpenBSD date synchronization version part to 06/23/2023\n        + Remove unused `__cur_db` variable in Berkeley DB code; ok millert@\n        + Spelling fixes (in comments only) for regex from Paul Tagliamonte\n        + Remove vestigial `?` case from `xinstall` top-level `getopt` loop;\n          Prompted by dlg@, help from dlg@, millert@; ok naddy@ millert@ dlg@\n        + Packaging improvements from @jswank to honor DESTDIR to support\n          relocatable installation, use relative symlinks for `view` and `ex`\n          programs, and not create `/var/tmp/vi.recover` during installation\n\nOpenVi 7.2.21 -> OpenVi 7.3.22: Tue Apr 11 07:54:09 2023\n        + OpenBSD 7.3 released; bumping OpenBSD major version part to 7.3\n        + Bump OpenBSD date synchronization version part to 01/29/2023\n        + Autoprint line number to match the confirmation line;\n          patch from `nvi2` via millert@\n        + Fix crash when tags file pattern has a trailing `\\`, patch\n          via `nvi2`; co-authored by Craig Leres <leres@FreeBSD.org>\n\nOpenVi 7.2.20 -> OpenVi 7.2.21: Tue Jan 31 03:26:07 2023\n        + Fix handling of ex_range escaped backslashes; If there are two\n          consecutive backslashes, skip past both so the second is not\n          mistakenly treated as an escape character. This is consistent\n          with how escaped backslashes are treated in ex_substitute()\n          and global(); from Bosco G. G.\n        + Spelling fixes from Paul Tagliamonte\n        + Fix ^^D and 0^D description in man page; pointed out by\n          Tomas Rippl; ok jmc@\n        + Fix typo in `.gitattributes`; no code changes\n\nOpenVi 7.2.19 -> OpenVi 7.2.20: Sun Jan 1 05:36:31 2023\n        + Correctly increment version identifier to match release\n        + Normalize comment blocks; no functional changes\n\nOpenVi 7.1.18 -> OpenVi 7.2.19: Tue Nov 29 22:04:43 2022\n        + Add translation of README.md into Brazilian Portuguese\n        + Suppress the \"UNLOCKED\" message on AIX\n        + Set OBJECT_MODE when stripping compiled binaries\n        + Add DEP5 metadata file to the source tree\n        + Add SPDX license identifiers for REUSE compliance\n        + OpenBSD 7.2 enters beta; bumping OpenBSD version to 7.2\n\nOpenVi 7.1.17 -> OpenVi 7.1.18: Sat Jul 23 19:40:59 2022\n        + Quiet complaints when recovery directory is non-existent\n        + Improve various status messages\n        + Add permission statements to `GNUmakefile`, `BSDmakefile`\n        + Update documentation\n\nOpenVi 7.0.16 -> OpenVi 7.1.17: Sun Apr 24 07:57:29 2022\n        + In v_event_get check qp->output for NULL before passing to\n          e_memcmp(); other users of qp->output already include a NULL check;\n          avoids a crash when cursor key support is disabled in cl/cl_term.c;\n          from Jeremy Mates; ok tb@\n        + Check tkp->output != NULL before taking strlen for both command\n          mappings and input mappings; this adds a missing check for command\n          mappings and simplifies the input mappings; ok millert@\n        + From upstream man page, add missing comma; ok jmc@\n        + Update documentation\n\nOpenVi 7.0.15 -> OpenVi 7.1.16: Sun Mar 13 22:10:37 2022\n        + Document that PCC (Portable C Compiler) is working\n        + Add a BSD make wrapper that calls gmake if available\n        + Add OpenBSD basename functionality for use with xinstall\n        + Simplify compatibility headers\n        + Make `sstrip` target depend on the `strip` target, since many\n          platforms do not support all the sstrip options, so still stripping\n          as much as is possible is the most user-friendly thing to try\n        + Add support for Solaris; tested on Oracle Solaris 11.4.0 with GCC,\n          Clang (V6+), and the Oracle Developer Studio V12.6 compiler\n        + Add support for illumos; tested on OpenIndiana Hipster 2022.03\n        + Add support for NetBSD; tested on NetBSD/amd64 9.2-stable with\n          the system provided GCC and Clang from binary packages; compiling\n          `CC=clang LTO=1` requires the LLVM LLD linker `ld.lld` installed\n        + Add support for IBM AIX 7+; tested on IBM AIX 7.2 and IBM AIX 7.3\n          with GNU GCC (8, 9, 10, 11), IBM XL C/C++ V16.1+ (gxlc, xlclang),\n          and IBM Open XL C/C++ V17.1+ using ncurses from IBM's AIX Toolbox;\n          AIX builds default to 64-bit (on 64-bit systems) when using a\n          supported compiler; the environment variable `MAIXBITS` can be set\n          to `32` or `64` to force compilation of a 32-bit or 64-bit binary\n        + Various portability improvements (portable BSD getopt, warn, etc.)\n        + Reorganize source tree to better separate logical components\n        + `WCOREDUMP` is not in POSIX.1-2008, so don't require it to build\n        + Similarly, check if `TIOCSCTTY` is available and don't require it\n        + Add `xinstall`, a BSD install utility based on OpenBSD `install(1)`\n        + Switch `vfork` to `fork` as `vfork` is now gone from POSIX.1-2008\n        + Clear pointer after ending screen for safety\n        + `README.md`, documentation, and man page corrections and improvements\n        + Apply `expandtab` to lines filtered with the `!` command, via `nvi2`\n        + OpenBSD 7.1 enters beta; bumping OpenBSD version to 7.1\n        + Modify text `%s/OpenBSD vi/OpenVi/` in `common/recover.c`\n        + Provide a more helpful message when ex-mode has background screens\n        + Fully redraw screen on refresh with ^L / ^R\n        + Make `taglength` work correctly; fix from nvi 1.8x\n        + Make `join` work as specified in the POSIX standard; nvi 1.8x\n        + Fix problem with autoindenting and ^^D input; patch from nvi 1.8x\n        + Fix to reset screen offset of top line exceeding number of screens\n          adapted from Sven Verdoolaege's nvi 1.8x patch\n        + Fix tty from ex-mode on `q` when there are multiple screens; patch\n          from Al Viro via nvi 1.8x; also minor redraw adjustments\n        + Use `-Wall` by default for all builds; drop `-Wextra` for release\n        + Switch default optimization level to `-Os` for release builds; use\n          `-D_FORTIFY_SOURCE=2` for non-debugging builds by default as well\n        + Do not warn about `ttyname` or `ioctl` failures for `stderr` unless\n          `stderr` is a tty according to `isatty()`\n        + Fix out of bounds access in file completion\n        + Raise the maximum ex-script mmap size limit\n        + `DEBUG=1` now defaults to `-O0` rather than `-Og`\n        + Avoid undefined behavior in `vs_crel()` and `ex_is_abbrev()`\n        + Suppress more warnings and potential warnings\n\nOpenVi 7.0.12 -> OpenVi 7.0.15: Thu Feb 24 12:18:33 2022\n        + Avoid O_PATH clash in source proper (rather than in awk script)\n        + Remove deprecated interpreter internals documentation\n        + Actually use `pledge()` for SerenityOS and OpenBSD 5.9 or later\n        + Add `superstrip` (aka `sstrip`) `GNUmakefile` target to\n          aggressively strip the compiled binary using `sstrip` if available\n        + Add `upx` `GNUmakefile` target to compress the compiled binary\n          using `upx` if available\n        + Cosmetic clean-up of `GNUmakefile` and normalize to 79-columns\n        + MSYS2 is also supported, tested with MSYS2 on Windows 11 (x86_64)\n        + Add support for Cygwin, tested with Cygwin64 on Windows 11 (x86_64)\n        + Update `README.md` to document usage for `LIBS` variable, correct\n          `OPTFLAGS` to `OPTLEVEL`, expand external links and information,\n          add citations regarding past multibyte efforts, use fancy quotes,\n          and correctly state that the traditional `ex` / `vi` was part of\n          the *first* Berkeley Software Distribution, mention OpenBSD's\n          standard secure coding practices, safe(r) functions, and ISC license\n        + Respect `LIBS` to set/override the default libraries for linking\n        + Make failure to strip non-fatal; fixes `install-strip` and `strip`\n          in the case where `./bin/vi` is un-strippable (i.e. `bin/vi` is\n          `upx` compressed or missing section headers from `sstrip`ing)\n        + Fixes for `vi` recovery mode. From `trondd@`, tested by various,\n          ok `afresh1@`; this advances OpenBSD release date to 02/20/2022\n        + Update `.gitignore` to add `compile_commands.json`\n        + Suppress a few possible warnings\n        + Since HiDPI screens are more common, allow terminal dimensions of\n          3640x2048; this might need to be further extended for 4K displays\n\nOpenVi 7.0.11 -> OpenVi 7.0.12: Sun Feb 20 12:57:42 2022\n        + Update `README.md` with some background info and rationale\n        + Always use internal reallocarray; closes GitHub Issue #5\n\nOpenVi 7.0.10 -> OpenVi 7.0.11: Sat Feb 12 19:42:28 2022\n        + Silence a few more warnings; adjust default debugging `CFLAGS`\n        + Add an initial regex man page (which is not yet installed),\n          `docs/USD.doc/re_format/vi_regex.7`\n        + Update `LICENSE.md`; fix email address for `millert@`\n        + Simplify `db/sys/issetugid.c` ifdef's\n        + Update bundled *OpenBSD* cclass.h to `1.7` (2020/12/30 08:54:42)\n        + Update bundled *OpenBSD* cname.h to `1.6` (2020/12/30 08:53:30)\n        + Update bundled *OpenBSD* engine.c to `1.26` (2020/12/28 21:41:55)\n        + Update bundled *OpenBSD* regerror.c to `1.15` (2020/12/30 08:56:38)\n        + Update bundled *OpenBSD* regex2.h to `1.12` (2021/01/03 17:07:58)\n        + Update bundled *OpenBSD* regexec.c to `1.14` (2018/07/11 12:38:46)\n        + Update bundled *OpenBSD* regcomp.c to `1.43` (2021/01/03 17:07:57)\n        + Update bundled *OpenBSD* strlcpy to `1.16` (2019/01/25 00:19:25)\n        + Update bundled *OpenBSD* reallocarray to `1.3` (2015/09/13 08:31:47)\n        + Update bundled *OpenBSD* getopt_long to `1.32` (2020/05/27 22:25:09)\n        + Update bundled *OpenBSD* basename to `1.17` (2020/10/20 19:30:14)\n        + Add initial *Midipix* support\n\nOpenVi 7.0.9 -> OpenVi 7.0.10: Wed Feb 9 16:36:25 2022\n        + Silence some warnings and general style clean-up\n        + Clarify text of some visual mode messages; use the POSIX thousands\n          numeric separator for displaying most line and character counts\n        + Use `/var/tmp` for the `vi.recover` directory; on most Linux\n          systems, this persists across reboots and gives a higher chance\n          of recovery than `/tmp` which is often a memory-backed filesystem\n        + New feature: If `ruler` is set, print a percentage after position\n        + New feature: If `windowname` is set, always show the file name\n        + New feature: If `bserase` (abbrev `bse`) is set, then any newly\n          backspaced characters are immediately erased from the screen\n        + Handle `SIGQUIT` like `SIGINT`, which prevents `^\\` (Control-\\)\n          from causing ex-mode to abort and drop core; aligns closer to\n          Vim's behavior in ex-mode - which uses non-canonical input mode,\n          so `^\\` doesn't cause a stop. The rationale for this change is\n          because we're using `^\\` as a shortcut to enter our ex-mode (which\n          for `nex` uses standard canonical line-based input), if `SIGQUIT`\n          is unhandled, it's easy to abort by accident by sending more than\n          one `^\\` in succession, which can happen on lagged connection\n        + Fix a long-standing bug where using `^G` on an empty file would\n          include a random garbage byte in the displayed output\n        + Reword \"Already in the first column\" to \"Already at the first\n          column\" to be consistent with the other movement error messages\n        + Remove documentation files not explicitly under the 3-BSD license\n        + Update header comment of all modified files\n        + Reformat the ChangeLog to break lines at 76 columns\n        + Convert tabs to spaces and adjust source indentation with `cppi`\n        + Update docs; Markdown-ify `LICENSE` and rename to `LICENSE.md`\n\nOpenVi 7.0.8 -> OpenVi 7.0.9: Mon Jan 31 21:43:08 2022\n        + Add a missing include to fix compilation on Void with musl libc\n        + Update `README.md`\n\nOpenVi 7.0.7 -> OpenVi 7.0.8: Mon Jan 31 10:12:53 2022\n        + Rework `README.md`\n        + Drop `-pipe` and `-fomit-frame-pointer` for wider compatibility\n        + Add support for building on macOS; tested on 12.1/x86_64/21C5021h\n        + Add support for building on OpenBSD; tested on 7.0-current/ARM64\n        + Normalize licensing, acknowledgements, and copyright statements\n        + Normalize macros and conditional if/ifdef/ifndef's with `cppi`\n        + Add support for building on FreeBSD; tested on 13.0-REL-p6/ARM64\n        + Minor code clean-up\n        + Drop memmove wrapper (for old systems with bcopy but no memmove)\n        + Add support for building with musl libc\n        + Add `imctrl` and `imkey` options, inspired by `cannactrl` and\n          `fepkey` options in `nvi-m17n` by itojun. If `imctrl` option is\n          set, the input method is controlled by using escape sequences\n          compatible with Tera Term and RLogin. The state of the input\n          method in commands specified by imkey option is saved and restored\n          automatically. This input method is deactivated upon returning to\n          command mode; this implementation taken from NetBSD-current's\n          `contrib/nvi`\n        + Improve `make` output and use more logical compilation order\n        + Add `virecover.8` man page adapted from NetBSD\n        + Add a missing break in `common/log.c`\n        + Avoid undefined behavior in `*BIT` macros; patch from NetBSD; also\n          apply patch from NetBSD for PR bin/52716\n        + Update `.gitignore` and `.gitattributes`\n\nOpenVi 7.0.6 -> OpenVi 7.0.7: Fri Jan 28 16:33:22 2022\n        + Important fix to `GNUmakefile` for linking with Clang's `lld`\n        + Remove installed man pages as part of the `uninstall` target\n        + Workaround to accommodate `make clean all -j N' where `N` > 1\n        + Clean-up whitespace and line lengths of sources and `GNUmakefile`\n\nOpenVi 7.0.5 -> OpenVi 7.0.6: Thu Jan 27 16:45:12 2022\n        + Important fix for back-end database file locking; also, switch\n          back to using the system libc-provided mkstemp functions to elide\n          some issues that would otherwise occur on networked filesystems\n        + Add proper attribution for DragonflyBSD Project to `LICENSE` text\n        + Correct `GNUmakefile` messages for the `make install` target\n        + Clean-up excess and trailing whitespace in source files\n\nOpenVi 7.0.4 -> OpenVi 7.0.5: Thu Jan 27 11:00:46 2022\n        + No verbose build by default; set variables V (or DEBUG) to enable\n        + No LTO and LGC by default; set variables LTO and/or LGC to enable\n        + Don't attempt linking with libjemalloc or libmtmalloc by default\n        + Clean-up headers to speed compilation time & decrease binary size\n        + Import and adapt OpenBSD Berkeley DB from OpenBSD 7-current libc\n        + Remove the unused \"maintainer-clean\" target from GNUmakefile\n        + Build against the newly bundled OpenBSD Berkeley DB by default;\n          the imperfect Berkeley DB 3/4/5 support will remain available\n\nOpenVi 7.0.3 -> OpenVi 7.0.4: Wed Jan 26 13:09:45 2022\n        + New feature: \"set visibletab\" (abbrev \"set vt\") which toggles\n          displaying tabs visibly while editing, useful for Makefiles, etc\n        + Decrease the width of the line-number column by one row\n        + Increase the length of the divider decoration string (by 2x)\n        + Improve the GNUmakefile to avoid remaking any non-stale targets\n        + Portably seed the standard random number generator\n\nOpenVi 7.0.2 -> OpenVi 7.0.3: Wed Jan 26 08:31:28 2022\n        + GNUmakefile \"install\" target now installs docs (man pages)\n        + Remove 'docs/internals/cscope.NOTES' and documentation references\n        + General build, portability, and code readability improvements\n        + Relicense new contributions under the same 3-clause BSD license\n          used by the overall project and update LICENSE file text\n        + Remove unused headers and legacy documentation from the sources\n        + Add standalone \"strip\" target to GNUmakefile which strips the\n          uninstalled compiled binary\n        + Set visible tab character to '~' and expose option in GNUmakefile\n        + Installed binaries are prefixed with 'o' (e.g. 'ovi') by default\n        + Explicitly invoke the POSIX-compliant version of the awk utility\n        + Clarify (and simplify) GNUmakefile and build configuration\n        + Update ChangeLog and docs for consistency, fix spelling and typos\n        + Update LICENSE text\n\nOpenVi 7.0.1 -> OpenVi 7.0.2: Tue Jan 25 13:44:48 2022\n        + Debugging builds now default to compiling with '-Wall -Wextra'\n        + Only pass '-pipe' to the compiler for non-debugging builds\n        + Expand the formatting of the usage help text for readability\n        + Change the DEBUG_VI GNUmakefile flag to simply DEBUG\n        + For debug builds, document the existence of -T (Trace) in usage\n        + Don't strip the binary by default; add \"install-strip\" target\n        + Make clean targets more robust; warn if unable to remove 'bin'\n        + Add standard \"clean\", \"distclean\", \"realclean\", \"mostlyclean\", and\n          \"maintainer-clean\" targets to GNUmakefile\n        + Increase the default escapetime to 2/10ths of a second\n        + Add OpenBSD-compatible mkstemp function allowing for longer names\n        + Silence some potential warnings with explicit casting\n        + Exit with an error when screen is too small for visual mode\n        + Apply Debian's patch to fix backwards sentence moving\n        + Fix horizontal scroll count; patch from Debian\n        + Change a #define to a typedef in regex library\n        + Fix some typos and minor errors in the documentation and tutorials\n        + Update .gitignore to include patch/diff detritus; no binary change\n        + Adjust calculation to avoid \"BDB0511 page sizes must be a\n          power-of-2\" warning; BDB 1.85 only cared if page size was even;\n          closes GH issue #3\n        + Apply .exrc writeability patch from hesso at\n          pool.math.tu-berlin.de\n        + If TERM is unset, set to NULL, or otherwise unknown, first attempt\n          to fallback to \"vt100\"; exit with a fatal error if \"vt100\" fails\n        + Disallow pattern spaces which would cause intermediate\n          calculations to overflow size_t. (CERT: VU#695940)\n        + Bump version and use -dev to denote development version\n        + Reformat ChangeLog for consistency; various spelling corrections\n        + Update ChangeLog with OpenVi history\n        + Format OpenBSD commits to ChangeLog format\n        + Clean-up of legacy documentation; adjust formatting\n        + Simplify GNUmakefile rules\n        + Update GNUmakefile; correct a typo in a message\n        + Update LICENSE text and formatting\n\nOpenBSD 7.0 -> OpenVi 7.0.1: Mon Jan 24 10:37:28 2022\n        + OpenVi created supporting glibc-based Linux systems\n        + Create GNUmakefile and adapt build system\n        + Attempt to detect and use mtmalloc or jemalloc if available\n        + Support builds with post-1.8.5 BerkeleyDB versions (using BDB\n          1.8.5 emulation) but at the expense of preservation and recovery\n        + Create initial README.md stub\n\nMon Oct 25 14:17:24 2021\n        + vi(1): fix use after free with unsaved buffer. Issuing a zero-arg\n          ex_edit command (:e) while using a named buffer with no backing\n          file caused vi(1)/ex(1) to free the strings representing the\n          buffer name and the name of the temporary file. This change\n          detects the situation and only frees the newly allocated EXF\n          structure (ep). Reported on bugs@ by kn@. OK millert@\n\nSun Oct 24 21:24:15 2021\n        + For open/openat, if the flags parameter does not contain\n          O_CREAT, the 3rd (variadic) mode_t parameter is irrelevant\n          Many developers in the past have passed mode_t (0, 044, 0644,\n          or such), which might lead future people to copy this broken\n          idiom, and perhaps even believe this parameter has some meaning\n          or implication or application. Delete them all. This comes out of\n          a conversation where tb@ noticed that a strange (but intentional)\n          pledge behavior is to always knock-out high-bits from mode_t on a\n          number of system calls as a safety factor, and his bewilderment\n          that this appeared to be happening against valid modes (at least\n          visually), but no sorry, they are all irrelevant junk. They could\n          all be 0xdeafbeef. ok millert@\n\nOpenBSD 6.9 -> OpenBSD 7.0: Thu Sep 2 11:19:02 2021\n        + Make all signal handler functions async-signal-safe by\n          deleting the redundant \"killersig\" struct member and using the\n          existing sig_atomic_t cl_sigterm variable instead\n        + While here, garbage collect the h_hup() signal handler which is\n          essentially identical to h_term(). This also gets rid of the\n          last #define & #undef in cl_main.c\n\nWed Sep 1 14:28:15 2021\n        + As a first step towards safe signal handling, improve the\n          h_int() and h_winch() signal handlers to make one single store\n          to a sig_atomic_t variable. Note that the h_hup() and h_term()\n          signal handlers are still unsafe after this commit because they\n          also set the \"killersig\" (how fitting!) field in a global struct\n\nOpenBSD 6.8 -> OpenBSD 6.9: Tue Apr 13 15:39:21 2021\n        + Require that the argument to the window option be non-zero. A\n          zero-row window would not be usable (no room to edit) and the\n          code is full of assumptions that \"sp->t_rows - 1\" >= 0. From\n          Erik Ruotsalainen, fixes a bug reported by Paul de Weerd\n        + Ignore expandtab setting when in command mode. Fixes things like\n          searching for a literal tab character when expandtab is enabled;\n          from `nvi2` (leres); OK martijn@\n\nMon Mar 8 02:47:25 2021\n        + Add some references, most of these were removed when we stopped\n          building and installing USD/SMM/PSD docs\n\nOpenBSD 6.7 -> OpenBSD 6.8: Tue Jan 26 18:19:43 2021\n        + Satisfy -fno-common by repairing one enum decl; ok mortimer@\n\nOpenBSD 6.6 -> OpenBSD 6.7: Sun May 24 13:33:55 2020\n        + Correct one statement about how the 'm' command works, talk\n          less about ancient terminals, employ the more usual term \"caret\"\n          rather than \"up-arrow\", and fix a few minor nits\n\nOpenBSD 6.5 -> OpenBSD 6.6: Thu Apr 30 10:40:21 2020\n        + Add an expandtab option, similar to what vim supports. If set,\n          expands tabs to spaces in insert mode as well as when shifting\n          and indenting/outdenting. If quoted with ^V, a literal tab is\n          inserted. Adapted from NetBSD, but this implementation more\n          closely matches vim's behavior. OK dlg@\n\nFri Oct 4 20:12:01 2019\n        + Better link \"set\" and \"SET OPTIONS\"; original diff from sven\n          falempin, tweaked a bit by myself\n\nMon Jul 22 12:39:02 2019\n        + In secure mode (-S), skip sending mail when executing the\n          :pre[serve] command or when dying from SIGTERM. This way,\n          creating the recovery file works again without re-adding \"proc\n          exec\" to the pledge(2). As reported by Jesper Wallin <jesper\n          at ifconfig dot se>, this got broken by common/main.c rev. 1.29\n          (Nov 19, 2015). The general direction of the fix was suggested\n          by brynet@. OK brynet@ and no opposition when shown on tech@\n\nOpenBSD 6.4 -> OpenBSD 6.5: Tue May 21 09:24:58 2019\n        + Also apply stricter pledge when secure mode is set via rc file\n          or command\n\nOpenBSD 6.3 -> OpenBSD 6.4: Thu Jan 24 15:09:41 2019\n        + Fix a crash on long lines when switching to another file by\n          setting SC_SCR_CENTER which will cause the offsets in HMAP to\n          be reset when painting the screen. OK martijn@ otto@\n\nMon Sep 17 15:41:17 2018\n        + Use the strict pragma for better warnings\n\nFri Jul 13 20:06:10 2018\n        + Remove Cscope leftover and a stray comma\n        + Unused variable\n\nWed Jul 11 06:39:23 2018\n        + Remove an old (and false) comment. REALLOC now free(3)s\n          the code if realloc fails\n\nOpenBSD 6.2 -> OpenBSD 6.3: Mon Feb 12 01:10:46 2018\n        + Simplify documentation of split-screen mode, avoiding abuse\n          of [] to sometimes mean \"character set\", which conflicts with\n          the normal meaning of \"optional element\" in manual pages\n        + While here, add a few related clarifications and tweak a\n          few details. Triggered by a minor bug report from <trondd at\n          kagu-tsuchi dot com>, and by bentley@ subsequently pointing out\n          the abuse of []. Patch using input from jmc@, who also agreed\n          with some previous versions\n\nSat Feb 3 15:44:36 2018\n        + The recover script should have the same sanity checks as\n          recover.c. Specifically, open files with O_NONBLOCK and enforce\n          a mode of 0600\n\nThu Dec 14 10:02:53 2017\n        + Enable the awk scripts to generate ex_def.h and\n          options_def.h. These scripts generate the enums required for\n          the ex commands and vi options. Before these lists had to\n          be maintained either by hand or someone had to stumble upon\n          these scripts and figure out how to use them. By enabling them\n          these headers are now always in sync based on the comments in\n          the corresponding source files, which are a lot harder to miss\n          during an update than an extra file\n\nSun Nov 26 09:59:41 2017\n        + Fix segfault which could be triggered by deleting a backwards\n          sentence if cursor's current line was blank:\n\nFri Nov 10 18:31:36 2017\n        + When tracing is compiled in make sure it flushes its content\n          to disk as soon as the TRACE function is called. This helps\n          while debugging crashes\n        + Fix a use after free when sending SIGHUP or SIGTERM to vi when\n          in editing mode\n        + Add rcv_openat() function that does the open, makes sure it\n          is a regular file with the expected permissions and locks it\n          Inspired by changes in NetBSD by Christos. OK martijn@\n        + Avoid using system(3) when running \"sendmail -t\". We already\n          have the recover file fd open so just run sendmail with stdin\n          set to the recover file. OK martijn@\n\nOpenBSD 6.1 -> OpenBSD 6.2: Tue Aug 22 20:27:18 2017\n        + Do not treat comma as part of the command modifier\n\nMon Jul 31 19:45:49 2017\n        + Silence some warnings generated by clang. Original diff by\n          espie@ with some minor tweaks by myself\n\nThu Jul 20 08:37:48 2017\n        + Replace usage of strtol() with strtonum()\n\nWed Jul 5 18:56:33 2017\n        + Avoid double space caused by end-of-sentence detection\n          requested by jmc@\n        + Nits about trailing punctuation found with mandoc -Tlint\n\nMon Jul 3 14:30:11 2017\n        + Markup fixes\n        + Remove settings that were unimplemented for 20 years; update\n          STANDARDS\n\nFri Jun 30 14:42:05 2017\n        + Add mdoc(7) macros to vi's built-in lists of roff\n          paragraph/section macros\n\nSat Jun 24 16:30:47 2017\n        + Fix a check in ADD_SPACE_{GOTO,RET} that potentially allowed\n          for a NULL-dereference\n\nTue Jun 20 07:32:56 2017\n        + Better document the :s ex command and its variants\n\nThu Jun 15 06:44:47 2017\n        + \"10th's of a second\" -> \"tenths of a second\"\n\nMon Jun 12 18:38:57 2017\n        + Use openat() and unlinkat() instead of chdir()ing to the\n          recovery dir. Since we use flock() and not fcntl() locking we\n          can open the recovery file read-only. OK martijn@\n\nWed Apr 26 13:14:28 2017\n        + Remove extraneous \", NULL\" in the assignment of msgstr which was\n          leftover from when msg_cat() was removed. From Anton Lindqvist\n\nTue Apr 18 01:45:33 2017\n        + free(NULL) is ok so use it; from Michael W. Bombardieri\n\nOpenBSD 6.0 -> OpenBSD 6.1: Fri Jan 20 00:55:52 2017\n        + Nuke some excess whitespace\n\nSun Dec 18 18:28:38 2016\n        + Use %zu/%d to print size_t/ssize_t. Cast recno_t\n          (a.k.a. u_int32_t) to (unsigned long) to match %lu formats. Makes\n          gcc happier and quieter\n        + Nuke more unused variables\n\nSat Nov 5 16:21:56 2016\n        + Remove syscall.ph from vi.recover\n\nFri Sep 2 15:38:42 2016\n        + Fix the begin of word issue in vi(1). Similar fix went in sed\n          and ed\n\nOpenBSD 5.9 -> OpenBSD 6.0: Sat Aug 27 04:07:42 2016\n        + Pull in <sys/time.h> for struct timespec and timeval\n\nSun Aug 14 21:47:16 2016\n        + Kill '#if defined(DEBUG) && 0' blocks that used %q\n\nMon Aug 8 15:09:32 2016\n        + /tmp and /var/tmp are the same, consistently use the former\n          in both build/recover and documentation\n\nMon Aug 1 18:27:35 2016\n        + Remove vi's \"directory\" option and TMPDIR support\n\nThu Jul 7 09:26:25 2016\n        + biff, mesg, vi: only consider ACCESSPERMS for setting tty mode\n\nWed Jun 29 20:38:39 2016\n        + If /tmp/vi.recover doesn't exist, don't create it. Warn once\n          that it doesn't exist, afterwards fail silently\n\nSat May 28 18:30:35 2016\n        + Test if stdin is a terminal before resetting the tty state. Diff\n          supplied by Kai Antweiler\n\nFri May 27 09:18:11 2016\n        + Revert CHAR_T removal. Some signedness flaws were\n          introduced. Found the hard way by jca@\n\nSat May 7 14:03:01 2016\n        + Free memory if realloc fails. The application is most likely\n          to terminate after a failure, but if it does not we better clean\n          up after ourselves\n\nThu May 5 20:36:41 2016\n        + Remove __sigblockset. This is a leftover after the removal of\n          the signal blocking code in common/gs.h rev 1.14\n\nMon May 2 20:51:35 2016\n        + Remove pointless comment. getcwd(3) is safe\n        + Remove CHAR_T in favor of native types\n\nWed Apr 20 19:34:32 2016\n        + Remove pointless reenter variable\n\nTue Apr 19 17:42:09 2016\n        + Remove not implemented declaration of sscr_pty\n        + Remove some useless code\n\nWed Mar 30 06:38:40 2016\n        + For some time now mandoc has not required MLINKS to function\n          correctly; logically complete that now by removing MLINKS\n          from base\n\nOpenBSD 5.8 -> OpenBSD 5.9: Sat Mar 19 00:21:28 2016\n        + By issuing :e +something in vi(1) this uncovers a backwards\n          memcpy with the code because the 2 buffers overlap and in order\n          to solve it then replace memcpy(3) call by memmove(3)\n\nThu Mar 17 03:44:05 2016\n        + Add error checking for COLUMNS/LINES environment variables\n\nSun Mar 13 18:30:43 2016\n        + Remove an extra space before ^\\ help message. Fixes alignment\n          in viusage\n\nThu Feb 11 16:34:12 2016\n        + Update comment: the #ifdef VDSUSP was removed in r1.22\n\nTue Feb 9 07:41:12 2016\n        + Avoid special characters; from Michael Reed\n\nWed Feb 3 01:47:25 2016\n        + Remove needless alias macros for malloc and calloc. No binary\n          change. I got this up-streamed a few weeks ago\n\nSat Jan 30 21:34:57 2016\n        + /var/tmp is dead, long live /tmp\n        + Replace tail with basename\n        + Replace progname variable in gs structure with getprogname\n\nWed Jan 27 22:46:02 2016\n        + remove v_estr in favor of warn and warnx\n\nWed Jan 27 22:38:12 2016\n        + Replace fprintf+exit with errx. No functional change\n\nWed Jan 20 08:43:27 2016\n        + Remove ARG_CHAR_T, a relic from when the code was written\n          K&R style\n\nSat Jan 9 16:13:26 2016\n        + decls before code; from Martijn van Duren\n\nWed Jan 6 22:46:59 2016\n        + Remove mention of message catalog dir\n        + We don't use configure so this file is full of lies and we\n          are better off without it\n        + Remove msgcat from the documentation\n        + Remove prototype for now-deleted f_msgcat()\n        + Remove the actual message catalogs. From Martijn van Duren\n        + Remove the msg_cat() function and adjust its former\n          callers. From Martijn van Duren\n        + Remove the numeric identifiers at the beginning of the messages\n          which used to be used as the message number to lookup in the\n          catalog. From Martijn van Duren\n        + Remove the message catalog DB. This removes the msg_open()\n          and msg_close() functions along with the msgcat command. From\n          Martijn van Duren\n\nMon Dec 28 19:24:01 2015\n        + Use err() instead of custom perr() function. Also applied by\n          `nvi2` upstream; From Martijn van Duren\n\nMon Dec 7 20:39:19 2015\n        + Remove needless type casts and corresponding type parameters\n          from allocation macros. No binary change\n\nThu Dec 3 08:13:15 2015\n        + After inserting a backslash, don't treat ^H ^? or ^U as special\n          cases. These days, ^V to escape is a universal feature and\n          needing two keystrokes to delete backslashes is really annoying\n\nTue Nov 24 12:56:31 2015\n        + Update the other documentation to match the new filec default\n        + Turn on filename tab completion in vi by default\n\nMon Nov 23 09:03:01 2015\n        + Remove Cscope references in documentation\n\nFri Nov 20 04:12:19 2015\n        + vi -S doesn't need proc or exec\n\nThu Nov 19 19:30:44 2015\n        + \"tty proc exec\", not \"proc exec tty\"\n        + Remove Cscope support in vi\n\nOpenBSD 5.7 -> OpenBSD 5.8: Sun Nov 15 01:22:36 2015\n        + Vi needs flock, for those who haven't set nolock in .exrc\n          for years\n        + Basic pledge for vi\n\nMon Sep 14 20:06:58 2015\n        + Avoid .Ns right after .Pf, it's pointless\n        + In some cases, do additional cleanup in the immediate vicinity\n\nTue Jul 7 18:34:12 2015\n        + Fix a regression caused by timespec changes when vi is run\n          without a file to edit. Based on a diff from Patrick Keshishian\n\nOpenBSD 5.6 -> OpenBSD 5.7: Fri Apr 24 21:48:31 2015\n        + struct timespec/clock_gettime(3) conversion for vi(1)\n\nTue Apr 21 01:41:42 2015\n        + init both fds passed to pipe as -1 instead of init'ing one\n          twice ok deraadt@ guenther@ miod@ millert@\n\nSun Apr 19 01:10:59 2015\n        + Don't lock the file for \"vi -R\" or \"view\". OK deraadt@\n\nFri Apr 10 18:05:51 2015\n        + This changes vi to use resizeterm(3) instead of re-initializing\n          curses on window re-sizes, which was leaking massive amounts\n          of memory\n\nSun Mar 29 01:04:23 2015\n        + Remove SA_INTERRUPT, HISTORIC_PRACTICE, and HISTORICAL_PRACTICE\n          using unifdef. It seems clear that no one was using these\n          (SA_INTERRUPT didn't even build the other way). Tweak comments\n          as appropriate\n\nSat Mar 28 12:54:37 2015\n        + vi was using two separate isblank functions: one defined in\n          <ctype.h> and the other #defined in common/key.h. There is no\n          reason to have both. For consistency use the isblank function\n          from <ctype.h>, remove the #define in common/key.h, and add\n          #include <ctype.h> to the files that were missing the header\n\nFri Mar 27 04:11:25 2015\n        + Some vi cleanup, unifdef's some signal blocking code that has\n          never been enabled in our tree, also removes some stragglers\n          from a global struct referencing nonexistent Tcl/TK and \"IP\n          support\". And finally.. delete an empty file missed by earlier\n          cleanup by bentley@\n\nTue Mar 17 10:08:18 2015\n        + Don't use the wrong escape for < and >. Tweak wording to match\n          the page\n\nFri Mar 13 19:58:40 2015\n        + Remove the first comma from constructs like \", and,\" and\n          \", or,\": you can use \"and\" and \"or\" to join sentence clauses,\n          and you can use commas, but both hinders reading\n\nTue Mar 10 00:10:59 2015\n        + Display \"Search wrapped\" even when searching from the end of\n          the file\n\nSat Feb 28 21:51:56 2015\n        + Reduce usage of predefined strings in man pages\n\nFri Feb 6 22:29:31 2015\n        + Do not rely on unspecified behavior for the size_t overflow\n          check. OK miod@\n\nFri Jan 16 06:39:28 2015\n        + Replace <sys/param.h> with <limits.h> and other less dirty\n          headers where possible\n        + Annotate <sys/param.h> lines with their current reasons\n        + Switch to PATH_MAX, NGROUPS_MAX, HOST_NAME_MAX+1, LOGIN_NAME_MAX\n        + Change MIN() and MAX() to local definitions of MINIMUM() and\n          MAXIMUM() where sensible to avoid pulling in the pollution. These\n          are the files confirmed through binary verification. ok guenther@,\n          millert@, doug@ (helped with the verification protocol)\n\nThu Nov 20 08:50:53 2014\n        + Remove the vi Perl API\n\nWed Nov 19 03:42:40 2014\n        + Remove ifdef checks for LIBRARY. It is undocumented and triggers\n          the same conditional inclusions as PURIFY does\n\nFri Nov 14 20:27:03 2014\n        + _PATH_BSHELL, _PATH_SENDMAIL, _PATH_TMP and _PATH_TTY are defined\n          in <paths.h> and _PATH_SYSV_TTY is unused. All of them can be\n          removed from pathnames.h. The other defines can be made\n          unconditionally\n        + The 'tcl' command in vi does nothing, except to print the message\n          \"Vi was not loaded with a Tcl interpreter\". Printing the standard\n          message for unknown commands would be equally descriptive with the\n          benefit of reducing code size\n        + The vi editor contains code for two different file locking\n          methods; one using flock(), the other using fcntl(). The fcntl\n          method is unused and has severe limitations (as described in a\n          code comment); removed it for sake of readability\n\nWed Nov 12 16:29:04 2014\n        + Remove more portability bits for older systems; from Martin\n          Natano\n        + ANSIfy vi\n\nMon Nov 10 21:40:11 2014\n        + Remove various bits of autoconf cruft. from Martin Natano\n        + Remove ipc leftovers. from Martin Natano\n        + Remove old, unnecessary compat code. from Martin Natano\n\nOpenBSD 5.5 -> OpenBSD 5.6: Thu Nov 6 11:35:02 2014\n        + Clean up unused header files and docs referring to them\n        + Remove old curses support in vi\n\nOpenBSD 5.4 -> OpenBSD 5.5: Tue Oct 14 22:23:12 2014\n        + Create a REALLOCARRAY macro, and use it where it gives us\n          overflow protection for free ok guenther@\n\nWed Oct 8 00:53:45 2014\n        + Bump max columns out to 768 since screens are getting bigger\n\nTue Sep 9 14:10:35 2014\n        + We no longer need to convert \"\\<\" and \"\\>\" to \"[[:<:]]\" and\n          \"[[:>:]]\" respectively now that the former is natively supported\n          OK jsg@\n\nThu Jul 10 20:33:42 2014\n        + Add missing include file to bring in protos\n\nSun Dec 1 20:22:34 2013\n        + Change the file reference queue from CIRCLEQ to TAILQ\n        + Change the tags queue from CIRCLEQ to TAILQ\n        + Change the tag queue from CIRCLEQ to TAILQ\n        + Convert the ranges CIRCLEQ to TAILQ\n\nThu Nov 28 22:12:40 2013\n        + Convert the display screens and hidden screens CIRCLEQ's\n          to TAILQ's\n\nWed Nov 27 08:52:41 2013\n        + Zap some pointer casts became extra (and thus dangerous)\n          after recent CIRCLEQ removal\n\nTue Nov 26 17:48:01 2013\n        + Fix a possible double-free/NULL deref in msg_print\n        + Tweak a tortuous manual loop into a TAILQ_FOREACH()\n        + Fix condition after CIRCLEQ -> TAILQ conversion; ok zhuk@\n        + Fix incorrectly converted CIRCLEQ_END comparison to prevent\n          NULL deref's\n\nMon Nov 25 23:27:11 2013\n        + Replace _texth CIRCLEQ with TAILQ. One down, five to go\n\nOpenBSD 5.3 -> OpenBSD 5.4: Thu Aug 22 04:43:40 2013\n        + Correct format string mismatches turned up by -Wformat=2\n\nSat Jun 22 18:52:52 2013\n        + Tweak optimization flags on landisk until I have time to\n          investigate further\n\nTue May 14 11:51:41 2013\n        + When ^W (WERASE) is hit in insert mode it's possible that the\n          line buffer is accessed out of bounds. If 'max' == 0 and\n          'tp->cno' == 1 the 'tp->cno' value is first reduced by one and\n          then 'tp->lb' is accessed at 'tp->cno' - 1. Also remove dead\n          (and incorrect) code in the TXT_ALTWERASE case; from Arto Jonsson\n          OK martynas@\n\nOpenBSD 5.2 -> OpenBSD 5.3: Fri May 3 20:43:25 2013\n        + Use open(2) / fstat(2) instead of stat(2) / open(2) for checking\n          proper permissions of \"local\" .exrc or .nexrc files\n\nMon Apr 29 00:28:23 2013\n        + Use FD_CLOEXEC instead of 1; from David Hill\n\nThu Dec 20 20:28:12 2012\n        + Use openpty() rather than hand-rolled pty opening code\n          ok millert@\n\nOpenBSD 5.1 -> OpenBSD 5.2: Mon Dec 3 22:05:46 2012\n        + Fix hang when exiting shell in script mode. OK naddy@\n\nOpenBSD 5.0 -> OpenBSD 5.1: Tue Jan 17 08:18:36 2012\n        + Flesh out the VI COMMANDS section somewhat; diff from Alexis\n          Fouilhe; help; ok sobrado@\n\nOpenBSD 4.9 -> OpenBSD 5.0: Wed Dec 28 01:52:33 2011\n        + These utilities were already part of 1BSD, and some authors\n          are known. All facts from the CSRG archive CD 1, also available\n          from minnie.tuhs.org. Feedback and OK sobrado@, ok jmc@\n\nFri Jul 29 13:24:50 2011\n        + Document vi/ex regular expressions, and where they differ from\n          those documented in re_format(7)\n\nSun Jul 10 13:20:25 2011\n        + Rename O_DIRECTORY to O_TMP_DIRECTORY to avoid a namespace\n          collision with sys/fcntl.h. OK deraadt@\n\nMon May 16 16:41:58 2011\n        + Better document some of the terminology used in the VI COMMANDS\n          section; from Alexis Fouilhe\n\nOpenBSD 4.8 -> OpenBSD 4.9: Mon May 2 11:14:11 2011\n        + No need to escape `|'; as discussed with schwartze@\n\nTue Apr 12 18:08:00 2011\n        + Better document vi's startup (in terms of environment variables\n          and config files)\n\nSun Apr 10 21:21:50 2011\n        + Fix display glitch leading to crash. If we're reformatting,\n          check the screens necessary to display the line and modify head\n          or tail of the smap accordingly; since it might have changed\n          due to e.g. smaller tabstop value\n\nThu Mar 31 20:40:51 2011\n        + Add a BUFFERS section, to explain how they work; from Alexis\n          Fouilhe - many thanks to him for his work on this\n\nThu Mar 17 11:34:53 2011\n        + since we stopped installing the USD docs, it no longer makes\n          sense for DESCRIPTION to point to SEE ALSO\n\nOpenBSD 4.7 -> OpenBSD 4.8: Wed Jan 5 14:01:32 2011\n        + Fix typo, PR#6538\n\nMon Oct 18 14:42:16 2010\n        + Remove references to now removed USD/PSD/SMM docs\n\nSun Oct 17 22:54:37 2010\n        + Stop installing me(1) and ms(1) source code. We will soon get\n          rid of groff in base, so there is no longer any way to use\n          these files with base. No opposition on tech@\n\nWed Sep 29 07:44:56 2010\n        + Various EXIT STATUS fixes; from Daniel Dickman\n\nFri Sep 24 06:40:12 2010\n        + Add a little padding to make SYNOPSIS line up nicely\n\nSun Jul 25 20:23:41 2010\n        + ^U scrolls backwards, not forwards; from marrob at lavabit com\n\nSun Jul 18 21:45:01 2010\n        + Remove some nasty hacks\n\nThu Jul 15 20:51:38 2010\n        + More delimiters that need quoting inside macros, hunted down\n          by jmc@, who asked me to commit because he is just running out\n          of the door\n\nOpenBSD 4.6 -> OpenBSD 4.7: Sat May 29 06:40:00 2010\n        + subsitution -> substitution; from Yoshihiro Ota, FreeBSD PR#130874\n\nSun Nov 22 22:51:58 2009\n        + Fix for flash defaulting to off, pointed out by jmc@\n        + Change the flash option to be off by default. Now that xterm\n          has the flash capability in terminfo, vi was using it instead\n          of beeping, but it is too slow for some machines\n\nSun Nov 15 04:32:31 2009\n        + Do not leak a lot of memory if a small memory allocation fails,\n          found by parfait@ ok kettenis@ guenther@\n\nSat Nov 14 17:44:53 2009\n        + Fix leaks in error paths found by parfait ok deraadt@\n\nTue Oct 27 23:59:19 2009\n        + Remove rcsid[] and sccsid[] and copyright[] that are essentially\n          unmaintained (and unmaintainable). These days, people use source\n          These id's do not provide any benefit, and do hurt the small\n          install media (the 33,000 line diff is essentially mechanical); ok\n          with the idea millert@, ok dms@\n\nOpenBSD 4.5 -> OpenBSD 4.6: Tue Oct 20 09:54:47 2009\n        + ex(1) and vi(1) are different editors. The diff is based on the\n          original printed edition of the User's Reference Manual from\n          USENIX and O'Reilly. 4.4BSD had exactly this, that is much\n          more accurate than our current description (while here, Jason\n          observed that both FreeBSD and NetBSD do the same)\n\nWed Jun 10 14:03:18 2009\n        + Use poll() instead of select(). The sscr_check_input()\n          bit is adapted from nvi 1.81. Tested by several people during\n          the hackathon\n\nOpenBSD 4.4 -> OpenBSD 4.5: Tue Jun 2 00:21:32 2009\n        + If the read from the tty fails with EAGAIN, pop back up to the\n          select. Seems to happen occasionally even though select reported\n          the fd is ready. OK ray@\n\nMon Apr 27 19:41:10 2009\n        + It's called `msgcat', not `mesgcat'\n\nSun Apr 19 13:12:28 2009\n        + Fix tagnext and tagprev; from Patrick Keshishian\n\nSun Feb 8 17:15:08 2009\n        + Bump the POSIX reference in STANDARDS to IEEE Std 1003.1-2008,\n          with a few updates to follow\n\nSun Feb 1 21:57:21 2009\n        + Move variable declarations around to compile with GCC 2\n\nOpenBSD 4.3 -> OpenBSD 4.4: Wed Jan 28 21:30:43 2009\n        + Remove undocumented support for \"-e\" in ex(1)\n        + ex(1), vi(1), and view(1) have different synopses; each nex/nvi\n          utility should manage the right set of options and return an\n          appropriate usage when required\n\nThu Sep 25 11:37:03 2008\n        + do not hard code the editor name in the message displayed by\n          \"-r\" when there are no files to recover as this flag is used\n          by ex(1) and view(1) too\n\nFri Aug 29 13:07:13 2008\n        + Fix nvi's Cscope support in the case that someone provided a\n          filename without a directory (e.g. :cscope add cscope.out)\n          Found and fixed by Paul Irofti, with help from me; Thanks!\n\nOpenBSD 4.2 -> OpenBSD 4.3: Thu Jun 12 21:22:48 2008\n        + Remove superfluous \"usage:\" from v_estr()\n\nFri Mar 28 17:58:20 2008\n        + Minor ansification from Gleydson Soares\n\nSat Mar 8 18:11:42 2008\n        + Avoid infinite recursion on certain error conditions; from\n          NetBSD; ok millert@\n        + Fix vs_columns() for the \"set nu\" case. Avoids segfaults for\n          very long lines containing tabs; from Nathan Houghton; ok millert@\n\nTue Mar 4 18:55:44 2008\n        + Fix ifdef DEBUG code; ok krw@ deraadt@\n\nOpenBSD 4.1 -> OpenBSD 4.2: Sat Nov 24 12:59:28 2007\n        + Some spelling fixes from Martynas Venckus\n\nWed Oct 17 20:10:44 2007\n        + Remove \"unused variable\" warnings\n\nFri Sep 14 14:29:20 2007\n        + Remove some warnings: unused variable `variable' `variable'\n          might be used uninitialized in this function\n\nTue Sep 11 15:47:17 2007\n        + Use strcspn to properly overwrite '\\n' in fgets returned buffer\n\nSun Sep 2 15:19:07 2007\n        + Use calloc() to avoid malloc(n * m) overflows; checked by djm@\n          canacar@ jsg@\n\nThu Jul 26 16:11:56 2007\n        + Add the correct file descriptor to rdfd when cycling through\n          the list of scripting windows. Appears to be a cut and paste\n          error. OK deraadt@\n\nThu May 31 19:19:00 2007\n        + Convert to new .Dd format\n\nWed May 30 04:41:33 2007\n        + Use a consistent text for STANDARDS\n        + Note which options are extensions to POSIX\n\nOpenBSD 4.0 -> OpenBSD 4.1: Mon May 14 12:32:29 2007\n        + Use sys/queue macros instead of accessing fields directly;\n          No binary change. ok krw@\n\nTue Mar 27 18:24:06 2007\n        + Catch OOB access for tag searches matching lines ending with a\n          '\\'; patch from Patrick Keshishian with a twist by me. ok thib@\n\nTue Mar 20 03:56:12 2007\n        + Remove some bogus *p tests from charles@ longeau@ ok deraadt@\n          millert@\n\nThu Dec 21 21:38:17 2006\n        + Fix !command piping by Alexander Bluhm in PR#5325. Tested by\n          quite a few on tech@\n\nOpenBSD 3.9 -> OpenBSD 4.0: Mon Dec 11 20:50:54 2006\n        + RFC 3834 support: Auto-Submitted: auto-generated on lots of\n          things; from Tamas TEVESZ; ok millert@\n\nFri Jul 7 12:05:10 2006\n        + Don't add space for line numbers twice\n\nSun Jun 18 20:41:24 2006\n        + Fix memleak; From Coverity Scan, CID#3135. From simonb@ NetBSD\n\nTue May 30 19:43:27 2006\n        + Avoid double fclose(), from coverity/NetBSD; ok otto@\n\nOpenBSD 3.8 -> OpenBSD 3.9: Sun May 21 19:21:30 2006\n        + Backport fix from nvi 1.81.5: do not go into loop if :set\n          number and :set leftright and the cursor moves to an empty line\n          PR#3154; ok beck@\n\nFri Apr 28 19:48:15 2006\n        + Ensure NULL termination after read(); ok ray@\n\nSat Apr 22 03:09:15 2006\n        + Removes unused variables and rename variables shadowing\n          external variables; no binary change\n\nMon Mar 20 01:00:36 2006\n        + If we're in visual mode reading a command, check the termination\n          value of v_tcmd() and bail if it's not TERM_OK as opposed to\n          in a more specific case. This is based on the NetBSD ^C fix\n          but after discussion with otto@. While it did not affect the\n          specific crash it is more correct\n\nWed Mar 15 23:43:27 2006\n        + Handle ^C correctly, morph it to escape key so the input\n          is correctly finished for a potential replay; if not, simply\n          bail out and notify that something wrong occurs. Callers will\n          cope. Consistent with what vim and Solaris vi do. Fixes a crash\n          described in NetBSD PR#11544, fixed by aymeric@ ok otto@ ray@\n\nSat Mar 11 07:04:53 2006\n        + Fixes the `optindx' might be used uninitialized in this function\n          warning, fixes a spacing nit in a macro, and cleans up a very\n          bad preprocessor abuse (``if LF_ISSET(OS_DEF)''!)\n        + Silence 2 warnings\n        + Silence another 39 warnings\n        + Silence uninitialized variable warning\n        + Make FLUSH macro more function-like, so there are no hidden\n          surprises; no binary change\n        + Initialize p to NULL to prevent gcc warning. Clarify a for\n          statement\n\nSat Mar 4 16:18:04 2006\n        + Fix \"the the\"\n\nFri Feb 17 19:12:41 2006\n        + Fix use after free. Problem hunted down by wilfried@; ok fgsch@\n          millert@\n\nOpenBSD 3.7 -> OpenBSD 3.8: Sun Jan 8 21:10:04 2006\n        + Remove unused NADD_USLONG macro, and remove unused sp argument\n          from NADD_SLONG; no functional change\n        + Fix one more uninitialized variable scenario; from Ray Lai\n        + Make sure we can exit from a loop in v_key_init() regardless\n          of the locale we're in; from Ray Lai\n        + Appease GCC 3 and the C gods by fixing a couple of undefined\n          statements; from Ray Lai\n        + Explicit braces around macro fields and logical operations,\n          gets rid of 148 warnings, no functional change\n\nOpenBSD 3.6 -> OpenBSD 3.7: Mon Oct 17 19:04:19 2005\n        + Use queue macros instead of directly accessing fields\n          ok pat@ \"put it in\" deraadt@\n\nThu Apr 21 15:39:31 2005\n        + Spelling typo in comment; from ray\n\nThu Apr 21 09:00:25 2005\n        + Avoid the \"tcsetattr: Interrupted system call\" fatal error\n          when resizing using a window manager that continuously sends\n          resize events; ok camield@ miod@\n\nThu Mar 10 18:03:45 2005\n        + -v description comes before -w\n        + Also a sentence tweak\n\nSun Jan 9 01:44:35 2005\n        + Tidy up FAST STARTUP\n        + Better example\n        + Better section reference\n\nSat Jan 8 05:22:25 2005\n        + Fix for FreeBSD PR#12801 from Sven Verdoolaege (nvi maintainer)\n          via FreeBSD (an infinite loop at certain case when options\n          \"comment\" and \"leftright\" are used)\n        + Move a line of code which was \"obviously\" misplaced. This\n          fixes a core dump when auto-completing filenames and at least\n          one of the file names is larger than the screen width\n        + When an error occurs in v_txt(), leave input mode\n          too. Otherwise, (among other things) db_get() thinks it can\n          reuse the TEXT buffers when it's not true, leading to a crash\n          because that TEXT buffer will be released just before it is\n          actually used to create a new one. From NetBSD, fixes NetBSD\n          PR#21797\n        + Move the license into the body of the man page; ok millert@\n\nFri Jan 7 15:04:02 2005\n        + Remove line in copyright declaration that conflicts with the\n          LICENSE file. OK bostic@sleepycat.com\n\nOpenBSD 3.5 -> OpenBSD 3.6: Mon Nov 29 21:51:08 2004\n        + Lowercase for consistency\n        + Spell precede correctly. 'looks fine' millert@ krw@ ok jmc@\n\nOpenBSD 3.4 -> OpenBSD 3.5: Mon Oct 4 21:45:59 2004\n        + Refer to re_format.7 rather than egrep.1 for a description\n          of EREs\n\nFri Apr 9 12:12:44 2004\n        + ex is not a screen editor\n\nFri Mar 19 08:14:52 2004\n        + Clarify -c\n\nFri Feb 20 20:05:05 2004\n        + Add `ruler' to the list of helpful options; suggested by millert@\n        + Add section on helpful ex options; suggested by and ok millert@\n\nFri Feb 20 13:15:51 2004\n        + Cleanup of 6.2: Options, set, and editor startup files\n\nMon Feb 9 21:16:06 2004\n        + Point people to ex tutorial\n        + Install edit USD; this has been updated/reworded to work as\n          an ex tutorial\n\nFri Jan 30 23:39:22 2004\n        + Use paper.txt, rather than some arbitrary target\n        + Some additional cleanup\n        + Point people to 13.ex, and remove some unnecessary text from\n          SEE ALSO\n\nFri Jan 30 23:14:25 2004\n        + Install exref; includes updates to sync with current behaviour\n          fixes, help, and ok millert@\n\nSun Jan 25 23:22:10 2004\n        + Install all the catalogs; as cvs forgot to check this file in\n          when those were added; millert@ ok\n\nSat Jan 24 12:32:55 2004\n        + Oops. no need for vitut comment\n        + Install vi tutorial docs; these have been updated to reflect\n          reality; help and ok millert@\n        + Document how file recovery works on OpenBSD; ok millert@\n        + Make vi reference card and vi tutorial easier to find\n        + Use -compact for FILES\n\nFri Jan 16 13:08:32 2004\n        + Point people to vi.ref now that it's installed (and get its\n          name right)\n        + Correct a path and Nm\n\nThu Jan 15 11:17:04 2004\n        + Return documented lines option to original (default) value\n          as pointed out by millert@, it's terminal dependent\n        + Update vi.ref to reflect reality; help and ok millert@\n\nWed Jan 14 23:40:01 2004\n        + Comment out reference to index.so when we are building index.so\n          itself. With changes by jmc@\n\nMon Jan 12 18:16:50 2004\n        + Install vi.ref in /usr/share/doc/usd (directories already\n          exist for it). OK jmc@\n        + There is no typewriter font in nroff so just use the roman font\n          instead. Fixes bizarre font problems formatting vi.ref via nroff\n          OK jmc@\n        + Use a 5n margin on the right & left sides so the text version\n          formats nicely. OK jmc@\n        + Pass groff the -U flag so that building the index works. OK jmc@\n\nWed Jan 7 12:46:48 2004\n        + Corrections to SET OPTIONS\n        + s/environmental/environment/\n        + Corrections to the EX COMMANDS section\n\nFri Jan 2 21:37:48 2004\n        + Some corrections/improvements to the VI COMMANDS section\n        + use standard section ENVIRONMENT, rather than ENVIRONMENT\n          VARIABLES; from deraadt@\n\nWed Dec 31 18:56:21 2003\n        + Fix -r description now that millert@ has fixed the code\n\nWed Dec 31 18:18:22 2003\n        + Both POSIX and the man page says that \"vi -r foo\" is run\n          where foo does not exist and has no recovery file that vi shall\n          present an error and edit foo as a new file. This change makes\n          the behavior match the documentation; previously it just spat\n          out an error and quit. Problem found by jmc@\n        + mdoc vi(1); also better document current behaviour and add\n          some missing commands\n\nOpenBSD 3.3 -> OpenBSD 3.4: Sat Nov 8 19:17:27 2003\n        + Typos from Jonathon Gray\n\nTue Sep 2 22:44:06 2003\n        + Switch to dynamic fd_set and poll; patch entirely from\n          millert@. ok deraadt@, dhartmei@\n\nFri Aug 1 16:47:25 2003\n        + When the -R option (read-only) is specified, there is no need\n          to print a warning that the file is read-only, it's obviously\n          what's expected... ok fgsch@ henning@\n\nMon Jul 21 16:21:12 2003\n        + Updated license from nvi-1.81.5 since we will be pulling in\n          patches from it\n        + Merge back some changes from skimo's tree, fixes endless\n          recursions in vs_paint() for some option combinations. ok millert@\n\nFri Jul 18 23:11:43 2003\n        + Add missing includes ok tedu@\n\nWed Jul 9 20:01:31 2003\n        + Fix double free; patch by Eric Jackson\n\nWed Jul 2 00:21:16 2003\n        + Bump randomness of mktemp to from 6 to 10 X's, as recommended\n          by mktemp(3)\n\nOpenBSD 3.2 -> OpenBSD 3.3: Tue Jun 3 02:56:05 2003\n        + Remove the advertising clause in the UCB license which Berkeley\n          rescinded 22 July 1999. Proofed by myself and Theo\n\nFri Apr 25 23:44:08 2003\n        + Oops; Fix comment\n\nThu Apr 17 02:22:56 2003\n        + Eliminate strcpy/sprintf. Reviewed by deraadt@ and millert@\n\nTue Apr 15 21:34:53 2003\n        + No, vi does not ignore SIGQUIT\n        + Change to use snprintf, of course\n\nMon Apr 7 21:13:52 2003\n        + Replace strcpy calls that got inlined by gcc\n          Hans-Joerg.Hoexer@yerbouti.franken.de\n\nMon Mar 10 03:53:32 2003\n        + Spelling fixes ok millert@\n\nSun Jan 12 18:15:16 2003\n        + Typos; jmc@prioris.mini.pw.edu.pl\n\nSun Dec 15 13:28:22 2002\n        + More writable spelling; torh@\n\nSat Nov 23 12:48:18 2002\n        + Typo: Edieroption->Editieroption ok mickey@\n\nOpenBSD 3.1 -> OpenBSD 3.2: Tue Nov 19 17:00:22 2002\n        + Update ru as it was 7bit stripped and add ua and pl; from\n          FreeBSD, pointed out by glebius@rinet.ru in PR#2552\n\nOpenBSD 3.0 -> OpenBSD 3.1: Wed Jun 12 06:07:15 2002\n        + A real pid_t cleanup\n\nTue Feb 19 19:39:35 2002\n        + We live in an ANSI C world; Remove lots of gratuitous #ifdef\n          __STDC__ cruft\n        + Oops, fix a left out ';'\n\nMon Feb 18 23:56:10 2002\n        + Format string fixes\n\nSun Feb 17 19:42:18 2002\n        + Manual cleanup of remaining userland __P use (excluding packages\n          maintained outside the tree)\n\nSat Feb 16 21:27:05 2002\n        + Part one of userland __P removal. Done with a simple regexp\n          with some minor hand editing to make comments line up correctly\n          Another pass is forthcoming that handles the cases that could\n          not be done automatically\n\nOpenBSD 2.9 -> OpenBSD 3.0: Thu Jan 31 11:10:39 2002\n        + Bug-fix picked up from NetBSD, and checked by pval: : date:\n          2001/10/20 10:04:50; author: aymeric : Fix a cut_line() caller\n          not using the right value for (former) ENTIRE_LINE, : by defining\n          the (newer) CUT_LINE_TO_EOL define in common/cut.h and using\n          it : where due. : Bug reported on current-users by Masanori\n          Kanaoka <kanaoka@ann.hi-ho.ne.jp> : diagnosed by Bang Jun-Young\n          <bjy@mogua.org>, : quick-fixed by Robert Elz <kre@munnari.OZ.AU>\n\nMon Nov 19 19:02:13 2001\n        + Kill more registers\n\nTue Nov 6 23:31:08 2001\n        + Change a stat() to lstat()\n\nMon Nov 5 22:43:49 2001\n        + Add more sanity checks of path data in the vi recovery file\n          potential problems pointed out by lumpy@the.whole.net\n\nWed Sep 19 02:43:19 2001\n        + Define ENTIRE_LINE to be -1 instead of 0 because we may want\n          to copy 0 characters, and use ENTIRE_LINE instead of hard coding\n          0 in a few places. Fixes a bug when dw on an empty line would\n          delete only the empty line, but copy the next line too\n        + Fix a bug where ^@ wouldn't behave as expected when reading\n          an ex command from vi. From NetBSD, ok millert@\n\nMon Sep 17 04:42:55 2001\n        + Make vi exit if it can't create a temp file. From NetBSD,\n          ok millert@\n\nSat Sep 15 15:41:19 2001\n        + Fix obvious omissions\n\nTue Sep 11 22:31:29 2001\n        + Locale ru_SU is obsolete, replace with ru_RU mickey@ ok\n\nSat Aug 18 20:35:13 2001\n        + Range check snprintf() return value\n        + Fix a pasto I made when adding snprintf() return val checks\n          ages ago\n\nFri Jul 20 18:48:03 2001\n        + Make this work, after espie changed other mk behaviours\n\nMon Jul 9 07:02:08 2001\n        + Correct type on last arg to execl(); nordin@cse.ogi.edu\n\nOpenBSD 2.8 -> OpenBSD 2.9: Mon Jun 18 21:39:25 2001\n        + When creating temp files, use fchmod() to set the perms to be\n          what we expect since the mode mkstemp() uses can be modified by\n          the umask. This fixes a problem where vi would spin trying to\n          create temp files, eating up inodes; reported by xyntrix@bitz.org\n\nMon May 28 22:44:32 2001\n        + Behave correctly when displaying an empty screen line when the\n          corresponding file line is not empty itself. Avoids core dumps\n          in the ':set list' mode (at least). NetBSD PR#4113; millert@ ok\n        + Print SYSERR instead of ERR when recdir does not exist. Makes\n          the message more useful for the user; from NetBSD, millert@ ok\n\nWed Jan 17 00:57:33 2001\n        + Don't dump core when a ``bad address'' error occurs and there\n          is neither a file nor a command underlying it\n        + Fix NetBSD PR#11543; the fix is from\n          Aymeric Vincent <aymeric@netbsd.org>\n\nOpenBSD 2.7 -> OpenBSD 2.8: Thu Jan 11 04:56:52 2001\n        + grep() returns a list of aliases to entries in the original\n          list so modifying them directly results in a munged line in the\n          resulting mail message that gets sent out. Similar to a patch\n          from cazz@wezl.org; closes PR#1617\n\nFri Nov 17 05:46:48 2000\n        + OpenBSD already has queue.h and this one gets in the way since\n          OpenBSD includes expect macros in sys/queue.h that the vi queue.h\n          didn't have\n        + Userland programs should not include sys/select.h\n\nSun Oct 22 00:16:27 2000\n        + Fix noprint/print/octal options; from NetBSD. Reviewed by\n          millert@\n\nThu Oct 12 09:38:19 2000\n        + When checking mmap return, check for MAP_FAILED, not -1\n\nFri Sep 15 07:13:43 2000\n        + Check return value for setenv(3) for failure, and deal\n          appropriately\n\nOpenBSD 2.6 -> OpenBSD 2.7: Wed Aug 2 04:10:44 2000\n        + $HOME paranoia: never use getenv(\"HOME\") w/o checking for NULL\n          and non-zero\n\nFri Apr 21 17:06:13 2000\n        + Remove the races so that this is safe to run anytime. We open\n          /var/tmp/vi.recover to get an fd and user O_NOFOLLOW to following\n          a symlink. Once we have a file handle we can use it to safely\n          chdir to the right place and form then on do operations relative\n          to \".\". Also restrict to root\n\nThu Apr 20 15:24:24 2000\n        + If recovery dir is not owned by root, chown it. If the mode\n          is not 01777, fix that too. This is safe because the script is\n          run before user processes start\n\nThu Mar 9 21:24:02 2000\n        + Pull in fnctl modeul so we are sure to get O_* for sysopen()\n\nSat Jan 22 22:57:35 2000\n        + Some minor doc updates that should have gotten committed ages ago\n\nOpenBSD 2.5 -> OpenBSD 2.6: Thu Jan 20 18:19:45 2000\n        + Use sysopen() when opening recover files. This is purely\n          paranoia since we check that the filename matches '^recover'\n          and hence the first character cannot play games with Perl's\n          magic open()\n\nFri Nov 26 22:49:08 1999\n        + Update README files etc. from nvi-1.79 so they have the\n          correct info\n        + Make port.h empty, since we there is nothing we lack\n        + Include <sys/param.h>, not <sys/types.h> in files that\n          use MIN/MAX macros\n        + Add Perl API support since we have libperl (off by default)\n\nMon Oct 11 20:07:19 1999\n        + Rewrite in Perl for safety and paranoia. It might have been\n          possible to play tricks with file names that include spaces\n\nSat Jul 10 10:09:48 1999\n        + Fix a SEGV after you HUP vi; dean@netbsd.org\n\nSat Jun 5 01:21:16 1999\n        + Remove trailing white space\n        + Remove arguments from .Os macros\n        + Remove arguments from .Nm macros, where appropriate\n        + Fix some more Dq/Sq/Ql insanity\n\nSat May 29 03:50:24 1999\n        + MLINKS, not MLINK\n\nOpenBSD 2.4 -> OpenBSD 2.5: Mon May 24 22:43:35 1999\n        + Set the close-on-exec flag for newly opened files\n\nWed Mar 10 21:25:25 1999\n        + Fix comma splices involving 'however'\n\nSat Mar 6 20:27:39 1999\n        + Back out changes that should not have escaped my local tree\n        + Add missing reference to infocmp\n\nWed Mar 3 01:22:33 1999\n        + Better grammar for err msg\n\nOpenBSD 2.3 -> OpenBSD 2.4: Mon Feb 8 01:26:56 1999\n        + Don't call curses routines beep() or flash() if the screen has\n          not been setup yet (as they will try to us SP which is NULL at\n          this point)\n\nFri Jul 24 00:43:40 1998\n        + Man pages Xrefs\n        + -D_USE_OLD_CURSE_ for -locurses and no more -ltermlib/-ltermcap\n\nOpenBSD 2.2 -> OpenBSD 2.3: Tue Jun 23 22:40:25 1998\n        + Fix snprintf return value usage\n\nOpenBSD 2.1 -> OpenBSD 2.2: Sat Apr 25 05:45:30 1998\n        + Fix relative tags in vi; Frank Mayhar <frank@exit.com>\n\nWed Sep 24 21:31:56 1997\n        + No, use new curses so that the build process works. Reevaluate\n          this later\n\nTue Sep 23 07:12:42 1997\n        + Make building with ocurses/termcap and curses/termlib\n          conditional on USE_OCURSES being defined, and define it for now\n          This switches nvi back to use BSD curses\n\nNvi 1.79 -> OpenBSD 2.1: Sun Aug 24 19:15:25 1997\n        + Check for >= UINT_MAX not > UINT_MAX\n        + 64bit fix wrt strtoul(3); fix sent to Keith\n\nNvi 1.78 -> Nvi 1.79: 10/23/1996\n        + Rename delete() to del(), for C++\n        + Add Spanish to the list of translations\n        + Update to Perl 5.003_06, and other Perl interpreter updates\n        + Update the set-edit-option interface for the scripting languages\n        + Rework ex command parsing to match historic practice for backslash\n          escaped <newline> characters inside of global commands\n        + Enhance the comment edit option to skip C++ comments\n        + Change installation to configure the recovery shell script to\n          match the system pathnames and to install it into the vi data\n          directory; move the recover script into the build directory, and\n          delete the recover directory\n        + Enhance LynxOS support\n\nNvi 1.76 -> Nvi 1.78: 10/1/1996\n        + Fix bugs when both the leftright scrolling and number edit options\n          were on\n        + Fix bug where splitting in the middle of the screen could repaint\n          incorrectly\n        + Fix first-NULL in input bug, where random garbage was inserted\n        + Correct search and mark-as-motion-command bug, it's a line mode\n          action if the search starts at or before the first non<blank>\n        + Fix bug autoindent bug, where ^D could shift too far in the line\n        + Fix core dump where ! command called from the .exrc file\n        + Add the -S command-line option, which initializes vi to have the\n          secure edit option preset\n\nNvi 1.75 -> Nvi 1.76: 9/15/1996\n        + Fix bug where ^V didn't keep input mapping from happening\n        + Fix a core dump bug in the R command\n        + Give up on licensing: no more shareware, adware, whatever\n        + Fix cursor positioning bug for C, S and c$ in an empty file\n\nNvi 1.74 -> Nvi 1.75: 8/22/1996\n        + Add French to the error message translations\n        + Move the UNLICENSED message to the end of the message line\n        + Fix bug where wide characters in a file name weren't calculated\n          correctly in the status message\n        + Fix bug where cl_rename was called directly, by the ex shell code\n        + Fix bug where splitting a screen resulting in a new screen at the\n          top of the display resulted in badly displayed status messages\n\nNvi 1.73 -> Nvi 1.74: 8/18/1996\n        + Fix bug where the status line wasn't redisplayed if the user ran\n          an ex command that trashed the screen\n        + Fix bug where the long version of the status line wasn't displayed\n          when switching screens\n        + Rework fast-path filename completion code to sort the entries, and\n          strip out . and .. by default\n        + Fix bug where ex went to the first line instead of the last one\n          when reading in a file\n\nNvi 1.72 -> Nvi 1.73: 8/12/1996\n        + Do filename completion and some file expansion internally for\n          speed\n        + Fix CSCOPE_DIRS environmental variable support\n        + Ex parser fix for global commands in script files\n        + Add the O_PATH option, so you can specify a directory search path\n          for files\n        + Make it possible to specify the database file to Cscope, allowing\n          multiple databases in a single directory\n        + Fix incremental search to overwrite erased characters so the user\n          can tell where they are on the colon-command line\n        + Fix incremental search to restart the search if the user enters an\n          un-escaped shell meta character\n\nNvi 1.71 -> Nvi 1.72: 7/12/1996\n        + Cscope fix: test for files newer than the database was reversed\n        + Display \"files to edit\" message for rewind, next and initial\n          screen\n        + Fix a bug in the R command where it could fail if the user\n          extended the file\n        + Fix a bug where text abbreviations could corrupt the line\n        + Fix a bug where the windowname edit option couldn't be set before\n          a file was loaded into the edit buffer\n        + Fix a bug where the system .exrc values weren't being overridden\n          by the user's $HOME .exrc values\n        + Fix a bug in the filename completion code, where garbage\n          characters could be added to the colon command line\n        + Fix bug where multiple edit sessions on a non-existent file could\n          all write the file without warning\n        + Fix bug where screen update was incorrect if a character triggered\n          both a wrapmargin and showmatch condition\n        + Fix bug in leftright scrolling where <CR> during text input didn't\n          return the cursor to the left margin\n        + Rev the Perl interpreter code, new version from Sven Verdoolaege,\n          based on Perl 5.003.01\n        + Fix bug in tags file pattern search introduced in 1.71\n\nNvi 1.70 -> Nvi 1.71: 7/1/1996\n        + Don't include <term.h> as neither HP-UX or Solaris can cope with\n          it\n        + Fix bug where ^M's in the original pattern were converted into new\n          lines in the file during substitution commands\n        + Make window resize events separate from interrupts - too many\n          users complained\n        + Fix bug in first-character-is-NULL text input semantic\n        + Rework search routines to take a length instead of a NULL\n          terminated string for a pattern. This fixes a couple of bugs in\n          searching, but probably introduces new ones\n        + Fix prompting the user after a write filter command, the way I did\n          it in 1.70 broke the display\n        + Don't switch to the alternate xterm screen when entering the ex\n          text input commands from vi mode\n        + Implement the Fg command, so can foreground a background screen\n          into a split screen\n        + Change the fg command to match screen names using the last\n          component of the filename the full filename fails\n\nNvi 1.69 -> Nvi 1.70: 6/28/1996\n        + Change the ex read command to support named pipes\n        + Copy the EXINIT/NEXINIT strings before executing their commands so\n          we don't step on the process environment\n        + Don't do \"line modification\" reports for intermediate commands\n          executed from the vi colon command line, it screws up filter\n          reads, causing nvi to prompt for the user to continue\n        + Add \"smd\" as an abbreviation for showmode: HP, ICL and SCO have it\n        + Change nvi to always prompt the user after a write filter command\n          to match historic practice\n        + Fix recovery information mailed to the user to reflect the\n          program's installed name\n        + Change configuration script to not cache option information, e.g.,\n          --disable-curses\n        + Fix a bug where the second character of the vi [[, ]] and ZZ\n          commands could start a command mapped sequence\n        + Fix 3 write bugs: partial writes (3,$write), were clearing the\n          modified flag, full writes using line numbers (1,$write) were\n          not, and append historically never cleared the modified flag, and\n          we didn't get that right\n        + Shorten the \"more files to edit\" message so it can gang on a\n          single line, lots of people have complained. Add the number of\n          files that are left to edit, it's historic practice\n        + Fix core dump where message catalogs collided with truncating the\n          write path. Add a new write message so the string \"appended\" is\n          taken from a message catalog\n        + Fix bug where an undo followed by '.' to repeat it wouldn't work\n          if no other repeatable commands had been entered\n        + Fix core dump when resolution of input lines' autoindent\n          characters invalidated cached display information\n        + Set the name of the X11 xterm icon/window to \"xterm\" when exiting,\n          if modified based on the windowname option\n        + Include <term.h> if it exists, fixes portability problems on IRIX\n          systems\n\nNvi 1.68 -> Nvi 1.69: 6/17/1996\n        + Add the windowname edit option and code to change the icon/window\n          name for xterm's\n        + Enhance the comment edit option to skip shell comments\n        + Add conditional prototypes to replacement C library functions\n        + Minor enhancements/reworking to Makefile.in, other build files\n        + Fix bug in vi text input ^D processing, could result in cursor\n          warp to the beginning of the line\n        + Fix leftright screen bug where the screen wasn't repainted when\n          being repainted from scratch\n        + Update the Swedish and Dutch catalogs\n        + Truncate paths in write commands if they don't fit on one line\n        + Fix alternate screen bug where the screen flashed and output lost\n          when switching to/from the X11 xterm alternate screen. Fix bug\n          where nvi switched into the alternate screen during filter-read\n          commands, which doesn't match historic practice\n        + Minor relative cursor positioning change, make cursor position\n          changes from ex real and permanent\n\nNvi 1.67 -> Nvi 1.68: 6/9/1996\n        + Fix core dump when tagging out of a modified file\n\nNvi 1.66 -> Nvi 1.67: 6/9/1996\n        + Convert the license to adware\n        + Leftright scrolling tweak, don't repaint the screen as often\n        + Change so that search warning/error messages don't appear during\n          an incremental search\n        + Cscope fix: test for files newer than the database was reversed\n        + Don't display ex `welcome message' if in ex batch mode\n        + Test for vsnprintf and snprintf separately, HP 10.10 has snprintf\n          but not vsnprintf\n        + Reverse lookup order between LC_MESSAGES and LANG\n        + Fix Tcl/Perl core dumps in common API code to get/set options\n        + Fix R command - it used a DB pinned page after discarding it\n        + Minor fixes in multiple edit buffer message handling code\n        + Fix yk command moving to shorter line core dump\n        + Rework message handling to try and gang more messages onto a\n          single line\n\nNvi 1.65 -> Nvi 1.66: 5/18/1996\n        + Convert vi man page to historic -man macro package, and install it\n        + Fix bug were !! on an empty line with a nonexistent command left\n          the cursor on the second character, not the first\n        + Fix bug where line redisplay was wrong when a <tab> replaced a\n          previous <tab> in the line\n        + Fix bug where D (d$) didn't reset the relative cursor position\n        + Fix bug where yG incorrectly reset the relative cursor position\n        + Fix bug where the window size couldn't be grown once it was shrunk\n        + Fix bug where the extended edit option caused tag searches to fail\n        + If multiple lines in the tags file with the same leading tag,\n          build a tags stack like the Cscope stack. This is the obvious\n          extension, and the way that Larry McVoy's ctags program works\n        + Send the appropriate TI/TE sequence in the curses screen whenever\n          entering ex/vi mode. This means that :shell now shows the correct\n          screen when using xterm alternate screens\n        + Rework the options display code to get five columns in an 80\n          column screen\n        + Interactive UNIX V3.0 port - mostly file name shortening, other\n          minor changes. Only preliminary, more work will be necessary\n        + Add debugging option to not read EXINIT/.exrc information\n        + Fix bug where re_compile printed an error message to the screen\n          when the user entered [ to an incremental search\n        + Turn off screen beeps when incremental search is failing\n        + Fix bug where the iclower option didn't trigger an RE\n          recompilation\n        + Fix bug where -t into an already locked file forced the user to\n          wait as if a startup command had failed\n        + LynxOS port - mostly adding <sys/types.h> even though\n          <sys/param.h> was already included\n        + Fix ex output bug, where it appeared as if an ex command was\n          skipped due to flags not being cleared in the vs_msg() routine\n        + Fix core dump when global command tried to switch screens\n\nNvi 1.64 -> Nvi 1.65: 5/13/1996\n        + Fix Cscope <blank>-matching pattern to use extended RE's, and bug\n          that kept Cscope from finding patterns containing <blank>s\n        + Fix core dumps in both leftright and folded screens when tabstops\n          edit option value was large, and tab characters occurred as the\n          last character in the logical screen\n        + Fix core dump where the second screen of a folded line wasn't\n          displayed correctly\n        + Fix incremental search to match the current location for strings\n          starting with \\< patterns\n        + Fix bug where margins were ignored during replay of text input\n        + Fix bug where motion components to shorter lines could lose\n          because the relative motion flags weren't ever set. This has been\n          broken forever, but the change almost certainly breaks something\n          else - I have no idea what\n        + Tags display: don't print the current entry separately, display\n          them all and add a trailing asterisk for the current one\n        + Change the Cscope add command to put the directory name through\n          standard file name expansion\n        + Fix Cscope use of buffers - search commands weren't NULL-\n          terminated\n\nNvi 1.63 -> Nvi 1.64: 5/8/1996\n        + Add installation target to the Makefile\n        + Add documentation on the new tags commands to the Vi Reference\n          Manual\n        + Make the sidescroll edit option work again\n        + Fix bug where messages output during startup by ex could be lost\n        + Change ex/vi commands errors into beeps, unless the verbose edit\n          option is set - there are too many macros that are expected to\n          eventually fail. This matches historic practice\n        + Truncate paths in initial vi screen if they won't fit on one line\n        + Make cursor position after filter write match historic practice\n        + Force the user to wait if there is output and the user is leaving\n          the screen for any reason - don't permit further ex commands\n        + Don't use a <newline> character to scroll the screen when exiting,\n          scroll in the vi screen before endwin() is called\n        + Fix bug where the column number could be incorrect because the old\n          screen wasn't updated after a screen split\n        + Fix ex print routine to correctly specify print flags\n        + Make -g/-O a separate make/configuration option\n        + Fix bug where ex/vi messages weren't being joined\n        + Fix bug where termcap strings were free'd twice\n        + Fix bug where TI/TE still weren't working - I didn't put in the\n          translation strings for BSD style curses\n        + Fix bug where I misspelled the iclower edit option as icloser\n\nNvi 1.62 -> Nvi 1.63: 4/29/1996\n        + Robustness and type/lint fixes for the Tcl interface code\n        + Fix core dump if TERM wasn't set or terminal type was unknown\n        + Fix bug where combining ex commands that did/did not require an\n          ex screen would overwrite the command with the want-to-continue\n          message\n        + Fix bug where the screen was never resolved if the user continued\n          entering ex commands using the : character, but then backspaced\n          over the prompt to quit or tried to edit their colon command-line\n          history\n        + Fix bug where cursor wasn't placed over the ^ placeholder\n          character when quoting using the literal-next character\n        + Fix bug where nvi under BSD style curses wasn't sending TI/TE\n          termcap strings when suspending the process\n        + Rename mic again, to iclower\n        + Fix bug where 'z' commands trailing / or ? commands weren't being\n          executed\n        + Change incremental search to leave the cursor at its last position\n          when searching for something that was never found\n        + Fix bug where search-with-confirmation from vi mode didn't\n          position the cursor correctly after displaying the confirm message\n        + Fix bug where the \"search wrapped\" message was dependent on the\n          verbose edit option, which doesn't match historic practice. Change\n          search messages to be in inverse video\n        + Fix bug where matched showmatch character wasn't being displayed\n          before the matching character was displayed\n        + Another cursor update bug required a change to vs_paint()\n        + Fix bug were initial line offset was wrong for the first split\n          screen (symptom is very strange column numbers and blank first\n          line)\n        + Create filename \"argument\" lists when creating new screens\n        + Fix bug where globals with associated commands that included both\n          buffer execution and other commands could fail to execute the\n          latter\n\nNvi 1.61 -> Nvi 1.62: 4/22/1996\n        + Rename the \"searchci\" edit option to be \"mic\"\n        + Fix memory corruption in global commands ending in searches\n        + Fix text resolution bug, corrected the cursor based on the\n          first line input, not the last\n        + Rework the readonly edit option to match historic practice\n        + Fix several minor incremental search bugs; make incremental\n          searches work in maps\n        + Fix long-line core dump, where an incorrect screen map could be\n          used\n\nNvi 1.60 -> Nvi 1.61: 4/12/1996\n        + The cursor now ends up on the FIRST character of the put text for\n          all versions of the vi put commands, regardless of the source\n          of the text. This matches System III/V behavior and POSIX 1003.2\n        + Fixed bug where showmatch messages were getting discarded\n        + Minor Perl integration fixes\n        + Integrate Cscope into the tags stack code - major change\n        + Fixed bug where ^T would drop core if returning to a temporary\n          file\n        + Changed vs_ routine to display ex output to replace tab characters\n          with spaces\n        + Fix autoindent code to not back up past beginning of line when ^T\n          inserted into the middle of a line, i.e. offset != 0\n        + Fix \"notimeout\" option, was being ignored, by a coding error\n        + Fix showmatch code to never flash on a match if keys are waiting\n        + Change the vi 'D' command to ignore any supplied count, matching\n          historic practice\n        + Fix viusage for D, S, C and Y (the aliased vi commands)\n        + Fix the Perl5 configuration bug in the configuration script\n        + Make file completion commands in empty lines work\n        + Fix where the change to let vi use the default ex command\n          structure broke the ex specification of the script or source file\n          name\n        + Fix to free saved RE structures when screens exit. This is a major\n          RE change, which fixed several bugs in the handling of saved/subst\n          RE's. It's likely to have added new bugs, however\n        + Add case-independent searching (the searchci edit option)\n        + Add incremental search (the searchincr edit option)\n        + Home the cursor when executing ex commands from vi\n\nNvi 1.59 -> Nvi 1.60: 3/29/1996\n        + Fix \":w >>\" core dump, make that command match historic practice\n        + Fix autoindent bug where the length of the line was incorrectly\n          calculated\n        + Fix cursor bug where cursor could end up at the wrong place if the\n          movement keys were entered quickly enough\n        + Change the read/write whirling indicator to appear only every 1/4\n          second, clean up the appearance\n        + Don't change the options real values until underlying functions\n          have returned OK - fix \"set tabstop=0\" core dump\n        + Fix resizing on Sun's: use SA_INTERRUPT to interrupt read calls\n        + Fix two forward mark command bugs: one where it wasn't setting the\n          \"favorite cursor\" position because of the refresh optimization,\n          and one where it didn't have VM_RCM_SET set in the command flags\n          for some reason\n        + Fix a bug were the 's' command on top of a <tab> didn't correctly\n          copy the buffer\n        + Make :exusage command work for commands having optional leading\n          capital letters, e.g. Next\n        + Previous changes broke the inital-matching-prefix code in the key\n          mapping part of v_event_get - fix it, and fix the infinite macro\n          interrupt code at the same time\n        + Add \"cedit\" edit option, so colon command-line editing is optional\n          Change filec/cedit so that you can set them to the same character,\n          and they do cedit if in column 1, and filec otherwise\n        + Fix \"source of non-existent file\" core dump\n        + Fix bug where functions keys specified in startup information were\n          never resolved/activated\n        + Fix v_txt bug where could infinitely loop if <escape> triggered an\n          abbreviation expansion\n        + Move version string into VERSION file, out of ex_version.c\n\nNvi 1.58 -> Nvi 1.59\n        + Configuration changes, several minor bug fixes, including a few\n          core dumps. No functional changes\n\nNvi 1.57 -> Nvi 1.58\n        + Fix the problem where colon command-line temporary files were\n          getting left in /tmp\n        + Fix the configuration scripts to quit immediately if the Perl\n          or Tk/Tcl libraries are specified but not found\n        + Several screen fixes - the changes in 1.57 weren't as safe as\n          I thought. More specifically, the refresh-only-if-waiting change\n          caused a lot of problems. In general, fixing them should provide\n          even more speedup, but I'm nervous\n        + Lots of changes in the configuration scripts, hopefully this is\n          just a first-round ordeal\n        + Several other minor bug fixes\n\nNvi 1.56 -> Nvi 1.57\n        + Add <esc> hook to colon commands, so you can edit colon commands\n        + Add Perl5 interpreter\n        + Change shell expansion code to fail if it doesn't read at least\n          one non-blank character from the shell. If the shell expansion\n          process fails, or if not at least one non-blank character, it\n          now displays an error message to the user\n        + Rework the screen display so that it matches the historic vi\n          screen refreshes\n        + Rework options processing: print/noprint are no longer cumulative,\n          provide more information to underlying edit options modules, move\n          O_MESG information into the screen specific code\n        + Make file completion character settable\n        + Rework terminal restart - you can now use \":set term\" to switch\n          terminal types. This cleaned up screen resizing considerably\n        + Character display fix, display \\177 as ^?, not in hex/octal\n        + Tag search bug fix, don't repeat search if successful\n        + Replace sys_siglist[] use with private sigmsg() routine\n        + Fix core dump if illegal screenId specified to Tcl routine\n        + Add get/set mark interface to Tcl Interpreter interface\n        + Fix core dump if file expansion code stressed (re: filec edit\n          option)\n        + Fix bug where filter commands in empty files couldn't find line 0\n        + Switch to GNU autoconf 2.7 for configuration, delete nvi/PORT\n        + Many random portability fixes\n\nNvi 1.55 -> Nvi 1.56: 11/26/1995\n        + Bug fix release - generally available beta release\n\nNvi 1.54 -> Nvi 1.55: 11/18/1995\n        + Bug fix release\n        + Integrate Tcl interpreter\n\nNvi 1.53 -> Nvi 1.54: 11/11/1995\n        + Bug fix release. A major change in reworking the ex commands, when\n          called from the colon command line, to match historic practice,\n          and permit them to be entered repeatedly after ex has trashed\n          the screen\n        + Use restart-able endwin() from System V curses to implement\n          screen suspend\n\nNvi 1.52 -> Nvi 1.53: 10/29/1995\n        + Switch to using vendor's curses library for all ports\n        + Back out the event driven version, leaving screen separation\n        + User configuration of <escape> timeout (the escapetime edit\n          option)\n        + Add Tcl/Tk screen support\n        + Add file name completion (the filec edit option)\n        + Disallow access to outside applications (the secure edit option)\n\nNvi 1.51 -> Nvi 1.52: 7/26/1995\n        + Minor cleanups\n        + Snapshot for SMI\n\nNvi 1.50 -> Nvi 1.51: 7/5/1995\n        + Lots and lots of changes for event driven model, largely in moving\n          the boundary between the screen code and the editor up and down\n          Private release for Rob Zimmermann @ Tartan and Bill Shannon @ SMI\n\nNvi 1.49 -> Nvi 1.50: Fri Jun 9 13:56:17 1995\n        + Minor bug fixes for stability\n        + Convert to an event driven model, with the usual Nachos Supreme\n          layering that results. This is a completely new version, nothing\n          done previously matters any more\n\nNvi 1.48 -> Nvi 1.49: Wed Mar 8 10:42:17 1995\n        + Changes in 1.46 broke ^A processing\n        + Add :previous to split screen commands\n        + Lots o' random bug fixes - passes purify testing again\n\nNvi 1.47 -> Nvi 1.48: Thu Feb 9 18:13:29 1995\n        + Random bug fixes for 1.47\n        + Move the FREF (file structure) list out of the screen and into\n          the global area\n        + Change semantics to :E to more closely match :e - \":E\" joins\n          the current file, so \":E /tmp\" is now the command to match the\n          historic \":split\"\n\nNvi 1.46 -> Nvi 1.47: Wed Feb 8 19:43:41 1995\n        + All ex commands (including visual and excluding global and v)\n          are now supported inside ex global commands\n        + Rework the append/change/insert commands to match historic\n          practice for text appended to the ex command line, and inside\n          of ex global commands\n        + Restructure to make single-line screens work\n        + Restructure to create curses independent screen routines\n        + Restructure to permit Edit, Next, and Tag routines to create new\n          screens on the fly\n        + Change hexadecimal output to be \\x## instead of 0x##\n        + Change ex commands run from vi to stay in vi mode for as long as\n          possible, i.e. until ex modifies the screen outside of the editor\n\nNvi 1.45 -> Nvi 1.46: Tue Jan 24 10:22:27 1995\n        + Restructure to build as a library\n\nNvi 1.44 -> Nvi 1.45: Thu Jan 12 21:33:06 1995\n        + Fix relative cursor motion to handle folded lines\n        + Recompile the search pattern if applicable edit options change\n        + Change +/-c command ordering to match historic practice\n        + Rework autoindent code to always resolve preceding <blank>\n          characters when a ^T or ^D are entered\n        + Add the print/noprint edit options, so can now specify if\n          a character is printable\n        + Change ex to run in canonical mode\n        + Fix ex text input to support the number edit option\n        + Vi text input fix for the R command to correctly restore\n          characters entered and then backspaced over\n        + Several vi increment command fixes\n\nNvi 1.43 -> Nvi 1.44\n        + Bug fix, vi was printing the last line number on the status line\n          at startup. Change to execute commands at first line set,\n          i.e. \"vi -t tag -c cmd\" executes cmd at the tag line, not EOF\n\nNvi 1.42 -> Nvi 1.43: Sat Dec 3 13:11:32 1994\n        + Marks, SunOS signed comparison fix for 1.42\n\nNvi 1.41 -> Nvi 1.42: Fri Dec 2 20:08:16 1994\n        + Make autowrite require the file not be read-only\n        + Make the ex insert command work in empty files\n        + Tab expansion is no longer limited to values < 20 (which matches\n          historical practice)\n        + Simplify (and fix limit detection for) the # command. It's no\n          longer possible to use the # command itself to repeat or modify\n          a previous # command, '.' is the only possibility\n        + Lots more reworking of the ex addresses, putting ? and / into\n          the ex addressing code broke the world\n        + Make the Put, Preserve and Print commands work (don't ask)\n        + Split stdout/stderr from shell expansions; stdout is expansion\n          text, stderr is entered on the message queue\n\nNvi 1.40 -> Nvi 1.41: Fri Nov 18 16:13:52 1994\n        + Addition of a port for AUX 3.1\n        + Addition of a message catalog for Russian\n        + Make vi ? and / commands be true ex addresses (historic practice)\n        + Display the date first in vi -r recovery list\n\nNvi 1.39 -> Nvi 1.40: Mon Nov 14 10:46:56 1994\n        + Two bug fixes for 1.39; -r option and v_change core dump\n\nNvi 1.38 -> Nvi 1.39: Sun Nov 13 18:04:08 1994\n        + Ex substitution with confirmation now matches historic practice\n          (except that it still runs in raw mode, not cooked)\n        + Nvi now clears the screen before painting, if repainting the\n          entire screen\n        + Fix final cursor position for put command entering text in a\n          single line\n        + Change to break error message lines on the last <blank> in the\n          line\n        + Always center the current line when returning to a previously\n          edited file or moving to a tag line that's not visible on the\n          screen\n        + Change write of the current file using an explicit name or % to\n          match the semantics of :w<CR>, not :w file<CR>\n        + Add command aliases to vi, and remap 6 historic commands to their\n          historic counterparts: D->d$, Y->y_, S->c_, C->c$, A->$a, I->^i\n        + Match option display to historic practice; if boolean or numeric\n          options changed to default values, not displayed by default\n          Nvi treats string options the same way, vi always displayed any\n          string option that was changed\n        + Added lock edit option, if not set, no file locking is done\n        + Rework ex to permit any ex command in the EXINIT variable or\n          exrc startup files. This fixes the bug were `vi +100 file'\n          painted the screen and then moved to line 100 and repainted\n          (Yanked to SCCS ID 9.1.)\n        + Bug fix: could report file modified more recently than it was\n          written, incorrectly\n        + Search fix: historically, motions with deltas were not corrected\n          to the previous/next line based on the starting/stopping column\n        + Addressing fixes: make trailing non-existent addresses work,\n          change % to be text substitution, not a unique address (to follow\n          future POSIX)\n\nNvi 1.37 -> Nvi 1.38: Mon Oct 24 12:51:58 1994\n        + Scrolling fix; ^B can move to nonexistent lines\n        + Fix to vi mapped commands; <escape> characters while already in\n          command mode did not historically cause the mapped characters to\n          be flushed\n        + Add the backup edit option, automatically version edit files\n        + Make it possible to edit files that db can't read, i.e. edit a\n          temporary file, with the correct file name\n        + Only anchor the last line of the file to the bottom line of the\n          screen if there's half or less of a screen between the target\n          line and the end of the file\n        + Fix wrapmargin text allocation bug\n        + Fix ex put command to work in any empty file\n        + Fix global command to handle move's to line 0 correctly\n        + Regularize the yank cursor motions, several bug fixes for historic\n          practice\n        + Fix N and n, when used as a motion command for the ! command,\n          repeat the last bang command instead of prompting for a new\n          one\n        + Timeout maps beginning with <escape> quickly, instead of based\n          on the keytime option\n        + Bug fix for wraplen option, wasn't triggered for input commands\n\nNvi 1.36 -> Nvi 1.37: Sun Oct 9 19:02:53 1994\n        + Change PORT directories to install patches before distribution\n        + Fix ^A to set search direction and pattern for consistency\n        + Fold the showdirty option into the showmode option\n        + Ex addressing fix: change search offset and line arguments\n          (e.g. the copy command) to be ex addressing offsets, matching\n          historic practice\n        + Ex addressing fix: support ^ as an offset/flag equivalent to -\n        + Ex addressing fix: historically, any missing address defaulted to\n          dot, e.g. \"4,,,\" was the same as \".,.\"\n        + Ex addressing fix: historically, <blank> separated numbers were\n          additive, e.g. \"3 5p\" displayed line 8\n        + Ex addressing fix: make ';' as a range delimiter match historic\n          practice\n        + Change nvi to exit immediately if stdout isn't a terminal\n        + Change alternate file name behavior to match historic practice,\n          make the :write command set the current file name\n        + Text input fix; input keys from a map, with an associated count,\n          weren't historically affected by the wrapmargin value\n        + Add wraplen option, same as wrapmargin, but from the left-hand\n          column, not the right\n        + Make ex address .<number> be equivalent to .+<number>, i.e. the\n          '+' is understood; matches historic practice, and it's widely\n          documented for ed(1)\n        + Input mode ^V^J historically mapped into a single ^J\n        + Minor catalog changes, fixes; don't use 's' to pluralize words\n\nNvi 1.35 -> Nvi 1.36: Thu Sep 8 08:40:25 1994\n        + Don't overwrite user's maps with standard (termcap) mappings\n        + Make \\ escape kill and erase characters in vi text input mode\n        + Fix ^D autoindent bug by resolving leading <blank>s at ^D\n        + Rework abbreviation tests (again!) to match historic practice\n        + Change ^D/^U default scrolling value to be based on window option\n          value, not screen lines, correct scrolling option value, both to\n          match historic practice. NOTE: System V does this differently!\n\nNvi 1.34 -> Nvi 1.35: Wed Aug 31 19:20:15 1994\n        + Add the historic -l option\n        + Message catalogs\n        + Display global messages at each flush, just in case some are there\n        + Fix global substitute code, `\\\\' wasn't handled correctly\n        + Fix abbreviation code to use <blank>s as the preceding character\n        + Fix ruler to display logical column, not physical column\n        + Block signals when user issues :preserve command, so no race\n          caused by SIGHUP/SIGTERM\n\nNvi 1.33 -> Nvi 1.34: Wed Aug 17 14:37:32 1994\n        + Back out sccsid string fix, it won't work on SunOS 4.1\n\nNvi 1.32 -> Nvi 1.33: Wed Aug 17 09:31:41 1994\n        + Get back 5K of data space for the sccsid strings\n        + Fix bug where cG fix in version 1.31 broke cw cursor positioning\n          when the change command extended the line\n        + Fix core dump in map/seq code if character larger than 7 bits\n        + Block signals when manipulating the SCR chains\n        + Fix memory allocation for machines with multiple pointer sizes\n\nNvi 1.31 -> Nvi 1.32: Mon Aug 15 14:27:49 1994\n        + Turn off recno mmap call for Solaris 2.4/SunOS 5.4\n\nNvi 1.30 -> Nvi 1.31: Sun Aug 14 13:13:35 1994\n        + Fix bug were cG on the last line of a file wasn't done in line\n          mode, and where the cursor wasn't positioned correctly after\n          exiting text insert mode\n        + Add termcap workaround to make function keys greater than 9 work\n          correctly (or fail if old-style termcap support)\n        + Change ex/vi to not flush mapped keys on error - this is historic\n          practice, and people depended on it\n        + Rework vi parser so that no command including a mapped key ever\n          becomes the '.' command, matching historic practice\n        + Make <escape> cancellation in the vi parser match POSIX 1003.2\n        + Fix curses bug where standout string was written for each standout\n          character, and where standout mode was never exited explicitly\n          Fix bugs in curses SF/sf and SR/sr scrolling, as seen on Sun and\n          x86 consoles\n        + The v/global commands execute the print command by default\n        + The number option historically applies to ex as well as vi\n\nNvi 1.29 -> Nvi 1.30: Mon Aug 8 10:30:42 1994\n        + Make first read into a temporary set the file's name\n        + Permit any key to continue scrolling or ex commands - this\n          allows stacked colon commands, and matches historic practice\n        + Don't output normal ! command commentary in ex silent mode\n        + Allow +/- flags after substitute commands, make line (flag)\n          offsets from vi mode match historic practice\n        + Return <eof> to ex immediately, even if preceded by spaces. Rework\n          ex parser to do erase the prompt instead of depending on the print\n          routines to do it. Minor fixes to the ex parser for display of\n          default and scrolling commands. MORE EX PARSER CHANGES\n\nNvi 1.28 -> Nvi 1.29: Fri Aug 5 10:18:07 1994\n        + Make the abbreviated ex delete command work (:dele---###lll for\n          example, is historically legal\n        + When autoprint fires, multiple flags may be set, use ex_print\n          directly instead of the stub routines\n        + Change v/global commands to turn off autoprint while running\n        + Minor changes to make the ! command display match historic output\n        + Rework the ex parser to permit multiple command separators without\n          commands - MAJOR CHANGE, likely to introduce all sorts of new bugs\n        + Fix cd command to expand argument in the context of each element\n          of the cdpath option, make relative paths always relative to the\n          current directory\n        + Rework write/quit cases for temporary files, so that user's don't\n          discard them accidentally\n        + Check for window size changes when continuing after a suspend\n        + Fix memory problem in svi_screen, used free'd memory\n        + Change the ex change, insert, append commands to match historic\n          cursor positions if no data entered by the user\n        + Change ex format flags (#, l, p) to affect future commands, not\n          just the current one, to match historic practice\n        + Make the user's EOF character an additional scroll character in ex\n        + Fix ex ^D scrolling to be the value of the scroll option, not half\n          the screen\n        + Fix buffer execution to match historic practice - bugs where the\n          '*' command didn't work, and @<carriage-return> didn't work\n        + Fix doubled reporting of deleted lines in filters\n        + Rework the % ` / ? ( ) N n { and ^A commands to always cut into\n          numeric buffers regardless of the location or length of the cut\n          This matches historic practice\n        + Fix the { command to check the current line if the cursor doesn't\n          start on the first character of the line\n        + Do '!' expansion in the ex read command arguments, it's historic\n          practice. In addition, it sets the last '!' command\n\nNvi 1.27 -> Nvi 1.28: Wed Jul 27 21:29:18 1994\n        + Add support for scrolling using the CS and SF/sf/SR/sr termcap\n          strings to the 4BSD curses\n        + Rework of getkey() introduced a bug where command interrupt put\n          nvi into an infinite loop\n        + Piping through a filter historically cut the replaced lines into\n          the default buffer, although not the numeric ones\n        + Read of a filter and !! historically moved to the first nonblank\n          of the resulting cursor line (most of the time)\n        + Rework cursor motion flags, to support '!' as a motion command\n\nNvi 1.26 -> Nvi 1.27: Tue Jul 26 10:27:58 1994\n        + Add the meta option, to specify characters the shell will expand\n        + Fix the read command to match historic practice, the white space\n          and bang characters weren't getting parsed correctly\n        + Change SIGALRM handler to save and restore errno\n        + Change SunOS include/compat.h to include <vfork.h> so that the\n          ex/filter.c code works again\n        + Don't put lines deleted by the ex delete command into the numeric\n          buffers, matching historic practice\n        + Fix; if appending to a buffer, default buffer historically only\n          references the appended text, not the resulting text\n        + Support multiple, semi-colon separated search strings, and 'z'\n          commands after search strings\n        + Make previous context mark setting match historic practice (see\n          docs/internals/context)\n        + Fix the set command to permit whitespace between the option and\n          the question mark, fix question marks in general\n        + Fix bug where ex error messages could be accidentally preceded\n          by a single space\n        + Fix bug where curses reorganization could lose screen specific\n          mappings as soon as any screen exited\n        + Fix bug in paragraph code where invalid macros could be matched\n        + Make paragraph motions stop at formfeed (^L) characters\n        + Change 'c' to match historic practice, it cut text into numeric\n          buffers\n\nNvi 1.25 -> Nvi 1.26: Tue Jul 19 17:46:24 1994\n        + Ignore SIGWINCH if the screen size is unchanged; SunOS systems\n          deliver one when a screen is uncovered\n        + Fix: don't permit a command with a motion component to wrap due\n          to wrapscan and return to the original cursor position\n        + Fix: ^E wasn't beeping when reaching the bottom of the file\n        + Fix bg/fg bug where tmp file exiting caused a NULL dereference\n        + Rework file locking code to use fcntl(2) explicitly\n        + Fix bug in section code where invalid macros could be matched\n        + Fix bug where line number reset by vi's Q command\n        + Add explicit character mode designation to character mode buffers\n        + Add <sys/ioctl.h> include to sex/sex_window.c, needed by NET/2\n          vintage systems\n        + Change to always flush a character during suspend, 4BSD curses\n          has the optimization where it doesn't flush after a standend()\n        + Fix bug on OSF1 where <curses.h> changes the values of VERASE,\n          VKILL and VWERASE to incorrect ones\n        + Fix bug where optarg used incorrectly in main.c\n        + Block all signals when acting on a signal delivery\n        + Fix recovery bug where RCV_EMAIL could fire even if there wasn't\n          a backing file; format recovery message\n\nNvi 1.24 -> Nvi 1.25: Sun Jul 17 14:33:38 1994\n        + Stop allowing keyboard suspends (^Z) in insert mode, it's hard\n          to get autowrite correct, and it's not historic practice\n        + Fix z^, z+ to match historic practice\n        + Bug in message handling, \"vi +35 non-existent_file\" lost the\n          status message because the \"+35\" pushed onto the stack erased\n          it. For now, change so that messages aren't displayed if there\n          are keys waiting - may need to add a \"don't-erase\" bit to the\n          character in the stack instead\n        + Bug in svi_msgflush(), where error messages could come out in\n          normal video\n\nNvi 1.23 -> Nvi 1.24: Sat Jul 16 18:30:18 1994\n        + Fix core dump in exf.c, where editing a non-existent file and\n          exiting could cause already free'd memory to be free'd\n        + Clean up numerous memory errors, courtesy of Purify\n        + Change process wait code to fail if wait fails, and not attempt\n          to interpret the wait return information\n        + Open recovery and DB files for writing as well as reading, System\n          V (fcntl) won't let you acquire LOCK_EX locks otherwise\n        + Fix substitute bug where could malloc 0 bytes (AIX breaks)\n        + Permit the mapping of <carriage-return>, it's historic practice\n        + Historic vi didn't eat <blank> characters before the force\n          flag, match historic practice\n        + Bug in ex argument parsing, corrected for literal characters\n          twice\n        + Delete screen specific maps when the screen closes\n        + Move to the first non-<blank> in the line on startup; historic\n          practice\n        + Change the ex visual command to move directly to a line if no\n          trailing 'z' command\n        + Fix \"[[\" and \"]]\" to match historic practice (yet again...)\n        + Fix \"yb\" and \"y{\" commands to update the cursor correctly\n        + Change \"~<motion>\" to match the yank cursor movement semantics\n          exactly\n        + Move all of the curses related code into sex/svi - major rework,\n          but should help in future ports\n        + Fix bug in split code caused by new file naming code, where would\n          drop core when a split screen exited\n        + Change svi_ex_write to do character display translation, so that\n          messages with file names in them are displayed correctly\n        + Display the file name on split screens instead of a divider line\n        + Fix move bug, wasn't copying lines before putting them\n        + Fix bug were :n dropped core if no arguments supplied\n        + Don't quote characters in executed buffer: \"ifoo<esc>\" should\n          leave insert mode after the buffer is executed\n        + Tagpop and tagpush should set the absolute mark in case only\n          moving within a file\n        + Skip leading whitespace characters before tags and cursor word\n          searches\n        + Fix bug in ex_global where re_conv() was allocating the temporary\n          buffer and not freeing it\n\nNvi 1.22 -> Nvi 1.23: Wed Jun 29 19:22:33 1994\n        + New <sys/cdefs.h> required \"inline\" to change to \"__inline\"\n        + Fix System V curses code for new ^Z support\n        + Fix off-by-one in the move code, avoid \":1,$mo$\" with only one\n          line in the buffer\n        + Line orientation of motion commands was remembered too long,\n          i.e. '.' command could be incorrectly marked as line oriented\n        + Move file modification time into EXF, so it's shared across\n          split screens\n        + Put the prev[ious] command back in, people complained\n        + Random fixes to next/prev semantics changed in 1.22\n        + Historically vi doesn't only move to the last address if there's\n          ANYTHING after the addresses, e.g. \":3\" moves to line 3, \":3|\"\n          prints line 3\n\nNvi 1.21 -> Nvi 1.22: Mon Jun 27 11:01:41 1994\n        + Make the line between split screens inverse video again\n        + Delete the prev[ious] command, it's not useful enough to keep\n        + Rework :args/file name handling from scratch - MAJOR CHANGE,\n          likely to introduce all sorts of new bugs\n        + Fix RE bug where no sub-expressions in the pattern but there were\n          sub-expressions referenced in the replacement, e.g. \"s/XXX/\\1/g\"\n        + Change recovery to not leave unmodified files around after a\n          crash, by using the owner 'x' bit on unmodified backup files\n          MAJOR CHANGE, the system recovery script has to change!\n        + Change -r option to delete recovery.* files that reference non-\n          existent vi.* files\n        + Rework recovery locking so that fcntl(2) locking will work\n        + Fix append (upper-case) buffers, broken by cut fixes\n        + Fix | to not set the absolute motion mark\n        + Read $HOME/.exrc file on startup if the effective user ID is\n          root. This makes running vi while su(1)'d work correctly\n        + Use the full pathname of the file as the recovery name, not\n          just the last component. Matches historic practice\n        + Keep marks in empty files from being destroyed\n        + Block all caught signals before calling the DB routines\n        + Make the line change report match historic practice (yanked\n          lines were different than everything else)\n        + Add section on multiple screens to the reference manual\n        + Display all messages at once, combine onto a single line if\n          possible. Delete the trailing period from all messages\n\nNvi: 1.20 -> Nvi 1.21: Thu May 19 12:21:58 1994\n        + Delete the -l flag from the recover mail\n        + Send the user email if ex command :preserve executed, this matches\n          historic practice. Lots of changes to the preserve and recovery\n          code, change preserve to snapshot files (again, historic practice)\n        + Make buffers match historic practice: \"add logically stores text\n          into buffer a, buffer 1, and the unnamed buffer\n        + Print <tab> characters as ^I on the colon command line if the\n          list option set\n        + Adjust ^F and ^B scroll values in the presence of split screens\n          and small windows\n        + Break msg* routines out from util.c into msg.c, start thinking\n          about message catalogs\n        + Add tildeop set option, based on STEVIE's option of the same name\n          Changes the ~ command into \"[count] ~ motion\", i.e. ~ takes a\n          trailing motion\n        + Chose NOT to match historic practice on cursor positioning after\n          consecutive undo commands on a single line; see vi/v_undo.c for\n          the comment\n        + Add a one line cache so that multiple changes to the same line\n          are only counted once (e.g. \"dl35p\" changes one line, not 35)\n        + Rework signals some more. Block file sync signals in vi routines\n          that interface to DB, so can sync the files at interrupt time\n          Write up all of the signal handling arguments, see signal.c\n\nNvi: 1.19 -> Nvi 1.20: Thu May 5 19:24:57 1994\n        + Return ^Z to synchronous handling. See the discussion in signal.c\n          and svi_screen.c:svi_curses_init()\n        + Fix bug where line change report was wrong in util.c:msg_rpt()\n\nNvi: 1.18 -> Nvi 1.19: Thu May 5 12:59:51 1994\n        + Block DSUSP so that ^Y isn't delivered at SIGTSTP\n        + Fix bug - put into an empty file leaves the cursor at 1,0,\n          not the first nonblank\n        + Fix bug were number of lines reported for the 'P' command was\n          off-by-one\n        + Fix bug were 0^D wasn't being handled correctly\n        + Delete remnants of ^Z as a raw character\n        + Fix bug where if a map was an entire colon command, it may never\n          have been displayed\n        + Final cursor position fixes for the vi T and t commands\n        + The ex :next command took an optional ex command as it's first\n          argument similar to the :edit commands to match historic practice\n\nNvi 1.17 -> Nvi 1.18: Wed May 4 13:57:10 1994\n        + Rework curses information in the PORT/Makefile's\n        + Minor fixes to ^Z asynchronous code\n\nNvi 1.16 -> Nvi 1.17: Wed May 4 11:15:56 1994\n        + Make ex comment handling match historic practice\n        + Make ^Z work asynchronously, we can no longer use the SIGTSTP\n          handler in the curses library\n\nNvi 1.15 -> Nvi 1.16: Mon May 2 19:42:07 1994\n        + Make the 'p' and 'P' commands support counts, i.e. \"Y10p\" works\n        + Make characters that map to themselves as the first part of the\n          mapping work, it's historic practice\n        + Fix bug where \"s/./\\& /\" discarded the space in the replacement\n          string\n        + Add support for up/down cursor arrows in text input mode, rework\n          left/right support to match industry practice\n        + Fix bug were enough character remapping could corrupt memory\n        + Delete O_REMAPMAX in favor of setting interrupts after N mapped\n          characters without a read, delete the map counter per character\n          MAJOR CHANGE. All of the interrupt signal handling has been\n          reworked so that interrupts are always turned on instead of\n          being turned on periodically, when an interruptible operation is\n          pending\n        + Fix bug where vi wait() was interrupted by the recovery alarm\n        + Make +cmd's and initial commands execute with the current line\n          set to the last line of the file. This is historic practice\n        + Change \"lock failed\" error message to a file status message\n          It always fails over NFS, and making all NFS files read-only\n          isn't going to fly\n        + Use the historic line number format, but check for overflow\n        + Fix bug where vi command parser ignored buffers specified as\n          part of the motion command\n        + Make [@*]buffer commands on character mode buffers match historic\n          practice\n        + Fix bug where the cmap/chf entries of the tty structure weren't\n          being cleared when new characters were read\n        + Fix bug where the default command motion flags were being set\n          when the command was a motion component\n        + Fix wrapmargin bug; if appending characters, and wrapmargin breaks\n          the line, an additional space is eaten\n\nNvi 1.14 -> Nvi 1.15: Fri Apr 29 07:44:57 1994\n        + Make the ex delete command work in any empty file\n        + Fix bug where 't' command placed the cursor on the character\n          instead of to its left\n        + ^D and ^U didn't set the scroll option value historically\n          Note, this change means that any user set value (e.g. 15^D)\n          will be lost when splitting the screen, since the split code\n          now resets the scroll value regardless\n        + Fix the ( command to set the absolute movement mark\n        + Only use TIOCGWINSZ for window information if SIGWINCH signal\n          caught\n        + Delete the -l flag, and make -r work for multiple arguments\n        + Add the ex \"recover[!] file\" command\n        + Switch into ex terminal mode and use the sex routines when\n          append/change/insert called from vi mode\n        + Make ^F and ^B match historic practice. This required a fairly\n          extensive rework of the svi scrolling code\n        + Cursor positioning in H, M, L, G (first non-blank for 1G) wasn't\n          being done correctly. Delete the SETLFNB flag. H, M, and L stay\n          logical movements (SETNNB) and G always moves to the first non-\n          blank\n        + System V uses \"lines\" and \"cols\", not \"li\" and \"co\", change as\n          necessary. Check termcap function returns for errors\n        + Fix `<character> command to do start/end of line correction,\n          and to set line mode if starting and stopping at column 0\n        + Fix bug in delete code where dropped core if deleted in character\n          mode to an empty line. (Rework the delete code for efficiency.)\n        + Give up on SunOS 4.1.X, and use \"cc\" instead of /usr/5bin/cc\n        + Protect ex_getline routine from interrupted system calls (if\n          possible, set SA_RESTART on SIGALRM, too)\n        + Fix leftright scrolling bug, when moving to a shorter line\n        + Do validity checking on the copy, move, t command target line\n          numbers\n        + Change for System V % pattern broke trailing flags for empty\n          replacement strings\n        + Fix bug when RCM flags retained in the saved dot structure\n        + Make the ex '=' command work for empty files\n        + Fix bug where special_key array was being free'd (it's no longer\n          allocated)\n        + Matches cut in line mode only if the starting cursor is at or\n          before the first non-blank in its line, and the ending cursor is\n          at or after the last non-blank in its line\n        + Add the :wn command, so you can write a file and switch to a new\n          file in one command\n        + Allow only a single key as an argument to :viusage\n        + New movement code broke filter/paragraph operations in empty\n          files (\"!}date\" in an empty file was dropping core)\n\nNvi 1.12 -> Nvi 1.14: Mon Apr 18 11:05:10 1994\n        + Fix FILE structure leakage in the ex filter code\n        + Rework suspend code for System V curses. Nvi has to do the\n          the work, there's no way to get curses to do it right\n        + Revert SunOS 4.1.X ports to the distributed curses. There's\n          a bug in Sun's implementation that we can't live with\n        + Quit immediately if row/column values are unreasonable\n        + Fix the function keys to match vi historic behavior\n        + Replace the echo/awk magic in the Makefile's with awk scripts\n        + Version for 4.4BSD\n\nNvi 1.11 -> Nvi 1.12: Thu Apr 14 11:10:19 1994\n        + Fix bug where only the first vi key was checked for validity\n        + Make 'R' continue to overwrite after a <carriage-return>\n        + Only display the \"no recovery\" message once\n        + Rework line backup code to restore the line to its previous\n          condition\n        + Don't permit :q in a .exrc file or EXINIT variable\n        + Fix wrapscan option bug where forward searches become backward\n          searches and do cursor correction accordingly\n        + Change \"dd\" to move the cursor to the first non-blank on the line\n        + Delete cursor attraction to the first non-blank, change non-blank\n          motions to set the most attractive cursor position instead\n        + Fix 'r' substitute option to set the RE to the last RE, not the\n          last substitute RE\n        + Fix 'c' and 'g' substitute options to always toggle, and fix\n          edcompatible option to not reset them\n        + Display ex error messages in inverse video\n        + Fix errorbells option to match historic practice\n        + Delete fixed character display table in favor of table built based\n          on the current locale\n        + Add \":set octal\" option, that displays unknown characters as octal\n          values instead of the default hexadecimal\n        + Make all command and text input modes interruptible\n        + Fix ex input mode to display error messages immediately, instead\n          of waiting for the lines to be resolved\n        + Fix bug where vi calling append could overwrite the command\n        + Fix off-by-one in the ex print routine tab code\n        + Fix incorrect ^D test in vi text input routines\n        + Add autoindent support for ex text insert routines\n        + Add System V substitute command replacement pattern semantics,\n          where '%' means the last replacement pattern\n        + Fix bug that \\ didn't escape newlines in ex commands\n        + Regularize the names of special characters to CH_*\n        + Change hex insert character from ^Vx<hex_char> to ^X<hex_char>\n        + Integrate System V style curses, so SunOS and Solaris ports can\n          use the native curses implementation\n\nNvi 1.10 -> Nvi 1.11: Thu Mar 24 16:07:45 EST 1994\n        + Change H, M, and L to set the absolute mark, historical practice\n        + Fix bug in stepping through multiple tags files\n        + Add \"remapmax\" option that turns off map counts so you can remap\n          infinitely. If it's off, term_key() can be interrupted from the\n          keyboard, which will cause the buffers to flush. I also dropped\n          the default max number of remaps to 50. (Only Dave Hitz's TM\n          macros and maze appear to go over that limit.)\n        + Change :mkexrc to not dump w{300,1200,9600}, lisp options\n        + Fix backward search within a line bug\n        + Change all the includes of \"pathnames.h\" to use <>'s so that the\n          PORT versions can use -I. to replace it with their own versions\n        + Make reads and writes interruptible. Rework code that enters and\n          leaves ex for '!' and filter commands, rework all interrupt and\n          timer code\n        + Fix core dump when user displayed option in .exrc file\n        + Fix bug where writing empty files didn't update the saved\n          modification time\n        + Fix bug where /pattern/ addressing was always a backward search\n        + Fix bug triggered by autoindent of more than 32 characters, where\n          nvi wasn't checking the right TEXT length\n        + Fix bug where joining only empty lines caused a core dump\n\nNvi 1.09 -> Nvi 1.10: Sat Mar 19 15:40:29 EST 1994\n        + Fix \"set all\" core dump\n\nNvi 1.08 -> Nvi 1.09: Sat Mar 19 10:11:14 EST 1994\n        + If the tag's file path is relative, and it doesn't exist, check\n          relative to the tag file location\n        + Fix ~ command to free temporary buffer on error return\n        + Create vi.ref, a first cut at a reference document for vi\n          The manual page and the reference document only document the\n          set options, so far\n        + Fix 1G bug not always going to the first non-blank\n        + Upgrade PORT/regex to release alpha3.4, from Henry Spencer\n        + Add MKS vi's \"cdpath\" option, supporting a cd search path\n        + Handle if search as a motion was discarded, i.e. \"d/<erase>\"\n        + Change nvi to not create multiple recovery files if modifying\n          a recovered file\n        + Decide to ignore that the cursor is before the '$' when inserting\n          in list mode. It's too hard to fix\n\nNvi 1.07 -> Nvi 1.08: Wed Mar 16 07:37:36 EST 1994\n        + Leftright and big line scrolling fixes. This meant more changes\n          to the screen display code, so there may be new problems\n        + Don't permit search-style addresses until a file has been read\n        + \"c[Ww]\" command incorrectly handled the \"in whitespace\" case\n        + Fix key space allocation bug triggered by cut/paste under SunOS\n        + Ex move command got the final cursor position wrong\n        + Delete \"optimize option not implemented\" message\n        + Make the literal-next character turn off mapping for the next\n          character in text input mode\n\nNvi 1.06 -> Nvi 1.07: Mon Mar 14 11:10:33 EST 1994\n        + The \"wire down\" change in 1.05 broke ex command parsing, there\n          wasn't a corresponding change to handle multiple K_VLNEXT chars\n        + Fix final position for vi's 't' command\n\nNvi 1.05 -> Nvi 1.06: Sun Mar 13 16:12:52 EST 1994\n        + Wire down ^D, ^H, ^W, and ^V, regardless of the user's termios\n          values\n        + Add ^D as the ex scroll command\n        + Support ^Q as a literal-next character\n        + Rework abbreviations to be delimited by any !inword() character\n        + Add options description to the manual page\n        + Minor screen cache fix for svi_get.c\n        + Rework beautify option support to match historical practice\n        + Exit immediately if not reading from a tty and a command fails\n        + Default the SunOS 4.* ports to the distributed curses, not SMI's\n\nNvi 1.04 -> Nvi 1.05: Thu Mar 24 16:07:45 EST 1994\n        + Make cursor keys work in input mode\n        + Rework screen column code in vi curses screen. MAJOR CHANGE -\n          after this, we'll be debugging curses screen presentation from\n          scratch\n        + Explode include files in vi.h into the source files\n\nNvi 1.03 -> Nvi 1.04: Sun Mar 6 14:14:16 EST 1994\n        + Make the ex move command keep the marks on the moved lines\n        + Change resize semantics so you can set the screen size to a\n          specific value. A couple of screen fixes for the resize code\n        + Fixes for foreground/background due to SIGWINCH\n        + Complete rework of all of vi's cursor movements. The underlying\n          assumption in the old code was that the starting cursor position\n          was part of the range of lines cut or deleted. The command\n          \"d[[\" is an example where this isn't true. Change it so that all\n          motion component commands set the final cursor position separately\n          from the range, as it can't be done correctly later. This is a\n          MAJOR CHANGE - after this change, we'll be debugging the cursor\n          positioning from scratch\n        + Rewrite the B, b, E, e commands to use vi's getc() interface\n          instead of rolling their own\n        + Add a second MARK structure, LMARK, which is the larger mark\n          needed by the logging and mark queue code. Everything else uses\n          the reworked MARK structure, which is simply a line/column pair\n        + Rework cut/delete to not expect 1-past-the-end in the range, but\n          to act on text to the end of the range, inclusive\n        + Sync on write's, to force NFS to flush\n\nNvi 1.01 -> Nvi 1.03: Sun Jan 23 17:50:35 EST 1994\n        + Tag stack fixes, was returning to the tag, not the position from\n          which the user tagged\n        + Only use from the cursor to the end of the word in cursor word\n          searches and tags. (Matches historical vi behavior.)\n        + Fix delete-last-line bug when line number option set\n        + Fix usage line for :split command\n        + If O_NUMBER set, long input lines would eventually fail, the\n          column count for the second screen of long lines wasn't set\n          correctly\n        + Fix for [[ reaching SOF with a column longer than the first line\n        + Fix for multiple error messages if no screen displayed\n        + Fix :read to set alternate file name as in historical practice\n        + Fix cut to rotate the numeric buffers if line mode flag set\n\nNvi 1.00 -> Nvi 1.01: Wed Jan 12 13:37:18 EST 1994\n        + Don't put cut items into numeric buffers if cutting less than\n          parts of two lines\n\nNvi 0.94 -> Nvi 1.00: Mon Jan 10 02:27:27 EST 1994\n        + Read-ahead not there; BSD tty driver problem, SunOS curses\n          problem\n        + Global command could error if it deleted the last line of\n          the file\n        + Change '.' to only apply to the 'u' if entered immediately\n          after the 'u' command. \"1pu.u.u. is still broken, but I\n          expect that it's going to be sacrificed for multiple undo\n        + If backward motion on a command, now move to the point; get\n          yank cursor positioning correct\n        + Rework cut buffers to match historic practice - yank/delete\n          numeric buffers redone sensibly, ignoring historic practice\n\nNvi 0.92 -> Nvi 0.93: Mon Dec 20 19:52:14 EST 1993\n        + Christos Zoulas reimplemented the script windows using pty's,\n          which means that they now work reasonably. The down side of\n          this is that almost all ports other than 4.4BSD need to include\n          two new files, login_tty.c and pty.c from the PORT/clib directory\n          I've added them to the Makefiles\n        + All calloc/malloc/realloc functions now cast their pointers, for\n          SunOS - there should be far fewer warning messages, during the\n          build. The remaining messages are where CHAR_T's meet char *'s,\n          i.e. where 8-bit clean meets strcmp\n        + The user's argument list handling has been reworked so that there\n          is always a single consistent position for use by :next, :prev and\n          :rewind\n        + All of the historical options are now at least accepted, although\n          not all of them are implemented. (Edcompatible, hardtabs, lisp,\n          optimize, redraw, and slowopen aren't implemented.)\n        + The RE's have been reworked so that matches of length 0 are\n          handled in the same way as vi used to handle them\n        + Several more mapping fixes and ex parser addressing fixes\n\n# vim: set filetype=changelog ts=8 sw=8 tw=79 expandtab colorcolumn=79 :\n"
  },
  {
    "path": "ChangeLog.license",
    "content": "SPDX-License-Identifier: BSD-3-Clause\nCopyright (c) 2021-2024 Jeffrey H. Johnson <johnsonjh.dev@gmail.com> and contributors\n"
  },
  {
    "path": "GNUmakefile",
    "content": "###############################################################################\n#                               -  O p e n V i  -                             #\n###############################################################################\n# vim: filetype=make:tabstop=8:tw=79:noexpandtab:colorcolumn=79:list:\n# SPDX-License-Identifier: BSD-3-Clause\n###############################################################################\n\n###############################################################################\n#\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n#\n# Copying and distribution of this file, with or without modification,\n# are permitted in any medium without royalty provided the copyright\n# notice and this notice are preserved.  This file is offered \"AS-IS\",\n# without any warranty.\n#\n###############################################################################\n\n###############################################################################\n\n# Default compiler settings\nCC          ?= cc\nDEPFLAGS    ?= -MMD -MP\nINCLDS       = -Iinclude -Icommon -Iregex -Iopenbsd\nCFLAGS      += -std=gnu99 $(INCLDS)\nWFLAGS      ?= -Wall -Wno-pointer-sign -Wno-uninitialized\n\n###############################################################################\n\n# Set DEBUG to enable debugging build\n#DEBUG       = 1\nDBGFLAGS    ?= -Wextra -ggdb -g3 -O0\n\n###############################################################################\n\n# Set LGC to enable link-time garbage collection (for non-debugging builds)\n#LGC         = 1\nLTGC         = -fdata-sections -ffunction-sections\nLTGL         = -Wl,--gc-sections\n\n###############################################################################\n\n# Set LTO to enable link-time optimization (for non-debugging builds)\n#LTO         = 1\nLTOC         = -flto\n\n###############################################################################\n\nPKGCFG      ?= pkg-config\nTR          ?= tr\nUNAME       ?= uname\n\nifndef OS\n    OS=$(shell $(UNAME) -s 2> /dev/null | \\\n        $(TR) '[:upper:]' '[:lower:]' 2> /dev/null)\nendif # OS\n\nifeq ($(OS),sunos)\n    OS=$(shell $(UNAME) -o 2> /dev/null | \\\n        $(TR) '[:upper:]' '[:lower:]' 2> /dev/null)\n    _SUNOS = 1\nendif # sunos\n\nifeq ($(OS),os400)\n    _OS400 = 1\n        OS = aix\nendif # os400\n\n###############################################################################\n\nifeq ($(OS),solaris)\n   ifneq (,$(findstring suncc,$(CC))) # suncc\n      OPTLEVEL ?= -O2\n      _OSLCC = 1\n   endif # suncc\nelse\n   OPTLEVEL    ?= -Os\nendif # solaris\n\n###############################################################################\n\nifeq ($(OS),netbsd)\n   ifneq (,$(findstring clang,$(CC))) # clang\n      WFLAGS += -Wno-unknown-warning-option  \\\n                -Wno-system-headers          \\\n                -Wno-char-subscripts\n   endif # clang\nendif # netbsd\nifeq ($(OS),illumos)\n   WFLAGS += -Wno-unknown-pragmas\nendif # illumos\nifeq ($(OS),solaris)\n   ifeq ($(_OSLCC),1)\n      SUNBITS ?= $$(command -p isainfo -b 2> /dev/null || printf '%s' 32)\n       WFLAGS += -erroff=E_EMPTY_DECLARATION            \\\n                 -erroff=E_STATEMENT_NOT_REACHED        \\\n                 -erroff=E_ARG_INCOMPATIBLE_WITH_ARG_L  \\\n                 -erroff=E_ASSIGNMENT_TYPE_MISMATCH     \\\n                 -erroff=E_ATTRIBUTE_UNKNOWN\n       CFLAGS += -m$(SUNBITS)\n      LDFLAGS += -m$(SUNBITS)\n   endif # suncc\nendif # solaris\n\n###############################################################################\n# Try to query pkg-config for ncurses flags and libraries\n\nifneq ($(OS),solaris)\n  ifneq ($(OS),netbsd)\n    ifndef CURSESLIB\n      CFLAGS    += $(shell $(PKGCFG) ncurses --cflags 2> /dev/null \\\n                         || $(PKGCFG) curses --cflags 2> /dev/null)\n      CURSESLIB += $(shell $(PKGCFG) ncurses --libs 2> /dev/null \\\n                         || $(PKGCFG) curses --libs 2> /dev/null)\n    endif #!CURSESLIB\n  endif #!netbsd\nendif #!solaris\n\n###############################################################################\n\n# Default libraries to link\nifeq ($(OS),netbsd)\n   CURSESLIB ?= -lcurses -lterminfo\n     LDFLAGS += -L\"/usr/local/lib\" -L\"/usr/pkg/lib\" -L\"/usr/lib\" -L\"/lib\"\n     LDFLAGS += -Wl,-R\"/lib\" -Wl,-R\"/usr/lib\" -Wl,-R\"/usr/pkg/lib\" \\\n                -Wl,-R\"/usr/local/bin\"\n   ifdef LTO\n      ifneq (,$(findstring clang,$(CC))) # clang\n         LLD     ?= ld.lld\n         LDFLAGS += -fuse-ld=\"$$(command -v $(LLD) || $(PRINTF) '%s' $(LLD))\"\n      endif # clang\n   endif # LTO\nelse # !netbsd\n   CURSESLIB ?= -lncurses\nendif # netbsd\nifeq ($(OS),aix) # aix/os400\n   MAIXBITS ?= $(shell command -p $(GETCONF) KERNEL_BITMODE 2> /dev/null || \\\n                    $(PRINTF) '%s' \"32\")\n      ifeq ($(_OS400),1)                 # IBM i (OS/400) PASE\n         CFLAGS  += -I/QOpenSys/pkgs/include/ncurses\n         LDFLAGS += -lutil -L/QOpenSys/pkgs/lib\n      endif\n      ifneq (,$(findstring gcc,$(CC)))   # gcc (GNU C)\n         CFLAGS  += $(WFLAGS) -maix$(MAIXBITS)\n         LDFLAGS += -maix$(MAIXBITS) -Wl,-b$(MAIXBITS)\n      endif # gcc\n      ifneq (,$(findstring clang,$(CC))) # xlclang / ibm-clang (IBM Open XL)\n         CFLAGS  += $(WFLAGS) -m$(MAIXBITS)\n         LDFLAGS += -m$(MAIXBITS) -Wl,-b$(MAIXBITS)\n      endif # clang\n      ifneq (,$(findstring gxlc,$(CC)))  # gxlc (IBM XL C)\n         CFLAGS  += -m$(MAIXBITS)\n         LDFLAGS += -m$(MAIXBITS) -Wl,-b$(MAIXBITS)\n         DEPFLAGS =\n      endif # gxlc\n   LDFLAGS  += -L/opt/freeware/lib\n   CFLAGS   += -I/opt/freeware/include\n   LINKLIBS ?= -lbsd $(CURSESLIB) -lcurses\nelse # !aix/os400\nifeq ($(OS),solaris)\n     CFLAGS += -U__EXTENSIONS__ -D_XPG4_2 -D__solaris__ -D_REENTRANT \\\n               -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_XOPEN_SOURCE=600\nendif # solaris\nifeq ($(OS),illumos)\n     CFLAGS += -D__illumos__\nendif # illumos\nifeq ($(_SUNOS),1)\n     CFLAGS += -Du_int32_t=uint32_t -Du_int16_t=uint16_t -Du_int8_t=uint8_t\n     CFLAGS += -DBYTE_ORDER=__BYTE_ORDER__\n     CFLAGS += -I/usr/include/ncurses\n   LINKLIBS ?= $(CURSESLIB)\nelse # !sunos\n   LINKLIBS ?= -lutil $(CURSESLIB)\nendif # sunos\n     CFLAGS += $(WFLAGS)\nendif # aix\nLINKLIBS    += $(EXTRA_LIBS)\n\n###############################################################################\n\nCFLAGS += -Ddbm_open=openbsd_dbm_open -Ddbm_close=openbsd_dbm_close          \\\n          -Ddbm_fetch=openbsd_dbm_fetch -Ddbm_firstkey=openbsd_dbm_firstkey  \\\n          -Ddbm_nextkey=openbsd_dbm_nextkey -Ddbm_delete=openbsd_dbm_delete  \\\n          -Ddbm_store=openbsd_dbm_store -Ddbm_error=openbsd_dbm_error        \\\n          -Ddbm_clearerr=openbsd_dbm_clearerr -Ddbm_rdonly=openbsd_dbm_rdonly\n\n###############################################################################\n\n# Installation directory prefix for install/uninstall\nPREFIX      ?= /usr/local\nDESTIDIR    ?=\n\n###############################################################################\n\n# Executable prefix and/or suffix (e.g. 'o', '-openbsd') for install/uninstall\nBINPREFIX   ?= o\n\n###############################################################################\n\n# Permissions and user:group to use for installed executables\nIPERM        = 755\nIUSGR        = root:bin\n\n###############################################################################\n\n# Using _FORTIFY_SOURCE=2 grows the binary by about ~2-3 KiB (on AMD64)\nifdef DEBUG\n   CFLAGS   += $(DBGFLAGS) -DDEBUG -DSTATISTICS -DHASH_STATISTICS\nelse # !DEBUG\n   CFLAGS   += $(OPTLEVEL) -D_FORTIFY_SOURCE=2\nendif # DEBUG\n\n###############################################################################\n\nifndef DEBUG\n    ifdef LTO\n        CFLAGS  += $(LTOC)\n        LDFLAGS += $(LTOC)\n    endif # LTO\n    ifdef LGC\n        CFLAGS  += $(LTGC)\n        LDFLAGS += $(LTGL)\n    endif # LGC\nendif # DEBUG\n\n###############################################################################\n\nAWK         ?= awk\nCHMOD       ?= chmod\nCHOWN       ?= chown\nCP          ?= cp -f\nPENV        := env\nGETCONF     ?= getconf\nLN          ?= ln\nLNS          = $(LN) -fs\nMKDIR       ?= mkdir -p\nPAWK         = command -p $(PENV) PATH=\"$$(command -p $(GETCONF) PATH)\" $(AWK)\nPRINTF      ?= printf\nRMDIR       ?= rmdir\nRM          ?= rm\nRMF          = $(RM) -f\nSLEEP       ?= sleep\nSTRIP       ?= strip\nSSTRIP      ?= sstrip\nTEST        ?= test\nTRUE        ?= true\nUPX         ?= upx\n\n###############################################################################\n\nifdef V\n    DEBUG = 1\nendif # V\n\n###############################################################################\n\nifdef DEBUG\n    VERBOSE = set -ex\nelse # !DEBUG\n    VERBOSE = $(TRUE)\nendif # DEBUG\n\n###############################################################################\n\nXSRC = openbsd/dirname.c       \\\n       openbsd/err.c           \\\n       openbsd/errc.c          \\\n       openbsd/errx.c          \\\n       openbsd/getopt_long.c   \\\n       openbsd/getprogname.c   \\\n       openbsd/issetugid.c     \\\n       openbsd/minpwcache.c    \\\n       openbsd/reallocarray.c  \\\n       openbsd/setmode.c       \\\n       openbsd/strlcat.c       \\\n       openbsd/strlcpy.c       \\\n       openbsd/strtonum.c      \\\n       openbsd/verr.c          \\\n       openbsd/verrc.c         \\\n       openbsd/verrx.c         \\\n       openbsd/vwarn.c         \\\n       openbsd/vwarnc.c        \\\n       openbsd/vwarnx.c        \\\n       openbsd/warn.c          \\\n       openbsd/warnc.c         \\\n       openbsd/warnx.c         \\\n       xinstall/xinstall.c\n\nSRCS = cl/cl_funcs.c           \\\n       cl/cl_main.c            \\\n       cl/cl_read.c            \\\n       cl/cl_screen.c          \\\n       cl/cl_term.c            \\\n       common/cut.c            \\\n       common/delete.c         \\\n       common/exf.c            \\\n       common/key.c            \\\n       common/line.c           \\\n       common/log.c            \\\n       common/main.c           \\\n       common/mark.c           \\\n       common/msg.c            \\\n       common/options.c        \\\n       common/options_f.c      \\\n       common/put.c            \\\n       common/recover.c        \\\n       common/screen.c         \\\n       common/search.c         \\\n       common/seq.c            \\\n       common/util.c           \\\n       db/btree/bt_close.c     \\\n       db/btree/bt_conv.c      \\\n       db/btree/bt_debug.c     \\\n       db/btree/bt_delete.c    \\\n       db/btree/bt_get.c       \\\n       db/btree/bt_open.c      \\\n       db/btree/bt_overflow.c  \\\n       db/btree/bt_page.c      \\\n       db/btree/bt_put.c       \\\n       db/btree/bt_search.c    \\\n       db/btree/bt_seq.c       \\\n       db/btree/bt_split.c     \\\n       db/btree/bt_utils.c     \\\n       db/db/db.c              \\\n       db/hash/hash_bigkey.c   \\\n       db/hash/hash_buf.c      \\\n       db/hash/hash.c          \\\n       db/hash/hash_func.c     \\\n       db/hash/hash_log2.c     \\\n       db/hash/hash_page.c     \\\n       db/hash/ndbm.c          \\\n       db/mpool/mpool.c        \\\n       db/recno/rec_close.c    \\\n       db/recno/rec_delete.c   \\\n       db/recno/rec_get.c      \\\n       db/recno/rec_open.c     \\\n       db/recno/rec_put.c      \\\n       db/recno/rec_search.c   \\\n       db/recno/rec_seq.c      \\\n       db/recno/rec_utils.c    \\\n       ex/ex_abbrev.c          \\\n       ex/ex_append.c          \\\n       ex/ex_args.c            \\\n       ex/ex_argv.c            \\\n       ex/ex_at.c              \\\n       ex/ex_bang.c            \\\n       ex/ex.c                 \\\n       ex/ex_cd.c              \\\n       ex/ex_cmd.c             \\\n       ex/ex_delete.c          \\\n       ex/ex_display.c         \\\n       ex/ex_edit.c            \\\n       ex/ex_equal.c           \\\n       ex/ex_file.c            \\\n       ex/ex_filter.c          \\\n       ex/ex_global.c          \\\n       ex/ex_init.c            \\\n       ex/ex_join.c            \\\n       ex/ex_map.c             \\\n       ex/ex_mark.c            \\\n       ex/ex_mkexrc.c          \\\n       ex/ex_move.c            \\\n       ex/ex_open.c            \\\n       ex/ex_preserve.c        \\\n       ex/ex_print.c           \\\n       ex/ex_put.c             \\\n       ex/ex_quit.c            \\\n       ex/ex_read.c            \\\n       ex/ex_screen.c          \\\n       ex/ex_script.c          \\\n       ex/ex_set.c             \\\n       ex/ex_shell.c           \\\n       ex/ex_shift.c           \\\n       ex/ex_source.c          \\\n       ex/ex_stop.c            \\\n       ex/ex_subst.c           \\\n       ex/ex_tag.c             \\\n       ex/ex_txt.c             \\\n       ex/ex_undo.c            \\\n       ex/ex_usage.c           \\\n       ex/ex_util.c            \\\n       ex/ex_version.c         \\\n       ex/ex_visual.c          \\\n       ex/ex_write.c           \\\n       ex/ex_yank.c            \\\n       ex/ex_z.c               \\\n       openbsd/basename.c      \\\n       openbsd/err.c           \\\n       openbsd/errx.c          \\\n       openbsd/getopt_long.c   \\\n       openbsd/getprogname.c   \\\n       openbsd/issetugid.c     \\\n       openbsd/open.c          \\\n       openbsd/pledge.c        \\\n       openbsd/reallocarray.c  \\\n       openbsd/strlcpy.c       \\\n       openbsd/strtonum.c      \\\n       openbsd/verr.c          \\\n       openbsd/verrx.c         \\\n       openbsd/vwarn.c         \\\n       openbsd/vwarnx.c        \\\n       openbsd/warn.c          \\\n       openbsd/warnx.c         \\\n       regex/regcomp.c         \\\n       regex/regerror.c        \\\n       regex/regexec.c         \\\n       regex/regfree.c         \\\n       vi/getc.c               \\\n       vi/v_at.c               \\\n       vi/v_ch.c               \\\n       vi/v_cmd.c              \\\n       vi/v_delete.c           \\\n       vi/v_ex.c               \\\n       vi/vi.c                 \\\n       vi/v_increment.c        \\\n       vi/v_init.c             \\\n       vi/v_itxt.c             \\\n       vi/v_left.c             \\\n       vi/v_mark.c             \\\n       vi/v_match.c            \\\n       vi/v_paragraph.c        \\\n       vi/v_put.c              \\\n       vi/v_redraw.c           \\\n       vi/v_replace.c          \\\n       vi/v_right.c            \\\n       vi/v_screen.c           \\\n       vi/v_scroll.c           \\\n       vi/v_search.c           \\\n       vi/v_section.c          \\\n       vi/v_sentence.c         \\\n       vi/vs_line.c            \\\n       vi/vs_msg.c             \\\n       vi/vs_refresh.c         \\\n       vi/vs_relative.c        \\\n       vi/vs_smap.c            \\\n       vi/vs_split.c           \\\n       vi/v_status.c           \\\n       vi/v_txt.c              \\\n       vi/v_ulcase.c           \\\n       vi/v_undo.c             \\\n       vi/v_util.c             \\\n       vi/v_word.c             \\\n       vi/v_xchar.c            \\\n       vi/v_yank.c             \\\n       vi/v_z.c                \\\n       vi/v_zexit.c\n\n###############################################################################\n\nVPATH = build:cl:common:db:ex:include:vi:regex:openbsd:bin\nOBJS := ${SRCS:.c=.o}\nXOBJ := ${XSRC:.c=.o}\nDEPS := ${OBJS:.o=.d}\nXDEP := ${XOBJ:.o=.d}\n\n###############################################################################\n\n.PHONY: all\nall: bin/vi                    \\\n     bin/ex                    \\\n     bin/view                  \\\n     bin/xinstall              \\\n     docs/USD.doc/vi.man/vi.1  \\\n     scripts/virecover.8\n\n###############################################################################\n\nex/ex_def.h: ex/ex.awk ex/ex_cmd.c\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(AWK):\\t%42s\\n\" \"ex/ex.awk\"\nendif # DEBUG\n\t@$(VERBOSE); $(RMF) \"./ex/ex_def.h\";  \\\n        $(PAWK) -f                            \\\n            \"./ex/ex.awk\" \"./ex/ex_cmd.c\"     \\\n                > \"./ex/ex_def.h\" &&          \\\n                    $(TEST) -f                \\\n                       \"./ex/ex_def.h\"\n\n###############################################################################\n\ncommon/options_def.h: common/options.awk common/options.c ex/ex_def.h\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(AWK):\\t%42s\\n\" \"command/options.awk\"\nendif # DEBUG\n\t@$(VERBOSE); $(RMF) \"./common/options_def.h\";    \\\n        $(PAWK) -f                                       \\\n            \"./common/options.awk\" \"./common/options.c\"  \\\n                > \"./common/options_def.h\" &&            \\\n                    $(TEST) -f \"./common/options_def.h\"\n\n###############################################################################\n\n.PHONY: clean distclean realclean mostlyclean maintainer-clean\nifneq (,$(findstring clean,$(MAKECMDGOALS)))\n.NOTPARALLEL: clean distclean realclean mostlyclean maintainer-clean\nendif # (,$(findstring clean,$(MAKECMDGOALS)))\nclean distclean realclean mostlyclean maintainer-clean:\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"common/options_def.h\"\nendif # DEBUG\n\t@$(VERBOSE); $(RMF) \"./common/options_def.h\"\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"ex/ex_def.h\"\nendif # DEBUG\n\t@$(VERBOSE); $(RMF) \"./ex/ex_def.h\"\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"objects\"\nendif # DEBUG\n\t@$(VERBOSE); $(RMF) $(OBJS) $(XOBJ)\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"dependencies\"\nendif # DEBUG\n\t@$(VERBOSE); $(RMF) $(DEPS) $(XDEP)\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"bin/vi\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -f \"./bin/vi\" && $(RMF) \"./bin/vi\" || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"bin/ex\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -e \"./bin/ex\" && $(RMF) \"./bin/ex\" || $(TRUE)\n\t@$(VERBOSE); $(TEST) -h \"./bin/ex\" && $(RMF) \"./bin/ex\" || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"bin/view\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -e \"./bin/view\" && $(RMF) \"./bin/view\" || $(TRUE)\n\t@$(VERBOSE); $(TEST) -h \"./bin/view\" && $(RMF) \"./bin/view\" || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"rm:\" \"bin/xinstall\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -f \"./bin/xinstall\" && \\\n            $(RMF) \"./bin/xinstall\" || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t%s\\t%42s\\n' \"$(RMDIR):\" \"bin\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -d \"./bin\" && $(RMDIR) \"./bin\" || $(TRUE)\n\t-@$(TEST) -d \"./bin\" && $(PRINTF) '\\r\\t%s\\t%42s\\n' \\\n            \"WARNING:\" \"Directory './bin' not removed.\" || $(TRUE)\n\n###############################################################################\n\n%.o: %.c common/options_def.h ex/ex_def.h\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(CC):\\t%42s\\n\" \"$@\"\nendif # DEBUG\n\t@$(VERBOSE); $(CC) $(CFLAGS) $(DEPFLAGS) -c -o \"$@\" \"$<\"\n-include $(wildcard $(DEPS))\n-include $(wildcaed $(XDEP))\n\n###############################################################################\n\nbin/xinstall: $(XOBJ)\n\t@$(TEST) -d \"./bin\" || $(MKDIR) \"./bin\"\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t$(LD):\\t%42s\\n' \"$@\"\nendif # DEBUG\n\t@$(VERBOSE); $(CC) -o \"$@\" $^ $(LDFLAGS) $(EXTRA_LIBS)\n\n.PHONY: xinstall\nxinstall: bin/xinstall\n\t-@$(TRUE)\n\n###############################################################################\n\nbin/vi: $(OBJS)\n\t@$(TEST) -d \"./bin\" || $(MKDIR) \"./bin\"\nifndef DEBUG\n\t-@$(PRINTF) '\\r\\t$(LD):\\t%42s\\n' \"$@\"\nendif # DEBUG\n\t@$(VERBOSE); $(CC) -o \"$@\" $^ $(LDFLAGS) $(LINKLIBS)\n\n.PHONY: vi\nvi: bin/vi\n\t-@$(TRUE)\n\n###############################################################################\n\nbin/ex: bin/vi\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(LN):\\t%42s\\n\" \"$@\"\nendif # DEBUG\n\t@$(VERBOSE); $(LNS) \"vi\" \"./bin/ex\"\n\n.PHONY: ex\nex: bin/ex\n\t-@$(TRUE)\n\n##############################################################################\n\nbin/view: bin/vi\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(LN):\\t%42s\\n\" \"$@\"\nendif # DEBUG\n\t@$(VERBOSE); $(LNS) \"vi\" \"./bin/view\"\n\n.PHONY: view\nview: bin/view\n\t-@$(TRUE)\n\n###############################################################################\n\n.PHONY: install\nifneq (,$(findstring install,$(MAKECMDGOALS)))\n.NOTPARALLEL: install\nendif # (,$(findstring install,$(MAKECMDGOALS)))\ninstall: bin/vi bin/ex bin/view docs/USD.doc/vi.man/vi.1  \\\n         scripts/virecover scripts/virecover.8\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \"mkdir:\" \"$(DESTDIR)$(PREFIX)/bin\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -d \"$(DESTDIR)$(PREFIX)/bin\" || \\\n            $(MKDIR) \"$(DESTDIR)$(PREFIX)/bin\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \"mkdir:\" \"$(DESTDIR)$(PREFIX)/libexec\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -d \"$(DESTDIR)$(PREFIX)/libexec\" || \\\n            $(MKDIR) \"$(DESTDIR)$(PREFIX)/libexec\"\nifndef DEBUG\n\t@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n            \"mkdir:\" \"$(DESTDIR)$(PREFIX)/share/man/man1\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -d \"$(DESTDIR)$(PREFIX)/share/man/man1\" || \\\n            $(MKDIR) \"$(DESTDIR)$(PREFIX)/share/man/man1\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n            \"mkdir:\" \"$(DESTDIR)$(PREFIX)/share/man/man8\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -d \"$(DESTDIR)$(PREFIX)/share/man/man8\" || \\\n            $(MKDIR) \"$(DESTDIR)$(PREFIX)/share/man/man8\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n        \"cp:\" \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\"\nendif # DEBUG\n\t@$(VERBOSE); $(CP) ./bin/vi                                 \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\" && \\\n        $(CHOWN) \"$(IUSGR)\"                                         \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\" && \\\n        $(CHMOD) \"$(IPERM)\"                                         \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\"\nifndef DEBUG\n\t@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n        \"ln:\" \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -x                                     \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\" && \\\n        $(LNS) \"$(BINPREFIX)vi$(BINSUFFIX)\"                         \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n        \"ln:\" \"$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)\"\nendif # DEBUG\n\t@$(VERBOSE); $(TEST) -x                                     \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\" && \\\n        $(LNS) \"$(BINPREFIX)vi$(BINSUFFIX)\"                         \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n        \"cp:\" \"$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)\"\nendif # DEBUG\n\t@$(VERBOSE); $(CP) \"./scripts/virecover\"                             \\\n         \"$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)\" && \\\n        $(CHMOD) \"$(IPERM)\"                                                  \\\n         \"$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n        \"cp:\" \\\n    \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX){vi,ex,view}$(BINSUFFIX).1\"\nendif # DEBUG\n\t@$(VERBOSE); $(CP) \"docs/USD.doc/vi.man/vi.1\"                       \\\n        \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1\"   \\\n         && $(LNS)                                                          \\\n        \"$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1\"   \\\n        \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)view$(BINSUFFIX).1\" \\\n         && $(LNS)                                                          \\\n        \"$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1\"   \\\n        \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)ex$(BINSUFFIX).1\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t%s\\t%42s\\n\" \\\n          \"cp:\" \\\n      \"$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8\"\nendif # DEBUG\n\t@$(VERBOSE); $(CP) \"scripts/virecover.8\" \\\n      \"$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8\"\n\n###############################################################################\n\n.PHONY: install-strip installstrip\nifneq (,$(findstring install-strip,$(MAKECMDGOALS)))\n.NOTPARALLEL: install-strip installstrip install\nendif # (,$(findstring install-strip,$(MAKECMDGOALS)))\ninstall-strip installstrip: install\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(STRIP):\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\"\nendif # DEBUG\n\t-@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS)                        \\\n            $(STRIP) \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\" || \\\n              $(TRUE)\n\n###############################################################################\n\n.PHONY: strip\nifneq (,$(findstring strip,$(MAKECMDGOALS)))\n.NOTPARALLEL: strip\nendif # (,$(findstring strip,$(MAKECMDGOALS)))\nstrip: bin/vi bin/ex bin/view\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(STRIP):\\t%42s\\n\" \"bin/vi\"\nendif # DEBUG\n\t-@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \\\n            $(STRIP) \"./bin/vi\" || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(STRIP):\\t%42s\\n\" \"bin/xinstall\"\nendif # DEBUG\n\t-@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \\\n            $(STRIP) \"./bin/xinstall\" || $(TRUE)\n\n###############################################################################\n\n.PHONY: superstrip sstrip\nifneq (,$(findstring strip,$(MAKECMDGOALS)))\n.NOTPARALLEL: superstrip sstrip\nendif # (,$(findstring strip,$(MAKECMDGOALS)))\nifneq ($(OS),freebsd)\n    STRIP_VERS=-R '.gnu.version'\nelse # freebsd\n    STRIP_VERS=\nendif # !freebsd\nifneq ($(OS),netbsd)\n    STRIP_NOTE=-R '.note.*'\nelse # netbsd\n    STRIP_NOTE=-R '.SUNW_ctf' -R '.jcr' -R '.ident'  \\\n               -R '.note.netbsd.mcmodel'             \\\n               -R '.note.netbsd.pax' -R '.gnu.hash'\nendif # !netbsd\nsuperstrip sstrip: strip bin/vi bin/ex bin/view\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(STRIP):\\t%42s\\n\" \"bin/vi\"\nendif # DEBUG\n\t-@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS)  \\\n            $(STRIP) --strip-all                       \\\n                -R '.gnu.build.attributes'             \\\n                -R '.eh_frame'                         \\\n                -R '.eh_frame*'                        \\\n                -R '.comment'   $(STRIP_NOTE)          \\\n                -R '.comment.*' $(STRIP_VERS)          \\\n                    \"./bin/vi\"                         \\\n                      2> /dev/null || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(SSTRIP):\\t%42s\\n\" \"bin/vi\"\nendif # DEBUG\n\t-@$(VERBOSE); $(SSTRIP) -z \"./bin/vi\" \\\n           2> /dev/null || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(STRIP):\\t%42s\\n\" \"bin/xinstall\"\nendif # DEBUG\n\t-@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS)  \\\n            $(STRIP) --strip-all                       \\\n                -R '.gnu.build.attributes'             \\\n                -R '.eh_frame'                         \\\n                -R '.eh_frame*'                        \\\n                -R '.comment'   $(STRIP_NOTE)          \\\n                -R '.comment.*' $(STRIP_VERS)          \\\n                    \"./bin/xinstall\"                   \\\n                      2> /dev/null || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(SSTRIP):\\t%42s\\n\" \"bin/xinstall\"\nendif # DEBUG\n\t-@$(VERBOSE); $(SSTRIP) -z \"./bin/xinstall\" \\\n            2> /dev/null || $(TRUE)\n\n###############################################################################\n\n.PHONY: upx\nifneq (,$(findstring upx,$(MAKECMDGOALS)))\n.NOTPARALLEL: upx\nendif # (,$(findstring upx,$(MAKECMDGOALS)))\nupx: sstrip\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(UPX):\\t%42s\\n\" \"bin/vi\"\nendif # DEBUG\n\t-@$(VERBOSE); $(UPX) -qqq9 --exact \"./bin/vi\" 2> /dev/null || $(TRUE)\n\t-@$(VERBOSE); $(UPX) -qqq9         \"./bin/vi\" 2> /dev/null || $(TRUE)\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\t$(SSTRIP):\\t%42s\\n\" \"bin/vi\"\nendif # DEBUG\n\t-@$(VERBOSE); $(SSTRIP) -z \"./bin/vi\" 2> /dev/null || $(TRUE)\n\n###############################################################################\n\n.PHONY: uninstall\nifneq (,$(findstring uninstall,$(MAKECMDGOALS)))\n.NOTPARALLEL: uninstall\nendif # (,$(findstring uninstall,$(MAKECMDGOALS)))\nuninstall:\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)ex$(BINSUFFIX).1\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)ex$(BINSUFFIX).1\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)view$(BINSUFFIX).1\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)view$(BINSUFFIX).1\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n      \"$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n      \"$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8\"\nifndef DEBUG\n\t-@$(PRINTF) \"\\r\\trm:\\t%42s\\n\" \\\n            \"$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)\"\nendif # DEBUG\n\t-@$(VERBOSE); $(RMF) \\\n            \"$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)\"\n\n###############################################################################\n\n# Local Variables:\n# mode: make\n# tab-width: 8\n# End:\n"
  },
  {
    "path": "LICENSE.md",
    "content": "```text\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1989, 1990, 1991, 1992, 1993 UNIX System Laboratories, Inc.\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 1992 Keith Muller\nCopyright (c) 1992, 1993, 1994 Henry Spencer\nCopyright (c) 1994, 1996 Rob Mayoff\nCopyright (c) 1997 Phillip F. Knaack\nCopyright (c) 1997, 1998, 2002, 2004, 2015 Todd C. Miller <millert@openbsd.org>\nCopyright (c) 1999, 2000, 2004, 2009, 2011 Sven Verdoolaege <skimo@kotnet.org>\nCopyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc.\nCopyright (c) 2002 Niels Provos <provos@citi.umich.edu>\nCopyright (c) 2004 Ted Unangst\nCopyright (c) 2008 Otto Moerbeek <otto@drijf.net>\nCopyright (c) 2012, 2013 Christian Neukirchen <chneukirchen@gmail.com>\nCopyright (c) 2013 Antoine Jacoutot <ajacoutot@openbsd.org>\nCopyright (c) 2014 Al Viro <viro@ZenIV.linux.org.uk>\nCopyright (c) 2015 Philip Guenther <guenther@openbsd.org>\nCopyright (c) 2015 The DragonFly Project\nCopyright (c) 2018 Duncan Overbruck\nCopyright (c) 2022 Ørjan Malde <red@foxi.me>\nCopyright (c) 2021, 2022, 2023, 2024 Jeffrey H. Johnson\n\nAll rights reserved.\n\nThis software was originally derived from code contributed to the University\nof California, Berkeley by Steve Kirkendall, the author of the \"Elvis\" editor.\n\nThis program contains code derived from software contributed to Berkeley by:\n  * Margo Seltzer    * Mike Olson    * Henry Spencer, University of Totonto\n  * Brian Hirt       * Paul Vixie    * David Hitz, Auspex Systems, Inc.\n  * Dave Borman, Cray Research, Inc. * Keith Muller, UCSD\n\nThis program contains code derived from software contributed to The NetBSD\nFoundation, Inc. by Dieter Baron, Thomas Klausner, and Jeremy C. Reed.\n\nThis program contains code derived from material licensed to the University\nof California, Berkeley by American Telephone and Telegraph Co. or UNIX System\nLaboratories, Inc. and reproduced herein with the permission of UNIX System\nLaboratories, Inc.\n\nThis program contains code developed for OpenBSD which was sponsored in part\nby the Defense Advanced Research Projects Agency (DARPA) and Air Force Research\nLaboratory, Air Force Materiel Command, United States Air Force (USAF), under\nagreement number F39502-99-1-0512.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice,\n    this list of conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n 3. Neither the name of the University nor the names of its contributors may\n    be used to endorse or promote products derived from this software without\n    specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY\nEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n```\n```text\nIn the following disclosure, the phrase \"this text\" refers to portions of the\nbundled documentation.\n\nThe Institute of Electrical and Electronics Engineers and the American National\nStandards Committee X3 on Information Processing Systems have given permission\nto reprint portions of their documentation.\n\nPortions of this text are reprinted and reproduced in electronic form from IEEE\nStd 1003.1-1988, IEEE Standard Portable Operating System Interface for Computer\nEnvironments (POSIX), Copyright (c) 1988-1992 by The Institute of Electrical\nand Electronics Engineers, Inc. In the event of any discrepancy between these\nversions and the original IEEE Standard, the original IEEE Standard is the\nreferee document.\n```\n"
  },
  {
    "path": "LICENSES/BSD-2-Clause.txt",
    "content": "Copyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc.\nCopyright (c) 2002 Niels Provos <provos@citi.umich.edu>\nCopyright (c) 2022-2024 Jeffrey H. Johnson\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS\n``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS\nBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "LICENSES/BSD-3-Clause.txt",
    "content": "Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1989, 1990, 1991, 1992, 1993 UNIX System Laboratories, Inc.\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 1992 Keith Muller\nCopyright (c) 1992, 1993, 1994 Henry Spencer\nCopyright (c) 1994, 1996 Rob Mayoff\nCopyright (c) 1997 Phillip F. Knaack\nCopyright (c) 1997, 1998, 2002, 2004, 2015 Todd C. Miller <millert@openbsd.org>\nCopyright (c) 1999, 2000, 2004, 2009, 2011 Sven Verdoolaege <skimo@kotnet.org>\nCopyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc.\nCopyright (c) 2002 Niels Provos <provos@citi.umich.edu>\nCopyright (c) 2004 Ted Unangst\nCopyright (c) 2008 Otto Moerbeek <otto@drijf.net>\nCopyright (c) 2012, 2013 Christian Neukirchen <chneukirchen@gmail.com>\nCopyright (c) 2013 Antoine Jacoutot <ajacoutot@openbsd.org>\nCopyright (c) 2014 Al Viro <viro@ZenIV.linux.org.uk>\nCopyright (c) 2015 Philip Guenther <guenther@openbsd.org>\nCopyright (c) 2015 The DragonFly Project\nCopyright (c) 2018 Duncan Overbruck\nCopyright (c) 2022 Ørjan Malde <red@foxi.me>\nCopyright (c) 2021, 2022, 2023, 2024 Jeffrey H. Johnson\n\nAll rights reserved.\n\nThis software was originally derived from code contributed to the University\nof California, Berkeley by Steve Kirkendall, the author of the \"Elvis\" editor.\n\nThis program contains code derived from software contributed to Berkeley by:\n  * Margo Seltzer    * Mike Olson    * Henry Spencer, University of Totonto\n  * Brian Hirt       * Paul Vixie    * David Hitz, Auspex Systems, Inc.\n  * Dave Borman, Cray Research, Inc. * Keith Muller, UCSD\n\nThis program contains code derived from software contributed to The NetBSD\nFoundation, Inc. by Dieter Baron, Thomas Klausner, and Jeremy C. Reed.\n\nThis program contains code derived from material licensed to the University\nof California, Berkeley by American Telephone and Telegraph Co. or UNIX System\nLaboratories, Inc. and reproduced herein with the permission of UNIX System\nLaboratories, Inc.\n\nThis program contains code developed for OpenBSD which was sponsored in part\nby the Defense Advanced Research Projects Agency (DARPA) and Air Force Research\nLaboratory, Air Force Materiel Command, United States Air Force (USAF), under\nagreement number F39502-99-1-0512.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice,\n    this list of conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n 3. Neither the name of the University nor the names of its contributors may\n    be used to endorse or promote products derived from this software without\n    specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY\nEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "LICENSES/ISC.txt",
    "content": "Copyright (c) 1997, 1998, 2002, 2004, 2015\n        Todd C. Miller <millert@openbsd.org>\nCopyright (c) 2004 Ted Unangst and Todd Miller\nCopyright (c) 2008 Otto Moerbeek <otto@drijf.net>\nCopyright (c) 2013 Antoine Jacoutot <ajacoutot@openbsd.org>\nCopyright (c) 2015 Philip Guenther <guenther@openbsd.org>\nCopyright (c) 2022-2024 Jeffrey H. Johnson\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<!-- SPDX-License-Identifier: BSD-3-Clause -->\n<!-- Copyright (c) 2021-2024 Jeffrey H Johnson -->\n[Uma tradução em português está disponível.](README_pt_BR.md)\n\n# OpenVi\n\n***OpenVi*** — Portable *OpenBSD* **`vi`** / **`ex`**\n\n## Table of Contents\n\n<!-- toc -->\n\n- [Overview](#overview)\n  * [Why?](#why)\n    + [Why not?](#why-not)\n- [Building](#building)\n  * [Prerequisites](#prerequisites)\n    + [Required prerequisites](#required-prerequisites)\n    + [Optional prerequisites](#optional-prerequisites)\n    + [Supported platforms](#supported-platforms)\n      - [Unsupported platforms](#unsupported-platforms)\n  * [Compilation](#compilation)\n    + [Platform Specifics](#platform-specifics)\n      - [AIX](#aix)\n      - [NetBSD](#netbsd)\n      - [illumos](#illumos)\n      - [Solaris](#solaris)\n      - [Windows](#windows)\n        * [Cygwin](#cygwin)\n- [Availability](#availability)\n  * [Source Code](#source-code)\n  * [Packages](#packages)\n- [Versioning](#versioning)\n- [History](#history)\n- [License](#license)\n- [Acknowledgements](#acknowledgements)\n- [Similar Projects](#similar-projects)\n- [See Also](#see-also)\n\n<!-- tocstop -->\n\n## Overview\n\n***OpenVi*** is an enhanced and portable implementation of the Berkeley\n**`vi`** / **`ex`** text editor, originally developed by *Bill Joy*.\n\n***OpenVi*** is a fork of the **`vi`** / **`ex`** editor included with\n*OpenBSD*, which is derived from version 1.79 of the `nvi` editor originally\ndistributed as part of the *Fourth Berkeley Software Distribution* (**4BSD**).\n\nThe **`nvi`** editor was developed by *Keith Bostic* of the *Computer Systems\nResearch Group* (**CSRG**) at the *University of California, Berkeley*, *Sven\nVerdoolaege*, and other contributors. **`Nvi`** itself was derived from *Steve\nKirkendall*'s **`Elvis`** editor.\n\n### Why?\n\nWhy would you want to use ***OpenVi*** instead of ***AnotherVi***?\n\n- Derived from the (extensively audited) *OpenBSD* base system code\n  - Focus on readability, simplicity, and correctness of implementation\n  - Adherence to *OpenBSD*'s standard secure coding practices\n    - Uses secure functions (*e.g.* `strlcpy`, `snprintf`, `mkstemp`, `pledge`)\n  - Reduced complexity for hopefully fewer program defects\n- Clean source code, distributed under a permissive 3-clause BSD license\n  - Some support code is distributed under the (more permissive) ISC license\n- Mostly conforming to relevant standards (*POSIX*, *SUS*), where applicable\n  - Enhancements, non-standard behaviors, and new features are conservatively\n    and sanely implemented with care taken to balance user expectations,\n    complexity, and historical accuracy\n- Extensions such as `bserase`, `expandtab`, `imctrl`, `visibletab`, etc.\n- Build requires only *GNU Make* and standard *POSIX* utilities\n  - Easy integration with embedded, minimal, or iteratively bootstrapped\n    environments and distributions (such as *Linux From Scratch* builds)\n- No compile-time or build-time configuration options\n  - Single standard build configuration with no incompatible variants\n  - No configuration-specific bugs resulting from untested combinations or\n    rarely exercised code paths\n  - Concise and understandable documentation; no subtle platform variations\n- Consistent user interface, script, and map behavior across all platforms\n- Utilizes *OpenBSD*'s extended *Spencer*-based regular expression engine\n  (also adopted by *LLVM*, *Tcl*, etc.) on all supported systems\n- Single, compact, self-contained binary\n  - No external data files required at run-time\n  - No external library dependencies (other than curses)\n    - Suitable for static linking and emergency “rescue” usage\n- All the various tweaks, fixes, improvements, and clean-ups accumulated\n  over 25+ years as part of the *OpenBSD* base system\n\n#### Why not?\n\nSo, why might you **not** want to use ***OpenVi***, then?\n\nSome of these points might be desirable features, depending on your point of\nview.\n\n- Internationalization support is currently lacking\n  - No support for Unicode / UTF-8 / wide character display\n    - Multibyte characters are shown as individual bytes, rather than glyphs\n    - Multibyte support is planned, but is unfortunately non-trivial, see:\n      - Schwarze, I. (2016, September 25). *Keep multibyte character support\n        simple* [Conference presentation]. EuroBSDCon 2016 Convention,\n        Belgrade, Serbia.\n        [[pdf:OpenBSD](https://openbsd.org/papers/eurobsdcon2016-utf8.pdf)]\n      - Jun-ichiro itojun Hagino [*KAME Project*] and Yoshitaka Tokugawa\n        [*WIDE Project*]. (1999, 6 June). *Multilingual vi clones: past, now\n        and the future* [Conference presentation]. In Proceedings of the\n        annual conference on USENIX, Annual Technical Conference\n        (*USENIX ATEC '99*). USENIX Association, Monterey, CA, USA, Page 45.\n        [[doi:10.5555/1268708.1268753](https://dl.acm.org/doi/10.5555/1268708.1268753)],\n        [[abstract:USENIX](https://www.usenix.org/conference/1999-usenix-annual-technical-conference/multilingual-vi-clones-past-now-and-future)]\n        (*legacy*)\n  - No support for bidirectional text\n  - No support for regional localization or message translation\n- Inefficient handling of extremely large (*e.g.* multi-GB) files\n- No support for syntax highlighting, context-aware code completion, code\n  folding, or “*language server*” integrations\n- No interactive macro recording and debugging functionality\n- No advanced scripting support (no *BASIC*, *COBOL*, *JavaScript*, *Lua*,\n  *Perl*, *PHP*, *Python*, *REXX*, *Ruby*, *S-Lang*, *Tcl*, or anything else)\n- Only curses-based visual-mode and line-based `ex`-mode interfaces available\n  - No support for X11/Wayland, OpenGL/Vulkan, Neuralink, augmented / virtual\n    reality, or any other graphical user interfaces\n\n## Building\n\n### Prerequisites\n\n#### Required prerequisites\n\n- **POSIX.1**-**2008** environment: *POSIX* shell (`sh`) and utilities,\n  **Awk** (`mawk`, `nawk`), etc.\n- **GNU Make** (version *3.81* or later)\n- **C99** compiler (*e.g.* `xlc`, `suncc`, `clang`, `gcc`, etc.)\n- **Curses** (`ncurses`, *NetBSD* `curses` V8+, `PDCurses` V2.8+,\n  `PDCursesMod`, etc.)\n\n#### Optional prerequisites\n\n- **pkg-config**\n- **Perl** 5+\n- **C shell** (`csh`, `tcsh`, etc.)\n- `nroff`, `groff`, etc.\n\n#### Supported platforms\n\n- **OpenVi** is easily portable to most platforms with *UNIX*-like operating\n  systems that are mostly conforming to the programming interface described by\n  *IEEE Std 1003.1-2008* and user environment described by *IEEE Std\n  1003.2-2008*, also known as *POSIX.1-2008* and *POSIX.2-2008*, respectively.\n\n- The following operating systems are fully supported and regularly tested\n  using ix86/AMD64, ARM/AArch64, m68k, MIPS, POWER, and RISC-V processors:\n  - *IBM* **AIX** 7.1+\n  - *Apple* **Darwin** (**macOS** / **Mac OS X**) (*ARM64*, *Intel*, *PowerPC*)\n  - **FreeBSD** 12.3+\n  - **GNU**/**Linux** distributions (*glibc*, *musl*)\n  - *illumos* **OpenIndiana** Hipster\n  - **NetBSD** 9+\n  - **OpenBSD** 6.9+\n  - *Oracle* **Solaris** 11+\n  - *Microsoft* **Windows** (*Cygwin*, *Midipix*, *MSYS2*, *WSL*)\n  - **Managarm**\n\n- The following compilers are fully supported and regularly tested:\n  - *LLVM* **Clang** (*BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*,\n    *Windows*) V6+\n  - *AMD* **Optimizing C**/**C++** (*Linux*) V3+\n  - *GNU* **GCC** (*AIX*, *BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*,\n    *Windows*) V4.6+\n  - *IBM* **Advance Toolchain** (*Linux on POWER*) V14.0+\n  - *IBM* **Open XL C**/**C++** (*AIX*) V17.1+\n  - *IBM* **XL C**/**C++** (*AIX*, *Linux*) V16.1+\n  - *Intel* **oneAPI DPC++**/**C++** (*Linux*) V2021+\n  - *Intel* **C Compiler Classic** (*Darwin*, *Linux*) V19.1+\n  - *Oracle* **Developer Studio** (*Linux*, *Solaris*) V12.6+\n  - *PCC* **Portable C Compiler** (*NetBSD*) V1.0.0+\n\nNewer or older operating system and compiler releases, within reason, should\nwork. The versions listed above are those regularly tested and known working.\n\n##### Unsupported platforms\n\n- The following platforms are **not** currently supported, but **support is\n  planned** for a future release:\n  - **Haiku** Walter\n  - *SGI* **IRIX**\n\nUser contributions to enhance platform support are welcomed.\n\n### Compilation\n\n- Compilation can be performed by invoking GNU Make (usually `gmake` or `make`)\n  from the top-level directory of a source release or git checkout.\n- GNU Make's `-j N` flag may be used to parallelize the compilation, where `N`\n  is a positive integer representing the number of parallel jobs requested.\n- The following environment variables influence compilation and installation:\n  - `CC` - C compiler to use\n    - (*e.g.* `CC=gcc`)\n  - `OPTLEVEL` - Optimization flags\n    - (*e.g.* `OPTLEVEL=-O2`)\n  - `CFLAGS` - Flags to pass to the C compiler\n    - (*e.g.* `CFLAGS=\"-Wall -pipe\"`)\n  - `LIBS` - Libraries (overriding defaults) to pass to the linker\n    - (*e.g.* `LIBS=\"-lpdcurses -lflock\"`)\n  - `LDFLAGS` - Flags to pass to the linker\n    - (*e.g.* `LDFLAGS=\"-L/lib/path -static\"`)\n  - `V` - Set to enable verbose compilation output\n    - (*e.g.* `V=1`)\n  - `DEBUG` - Set to compile a debugging build\n    - (*e.g.* `DEBUG=1`)\n  - `LGC` - Set to enable link-time garbage collection\n    - (*e.g.* `LGC=1`)\n  - `LTO` - Set to enable link-time optimization\n    - (*e.g.* `LTO=1`)\n  - `EXTRA_LIBS` - Extra libraries for linking\n    - (*e.g.* `EXTRA_LIBS=-lmtmalloc`)\n  - `PREFIX` - Directory prefix for use with `install` and `uninstall` targets\n    - (*e.g.* `PREFIX=/opt/OpenVi`)\n- The usual targets (`all`, `strip`, `superstrip`, `clean`, `distclean`,\n  `install`, `install-strip`, `uninstall`, `upx`, etc.) are available; review\n  the `GNUmakefile` to see all the available targets and options.\n\nFor example, to compile an aggressively size-optimized build, enabling\nlink-time optimization and link-time garbage collection, explicitly using\n*GCC*:\n```sh\nenv CC=gcc OPTLEVEL=-Os LGC=1 LTO=1 gmake sstrip\n```\nor, to verbosely compile a debugging build, explicitly using *Clang*:\n```sh\nenv CC=clang DEBUG=1 V=1 gmake\n```\nFor systems with *GNU Make* as `make` (*e.g.* *GNU/Linux*), basic compilation\nshould succeed without any options or additional configuration needed:\n```sh\nmake\n```\nWith the appropriate privileges to manipulate files within the chosen `PREFIX`\n(using `doas`, `sudo`, `su`, etc.), the compiled executable may be installed —\nas-is or stripped — using an invocation such as:\n```sh\ndoas gmake install-strip\n```\nor\n```sh\nsudo env PREFIX=/usr/local make install\n```\n\n#### Platform Specifics\n\nThe following sections document ***only*** platform specific differences, and\nare not intended to be a general or exhaustive reference. For installation of\nprerequisite software packages or other system configuration, consult the\nvendor's documentation.\n\n##### AIX\n\n- Before building ***OpenVi*** on **AIX**, install the `ncurses` libraries and\n  headers. *IBM* provides the necessary packages, `ncurses` and\n  `ncurses-devel`, in *RPM* format as part of the *AIX Toolbox for Linux and\n  Open Source Software*. With the appropriate permissions (*e.g.* `root`), these\n  packages are installable on most systems using the `dnf` or `yum` utilities,\n  for example:\n  ```sh\n  dnf install ncurses ncurses-devel\n  ```\n  or\n  ```sh\n  yum install ncurses ncurses-devel\n  ```\n  The *IBM* **AIX** base system (and **PASE for i**, an integrated runtime\n  environment for **AIX** applications on the **IBM i** operating system)\n  provides `libxcurses`, an *XPG4*/*XSI* Extended Curses implementation\n  derived from *AT&T System V*, which is **not** yet supported for use with\n  ***OpenVi***.\n\n- Compilation is supported using *IBM* **XL C**/**C++** V16.1+ (`gxlc` or\n  `xlclang`), *IBM* **Open XL C**/**C++** V17.1+ (`ibm-clang`), or *GNU*\n  **GCC** (usually `gcc`, `gcc-8`, `gcc-9`, `gcc-10`, `gcc-11`):\n  - Link-time optimization (`LTO=1`) requires **Open XL C**/**C++** V17.1+.\n    The *IBM* (*AIX Toolbox*) and *Bull*/*Atos* (*Bull Freeware*) **GCC**\n    packages, and *IBM* **XL C**/**C++** versions earlier than V17.1 are\n    **not** LTO-enabled.\n  - Link-time garbage collection (`LGC=1`) is **not** supported on *IBM*\n    **AIX**.\n  - A 64-bit build is the default on systems operating in 64-bit mode; for a\n    32-bit build, set the value of the `MAIXBITS` environment variable to\n    `32` (*e.g.* `export MAIXBITS=32`).\n  - The value of the `CC` environment variable must be set to the full path\n    of the compiler (*e.g.* `/opt/freeware/bin/gcc`,\n    `/opt/IBM/xlC/16.1.0/bin/gxlc`, `/opt/IBM/openxlC/17.1.0/bin/ibm-clang`,\n    etc.) unless the compiler directory is already part of the current `PATH`.\n\n- File locking (via `flock()` as provided by the **AIX** `libbsd` library) is\n  non-functional; this will be investigated and corrected in a future release.\n\n- ***OpenVi*** man pages are authored with `mandoc` and require conversion\n  before use with the **AIX** `man` software (which is derived from *AT&T\n  UNIX System V*.)\n\n##### NetBSD\n\n- On **NetBSD** installations, the default ***OpenVi*** builds use the BSD\n  `curses` library provided by the NetBSD base system. To use `ncurses`\n  instead, set the values of the `CFLAGS`, `LDFLAGS`, and `CURSESLIB`\n  environment variables appropriately (*i.e.* `CFLAGS=-I/usr/pkg/include`\n  `LDFLAGS=-L/usr/pkg/lib` `CURSESLIB=-lncurses`).\n\n- The *LLVM* **LLD** linker is required for link-time optimization (`LTO=1`)\n  using **Clang**. It is available as an installable package (*i.e.* `pkgin\n  install lld`).\n\n##### illumos\n\n- Before building ***OpenVi*** on an **illumos** distribution (*i.e.*\n  **OpenIndiana**), install the `ncurses` libraries and headers. The\n  **OpenIndiana** distribution provides the necessary `ncurses` package in\n  *IPS* format. With the appropriate permissions (*e.g.* `root`), the package\n  can be installed using the **OpenIndiana** `pkg` utility, for example:\n  ```sh\n  pkg install ncurses\n  ```\n  The **OpenIndiana** base system provides `libcurses`, an *XPG4*/*XSI*\n  Extended Curses implementation derived from *AT&T System V*, which is **not**\n  yet supported for use with ***OpenVi***.\n\n- Link-time garbage collection (`LGC=1`) is **not** supported on\n  **OpenIndiana**.\n\n##### Solaris\n\n- Before building ***OpenVi*** on *Oracle* **Solaris** 11, install the\n  `ncurses` libraries and headers. *Oracle* provides provides the necessary\n  `ncurses` package for **Solaris** 11 in *IPS* format. With the appropriate\n  permissions (*e.g.* `root`), the package can be installed using the **Solaris**\n  `pkg` utility, for example:\n  ```sh\n  pkg install ncurses\n  ```\n  The base *Oracle* **Solaris** system provides `libcurses`, an *XPG4*/*XSI*\n  Extended Curses implementation derived from *AT&T System V*, which is **not**\n  yet supported for use with ***OpenVi***.\n\n- Compilation is supported using *Oracle* **Developer Studio**, **GCC**, and\n  **Clang**:\n  - When using *Oracle* **Developer Studio**, invoke the compiler as `suncc`\n    or set the value of the `_OSLCC` environment variable to `1`.\n  - Link-time optimization (`LTO=1`) is currently supported **only** when using\n    **GCC** or **Clang**.\n  - Link-time garbage collection (`LGC=1`) is **not** supported on **Solaris**.\n  - When using the *Oracle* **Developer Studio** (`suncc`) compiler, a 64-bit\n    build is the default on systems operating in 64-bit mode; for a 32-bit\n    build, set the value of the `SUNBITS` environment variable to `32` (*e.g.*\n    `export SUNBITS=32`).\n\n- File locking is unavailable due to the absence of `flock()` on **Solaris**.\n  This will be addressed by supporting *System V*-style `fcntl()` locking in a\n  future release.\n\n##### Windows\n\n- *Microsoft* **Windows** supports various development and runtime\n  environments, including *MSVC*, *Cygwin*, *Midipix*, *MSYS2*, *UWIN*, the\n  *Git Bash* environment, and others. Care must be taken to avoid mixing\n  incompatible libraries and tools.\n\n###### Cygwin\n\n- Compilation problems in the **Cygwin** environment are often caused by\n  incomplete or interrupted package installations, or by the installation of\n  packages using non-standard tools (*e.g.* `apt-cyg`), which can result in\n  missing files and dangling or missing symbolic links.\n- **Before** compiling ***OpenVi*** under **Cygwin**, it is *highly*\n  recommended to:\n  - Update the **Cygwin** `setup.exe` application to the latest available\n    version.\n  - Update all installed packages using the new **Cygwin** `setup.exe`\n    application.\n  - Install the required prerequisite packages (*i.e.* `make`, `gcc`, `ncurses`,\n    `ncurses-devel`) using the **Cygwin** `setup.exe` application.\n  - Invoke the `cygcheck` utility (*i.e.* `cygcheck -cv | grep -v \"OK$\"`) to\n    verify the integrity of all currently installed packages.\n\n## Availability\n\n### Source Code\n\n- [GitHub source repository](https://github.com/johnsonjh/OpenVi)\n- [Latest source release](http://github.com/johnsonjh/OpenVi/releases/latest)\n\n### Packages\n\n**OpenVi** is available to Linux and macOS users via the\n[Homebrew](https://formulae.brew.sh/formula/openvi) package manager.\n\n[![Homebrew](https://repology.org/badge/version-for-repo/homebrew/openvi.svg)](https://repology.org/project/openvi/versions)\n\n```sh\nbrew install openvi\n```\n\nOther (unofficial) distribution packages may be available.\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/openvi.svg)](https://repology.org/project/openvi/versions)\n\n## Versioning\n\nThe ***OpenVi*** version number is based on the version of the corresponding\n*OpenBSD* release, followed by the ***OpenVi*** release number. The `version`\ncommand can be used to display this information in the format shown below.\n\n```text\nVersion 7.0.1 (OpenVi) 10/25/2021.\n```\n\nThis message indicates the editor in use is ***OpenVi***, release **1**,\nderived from *OpenBSD* version **7.0**, and is fully synchronized with the\n*OpenBSD* versions of ***`vi`***, ***`ex`***, ***`db`***, and ***`regex`***\nas of **10/25/2021** (*October 25th 2021*).\n\nChanges **not** derived from *OpenBSD* commits do not advance this date.\nNew *OpenBSD* releases do not reset the ***OpenVi*** release number.\n\n## History\n\n- ***OpenVi***\n  - [`ChangeLog`](/ChangeLog)\n  - [Release history](http://github.com/johnsonjh/OpenVi/releases/)\n  - [Commit history](https://github.com/johnsonjh/OpenVi/commits/master)\n\n- *OpenBSD* ***`vi`***\n  - *OpenBSD* ***`vi`*** / ***`ex`***\n    - [Commit history](https://github.com/openbsd/src/commits/master/usr.bin/vi)\n  - *OpenBSD* ***`db`***\n    - [Commit history](https://github.com/openbsd/src/commits/master/lib/libc/db)\n  - *OpenBSD* ***`regex`***\n    - [Commit history](https://github.com/openbsd/src/commits/master/lib/libc/regex)\n\n## License\n\n- ***OpenVi*** is distributed under the terms of a **3-clause BSD** license.\n- See the [`LICENSE.md`](/LICENSE.md) file for the full license and\n  distribution terms.\n\n## Acknowledgements\n\n- *rqsd* of *Libera.Chat* for the idea that inspired the project and testing.\n- [*S. V. Nickolas*](https://github.com/buricco/),\n  [*Jason Stevens*](https://virtuallyfun.com/), and the\n  [***Virtually Fun*** *Discord*](https://discord.gg/HMwevcN) community, for\n  support and feedback.\n- From the original **`vi`** acknowledgements (by *Bill Joy* & *Mark Horton*):\n  - *Bruce Englar* encouraged the early development of this display editor.\n  - *Peter Kessler* helped bring sanity to version 2's command layout.\n  - *Bill Joy* wrote version **1**, versions **2.0** through **2.7**, and\n    created the framework that users see in the present editor.\n  - *Mark Horton* added macros and other features, and made the editor work on\n    a large number of terminals and *UNIX* systems.\n  - The financial support of *UUNET Communications Services* is gratefully\n    acknowledged.\n\n## Similar Projects\n\n- *Martin Guy*'s [**`Xvi`**](http://martinwguy.github.io/xvi/), an enhanced\n  fork of *Tim Thompson*'s [**`STEVIE`**](https://timthompson.com/tjt/stevie/)\n- *S. V. Nickolas*'\n  [**`Sivle`**](https://github.com/buricco/lunaris/tree/main/src/usr.bin/ex), a\n  cleaned-up fork of *Steve Kirkendall*'s\n  [**`Elvis`**](http://elvis.the-little-red-haired-girl.org/)\n- *Andy Valencia*'s [**`Vim57`**](https://sources.vsta.org:7100/vim57/tree), a\n  simplified fork of version 5.7 of *Bram Moolenaar*'s\n  [**`Vim`**](https://www.vim.org/)\n\n## See Also\n\n- [*Carsten Kunze*'s **`vi`**](https://github.com/n-t-roff/heirloom-ex-vi/)\n  is a currently maintained fork of the original (**1BSD**/**2BSD**) branch\n  of the **`vi`** / **`ex`** editor, derived from *Gunnar Ritter*'s enhanced\n  version of the [**traditional** **`vi`**](http://ex-vi.sourceforge.net/)\n  editor.\n- [**`Nvi2`**](https://github.com/lichray/nvi2) is a currently maintained\n  *feature branch* of the new (**4BSD**) version of the **`nvi`** / **`nex`**\n  editor, with a focus on extensibility and new features.\n- [**`Nvi1`**](https://repo.or.cz/nvi.git) (*version* *1.8+*) is the\n  currently maintained *traditional branch* of the new (**4BSD**) version of\n  the **`nvi`** / **`nex`** editor, now developed by *Sven Verdoolaege*.\n"
  },
  {
    "path": "README_pt_BR.md",
    "content": "<!-- SPDX-License-Identifier: BSD-3-Clause -->\n<!-- Copyright (c) 2021-2024 Jeffrey H Johnson -->\n[An English version of this README is available.](README.md)\n\n# OpenVi\n\n***OpenVi*** — Portable *OpenBSD* **`vi`** / **`ex`**\n\n## Índice\n\n<!-- toc -->\n\n- [Visão geral](#visão-geral)\n  * [Porquê?](#porquê)\n    + [Por que não?](#por-que-não)\n- [Construindo](#building)\n  * [Pré-requisitos](#pré-requisitos)\n    + [Pré-requisitos necessários](#pré-requisitos-necessários)\n    + [Pré-requisitos opcionais](#pré-requisitos-opcionais)\n    + [Plataformas suportadas](#plataformas-suportadas)\n      - [Plataformas não suportadas](#plataformas-não-suportadas)\n  * [Compilação](#compilação)\n    + [Especificações da plataforma](#especificações-da-plataforma)\n      - [AIX](#aix)\n      - [NetBSD](#netbsd)\n      - [illumos](#illumos)\n      - [Solaris](#solaris)\n      - [Windows](#windows)\n        * [Cygwin](#cygwin)\n- [Disponibilidade](#disponibilidade)\n  * [Códgo fonte](#código-fonte)\n  * [Pacotes](#pacotes)\n- [Versionamento](#versionamento)\n- [Histórico](#histórico)\n- [Licença](#licença)\n- [Agradecimentos](#agradecimentos)\n- [Projetos similares](#projetos-similares)\n- [Veja também](#veja-também)\n\n<!-- tocstop -->\n\n## Visão geral\n\n***OpenVi*** é uma implementação aprimorada e portátil do Berkeley\n**`vi`** / **`ex`** editor de texto, originalmente desenvolvido por *Bill Joy*.\n\n***OpenVi*** é um fork do editor **`vi`** / **`ex`** incluído no\n*OpenBSD*, que é derivado da versão 1.79 do editor `nvi` originalmente\ndistribuído como parte da *Fourth Berkeley Software Distribution* (**4BSD**).\n\nO editor **`nvi`** foi desenvolvido por *Keith Bostic* da *Computer Systems\nResearch Group* (**CSRG**) na *Universidade da Califórnia, Berkeley*, *Sven\nVerdoolaege* e outros colaboradores. O próprio **`Nvi`** foi derivado de *Steve\nO editor do **`Elvis`** de Kirkendall*.\n\n  ### Porquê?\n\nPor que você deveria usar ***OpenVi*** em vez de ***AnotherVi***?\n\n- Derivado do código do sistema base *OpenBSD* (extensivamente auditado)\n  - Foco na legibilidade, simplicidade e correção da implementação\n  - Aderência às práticas padrão de codificação segura do *OpenBSD*\n    - Usa funções seguras (*por exemplo* `strlcpy`, `snprintf`, `mkstemp`, `pledge`)\n  - Complexidade reduzida para menos defeitos de programa\n- Código-fonte limpo, distribuído sob uma licença BSD permissiva de 3 cláusulas\n  - Código de suporte é distribuído sob a licença ISC (mais permissiva)\n- Principalmente em conformidade com os padrões relevantes (*POSIX*, *SUS*), quando aplicável\n  - Aprimoramentos, comportamentos fora do padrão e novos recursos são conservadoramente\n    e implementados de forma sensata com cuidado para equilibrar as expectativas do usuário,\n    complexidade e precisão histórica\n- Extensões como `bserase`, `expandtab`, `imctrl`, `visibletab`, etc.\n- A compilação requer apenas utilitários *GNU Make* e *POSIX* padrão\n  - Fácil integração com bootstrap incorporado, mínimo ou iterativo\n    ambientes e distribuições (como compilações *Linux From Scratch*)\n- Sem opções de configuração em tempo de compilação ou em tempo de compilação\n  - Configuração de compilação padrão única sem variantes incompatíveis\n  - Nenhum bug específico de configuração resultante de combinações não testadas ou\n    caminhos de código raramente exercitados\n  - Documentação concisa e compreensível; sem variações sutis de plataforma\n- Interface de usuário, script e comportamento de mapa consistentes em todas as plataformas\n- Utiliza o mecanismo de expressão regular estendido baseado em *Spencer* do *OpenBSD*\n  (também adotado por *LLVM*, *Tcl*, etc.) em todos os sistemas suportados\n- Binário único, compacto e independente\n  - Não são necessários arquivos de dados externos em tempo de execução\n  - Sem dependências de bibliotecas externas (além de curses)\n    - Adequado para ligação estática e uso de “resgate” de emergência\n- Todos os vários ajustes, correções, melhorias e limpezas acumuladas\n  mais de 25 anos como parte do sistema base *OpenBSD*\n\n#### Por que não?\n\nEntão, por que você **não** quer usar o ***OpenVi***?\n\nAlguns desses pontos podem ser características desejáveis, dependendo do seu ponto de vista.\n\n- O suporte à internacionalização está faltando no momento\n  - Sem suporte para Unicode / UTF-8 / exibição de caracteres amplos\n    - Os caracteres multibyte são mostrados como bytes individuais, em vez de glifos\n    - O suporte multibyte está planejado, mas infelizmente não é trivial, veja:\n      - Schwarze, I. (2016, 25 de setembro). *Mantenha o suporte a caracteres multibyte\n        simples* [Apresentação em conferência]. Convenção EuroBSDCon 2016,\n        Belgrado, Sérvia.\n        [[pdf:OpenBSD](https://openbsd.org/papers/eurobsdcon2016-utf8.pdf)]\n      - Jun-ichiro itojun Hagino [*KAME Project*] e Yoshitaka Tokugawa\n        [*Projeto WIDE*]. (1999, 6 de junho). *Clones vi multilíngues: passado, agora\n        e o futuro* [Apresentação em conferência]. Em Anais do\n        conferência anual sobre USENIX, Conferência Técnica Anual\n        (*USENIX ATEC '99*). Associação USENIX, Monterey, CA, EUA, página 45.\n        [[doi:10.5555/1268708.1268753](https://dl.acm.org/doi/10.5555/1268708.1268753)],\n        [[resumo: USENIX](https://www.usenix.org/conference/1999-usenix-annual-technical-conference/multilingual-vi-clones-past-now-and-future)]\n        (*legado*)\n  - Sem suporte para texto bidirecional\n  - Sem suporte para localização regional ou tradução de mensagens\n- Manuseio ineficiente de arquivos extremamente grandes (*por exemplo* multi-GB)\n- Sem suporte para realce de sintaxe, conclusão de código com reconhecimento de contexto, código\n  integrações de dobramento ou “*servidor de idiomas*”\n- Nenhuma funcionalidade interativa de gravação e depuração de macro\n- Sem suporte de script avançado (sem *BASIC*, *COBOL*, *JavaScript*, *Lua*,\n  *Perl*, *PHP*, *Python*, *REXX*, *Ruby*, *S-Lang*, *Tcl* ou qualquer outro)\n- Somente interfaces de modo visual baseado em maldições e modo `ex` baseado em linha disponíveis\n  - Sem suporte para X11/Wayland, OpenGL/Vulkan, Neuralink, realidade aumentada/virtual ou qualquer outra interface gráfica do usuário\n\n## Construind\n\n### Pré-requisitos\n\n#### Pré-requisitos necessários\n\n- ambiente **POSIX.1**-**2008**: shell *POSIX* (`sh`) e utilitários,\n  **Awk** (`mawk`, `nawk`), etc.\n- **GNU Make** (versão *3.81* ou posterior)\n- Compilador **C99** (*por exemplo* `xlc`, `suncc`, `clang`, `gcc`, etc.)\n- **Curses** (`ncurses`, *NetBSD* `curses` V8+, `PDCurses` V2.8+,\n  `PDCursesMod`, etc.)\n\n#### Pré-requisitos opcionais\n\n- **pkg-config**\n- **Perl** 5+\n- **Cshell** (`csh`, `tcsh`, etc.)\n- `nroff`, `groff`, etc.\n\n#### Plataformas suportadas\n\n- **OpenVi** é facilmente portátil para a maioria das plataformas com operação semelhante ao *UNIX*\n  sistemas que estão em conformidade com a interface de programação descrita por\n  *IEEE Std 1003.1-2008* e ambiente do usuário descrito por *IEEE Std\n  1003.2-2008*, também conhecido como *POSIX.1-2008* e *POSIX.2-2008*, respectivamente.\n\n- Os seguintes sistemas operacionais são totalmente suportados e testados regularmente\n  usando processadores ix86/AMD64, ARM/AArch64, m68k, MIPS, POWER e RISC-V:\n  - *IBM* **AIX** 7.1+\n  - *Apple* **Darwin** (**macOS** / **Mac OS X**) (*ARM64*, *Intel*, *PowerPC*)\n  - **FreeBSD** 12.3+\n  - Distribuições **GNU**/**Linux** (*glibc*, *musl*)\n  - *illumos* **OpenIndiana** Hipster\n  - **NetBSD** 9+\n  - **OpenBSD** 6.9+\n  - *OracleSolaris** 11+\n  - *Microsoft* **Windows** (*Cygwin*, *Midipix*, *MSYS2*, *WSL*)\n  - **Managarm**\n\n- Os seguintes compiladores são totalmente suportados e testados regularmente:\n  - *LLVM* **Clang** (*BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*,\n    *Windows*) V6+\n  - *AMD* **Optimizing C**/**C++** (*Linux*) V3+\n  - *GNU* **GCC** (*AIX*, *BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*,\n    *Windows*) V4.6+\n  - *IBM* **Advance Toolchain** (*Linux on POWER*) V14.0+\n  - *IBM* **Open XL C**/**C++** (*AIX*) V17.1+\n  - *IBM* **XL C**/**C++** (*AIX*, *Linux*) V16.1+\n  - *Intel* **oneAPI DPC++**/**C++** (*Linux*) V2021+\n  - *Intel* **C Compiler Classic** (*Darwin*, *Linux*) V19.1+\n  - *Oracle* **Developer Studio** (*Linux*, *Solaris*) V12.6+\n  - *PCC* **Portable C Compiler** (*NetBSD*) V1.0.0+\n\nVersões de sistema operacional e compilador mais recentes ou mais antigas, dentro do razoável, devem\ntrabalhar. As versões listadas acima são aquelas regularmente testadas e comprovadamente funcionando.\n\n##### Plataformas não suportadas\n\n- As seguintes plataformas **não** são suportadas atualmente, mas **suporte é\n  planejado** para um lançamento futuro:\n  - **Haicai** Walter\n  - *SGI* **IRIX**\n\nContribuições de usuários para melhorar o suporte da plataforma são bem-vindas.\n\n### Compilação\n\n- A compilação pode ser realizada invocando o GNU Make (geralmente `gmake` ou `make`)\n  do diretório de nível superior de uma versão de código-fonte ou git checkout.\n- O sinalizador `-j N` do GNU Make pode ser usado para paralelizar a compilação, onde `N`\n  é um número inteiro positivo que representa o número de tarefas paralelas solicitadas.\n- As seguintes variáveis ​​de ambiente influenciam a compilação e instalação:\n  - `CC` - Compilador C a ser usado\n    - (*ex.* ​​`CC=gcc`)\n  - `OPTLEVEL` - sinalizadores de otimização\n    - (*por exemplo* `OPTLEVEL=-O2`)\n  - `CFLAGS` - Flags para passar para o compilador C\n    - (*por exemplo* `CFLAGS=\"-Wall -pipe\"`)\n  - `LIBS` - Bibliotecas (substituindo padrões) para passar para o vinculador\n    - (*por exemplo* `LIBS=\"-lpdcurses -lflock\"`)\n  - `LDFLAGS` - Flags para passar para o linker\n    - (*por exemplo* `LDFLAGS=\"-L/lib/path -static\"`)\n  - `V` - Definido para ativar a saída de compilação detalhada\n    - (*por exemplo* `V=1`)\n  - `DEBUG` - Definido para compilar uma compilação de depuração\n    - (*ex.* ​​`DEBUG=1`)\n  - `LGC` - Definido para habilitar a coleta de lixo de tempo de link\n    - (*por exemplo* `LGC=1`)\n  - `LTO` - Definido para ativar a otimização de tempo de link\n    - (*por exemplo* `LTO=1`)\n  - `EXTRA_LIBS` - Bibliotecas extras para vinculação\n    - (*ex.* ​​`EXTRA_LIBS=-lmtmalloc`)\n  - `PREFIX` - Prefixo de diretório para uso com alvos `install` e `uninstall`\n    - (*ex.* ​​`PREFIX=/opt/OpenVi`)\n- Os alvos usuais (`all`, `strip`, `superstrip`, `clean`, `distclean`,\n  `install`, `install-strip`, `uninstall`, `upx`, etc.) estão disponíveis; Reveja\n  o `GNUmakefile` para ver todos os alvos e opções disponíveis.\n\nPor exemplo, para compilar uma compilação de depuração de tamanho agressivamente otimizado, permitindo\notimização de tempo de link e coleta de lixo de tempo de link, usando explicitamente\n*CCG*:\n```sh\nenv CC=gcc OPTLEVEL=-Os LGC=1 LTO=1 gmake sstrip\n```\nou, para compilar detalhadamente uma compilação de depuração, explicitamente usando *Clang*:\n```sh\nenv CC=clang DEBUG=1 V=1 gmake\n```\nPara sistemas com *GNU Make* como `make` (*por exemplo* *GNU/Linux*), compilação básica\ndeve ser bem-sucedido sem nenhuma opção ou configuração adicional necessária:\n```sh\nmake\n```\nCom os privilégios apropriados para manipular arquivos dentro do `PREFIX` escolhido\n(usando `doas`, `sudo`, `su`, etc.), o executável compilado pode ser instalado —\ncomo está ou despojado — usando uma invocação como:\n```sh\ndoas gmake install-strip\n```\nou\n```sh\nsudo env PREFIX=/usr/local make install\n```\n\n#### Especificações da plataforma\n\nAs seções a seguir documentam ***apenas*** diferenças específicas de plataforma e\nnão pretendem ser uma referência geral ou exaustiva. Para instalação de\npacotes de software de pré-requisito ou outra configuração do sistema, consulte a documentação do fornecedor.\n\n##### AIX\n\n- Antes de compilar o ***OpenVi*** no **AIX**, instale as bibliotecas `ncurses` e\n  cabeçalhos. *IBM* fornece os pacotes necessários, `ncurses` e\n  `ncurses-devel`, no formato *RPM* como parte do *AIX Toolbox for Linux e\n  Software livre*. Com as permissões apropriadas (*por exemplo* `root`), estes\n  os pacotes são instaláveis ​​na maioria dos sistemas usando os utilitários `dnf` ou `yum`,\n  por exemplo:\n  ```sh\n  dnf install ncurses ncurses-devel\n  ```\n  ou\n  ```sh\n  yum install ncurses ncurses-devel\n  ```\n  O sistema base *IBM* **AIX** (e **PASE for i**, um sistema de tempo de execução integrado\n  ambiente para aplicativos **AIX** no sistema operacional **IBM i**)\n  fornece `libxcurses`, uma implementação *XPG4*/*XSI* Extended Curses\n  derivado do *AT&T System V*, que **ainda não** é suportado para uso com\n  ***OpenVi***.\n\n- A compilação é suportada usando *IBM* **XL C**/**C++** V16.1+ (`gxlc` ou\n  `xlclang`), *IBM* **Open XL C**/**C++** V17.1+ (`ibm-clang`), ou *GNU*\n  **GCC** (geralmente `gcc`, `gcc-8`, `gcc-9`, `gcc-10`, `gcc-11`):\n  - Otimização de tempo de link (`LTO=1`) requer **Open XL C**/**C++** V17.1+.\n    O *IBM* (*AIX Toolbox*) e *Bull*/*Atos* (*Bull Freeware*) **GCC**\n    pacotes e versões *IBM* **XL C**/**C++** anteriores à V17.1 são\n    **não** habilitado para LTO.\n  - A coleta de lixo em tempo de link (`LGC=1`) **não** é suportada no *IBM*\n    **AIX**.\n  - Uma compilação de 64 bits é o padrão em sistemas operando no modo de 64 bits; para\n    compilação de 32 bits, defina o valor da variável de ambiente `MAIXBITS` para\n    `32` (*ex.* ​​`export MAIXBITS=32`).\n  - O valor da variável de ambiente `CC` deve ser definido como o caminho completo\n    do compilador (*por exemplo* `/opt/freeware/bin/gcc`,\n    `/opt/IBM/xlC/16.1.0/bin/gxlc`, `/opt/IBM/openxlC/17.1.0/bin/ibm-clang`,\n    etc.) a menos que o diretório do compilador já faça parte do `PATH` atual.\n\n- Bloqueio de arquivo (via `flock()` conforme fornecido pela biblioteca `libbsd` do **AIX**) é\n  não funcional; isso será investigado e corrigido em uma versão futura.\n\n- As páginas man ***OpenVi*** são criadas com `mandoc` e requerem conversão\n  antes de usar com o software `man` do **AIX** (que é derivado do *AT&T\n  Sistema UNIX V*.)\n\n##### NetBSD\n\n- Nas instalações do **NetBSD**, as compilações padrão do ***OpenVi*** usam a biblioteca BSD `curses`\n  fornecida pelo sistema base do NetBSD. Para usar `ncurses`, defina\n  os valores de `CFLAGS`, `LDFLAGS` e `CURSESLIB`\n  variáveis ​​de ambiente apropriadamente (*isto é* `CFLAGS=-I/usr/pkg/include`\n  `LDFLAGS=-L/usr/pkg/lib` `CURSESLIB=-lncurses`).\n\n- O linker *LLVM* **LLD** é necessário para otimização de tempo de link (`LTO=1`)\n  usando **Clang**. Está disponível como um pacote instalável (*i.e.* `pkgin\n  instalar lld`).\n\n##### illumos\n\n- Antes de compilar o ***OpenVi*** em uma distribuição **illumos** (*i.e.*\n  **OpenIndiana**), instale as bibliotecas e cabeçalhos `ncurses`.\n  A distribuição **OpenIndiana** fornece o pacote `ncurses` necessário no formato *IPS*.\n  Com as permissões apropriadas (*por exemplo*       `root`), o pacote\n  pode ser instalado usando o utilitário `pkg` **OpenIndiana**, por exemplo:\n  ```sh\n  pkg install ncurses\n  ```\n  O sistema base **OpenIndiana** fornece `libcurses`, um *XPG4*/*XSI*\n  Implementação Extended Curses derivada do *AT&T System V*, que **não**\n  ainda suportado para uso com ***OpenVi***.\n\n- A coleta de lixo em tempo de link (`LGC=1`) **não** é suportada em\n  **OpenIndiana**.\n\n##### Solaris\n\n- Antes de compilar o ***OpenVi*** no *Oracle* **Solaris** 11, instale as\n  bibliotecas e cabeçalhos `ncurses`. *Oracle* fornece o pacote\n  `ncurses` necessário para **Solaris** 11 no formato *IPS*. Com as permissões\n  apropriadas (*por exemplo* `root`), o pacote pode ser instalado usando o utilitário\n  **Solaris** `pkg`, por exemplo:\n  ```sh\n  pkg install ncurses\n  ```\n  O sistema base *Oracle* **Solaris** fornece `libcurses`, uma *XPG4*/*XSI*\n  Implementação Extended Curses derivada do *AT&T System V*, que ainda **não**\n  não é suportado para uso com ***OpenVi***.\n\n- A compilação é suportada usando *Oracle* **Developer Studio**, **GCC** e\n  **Clang**:\n  - Ao usar *Oracle* **Developer Studio**, invoque o compilador como `suncc`\n    ou defina o valor da variável de ambiente `_OSLCC` para `1`.\n  - A otimização de tempo de link (`LTO=1`) é atualmente suportada **apenas** ao usar\n    **GCC** ou **Clang**.\n  - A coleta de lixo em tempo de link (`LGC=1`) **não** é suportada no **Solaris**.\n  - Ao usar o compilador *Oracle* **Developer Studio** (`suncc`), uma build 64-bit\n    é o padrão em sistemas operando no modo de 64 bits; para uma build em 32 bits, defina\n    o valor da variável de ambiente `SUNBITS` para `32` (*ex.*\n    `export SUNBITS=32`).\n\n- O bloqueio de arquivo não está disponível devido à ausência de `flock()` no **Solaris**.\n  Isso será resolvido com o suporte ao bloqueio `fcntl()` estilo *System V* em um\n  lançamento futuro.\n\n##### Windows\n\n- *Microsoft* **Windows** suporta vários desenvolvimentos e tempo de execução, incluindo\n  *MSVC*, *Cygwin*, *Midipix*, *MSYS2*, *UWIN*, o\n  ambiente *Git Bash* e outros. Deve-se tomar cuidado para evitar a mistura de\n  bibliotecas e ferramentas incompatíveis.\n\n###### Cygwin\n\n- Problemas de compilação no ambiente **Cygwin** geralmente são causados ​​por\n  instalações de pacotes incompletos ou interrompidos, ou pela instalação de\n  pacotes usando ferramentas não padrão (*por exemplo* `apt-cyg`), o que pode resultar em\n  arquivos ausentes e links simbólicos pendentes ou ausentes.\n- **Antes** de compilar ***OpenVi*** em **Cygwin**, é *altamente*\n  recomendado que:\n  - Atualize o aplicativo **Cygwin** `setup.exe` para a última versão\n    disponível.\n  - Atualize todos os pacotes instalados usando a nova aplicação **Cygwin** `setup.exe`\n  - Instale os pacotes de pré-requisito necessários (*ou seja,* `make`, `gcc`, `ncurses`,\n    `ncurses-devel`) usando o aplicativo **Cygwin** `setup.exe`.\n  - Invoque o utilitário `cygcheck` (*i.e.* `cygcheck -cv | grep -v \"OK$\"`) para\n    verificar a integridade de todos os pacotes atualmente instalados.\n\n## Disponibilidade\n\n### Código fonte\n\n- [Repositório de origem do GitHub](https://github.com/johnsonjh/OpenVi)\n- [Lançamento da fonte mais recente](http://github.com/johnsonjh/OpenVi/releases/latest)\n\n### Pacotes\n\n**OpenVi** está disponível para usuários de Linux e macOS por meio do\nGerenciador de pacotes [Homebrew](https://formulae.brew.sh/formula/openvi).\n\n[![Homebrew](https://repology.org/badge/version-for-repo/homebrew/openvi.svg)](https://repology.org/project/openvi/versions)\n\n```sh\nbrew install openvi\n```\n\nOutros pacotes de distribuição (não oficiais) podem estar disponíveis.\n\n[![Status da embalagem](https://repology.org/badge/vertical-allrepos/openvi.svg)](https://repology.org/project/openvi/versions)\n\n## Versionamento\n\nO número da versão do ***OpenVi*** é baseado na versão do lançamento do *OpenBSD* correspondente,\nseguido pelo número do lançamento do ***OpenVi***. O comando `version` pode ser usado para exibir esta\ninformação no formato mostrado abaixo.\n\n```text\nVersion 7.0.1 (OpenVi) 10/25/2021.\n```\n\nEsta mensagem indica que o editor em uso é ***OpenVi***, versão **1**,\nderivado do *OpenBSD* versão **7.0**, e é totalmente sincronizado com as versões *OpenBSD* de\n***`vi`***, ***`ex`***, ***`db`***, e ***`regex`***\na partir de **10/25/2021** (*25 de outubro de 2021*).\n\nAlterações **não** derivadas de commits do *OpenBSD* não avançam nesta data.\nNovos lançamentos do *OpenBSD* não redefinem o número do lançamento do ***OpenVi***.\n\n## Histórico\n\n- ***OpenVi***\n  - [`ChangeLog`](/ChangeLog)\n  - [Histórico de lançamento](http://github.com/johnsonjh/OpenVi/releases/)\n  - [Histórico de commits](https://github.com/johnsonjh/OpenVi/commits/master)\n\n- *OpenBSD* ***`vi`***\n  - *OpenBSD* ***`vi`*** / ***`ex`***\n    - [Histórico de commits](https://github.com/openbsd/src/commits/master/usr.bin/vi)\n  - *OpenBSD* ***`db`***\n    - [Histórico de commits](https://github.com/openbsd/src/commits/master/lib/libc/db)\n  - *OpenBSD* ***`regex`***\n    - [Histórico de commits](https://github.com/openbsd/src/commits/master/lib/libc/regex)\n\n## Licença\n\n- ***OpenVi*** é distribuído sob os termos de uma licença **3-clause BSD**.\n- Veja o arquivo [`LICENSE.md`](/LICENSE.md) para a licença completa e\n  termos de distribuição.\n\n## Agradecimentos\n\n- *rqsd* de *Libera.Chat* pela ideia que inspirou o projeto e os testes.\n- [*S. V. Nickolas*](https://github.com/buricco/),\n  [*Jason Stevens*](https://virtuallyfun.com/), e o\n  [***Virtually Fun*** *Discord*](https://discord.gg/HMwevcN) comunidade, pelo\n  suporte e feedback.\n- Dos agradecimentos originais do **`vi`** (por *Bill Joy* e *Mark Horton*):\n  - *Bruce Englar* encorajou o desenvolvimento inicial deste editor de exibição.\n  - *Peter Kessler* ajudou a trazer sanidade ao layout de comando da versão 2.\n  - *Bill Joy* escreveu a versão **1**, versões **2.0** até **2.7** e\n    criou a estrutura que os usuários veem no editor atual.\n  - *Mark Horton* adicionou macros e outros recursos e fez o editor funcionar\n    em um grande número de terminais e sistemas *UNIX*.\n  - Agradecemos o apoio financeiro dos *UUNET Communications Services*.\n\n## Projetos Similares\n\n- [**`Xvi`**](http://martinwguy.github.io/xvi/) de *Martin Guy*,\n  uma versão melhorada de [**`STEVIE`**]( de *Tim Thompson* https://timthompson.com/tjt/stevie/)\n- *S. V. Nickolas*'\n  [**`Sivle`**](https://github.com/buricco/lunaris/tree/main/src/usr.bin/ex), um fork limpo de *Steve Kirkendall*\n  [**`Elvis`**](http://elvis.the-little-red-haired-girl.org/)\n- *Andy Valencia*'s [**`Vim57`**](https://sources.vsta.org:7100/vim57/tree), um\n  fork simplificado da versão 5.7 de *Bram Moolenaar*\n  [**`Vim`**](https://www.vim.org/)\n\n## Veja também\n\n- [*Carsten Kunze*'s **`vi`**](https://github.com/n-t-roff/heirloom-ex-vi/)\n  é um fork atualmente mantido do ramo original (**1BSD**/**2BSD**) do editor **`vi`** / **`ex`**,\n  derivado do editor aprimorado de *Gunnar Ritter* versão do [**tradicional** do editor **`vi`**](http://ex-vi.sourceforge.net/).\n- [**`Nvi2`**](https://github.com/lichray/nvi2) é um *branch de recursos* atualmente mantido da nova versão (**4BSD**) do\n  editor **`nvi`* * / **`nex`**, com foco em extensibilidade e novas funcionalidades.\n- [**`Nvi1`**](https://repo.or.cz/nvi.git) (*versão* *1.8+*) é o *branch tradicional* atualmente mantido do novo (**4BSD* *) versão do\n  editor **`nvi`** / **`nex`**, agora desenvolvido por *Sven Verdoolaege*.\n"
  },
  {
    "path": "REUSE.toml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\nversion = 1\nSPDX-PackageName = \"OpenVi\"\nSPDX-PackageSupplier = \"Jeffrey H. Johnson <johnsonjh.dev@gmail.com>\"\nSPDX-PackageDownloadLocation = \"https://github.com/johnsonjh/OpenVi\"\nannotations = []\n"
  },
  {
    "path": "cl/cl.h",
    "content": "/*      $OpenBSD: cl.h,v 1.12 2021/09/02 11:19:02 schwarze Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)cl.h        10.19 (Berkeley) 9/24/96\n */\n\nextern  volatile sig_atomic_t cl_sigint;\nextern  volatile sig_atomic_t cl_sigterm;\nextern  volatile sig_atomic_t cl_sigwinch;\n\ntypedef struct _cl_private {\n        CHAR_T   ibuf[512];     /* Input keys. */\n\n        int      eof_count;     /* EOF count. */\n\n        struct termios orig;    /* Original terminal values. */\n        struct termios ex_enter;/* Terminal values to enter ex. */\n        struct termios vi_enter;/* Terminal values to enter vi. */\n\n        char    *el;            /* Clear to EOL terminal string. */\n        char    *cup;           /* Cursor movement terminal string. */\n        char    *cuu1;          /* Cursor up terminal string. */\n        char    *rmso, *smso;   /* Inverse video terminal strings. */\n        char    *smcup, *rmcup; /* Terminal start/stop strings. */\n\n#define INDX_HUP        0\n#define INDX_INT        1\n#define INDX_TERM       2\n#define INDX_WINCH      3\n#define INDX_MAX        4       /* Original signal information. */\n        struct sigaction oact[INDX_MAX];\n\n        enum {                  /* Tty group write mode. */\n            TGW_UNKNOWN=0, TGW_SET, TGW_UNSET } tgw;\n\n        enum {                  /* Terminal initialization strings. */\n            TE_SENT=0, TI_SENT } ti_te;\n\n#define CL_IN_EX        0x0001  /* Currently running ex. */\n#define CL_RENAME       0x0002  /* X11 xterm icon/window renamed. */\n#define CL_RENAME_OK    0x0004  /* User wants the windows renamed. */\n#define CL_SCR_EX_INIT  0x0008  /* Ex screen initialized. */\n#define CL_SCR_VI_INIT  0x0010  /* Vi screen initialized. */\n#define CL_STDIN_TTY    0x0020  /* Talking to a terminal. */\n        u_int32_t flags;\n} CL_PRIVATE;\n\n#define CLP(sp)         ((CL_PRIVATE *)((sp)->gp->cl_private))\n#define GCLP(gp)        ((CL_PRIVATE *)(gp)->cl_private)\n\n/* Return possibilities from the keyboard read routine. */\ntypedef enum { INP_OK=0, INP_EOF, INP_ERR, INP_INTR, INP_TIMEOUT } input_t;\n\n/* The screen line relative to a specific window. */\n#define RLNO(sp, lno)   (sp)->woff + (lno)\n\n/* X11 xterm escape sequence to rename the icon/window. */\n#define XTERM_RENAME    \"\\033]0;%s\\007\"\n\n#include \"cl_extern.h\"\n"
  },
  {
    "path": "cl/cl_extern.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nint cl_addstr(SCR *, const char *, size_t);\nint cl_attr(SCR *, scr_attr_t, int);\nint cl_baud(SCR *, unsigned long *);\nint cl_bell(SCR *);\nint cl_clrtoeol(SCR *);\nint cl_cursor(SCR *, size_t *, size_t *);\nint cl_deleteln(SCR *);\nint cl_ex_adjust(SCR *, exadj_t);\nint cl_insertln(SCR *);\nint cl_keyval(SCR *, scr_keyval_t, CHAR_T *, int *);\nint cl_move(SCR *, size_t, size_t);\nint cl_refresh(SCR *, int);\nint cl_rename(SCR *, char *, int);\nint cl_suspend(SCR *, int *);\nvoid cl_usage(void);\nint sig_init(GS *, SCR *);\nint cl_event(SCR *, EVENT *, u_int32_t, int);\nint cl_screen(SCR *, u_int32_t);\nint cl_quit(GS *);\nint cl_getcap(SCR *, char *, char **);\nint cl_term_init(SCR *);\nint cl_term_end(GS *);\nint cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t);\nint cl_optchange(SCR *, int, char *, unsigned long *);\nint cl_omesg(SCR *, CL_PRIVATE *, int);\nint cl_ssize(SCR *, int, size_t *, size_t *, int *);\nint cl_putchar(int);\n"
  },
  {
    "path": "cl/cl_funcs.c",
    "content": "/*      $OpenBSD: cl_funcs.c,v 1.23 2022/12/26 19:16:03 jmc Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#ifndef __solaris__\n# include <sys/types.h>\n# include <sys/time.h>\n#endif /* ifndef __solaris__ */\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <curses.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <term.h>\n#ifdef __solaris__\n# define _XPG7\n# undef __EXTENSIONS__\n# include <sys/procset.h>\n#endif /* ifdef __solaris__ */\n#include <signal.h>\n#include <bsd_termios.h>\n#include <bsd_unistd.h>\n\n#ifdef __solaris__\n# undef GS\n#endif /* ifdef __solaris__ */\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n#include \"cl.h\"\n\n/*\n * cl_addstr --\n *      Add len bytes from the string at the cursor, advancing the cursor.\n *\n * PUBLIC: int cl_addstr(SCR *, const char *, size_t);\n */\nint\ncl_addstr(SCR *sp, const char *str, size_t len)\n{\n        size_t oldy, oldx;\n        int iv;\n\n        (void)oldx;\n        (void)oldy;\n\n        /*\n         * If ex isn't in control, it's the last line of the screen and\n         * it's a split screen, use inverse video.\n         */\n        iv = 0;\n        getyx(stdscr, oldy, oldx);\n        if (!F_ISSET(sp, SC_SCR_EXWROTE) &&\n            oldy == RLNO(sp, LASTLINE(sp)) && IS_SPLIT(sp)) {\n                iv = 1;\n                (void)standout();\n        }\n\n        if (addnstr(str, len) == ERR)\n                return (1);\n\n        if (iv)\n                (void)standend();\n        return (0);\n}\n\n/*\n * cl_attr --\n *      Toggle a screen attribute on/off.\n *\n * PUBLIC: int cl_attr(SCR *, scr_attr_t, int);\n */\nint\ncl_attr(SCR *sp, scr_attr_t attribute, int on)\n{\n        CL_PRIVATE *clp;\n\n        clp = CLP(sp);\n\n        switch (attribute) {\n        case SA_ALTERNATE:\n        /*\n         * !!!\n         * There's a major layering violation here.  The problem is that the\n         * X11 xterm screen has what's known as an \"alternate\" screen.  Some\n         * xterm termcap/terminfo entries include sequences to switch to/from\n         * that alternate screen as part of the ti/te (smcup/rmcup) strings.\n         * Vi runs in the alternate screen, so that you are returned to the\n         * same screen contents on exit from vi that you had when you entered\n         * vi.  Further, when you run :shell, or :!date or similar ex commands,\n         * you also see the original screen contents.  This wasn't deliberate\n         * on vi's part, it's just that it historically sent terminal init/end\n         * sequences at those times, and the addition of the alternate screen\n         * sequences to the strings changed the behavior of vi.  The problem\n         * caused by this is that we don't want to switch back to the alternate\n         * screen while getting a new command from the user, when the user is\n         * continuing to enter ex commands, e.g.:\n         *\n         *      :!date                          <<< switch to original screen\n         *      [Hit return to continue]        <<< prompt user to continue\n         *      :command                        <<< get command from user\n         *\n         * Note that the :command input is a true vi input mode, e.g., input\n         * maps and abbreviations are being done.  So, we need to be able to\n         * switch back into the vi screen mode, without flashing the screen.\n         *\n         * To make matters worse, the curses initscr() and endwin() calls will\n         * do this automatically -- so, this attribute isn't as controlled by\n         * the higher level screen as closely as one might like.\n         */\n        if (on) {\n                if (clp->ti_te != TI_SENT) {\n                        clp->ti_te = TI_SENT;\n                        if (clp->smcup == NULL)\n                                (void)cl_getcap(sp, \"smcup\", &clp->smcup);\n                        if (clp->smcup != NULL)\n                                (void)tputs(clp->smcup, 1, cl_putchar);\n                }\n        } else\n                if (clp->ti_te != TE_SENT) {\n                        clp->ti_te = TE_SENT;\n                        if (clp->rmcup == NULL)\n                                (void)cl_getcap(sp, \"rmcup\", &clp->rmcup);\n                        if (clp->rmcup != NULL)\n                                (void)tputs(clp->rmcup, 1, cl_putchar);\n                        (void)fflush(stdout);\n                }\n        break;\n        case SA_INVERSE:\n                if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {\n                        if (clp->smso == NULL)\n                                return (1);\n                        if (on)\n                                (void)tputs(clp->smso, 1, cl_putchar);\n                        else\n                                (void)tputs(clp->rmso, 1, cl_putchar);\n                        (void)fflush(stdout);\n                } else {\n                        if (on)\n                                (void)standout();\n                        else\n                                (void)standend();\n                }\n                break;\n        default:\n                (void)fflush(stdout);\n                abort();\n        }\n        (void)fflush(stdout);\n        return (0);\n}\n\n/*\n * cl_baud --\n *      Return the baud rate.\n *\n * PUBLIC: int cl_baud(SCR *, unsigned long *);\n */\nint\ncl_baud(SCR *sp, unsigned long *ratep)\n{\n        CL_PRIVATE *clp;\n\n        /*\n         * XXX\n         * There's no portable way to get a \"baud rate\" -- cfgetospeed(3)\n         * returns the value associated with some #define, which we may\n         * never have heard of, or which may be a purely local speed.  Vi\n         * only cares if it's SLOW (w300), slow (w1200) or fast (w9600).\n         * Try and detect the slow ones, and default to fast.\n         */\n        clp = CLP(sp);\n        switch (cfgetospeed(&clp->orig)) {\n        case B50:\n        case B75:\n        case B110:\n        case B134:\n        case B150:\n        case B200:\n        case B300:\n        case B600:\n                *ratep = 600;\n                break;\n        case B1200:\n                *ratep = 1200;\n                break;\n        default:\n                *ratep = 9600;\n                break;\n        }\n        return (0);\n}\n\n/*\n * cl_bell --\n *      Ring the bell/flash the screen.\n *\n * PUBLIC: int cl_bell(SCR *);\n */\nint\ncl_bell(SCR *sp)\n{\n        if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE))\n                (void)!write(STDOUT_FILENO, \"\\07\", 1);           /* \\a */\n        else {\n                /*\n                 * If the screen has not been setup we cannot call\n                 * curses routines yet.\n                 */\n                if (F_ISSET(sp, SC_SCR_VI)) {\n                        /*\n                         * Vi has an edit option which determines if the\n                         * terminal should be beeped or the screen flashed.\n                         */\n                        if (O_ISSET(sp, O_FLASH))\n                                (void)flash();\n                        else\n                                (void)beep();\n                } else if (!O_ISSET(sp, O_FLASH))\n                        (void)!write(STDOUT_FILENO, \"\\07\", 1);\n        }\n        return (0);\n}\n\n/*\n * cl_clrtoeol --\n *      Clear from the current cursor to the end of the line.\n *\n * PUBLIC: int cl_clrtoeol(SCR *);\n */\nint\ncl_clrtoeol(SCR *sp)\n{\n        return (clrtoeol() == ERR);\n}\n\n/*\n * cl_cursor --\n *      Return the current cursor position.\n *\n * PUBLIC: int cl_cursor(SCR *, size_t *, size_t *);\n */\nint\ncl_cursor(SCR *sp, size_t *yp, size_t *xp)\n{\n        /*\n         * The curses screen support splits a single underlying curses screen\n         * into multiple screens to support split screen semantics.  For this\n         * reason the returned value must be adjusted to be relative to the\n         * current screen, and not absolute.  Screens that implement the split\n         * using physically distinct screens won't need this hack.\n         */\n        getyx(stdscr, *yp, *xp);\n        *yp -= sp->woff;\n        return (0);\n}\n\n/*\n * cl_deleteln --\n *      Delete the current line, scrolling all lines below it.\n *\n * PUBLIC: int cl_deleteln(SCR *);\n */\nint\ncl_deleteln(SCR *sp)\n{\n        size_t oldy, oldx;\n\n        /*\n         * This clause is required because the curses screen uses reverse\n         * video to delimit split screens.  If the screen does not do this,\n         * this code won't be necessary.\n         *\n         * If the bottom line was in reverse video, rewrite it in normal\n         * video before it's scrolled.\n         *\n         * Check for the existence of a chgat function; XSI requires it, but\n         * historic implementations of System V curses don't.   If it's not\n         * a #define, we'll fall back to doing it by hand, which is slow but\n         * acceptable.\n         *\n         * By hand means walking through the line, retrieving and rewriting\n         * each character.  Curses has no EOL marker, so track strings of\n         * spaces, and copy the trailing spaces only if there's a non-space\n         * character following.\n         */\n        if (!F_ISSET(sp, SC_SCR_EXWROTE) && IS_SPLIT(sp)) {\n                getyx(stdscr, oldy, oldx);\n                mvchgat(RLNO(sp, LASTLINE(sp)), 0, -1, A_NORMAL, 0, NULL);\n                (void)move(oldy, oldx);\n        }\n\n        /*\n         * The bottom line is expected to be blank after this operation,\n         * and other screens must support that semantic.\n         */\n        return (deleteln() == ERR);\n}\n\n/*\n * cl_ex_adjust --\n *      Adjust the screen for ex.  This routine is purely for standalone\n *      ex programs.  All special purpose, all special case.\n *\n * PUBLIC: int cl_ex_adjust(SCR *, exadj_t);\n */\nint\ncl_ex_adjust(SCR *sp, exadj_t action)\n{\n        CL_PRIVATE *clp;\n        int cnt;\n\n        clp = CLP(sp);\n        switch (action) {\n        case EX_TERM_SCROLL:\n                /* Move the cursor up one line if that's possible. */\n                if (clp->cuu1 != NULL)\n                        (void)tputs(clp->cuu1, 1, cl_putchar);\n                else if (clp->cup != NULL)\n                        (void)tputs(tgoto(clp->cup,\n                            0, LINES - 2), 1, cl_putchar);\n                else\n                        return (0);\n                /* FALLTHROUGH */\n        case EX_TERM_CE:\n                /* Clear the line. */\n                if (clp->el != NULL) {\n                        (void)putchar('\\r');\n                        (void)tputs(clp->el, 1, cl_putchar);\n                } else {\n                        /*\n                         * Historically, ex didn't erase the line, so, if the\n                         * displayed line was only a single glyph, and <eof>\n                         * was more than one glyph, the output would not fully\n                         * overwrite the user's input.  To fix this, output\n                         * the maximum character number of spaces.  Note,\n                         * this won't help if the user entered extra prompt\n                         * or <blank> characters before the command character.\n                         * We'd have to do a lot of work to make that work, and\n                         * it's almost certainly not worth the effort.\n                         */\n                        for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt)\n                                (void)putchar('\\b');\n                        for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt)\n                                (void)putchar(' ');\n                        (void)putchar('\\r');\n                        (void)fflush(stdout);\n                }\n                break;\n        default:\n                abort();\n        }\n        return (0);\n}\n\n/*\n * cl_imctrl --\n *      Control the state of input method by using escape sequences compatible\n *      to Tera Term and RLogin.\n *\n * PUBLIC: void cl_imctrl __P((SCR *, imctrl_t));\n */\nvoid\ncl_imctrl(SCR *sp, imctrl_t action)\n{\n#define TT_IM_OFF       \"\\033[<t\"       /* TTIMEST */\n#define TT_IM_RESTORE   \"\\033[<r\"       /* TTIMERS */\n#define TT_IM_SAVE      \"\\033[<s\"       /* TTIMESV */\n\n        if (!O_ISSET(sp, O_IMCTRL) && action != IMCTRL_INIT)\n                return;\n\n        switch (action) {\n        case IMCTRL_INIT:\n                (void)printf(TT_IM_OFF TT_IM_SAVE);\n                break;\n        case IMCTRL_OFF:\n                (void)printf(TT_IM_SAVE TT_IM_OFF);\n                break;\n        case IMCTRL_ON:\n                (void)printf(TT_IM_RESTORE);\n                break;\n        default:\n                abort();\n        }\n        (void)fflush(stdout);\n}\n\n/*\n * cl_insertln --\n *      Push down the current line, discarding the bottom line.\n *\n * PUBLIC: int cl_insertln(SCR *);\n */\nint\ncl_insertln(SCR *sp)\n{\n        /*\n         * The current line is expected to be blank after this operation,\n         * and the screen must support that semantic.\n         */\n        return (insertln() == ERR);\n}\n\n/*\n * cl_keyval --\n *      Return the value for a special key.\n *\n * PUBLIC: int cl_keyval(SCR *, scr_keyval_t, CHAR_T *, int *);\n */\nint\ncl_keyval(SCR *sp, scr_keyval_t val, CHAR_T *chp, int *dnep)\n{\n        CL_PRIVATE *clp;\n\n        /*\n         * VEOF, VERASE and VKILL are required by POSIX 1003.1-1990,\n         * VWERASE is a 4BSD extension - only use it if available.\n         */\n        clp = CLP(sp);\n        switch (val) {\n        case KEY_VEOF:\n                *dnep = (*chp = clp->orig.c_cc[VEOF]) == _POSIX_VDISABLE;\n                break;\n        case KEY_VERASE:\n                *dnep = (*chp = clp->orig.c_cc[VERASE]) == _POSIX_VDISABLE;\n                break;\n        case KEY_VKILL:\n                *dnep = (*chp = clp->orig.c_cc[VKILL]) == _POSIX_VDISABLE;\n                break;\n#ifdef VWERASE\n        case KEY_VWERASE:\n                *dnep = (*chp = clp->orig.c_cc[VWERASE]) == _POSIX_VDISABLE;\n                break;\n#endif /* ifdef VWERASE */\n        default:\n                *dnep = 1;\n                break;\n        }\n        return (0);\n}\n\n/*\n * cl_move --\n *      Move the cursor.\n *\n * PUBLIC: int cl_move(SCR *, size_t, size_t);\n */\nint\ncl_move(SCR *sp, size_t lno, size_t cno)\n{\n        /* See the comment in cl_cursor. */\n        if (move(RLNO(sp, lno), cno) == ERR) {\n                msgq(sp, M_ERR,\n                    \"Error: move: l(%u) c(%u) o(%u)\", lno, cno, sp->woff);\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * cl_refresh --\n *      Refresh the screen.\n *\n * PUBLIC: int cl_refresh(SCR *, int);\n */\nint\ncl_refresh(SCR *sp, int repaint)\n{\n        CL_PRIVATE *clp;\n\n        clp = CLP(sp);\n        (void)clp;\n\n        /*\n         * If we received a killer signal, we're done, there's no point\n         * in refreshing the screen.\n         */\n        if (cl_sigterm)\n                return (0);\n\n        /*\n         * If repaint is set, the editor is telling us that we don't know\n         * what's on the screen, so we have to repaint from scratch.\n         *\n         * In the curses library, doing wrefresh(curscr) is okay, but the\n         * screen flashes when we then apply the refresh() to bring it up\n         * to date.  So, use clearok().\n         */\n        if (repaint)\n                clearok(curscr, 1);\n        return (refresh() == ERR);\n}\n\n/*\n * cl_rename --\n *      Rename the file.\n *\n * PUBLIC: int cl_rename(SCR *, char *, int);\n */\nint\ncl_rename(SCR *sp, char *name, int on)\n{\n        GS *gp;\n        CL_PRIVATE *clp;\n        char *ttype;\n\n        gp = sp->gp;\n        clp = CLP(sp);\n\n        ttype = OG_STR(gp, GO_TERM);\n\n        /*\n         * XXX\n         * We can only rename windows for xterm.\n         */\n        if (on) {\n                if (F_ISSET(clp, CL_RENAME_OK) &&\n                    !strncmp(ttype, \"xterm\", sizeof(\"xterm\") - 1)) {\n                        F_SET(clp, CL_RENAME);\n                        (void)printf(XTERM_RENAME, name);\n                        (void)fflush(stdout);\n                }\n        } else\n                if (F_ISSET(clp, CL_RENAME)) {\n                        F_CLR(clp, CL_RENAME);\n                        (void)printf(XTERM_RENAME, ttype);\n                        (void)fflush(stdout);\n                }\n        return (0);\n}\n\n/*\n * cl_suspend --\n *      Suspend a screen.\n *\n * PUBLIC: int cl_suspend(SCR *, int *);\n */\nint\ncl_suspend(SCR *sp, int *allowedp)\n{\n        struct termios t;\n        CL_PRIVATE *clp;\n        size_t oldy, oldx;\n        int changed;\n\n        clp = CLP(sp);\n        *allowedp = 1;\n\n        /*\n         * The ex implementation of this function isn't needed by screens not\n         * supporting ex commands that require full terminal canonical mode\n         * (e.g. :suspend).\n         *\n         * The vi implementation of this function isn't needed by screens not\n         * supporting vi process suspension, i.e. any screen that isn't backed\n         * by a UNIX shell.\n         *\n         * Setting allowedp to 0 will cause the editor to reject the command.\n         */\n        if (F_ISSET(sp, SC_EX)) {\n                /* Save the terminal settings, and restore the original ones. */\n                if (F_ISSET(clp, CL_STDIN_TTY)) {\n                        (void)tcgetattr(STDIN_FILENO, &t);\n                        (void)tcsetattr(STDIN_FILENO,\n                            TCSASOFT | TCSADRAIN, &clp->orig);\n                }\n\n                /* Stop the process group. */\n                (void)kill(0, SIGTSTP);\n\n                /* Time passes ... */\n\n                /* Restore terminal settings. */\n                if (F_ISSET(clp, CL_STDIN_TTY))\n                        (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t);\n                return (0);\n        }\n\n        /*\n         * Move to the lower left-hand corner of the screen.\n         *\n         * XXX\n         * Not sure this is necessary in System V implementations, but it\n         * shouldn't hurt.\n         */\n        getyx(stdscr, oldy, oldx);\n        (void)move(LINES - 1, 0);\n        (void)refresh();\n\n        /*\n         * Temporarily end the screen.  System V introduced a semantic where\n         * endwin() could be restarted.  We use it because restarting curses\n         * from scratch often fails in System V.\n         */\n\n        /* Restore the cursor keys to normal mode. */\n        (void)keypad(stdscr, FALSE);\n\n        /* Restore the window name. */\n        (void)cl_rename(sp, NULL, 0);\n\n        (void)endwin();\n\n        /*\n         * XXX\n         * Restore the original terminal settings.  This is bad -- the\n         * reset can cause character loss from the tty queue.  However,\n         * we can't call endwin() in BSD curses implementations, and too\n         * many System V curses implementations don't get it right.\n         */\n        (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig);\n\n        /* Stop the process group. */\n        (void)kill(0, SIGTSTP);\n\n        /* Time passes ... */\n\n        /*\n         * If we received a killer signal, we're done.  Leave everything\n         * unchanged.  In addition, the terminal has already been reset\n         * correctly, so leave it alone.\n         */\n        if (cl_sigterm) {\n                F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT);\n                return (0);\n        }\n\n        /* Set the window name. */\n        (void)cl_rename(sp, sp->frp->name, 1);\n\n        /* Put the cursor keys into application mode. */\n        (void)keypad(stdscr, TRUE);\n\n        /* Refresh and repaint the screen. */\n        (void)move(oldy, oldx);\n        (void)cl_refresh(sp, 1);\n\n        /* If the screen changed size, set the SIGWINCH bit. */\n        if (cl_ssize(sp, 1, NULL, NULL, &changed))\n                return (1);\n        if (changed)\n                cl_sigwinch = 1;\n\n        return (0);\n}\n\n/*\n * cl_usage --\n *      Print out the curses usage messages.\n *\n * PUBLIC: void cl_usage(void);\n */\nvoid\ncl_usage()\n{\n        switch (pmode) {\n        case MODE_EX:\n                (void)fprintf(stderr, \"Usage: \"\n#ifdef DEBUG\n                    \"ex [ -FRrSsv ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ -T tracefile ] [ file ... ]\\n\");\n#else\n                    \"ex [ -FRrSsv ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ file ... ]\\n\");\n#endif /* ifdef DEBUG */\n                break;\n        case MODE_VI:\n                (void)fprintf(stderr, \"Usage: \"\n#ifdef DEBUG\n                    \"vi [ -eFRrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ -T tracefile ] [ file ... ]\\n\");\n#else\n                    \"vi [ -eFRrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ file ... ]\\n\");\n#endif /* ifdef DEBUG */\n                break;\n        case MODE_VIEW:\n                (void)fprintf(stderr, \"Usage: \"\n#ifdef DEBUG\n                    \"view [ -eFrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ -T tracefile ] [ file ... ]\\n\");\n#else\n                    \"view [ -eFrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ file ... ]\\n\");\n#endif /* ifdef DEBUG */\n                break;\n        }\n}\n\n#ifdef DEBUG\n/*\n * gdbrefresh --\n *      Stub routine so can flush out curses screen changes using gdb.\n */\nint\ngdbrefresh()\n{\n        refresh();\n        return (0);             /* XXX Convince gdb to run it. */\n}\n#endif /* ifdef DEBUG */\n"
  },
  {
    "path": "cl/cl_main.c",
    "content": "/*      $OpenBSD: cl_main.c,v 1.36 2021/10/24 21:24:17 deraadt Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <curses.h>\n#include <bsd_err.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <paths.h>\n#ifdef __solaris__\n# define _XPG7\n# undef __EXTENSIONS__\n# define __EXTENSIONS__\n# include <sys/procset.h>\n#endif /* ifdef __solaris__ */\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <term.h>\n#include <bsd_termios.h>\n#include <bsd_unistd.h>\n\n#include \"errc.h\"\n\n#ifdef __solaris__\n# undef GS\n#endif /* ifdef __solaris__ */\n#include \"../common/common.h\"\n#include \"cl.h\"\n\n#undef open\n\nvoid cl_imctrl (SCR *, imctrl_t);\n\nGS *__global_list;                              /* GLOBAL: List of screens. */\n\nvolatile sig_atomic_t cl_sigint;\nvolatile sig_atomic_t cl_sigterm;\nvolatile sig_atomic_t cl_sigwinch;\n\nstatic void        cl_func_std(GS *);\nstatic CL_PRIVATE *cl_init(GS *);\nstatic GS         *gs_init(void);\nstatic int         setsig(int, struct sigaction *, void (*)(int));\nstatic void        sig_end(GS *);\nstatic void        term_init(char *);\n\n/*\n * main --\n *      This is the main loop for the standalone curses editor.\n */\nint\nmain(int argc, char *argv[])\n{\n        CL_PRIVATE *clp;\n        GS *gp;\n        size_t rows, cols;\n        int rval;\n        char *ttype;\n\n        /* Create and initialize the global structure. */\n        __global_list = gp = gs_init();\n\n        /* Create and initialize the CL_PRIVATE structure. */\n        clp = cl_init(gp);\n\n        /*\n         * Initialize the terminal information.\n         *\n         * We have to know what terminal it is from the start, since we may\n         * have to use termcap/terminfo to find out how big the screen is.\n         */\n        ttype = getenv(\"TERM\");\n        if (ttype == NULL)\n                ttype = \"unknown\";\n        term_init(ttype);\n        ttype = getenv(\"TERM\");\n\n        /* Add the terminal type to the global structure. */\n        if ((OG_D_STR(gp, GO_TERM) =\n            OG_STR(gp, GO_TERM) = strdup(ttype)) == NULL)\n                openbsd_err(1, NULL);\n\n        /* Figure out how big the screen is. */\n        if (cl_ssize(NULL, 0, &rows, &cols, NULL))\n                exit (1);\n\n        /* Add the rows and columns to the global structure. */\n        OG_VAL(gp, GO_LINES) = OG_D_VAL(gp, GO_LINES) = rows;\n        OG_VAL(gp, GO_COLUMNS) = OG_D_VAL(gp, GO_COLUMNS) = cols;\n\n        /* Ex wants stdout to be buffered. */\n        (void)setvbuf(stdout, NULL, _IOFBF, 0);\n\n        /* Start catching signals. */\n        if (sig_init(gp, NULL))\n                exit (1);\n\n        /* Run ex/vi. */\n        rval = editor(gp, argc, argv);\n\n        /* Clean up signals. */\n        sig_end(gp);\n\n        /* Clean up the terminal. */\n        (void)cl_quit(gp);\n\n        /*\n         * XXX\n         * Reset the O_MESG option.\n         */\n        if (clp->tgw != TGW_UNKNOWN)\n                (void)cl_omesg(NULL, clp, clp->tgw == TGW_SET);\n\n        /*\n         * XXX\n         * Reset the X11 xterm icon/window name.\n         */\n        if (F_ISSET(clp, CL_RENAME)) {\n                (void)printf(XTERM_RENAME, ttype);\n                (void)fflush(stdout);\n        }\n\n        /* If a killer signal arrived, pretend we just got it. */\n        if (cl_sigterm) {\n                (void)signal(cl_sigterm, SIG_DFL);\n                (void)kill(getpid(), cl_sigterm);\n                /* NOTREACHED */\n        }\n\n        /* Free the global and CL private areas. */\n#if defined(DEBUG) || defined(PURIFY)\n        free(clp);\n        free(gp);\n#endif /* if defined(DEBUG) || defined(PURIFY) */\n\n        exit (rval);\n}\n\n/*\n * gs_init --\n *      Create and partially initialize the GS structure.\n */\nstatic GS *\ngs_init(void)\n{\n        GS *gp;\n\n        /* Allocate the global structure. */\n        if ((gp = calloc(1, sizeof(GS))) == NULL)\n                openbsd_err(1, NULL);\n\n        return (gp);\n}\n\n/*\n * cl_init --\n *      Create and partially initialize the CL structure.\n */\nstatic CL_PRIVATE *\ncl_init(GS *gp)\n{\n        CL_PRIVATE *clp;\n        int fd;\n\n        /* Allocate the CL private structure. */\n        if ((clp = calloc(1, sizeof(CL_PRIVATE))) == NULL)\n                openbsd_err(1, NULL);\n        gp->cl_private = clp;\n\n        /*\n         * Set the CL_STDIN_TTY flag.  It's purpose is to avoid setting\n         * and resetting the tty if the input isn't from there.  We also\n         * use the same test to determine if we're running a script or\n         * not.\n         */\n        if (isatty(STDIN_FILENO))\n                F_SET(clp, CL_STDIN_TTY);\n        else\n                F_SET(gp, G_SCRIPTED);\n\n        /*\n         * We expect that if we've lost our controlling terminal that the\n         * open() (but not the tcgetattr()) will fail.\n         */\n        if (F_ISSET(clp, CL_STDIN_TTY)) {\n                if (tcgetattr(STDIN_FILENO, &clp->orig) == -1)\n                        goto tcfail;\n        } else if ((fd = open(_PATH_TTY, O_RDONLY)) != -1) {\n                if (tcgetattr(fd, &clp->orig) == -1)\ntcfail:                 openbsd_err(1, \"tcgetattr\");\n                (void)close(fd);\n        }\n\n        /* Initialize the list of curses functions. */\n        cl_func_std(gp);\n\n        return (clp);\n}\n\n/*\n * term_init --\n *      Initialize terminal information.\n */\nstatic void\nterm_init(char *ttype)\n{\n        int err;\n\n        /* Set up the terminal database information. */\n        setupterm(ttype, STDOUT_FILENO, &err);\n        if (err == 0) {\n                if (strlen(ttype) == 0)\n                        ttype = \"unknown\";\n                (void)fprintf(stderr,\n                        \"%s: Unknown terminal type '%s'; falling back to 'vt100'\\n\",\n                                bsd_getprogname(), ttype);\n                sleep(5);\n                setenv(\"TERM\", \"vt100\", 1);\n                setupterm(\"vt100\", STDOUT_FILENO, &err);\n        }\n        switch (err) {\n        case -1:\n                openbsd_errx(1, \"No terminal database found\");\n        case 0:\n                openbsd_errx(1, \"%s: unknown terminal type\", ttype);\n        }\n}\n\nstatic void\nh_int(int signo)\n{\n        cl_sigint = 1;\n}\n\nstatic void\nh_term(int signo)\n{\n        cl_sigterm = signo;\n}\n\nstatic void\nh_winch(int signo)\n{\n        cl_sigwinch = 1;\n}\n\n/*\n * sig_init --\n *      Initialize signals.\n *\n * PUBLIC: int sig_init(GS *, SCR *);\n */\nint\nsig_init(GS *gp, SCR *sp)\n{\n        CL_PRIVATE *clp;\n\n        clp = GCLP(gp);\n\n        cl_sigint   = 0;\n        cl_sigterm  = 0;\n        cl_sigwinch = 0;\n\n        if (sp == NULL) {\n                if (setsig(SIGHUP,   &clp->oact[INDX_HUP],   h_term) ||\n                    setsig(SIGINT,   &clp->oact[INDX_INT],   h_int)  ||\n                    setsig(SIGQUIT,  &clp->oact[INDX_INT],   h_int)  ||\n                    setsig(SIGTERM,  &clp->oact[INDX_TERM],  h_term) ||\n                    setsig(SIGWINCH, &clp->oact[INDX_WINCH], h_winch)\n                    )\n                        openbsd_err(1, NULL);\n        } else\n                if (setsig(SIGHUP,   NULL, h_term) ||\n                    setsig(SIGINT,   NULL, h_int)  ||\n                    setsig(SIGQUIT,  NULL, h_int)  ||\n                    setsig(SIGTERM,  NULL, h_term) ||\n                    setsig(SIGWINCH, NULL, h_winch)\n                    ) {\n                        msgq(sp, M_SYSERR, \"signal-reset\");\n                }\n        return (0);\n}\n\n/*\n * setsig --\n *      Set a signal handler.\n */\nstatic int\nsetsig(int signo, struct sigaction *oactp, void (*handler)(int))\n{\n        struct sigaction act;\n\n        /*\n         * Use sigaction(2), not signal(3), since we don't always want to\n         * restart system calls.  The example is when waiting for a command\n         * mode keystroke and SIGWINCH arrives.  Besides, you can't portably\n         * restart system calls (thanks, POSIX!).\n         */\n        act.sa_handler = handler;\n        sigemptyset(&act.sa_mask);\n\n        act.sa_flags = 0;\n        return (sigaction(signo, &act, oactp));\n}\n\n/*\n * sig_end --\n *      End signal setup.\n */\nstatic void\nsig_end(GS *gp)\n{\n        CL_PRIVATE *clp;\n\n        clp = GCLP(gp);\n        (void)sigaction(SIGHUP,   NULL, &clp->oact[INDX_HUP]);\n        (void)sigaction(SIGINT,   NULL, &clp->oact[INDX_INT]);\n        (void)sigaction(SIGQUIT,  NULL, &clp->oact[INDX_INT]);\n        (void)sigaction(SIGTERM,  NULL, &clp->oact[INDX_TERM]);\n        (void)sigaction(SIGWINCH, NULL, &clp->oact[INDX_WINCH]);\n}\n\n/*\n * cl_func_std --\n *      Initialize the standard curses functions.\n */\nstatic void\ncl_func_std(GS *gp)\n{\n        gp->scr_addstr    = cl_addstr;\n        gp->scr_attr      = cl_attr;\n        gp->scr_baud      = cl_baud;\n        gp->scr_bell      = cl_bell;\n        gp->scr_busy      = NULL;\n        gp->scr_clrtoeol  = cl_clrtoeol;\n        gp->scr_cursor    = cl_cursor;\n        gp->scr_deleteln  = cl_deleteln;\n        gp->scr_event     = cl_event;\n        gp->scr_ex_adjust = cl_ex_adjust;\n        gp->scr_fmap      = cl_fmap;\n        gp->scr_imctrl    = cl_imctrl;\n        gp->scr_insertln  = cl_insertln;\n        gp->scr_keyval    = cl_keyval;\n        gp->scr_move      = cl_move;\n        gp->scr_msg       = NULL;\n        gp->scr_optchange = cl_optchange;\n        gp->scr_refresh   = cl_refresh;\n        gp->scr_rename    = cl_rename;\n        gp->scr_screen    = cl_screen;\n        gp->scr_suspend   = cl_suspend;\n        gp->scr_usage     = cl_usage;\n}\n"
  },
  {
    "path": "cl/cl_read.c",
    "content": "/*      $OpenBSD: cl_read.c,v 1.23 2021/09/02 11:19:02 schwarze Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n#include <sys/ioctl.h>\n\n#include <bitstring.h>\n#include <curses.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#ifdef __solaris__\n# define _XPG7\n# undef __EXTENSIONS__\n# define __EXTENSIONS__\n# include <sys/procset.h>\n#endif /* ifdef __solaris__ */\n#include <poll.h>\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#if defined(__solaris__)\n# define __EXTENSIONS__\n# include <termios.h>\n# include <sys/termios.h>\n#endif /* if defined(__solaris__) */\n\n#include <bsd_termios.h>\n#include <bsd_unistd.h>\n\n#ifdef __solaris__\n# undef GS\n#endif /* ifdef __solaris__ */\n#include \"../common/common.h\"\n#include \"../ex/script.h\"\n#include \"cl.h\"\n\n#undef open\n\n#if defined(_AIX) || defined(__illumos__)\n# undef lines\n# undef columns\n#endif /* if defined(_AIX) || defined(__illumos__) */\n\nstatic input_t  cl_read(SCR *,\n                    u_int32_t, CHAR_T *, size_t, int *, struct timeval *);\nstatic int      cl_resize(SCR *sp, size_t lines, size_t columns);\n\n/*\n * cl_event --\n *      Return a single event.\n *\n * PUBLIC: int cl_event(SCR *, EVENT *, u_int32_t, int);\n */\nint\ncl_event(SCR *sp, EVENT *evp, u_int32_t flags, int ms)\n{\n        struct timeval t, *tp;\n        CL_PRIVATE *clp;\n        size_t lines, columns;\n        int changed, nr;\n\n        /*\n         * Queue signal based events.  We never clear SIGHUP or SIGTERM events,\n         * so that we just keep returning them until the editor dies.\n         */\n        clp = CLP(sp);\nretest: if (LF_ISSET(EC_INTERRUPT) || cl_sigint) {\n                if (cl_sigint) {\n                        cl_sigint = 0;\n                        evp->e_event = E_INTERRUPT;\n                } else\n                        evp->e_event = E_TIMEOUT;\n                return (0);\n        }\n        switch (cl_sigterm) {\n        case SIGHUP:\n                evp->e_event = E_SIGHUP;\n                return (0);\n        case SIGTERM:\n                evp->e_event = E_SIGTERM;\n                return (0);\n        default:\n                break;\n        }\n        if (cl_sigwinch) {\n                cl_sigwinch = 0;\n                if (cl_ssize(sp, 1, &lines, &columns, &changed))\n                        return (1);\n                if (changed) {\n                        (void)cl_resize(sp, lines, columns);\n                        evp->e_event = E_WRESIZE;\n                        return (0);\n                }\n                /* No real change, ignore the signal. */\n        }\n\n        /* Set timer. */\n        if (ms == 0)\n                tp = NULL;\n        else {\n                t.tv_sec = ms / 1000;\n                t.tv_usec = (ms % 1000) * 1000;\n                tp = &t;\n        }\n\n        /* Read input characters. */\n        switch (cl_read(sp, LF_ISSET(EC_QUOTED | EC_RAW),\n            clp->ibuf, sizeof(clp->ibuf), &nr, tp)) {\n        case INP_OK:\n                evp->e_csp = clp->ibuf;\n                evp->e_len = nr;\n                evp->e_event = E_STRING;\n                break;\n        case INP_EOF:\n                evp->e_event = E_EOF;\n                break;\n        case INP_ERR:\n                evp->e_event = E_ERR;\n                break;\n        case INP_INTR:\n                goto retest;\n        case INP_TIMEOUT:\n                evp->e_event = E_TIMEOUT;\n                break;\n        default:\n                abort();\n        }\n        return (0);\n}\n\n/*\n * cl_read --\n *      Read characters from the input.\n */\nstatic input_t\ncl_read(SCR *sp, u_int32_t flags, CHAR_T *bp, size_t blen, int *nrp,\n    struct timeval *tp)\n{\n        struct termios term1, term2;\n        CL_PRIVATE *clp;\n        GS *gp;\n        struct pollfd pfd[1];\n        input_t rval;\n        int nr, term_reset, timeout;\n\n        gp = sp->gp;\n        clp = CLP(sp);\n        term_reset = 0;\n\n        /*\n         * 1: A read from a file or a pipe.  In this case, the reads\n         *    never timeout regardless.  This means that we can hang\n         *    when trying to complete a map, but we're going to hang\n         *    on the next read anyway.\n         */\n        if (!F_ISSET(clp, CL_STDIN_TTY)) {\n                switch (nr = read(STDIN_FILENO, bp, blen)) {\n                case 0:\n                        return (INP_EOF);\n                case -1:\n                        goto err;\n                default:\n                        *nrp = nr;\n                        return (INP_OK);\n                }\n                /* NOTREACHED */\n        }\n\n        /*\n         * 2: A read with an associated timeout, e.g., trying to complete\n         *    a map sequence.  If input exists, we fall into #3.\n         */\ntty_retry:\n        if (tp != NULL) {\n                pfd[0].fd = STDIN_FILENO;\n                pfd[0].events = POLLIN;\n                timeout = tp ? (tp->tv_sec * 1000) + (tp->tv_usec / 1000) : 0;\n                switch (poll(pfd, 1, timeout)) {\n                case 0:\n                        return (INP_TIMEOUT);\n                case -1:\n                        goto err;\n                default:\n                        break;\n                }\n        }\n\n        /*\n         * The user can enter a key in the editor to quote a character.  If we\n         * get here and the next key is supposed to be quoted, do what we can.\n         * Reset the tty so that the user can enter a ^C, ^Q, ^S.  There's an\n         * obvious race here, when the key has already been entered, but there's\n         * nothing that we can do to fix that problem.\n         *\n         * The editor can ask for the next literal character even thought it's\n         * generally running in line-at-a-time mode.  Do what we can.\n         */\n        if (LF_ISSET(EC_QUOTED | EC_RAW) && !tcgetattr(STDIN_FILENO, &term1)) {\n                term_reset = 1;\n                if (LF_ISSET(EC_QUOTED)) {\n                        term2 = term1;\n                        term2.c_lflag &= ~ISIG;\n                        term2.c_iflag &= ~(IXON | IXOFF);\n                        (void)tcsetattr(STDIN_FILENO,\n                            TCSASOFT | TCSADRAIN, &term2);\n                } else\n                        (void)tcsetattr(STDIN_FILENO,\n                            TCSASOFT | TCSADRAIN, &clp->vi_enter);\n        }\n\n        /*\n         * 3: Wait for input.\n         *\n         * Select on the command input and scripting window file descriptors.\n         * It's ugly that we wait on scripting file descriptors here, but it's\n         * the only way to keep from locking out scripting windows.\n         */\n        if (F_ISSET(gp, G_SCRWIN)) {\n                if (sscr_check_input(sp))\n                        goto err;\n        }\n\n        /*\n         * 4: Read the input.\n         *\n         * !!!\n         * What's going on here is some scary stuff.  Ex runs the terminal in\n         * canonical mode.  So, the <newline> character terminating a line of\n         * input is returned in the buffer, but a trailing <EOF> character is\n         * not similarly included.  As ex uses 0<EOF> and ^<EOF> as autoindent\n         * commands, it has to see the trailing <EOF> characters to determine\n         * the difference between the user entering \"0ab\" and \"0<EOF>ab\".  We\n         * leave an extra slot in the buffer, so that we can add a trailing\n         * <EOF> character if the buffer isn't terminated by a <newline>.  We\n         * lose if the buffer is too small for the line and exactly N characters\n         * are entered followed by an <EOF> character.\n         */\n#define ONE_FOR_EOF     1\n        switch (nr = read(STDIN_FILENO, bp, blen - ONE_FOR_EOF)) {\n        case  0:                                /* EOF. */\n                /*\n                 * ^D in canonical mode returns a read of 0, i.e. EOF.  EOF is\n                 * a valid command, but we don't want to loop forever because\n                 * the terminal driver is returning EOF because the user has\n                 * disconnected. The editor will almost certainly try to write\n                 * something before this fires, which should kill us, but You\n                 * Never Know.\n                 */\n                if (++clp->eof_count < 50) {\n                        bp[0] = clp->orig.c_cc[VEOF];\n                        *nrp = 1;\n                        rval = INP_OK;\n\n                } else\n                        rval = INP_EOF;\n                break;\n        case -1:                                /* Error or interrupt. */\nerr:            if (errno == EINTR)\n                        rval = INP_INTR;\n                else if (errno == EAGAIN)\n                        goto tty_retry;\n                else {\n                        rval = INP_ERR;\n                        msgq(sp, M_SYSERR, \"input\");\n                }\n                break;\n        default:                                /* Input characters. */\n                if (F_ISSET(sp, SC_EX) && bp[nr - 1] != '\\n')\n                        bp[nr++] = clp->orig.c_cc[VEOF];\n                *nrp = nr;\n                clp->eof_count = 0;\n                rval = INP_OK;\n                break;\n        }\n\n        /* Restore the terminal state if it was modified. */\n        if (term_reset)\n                (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term1);\n        return (rval);\n}\n\n/*\n * cl_resize --\n *      Reset the options for a resize event.\n */\nstatic int\ncl_resize(SCR *sp, size_t lines, size_t columns)\n{\n        ARGS *argv[2], a, b;\n        char b1[1024];\n\n        a.bp = b1;\n        b.bp = NULL;\n        a.len = b.len = 0;\n        argv[0] = &a;\n        argv[1] = &b;\n\n        (void)snprintf(b1, sizeof(b1), \"lines=%lu\", (unsigned long)lines);\n        a.len = strlen(b1);\n        if (opts_set(sp, argv, NULL))\n                return (1);\n        (void)snprintf(b1, sizeof(b1), \"columns=%lu\", (unsigned long)columns);\n        a.len = strlen(b1);\n        if (opts_set(sp, argv, NULL))\n                return (1);\n        return (0);\n}\n"
  },
  {
    "path": "cl/cl_screen.c",
    "content": "/*      $OpenBSD: cl_screen.c,v 1.28 2017/04/18 01:45:33 deraadt Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#if defined(__solaris__)\n# define __EXTENSIONS__\n# include <termios.h>\n# include <sys/termios.h>\n#endif /* if defined(__solaris__) */\n\n#include <curses.h>\n#include <term.h>\n#include <bsd_termios.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"cl.h\"\n\nstatic int      cl_ex_end(GS *);\nstatic int      cl_ex_init(SCR *);\nstatic void     cl_freecap(CL_PRIVATE *);\nstatic int      cl_vi_end(GS *);\nstatic int      cl_vi_init(SCR *);\nstatic int      cl_putenv(char *, char *, unsigned long);\n\n/*\n * cl_screen --\n *      Switch screen types.\n *\n * PUBLIC: int cl_screen(SCR *, u_int32_t);\n */\nint\ncl_screen(SCR *sp, u_int32_t flags)\n{\n        CL_PRIVATE *clp;\n        GS *gp;\n\n        gp = sp->gp;\n        clp = CLP(sp);\n\n        /* See if the current information is incorrect. */\n        if (F_ISSET(gp, G_SRESTART)) {\n                if ((!F_ISSET(sp, SC_SCR_EX | SC_SCR_VI) ||\n                    resizeterm(O_VAL(sp, O_LINES), O_VAL(sp, O_COLUMNS))) &&\n                    cl_quit(gp))\n                        return (1);\n                F_CLR(gp, G_SRESTART);\n        }\n\n        /* See if we're already in the right mode. */\n        if ((LF_ISSET(SC_EX) && F_ISSET(sp, SC_SCR_EX)) ||\n            (LF_ISSET(SC_VI) && F_ISSET(sp, SC_SCR_VI)))\n                return (0);\n\n        /*\n         * Fake leaving ex mode.\n         *\n         * We don't actually exit ex or vi mode unless forced (e.g. by a window\n         * size change).  This is because many curses implementations can't be\n         * called twice in a single program.  Plus, it's faster.  If the editor\n         * \"leaves\" vi to enter ex, when it exits ex we'll just fall back into\n         * vi.\n         */\n        if (F_ISSET(sp, SC_SCR_EX))\n                F_CLR(sp, SC_SCR_EX);\n\n        /*\n         * Fake leaving vi mode.\n         *\n         * Clear out the rest of the screen if we're in the middle of a split\n         * screen.  Move to the last line in the current screen -- this makes\n         * terminal scrolling happen naturally.  Note: *don't* move past the\n         * end of the screen, as there are ex commands (e.g., :read ! cat file)\n         * that don't want to.  Don't clear the info line, its contents may be\n         * valid, e.g. :file|append.\n         */\n        if (F_ISSET(sp, SC_SCR_VI)) {\n                F_CLR(sp, SC_SCR_VI);\n\n                if (TAILQ_NEXT(sp, q)) {\n                        (void)move(RLNO(sp, sp->rows), 0);\n                        clrtobot();\n                }\n                (void)move(RLNO(sp, sp->rows) - 1, 0);\n                refresh();\n        }\n\n        /* Enter the requested mode. */\n        if (LF_ISSET(SC_EX)) {\n                if (cl_ex_init(sp))\n                        return (1);\n                F_SET(clp, CL_IN_EX | CL_SCR_EX_INIT);\n\n                /*\n                 * If doing an ex screen for ex mode, move to the last line\n                 * on the screen.\n                 */\n                if (F_ISSET(sp, SC_EX) && clp->cup != NULL)\n                        tputs(tgoto(clp->cup,\n                            0, O_VAL(sp, O_LINES) - 1), 1, cl_putchar);\n        } else {\n                if (cl_vi_init(sp))\n                        return (1);\n                F_CLR(clp, CL_IN_EX);\n                F_SET(clp, CL_SCR_VI_INIT);\n        }\n        return (0);\n}\n\n/*\n * cl_quit --\n *      Shutdown the screens.\n *\n * PUBLIC: int cl_quit(GS *);\n */\nint\ncl_quit(GS *gp)\n{\n        CL_PRIVATE *clp;\n        int rval;\n\n        rval = 0;\n        clp = GCLP(gp);\n\n        /*\n         * If we weren't really running, ignore it.  This happens if the\n         * screen changes size before we've called curses.\n         */\n        if (!F_ISSET(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT))\n                return (0);\n\n        /* Clean up the terminal mappings. */\n        if (cl_term_end(gp))\n                rval = 1;\n\n        /* Really leave vi mode. */\n        if (F_ISSET(clp, CL_STDIN_TTY) &&\n            F_ISSET(clp, CL_SCR_VI_INIT) && cl_vi_end(gp))\n                rval = 1;\n\n        /* Really leave ex mode. */\n        if (F_ISSET(clp, CL_STDIN_TTY) &&\n            F_ISSET(clp, CL_SCR_EX_INIT) && cl_ex_end(gp))\n                rval = 1;\n\n        /*\n         * If we were running ex when we quit, or we're using an implementation\n         * of curses where endwin() doesn't get this right, restore the original\n         * terminal modes.\n         *\n         * XXX\n         * We always do this because it's too hard to figure out what curses\n         * implementations get it wrong.  It may discard type-ahead characters\n         * from the tty queue.\n         */\n        if (F_ISSET(clp, CL_STDIN_TTY))\n                (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig);\n\n        F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT);\n        return (rval);\n}\n\n/*\n * cl_vi_init --\n *      Initialize the curses vi screen.\n */\nstatic int\ncl_vi_init(SCR *sp)\n{\n        CL_PRIVATE *clp;\n        char *o_cols, *o_lines, *o_term, *ttype;\n\n        clp = CLP(sp);\n\n        /* If already initialized, just set the terminal modes. */\n        if (F_ISSET(clp, CL_SCR_VI_INIT))\n                goto fast;\n\n        /* Curses vi always reads from (and writes to) a terminal. */\n        if (!F_ISSET(clp, CL_STDIN_TTY) || !isatty(STDOUT_FILENO)) {\n                msgq(sp, M_ERR,\n                    \"Standard input and standard output must be a terminal\");\n                return (1);\n        }\n\n        /* We'll need a terminal type. */\n        if (opts_empty(sp, O_TERM, 0))\n                return (1);\n        ttype = O_STR(sp, O_TERM);\n\n        /*\n         * XXX\n         * Changing the row/column and terminal values is done by putting them\n         * into the environment, which is then read by curses.  What this loses\n         * in ugliness, it makes up for in stupidity.  We can't simply put the\n         * values into the environment ourselves, because in the presence of a\n         * kernel mechanism for returning the window size, entering values into\n         * the environment will screw up future screen resizing events, e.g. if\n         * the user enters a :shell command and then resizes their window.  So,\n         * if they weren't already in the environment, we make sure to delete\n         * them immediately after setting them.\n         *\n         * XXX\n         * Putting the TERM variable into the environment is necessary, even\n         * though we're using newterm() here.  We may be using initscr() as\n         * the underlying function.\n         */\n        o_term = getenv(\"TERM\");\n        cl_putenv(\"TERM\", ttype, 0);\n        o_lines = getenv(\"LINES\");\n        cl_putenv(\"LINES\",   NULL, (unsigned long)O_VAL(sp, O_LINES));\n        o_cols = getenv(\"COLUMNS\");\n        cl_putenv(\"COLUMNS\", NULL, (unsigned long)O_VAL(sp, O_COLUMNS));\n\n        /*\n         * The terminal is always initialized, either in `main`, or by a\n         * previous call to newterm(3).\n         */\n        (void)del_curterm(cur_term);\n\n        /*\n         * We don't care about the SCREEN reference returned by newterm, we\n         * never have more than one SCREEN at a time.\n         */\n        errno = 0;\n        if (newterm(ttype, stdout, stdin) == NULL) {\n                if (errno)\n                        msgq(sp, M_SYSERR, \"%s\", ttype);\n                else\n                        msgq(sp, M_ERR, \"%s: unknown terminal type\", ttype);\n                return (1);\n        }\n\n        if (o_term == NULL)\n                unsetenv(\"TERM\");\n        if (o_lines == NULL)\n                unsetenv(\"LINES\");\n        if (o_cols == NULL)\n                unsetenv(\"COLUMNS\");\n\n        /*\n         * XXX\n         * Someone got let out alone without adult supervision -- the SunOS\n         * newterm resets the signal handlers.  There's a race, but it's not\n         * worth closing.\n         */\n        (void)sig_init(sp->gp, sp);\n\n        /*\n         * We use raw mode.  What we want is 8-bit clean, however, signals\n         * and flow control should continue to work.  Admittedly, it sounds\n         * like cbreak, but it isn't.  Using cbreak() can get you additional\n         * things like IEXTEN, which turns on flags like DISCARD and LNEXT.\n         *\n         * !!!\n         * If raw isn't turning off echo and newlines, something's wrong.\n         * However, it shouldn't hurt.\n         */\n        noecho();                       /* No character echo. */\n        nonl();                         /* No CR/NL translation. */\n        raw();                          /* 8-bit clean. */\n        idlok(stdscr, 1);               /* Use hardware insert/delete line. */\n\n        /* Put the cursor keys into application mode. */\n        (void)keypad(stdscr, TRUE);\n\n        /*\n         * XXX\n         * The screen TI sequence just got sent.  See the comment in\n         * cl_funcs.c:cl_attr().\n         */\n        clp->ti_te = TI_SENT;\n\n        /*\n         * XXX\n         * Historic implementations of curses handled SIGTSTP signals\n         * in one of three ways.  They either:\n         *\n         *      1: Set their own handler, regardless.\n         *      2: Did not set a handler if a handler was already installed.\n         *      3: Set their own handler, but then called any previously set\n         *         handler after completing their own cleanup.\n         *\n         * We don't try and figure out which behavior is in place, we force\n         * it to SIG_DFL after initializing the curses interface, which means\n         * that curses isn't going to take the signal.  Since curses isn't\n         * reentrant (i.e., the whole curses SIGTSTP interface is a fantasy),\n         * we're doing The Right Thing.\n         */\n        (void)signal(SIGTSTP, SIG_DFL);\n\n        /*\n         * If flow control was on, turn it back on.  Turn signals on.  ISIG\n         * turns on VINTR, VQUIT, VDSUSP and VSUSP.   The main curses code\n         * already installed a handler for VINTR.  We're going to disable the\n         * other three.\n         *\n         * XXX\n         * We want to use ^Y as a vi scrolling command.  If the user has the\n         * DSUSP character set to ^Y (common practice) clean it up.  As it's\n         * equally possible that the user has VDSUSP set to 'a', we disable\n         * it regardless.  It doesn't make much sense to suspend vi at read,\n         * so I don't think anyone will care.  Alternatively, we could look\n         * it up in the table of legal command characters and turn it off if\n         * it matches one.\n         *\n         * XXX\n         * We don't check to see if the user had signals enabled originally.\n         * If they didn't, it's unclear what we're supposed to do here, but\n         * it's also pretty unlikely.\n         */\n        if (tcgetattr(STDIN_FILENO, &clp->vi_enter)) {\n                msgq(sp, M_SYSERR, \"tcgetattr\");\n                goto err;\n        }\n        if (clp->orig.c_iflag & IXON)\n                clp->vi_enter.c_iflag |= IXON;\n        if (clp->orig.c_iflag & IXOFF)\n                clp->vi_enter.c_iflag |= IXOFF;\n\n        clp->vi_enter.c_lflag |= ISIG;\n#ifdef VDSUSP\n        clp->vi_enter.c_cc[VDSUSP] = _POSIX_VDISABLE;\n#endif /* ifdef VDSUSP */\n        clp->vi_enter.c_cc[VQUIT] = _POSIX_VDISABLE;\n        clp->vi_enter.c_cc[VSUSP] = _POSIX_VDISABLE;\n\n        /*\n         * XXX\n         * OSF/1 doesn't turn off the <discard>, <literal-next> or <status>\n         * characters when curses switches into raw mode.  It should be OK\n         * to do it explicitly for everyone.\n         */\n#ifdef VDISCARD\n        clp->vi_enter.c_cc[VDISCARD] = _POSIX_VDISABLE;\n#endif /* ifdef VDISCARD */\n#ifdef VLNEXT\n        clp->vi_enter.c_cc[VLNEXT] = _POSIX_VDISABLE;\n#endif /* ifdef VLNEXT */\n#ifdef VSTATUS\n        clp->vi_enter.c_cc[VSTATUS] = _POSIX_VDISABLE;\n#endif /* ifdef VSTATUS */\n\n        /* Initialize terminal based information. */\n        if (cl_term_init(sp))\n                goto err;\n\nfast:   /* Set the terminal modes. */\n        if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter)) {\n                if (errno == EINTR)\n                        goto fast;\n                msgq(sp, M_SYSERR, \"tcsetattr\");\nerr:            (void)cl_vi_end(sp->gp);\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * cl_vi_end --\n *      Shutdown the vi screen.\n */\nstatic int\ncl_vi_end(GS *gp)\n{\n        CL_PRIVATE *clp;\n\n        clp = GCLP(gp);\n\n        /* Restore the cursor keys to normal mode. */\n        (void)keypad(stdscr, FALSE);\n\n        /*\n         * If we were running vi when we quit, scroll the screen up a single\n         * line so we don't lose any information.\n         *\n         * Move to the bottom of the window (some endwin implementations don't\n         * do this for you).\n         */\n        if (!F_ISSET(clp, CL_IN_EX)) {\n                (void)move(0, 0);\n                (void)deleteln();\n                (void)move(LINES - 1, 0);\n                (void)refresh();\n        }\n\n        cl_freecap(clp);\n\n        /* End curses window. */\n        (void)endwin();\n\n        /* Free the SCREEN created by newterm(3). */\n#if defined(NCURSES_VERSION)\n        delscreen(set_term(NULL));\n#endif /* if defined(NCURSES_VERSION) */\n\n        /*\n         * XXX\n         * The screen TE sequence just got sent.  See the comment in\n         * cl_funcs.c:cl_attr().\n         */\n        clp->ti_te = TE_SENT;\n\n        return (0);\n}\n\n/*\n * cl_ex_init --\n *      Initialize the ex screen.\n */\nstatic int\ncl_ex_init(SCR *sp)\n{\n        CL_PRIVATE *clp;\n\n        clp = CLP(sp);\n\n        /* If already initialized, just set the terminal modes. */\n        if (F_ISSET(clp, CL_SCR_EX_INIT))\n                goto fast;\n\n        /* If not reading from a file, we're done. */\n        if (!F_ISSET(clp, CL_STDIN_TTY))\n                return (0);\n\n        /* Get the ex termcap/terminfo strings. */\n        (void)cl_getcap(sp, \"cup\",  &clp->cup);\n        (void)cl_getcap(sp, \"smso\", &clp->smso);\n        (void)cl_getcap(sp, \"rmso\", &clp->rmso);\n        (void)cl_getcap(sp, \"el\",   &clp->el);\n        (void)cl_getcap(sp, \"cuu1\", &clp->cuu1);\n\n        /* Enter_standout_mode and exit_standout_mode are paired. */\n        if (clp->smso == NULL || clp->rmso == NULL) {\n                free(clp->smso);\n                clp->smso = NULL;\n                free(clp->rmso);\n                clp->rmso = NULL;\n        }\n\n        /*\n         * Turn on canonical mode, with normal input and output processing.\n         * Start with the original terminal settings as the user probably\n         * had them (including any local extensions) set correctly for the\n         * current terminal.\n         *\n         * !!!\n         * We can't get everything that we need portably; for example, ONLCR,\n         * mapping <newline> to <carriage-return> on output isn't required\n         * by POSIX 1003.1b-1993.  If this turns out to be a problem, then\n         * we'll either have to play some games on the mapping, or we'll have\n         * to make all ex printf's output \\r\\n instead of \\n.\n         */\n        clp->ex_enter = clp->orig;\n        clp->ex_enter.c_lflag |=\n            ECHO\n#if !defined(__managarm__)\n\t    | ECHOCTL\n#endif\n\t    | ECHOE | ECHOK\n#if !defined(__managarm__)\n\t    | ECHOKE\n#endif\n\t    | ICANON | IEXTEN | ISIG;\n        clp->ex_enter.c_iflag |= ICRNL;\n        clp->ex_enter.c_oflag |= ONLCR | OPOST;\n\nfast:   if (tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->ex_enter)) {\n                if (errno == EINTR)\n                        goto fast;\n                msgq(sp, M_SYSERR, \"tcsetattr\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * cl_ex_end --\n *      Shutdown the ex screen.\n */\nstatic int\ncl_ex_end(GS *gp)\n{\n        CL_PRIVATE *clp;\n\n        clp = GCLP(gp);\n\n        cl_freecap(clp);\n\n        return (0);\n}\n\n/*\n * cl_getcap --\n *      Retrieve termcap/terminfo strings.\n *\n * PUBLIC: int cl_getcap(SCR *, char *, char **);\n */\nint\ncl_getcap(SCR *sp, char *name, char **elementp)\n{\n        size_t len;\n        char *t;\n\n        if ((t = tigetstr(name)) != NULL &&\n            t != (char *)-1 && (len = strlen(t)) != 0) {\n                MALLOC_RET(sp, *elementp, len + 1);\n                memmove(*elementp, t, len + 1);\n        }\n        return (0);\n}\n\n/*\n * cl_freecap --\n *      Free any allocated termcap/terminfo strings.\n */\nstatic void\ncl_freecap(CL_PRIVATE *clp)\n{\n        free(clp->el);\n        clp->el = NULL;\n        free(clp->cup);\n        clp->cup = NULL;\n        free(clp->cuu1);\n        clp->cuu1 = NULL;\n        free(clp->rmso);\n        clp->rmso = NULL;\n        free(clp->smso);\n        clp->smso = NULL;\n}\n\n/*\n * cl_putenv --\n *      Put a value into the environment.\n */\nstatic int\ncl_putenv(char *name, char *str, unsigned long value)\n{\n        char buf[40];\n\n        if (str == NULL) {\n                (void)snprintf(buf, sizeof(buf), \"%lu\", value);\n                return (setenv(name, buf, 1));\n        } else\n                return (setenv(name, str, 1));\n}\n"
  },
  {
    "path": "cl/cl_term.c",
    "content": "/*      $OpenBSD: cl_term.c,v 1.29 2022/04/22 15:50:07 tb Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/ioctl.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#if defined(__solaris__)\n# define __EXTENSIONS__\n# include <termios.h>\n# include <sys/termios.h>\n#endif /* if defined(__solaris__) */\n\n#include <curses.h>\n#include <bsd_termios.h>\n#ifdef __NetBSD__\n# include <term.h>\n#endif /* ifdef __NetBSD__ */\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"cl.h\"\n\nstatic int cl_pfmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t);\n\n/*\n * XXX\n * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE.\n */\ntypedef struct _tklist {\n        char    *ts;                    /* Key's termcap string. */\n        char    *output;                /* Corresponding vi command. */\n        char    *name;                  /* Name. */\n} TKLIST;\nstatic TKLIST const c_tklist[] = {      /* Command mappings. */\n        {\"kil1\",        \"O\",    \"insert line\"},\n        {\"kdch1\",       \"x\",    \"delete character\"},\n        {\"kcud1\",       \"j\",    \"cursor down\"},\n        {\"kel\",         \"D\",    \"delete to eol\"},\n        {\"kind\",     \"\\004\",    \"scroll down\"},                 /* ^D */\n        {\"kll\",         \"$\",    \"go to eol\"},\n        {\"khome\",       \"^\",    \"go to sol\"},\n        {\"kich1\",       \"i\",    \"insert at cursor\"},\n        {\"kdl1\",       \"dd\",    \"delete line\"},\n        {\"kcub1\",       \"h\",    \"cursor left\"},\n        {\"knp\",      \"\\006\",    \"page down\"},                   /* ^F */\n        {\"kpp\",      \"\\002\",    \"page up\"},                     /* ^B */\n        {\"kri\",      \"\\025\",    \"scroll up\"},                   /* ^U */\n        {\"ked\",        \"dG\",    \"delete to end of screen\"},\n        {\"kcuf1\",       \"l\",    \"cursor right\"},\n        {\"kcuu1\",       \"k\",    \"cursor up\"},\n        {NULL, NULL, NULL},\n};\nstatic TKLIST const m1_tklist[] = {     /* Input mappings (set or delete). */\n        {\"kcud1\",  \"\\033ja\",    \"cursor down\"},                 /* ^[ja */\n        {\"kcub1\",  \"\\033ha\",    \"cursor left\"},                 /* ^[ha */\n        {\"kcuu1\",  \"\\033ka\",    \"cursor up\"},                   /* ^[ka */\n        {\"kcuf1\",  \"\\033la\",    \"cursor right\"},                /* ^[la */\n        {NULL, NULL, NULL},\n};\n\n/*\n * cl_term_init --\n *      Initialize the special keys defined by the termcap/terminfo entry.\n *\n * PUBLIC: int cl_term_init(SCR *);\n */\nint\ncl_term_init(SCR *sp)\n{\n        SEQ *qp;\n        TKLIST const *tkp;\n        size_t output_len;\n        char *t;\n\n        /* Command mappings. */\n        for (tkp = c_tklist; tkp->name != NULL; ++tkp) {\n                if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)\n                        continue;\n                output_len = 0;\n                if (tkp->output != NULL)\n                        output_len = strlen(tkp->output);\n                if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t),\n                    tkp->output, output_len, SEQ_COMMAND,\n                    SEQ_NOOVERWRITE | SEQ_SCREEN))\n                        return (1);\n        }\n\n        /* Input mappings that are already set or are text deletions. */\n        for (tkp = m1_tklist; tkp->name != NULL; ++tkp) {\n                if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)\n                        continue;\n                /*\n                 * !!!\n                 * Some terminals' <cursor_left> keys send single <backspace>\n                 * characters.  This is okay in command mapping, but not okay\n                 * in input mapping.  That combination is the only one we'll\n                 * ever see, hopefully, so kluge it here for now.\n                 */\n                if (!strcmp(t, \"\\b\"))\n                        continue;\n                output_len = 0;\n                if (tkp->output != NULL)\n                        output_len = strlen(tkp->output);\n                if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t),\n                        tkp->output, output_len, SEQ_INPUT,\n                        SEQ_NOOVERWRITE | SEQ_SCREEN))\n                        return (1);\n        }\n\n        /*\n         * Rework any function key mappings that were set before the\n         * screen was initialized.\n         */\n        LIST_FOREACH(qp, & sp->gp->seqq, q)\n                if (F_ISSET(qp, SEQ_FUNCMAP))\n                        (void)cl_pfmap(sp, qp->stype,\n                            qp->input, qp->ilen, qp->output, qp->olen);\n        return (0);\n}\n\n/*\n * cl_term_end --\n *      End the special keys defined by the termcap/terminfo entry.\n *\n * PUBLIC: int cl_term_end(GS *);\n */\nint\ncl_term_end(GS *gp)\n{\n        SEQ *qp, *nqp;\n\n        /* Delete screen specific mappings. */\n        for (qp = LIST_FIRST(&gp->seqq); qp != NULL; qp = nqp) {\n                nqp = LIST_NEXT(qp, q);\n                if (F_ISSET(qp, SEQ_SCREEN))\n                        (void)seq_mdel(qp);\n        }\n        return (0);\n}\n\n/*\n * cl_fmap --\n *      Map a function key.\n *\n * PUBLIC: int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t);\n */\nint\ncl_fmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to,\n    size_t tlen)\n{\n        /* Ignore until the screen is running, do the real work then. */\n        if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI))\n                return (0);\n        if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX))\n                return (0);\n\n        return (cl_pfmap(sp, stype, from, flen, to, tlen));\n}\n\n/*\n * cl_pfmap --\n *      Map a function key (private version).\n */\nstatic int\ncl_pfmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to,\n    size_t tlen)\n{\n        size_t nlen;\n        char *p, key_name[64];\n\n        (void)snprintf(key_name, sizeof(key_name), \"kf%d\", atoi(from + 1));\n        if ((p = tigetstr(key_name)) == NULL ||\n            p == (char *)-1 || strlen(p) == 0)\n                p = NULL;\n        if (p == NULL) {\n                msgq_str(sp, M_ERR, from, \"This terminal has no %s key\");\n                return (1);\n        }\n\n        nlen = snprintf(key_name,\n            sizeof(key_name), \"function key %d\", atoi(from + 1));\n        if (nlen >= sizeof(key_name))\n                nlen = sizeof(key_name) - 1;\n        return (seq_set(sp, key_name, nlen,\n            p, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN));\n}\n\n/*\n * cl_optchange --\n *      Curses screen specific \"option changed\" routine.\n *\n * PUBLIC: int cl_optchange(SCR *, int, char *, unsigned long *);\n */\nint\ncl_optchange(SCR *sp, int opt, char *str, unsigned long *valp)\n{\n        CL_PRIVATE *clp;\n\n        clp = CLP(sp);\n\n        switch (opt) {\n        case O_TERM:\n                F_CLR(sp, SC_SCR_EX | SC_SCR_VI);\n                /* FALLTHROUGH */\n        case O_COLUMNS:\n        case O_LINES:\n                /*\n                 * Changing the terminal type requires that we reinitialize\n                 * curses, while resizing does not.\n                 */\n                F_SET(sp->gp, G_SRESTART);\n                break;\n        case O_MESG:\n                (void)cl_omesg(sp, clp, !*valp);\n                break;\n        case O_WINDOWNAME:\n                if (*valp) {\n                        F_CLR(clp, CL_RENAME_OK);\n\n                        (void)cl_rename(sp, NULL, 0);\n                } else {\n                        F_SET(clp, CL_RENAME_OK);\n\n                        /*\n                         * If the screen is live, i.e. we're not reading the\n                         * .exrc file, update the window.\n                         */\n                        if (sp->frp != NULL && sp->frp->name != NULL)\n                                (void)cl_rename(sp, sp->frp->name, 1);\n                }\n                break;\n        }\n        return (0);\n}\n\n/*\n * cl_omesg --\n *      Turn the tty write permission on or off.\n *\n * PUBLIC: int cl_omesg(SCR *, CL_PRIVATE *, int);\n */\nint\ncl_omesg(SCR *sp, CL_PRIVATE *clp, int on)\n{\n        struct stat sb;\n        char *tty;\n\n        /* Find the tty, get the current permissions. */\n        if ((tty = ttyname(STDERR_FILENO)) == NULL) {\n                if (sp != NULL && isatty(STDERR_FILENO))\n                        msgq(sp, M_SYSERR, \"stderr\");\n                return (1);\n        }\n        if (stat(tty, &sb) < 0) {\n                if (sp != NULL)\n                        msgq(sp, M_SYSERR, \"%s\", tty);\n                return (1);\n        }\n        sb.st_mode &= ACCESSPERMS;\n\n        /* Save the original status if it's unknown. */\n        if (clp->tgw == TGW_UNKNOWN)\n                clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET;\n\n        /* Toggle the permissions. */\n        if (on) {\n                if (chmod(tty, sb.st_mode | S_IWGRP) < 0) {\n                        if (sp != NULL)\n                                msgq(sp, M_SYSERR,\n                                    \"messages not turned on: %s\", tty);\n                        return (1);\n                }\n        } else\n                if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) {\n                        if (sp != NULL)\n                                msgq(sp, M_SYSERR,\n                                    \"messages not turned off: %s\", tty);\n                        return (1);\n                }\n        return (0);\n}\n\n/*\n * cl_ssize --\n *      Return the terminal size.\n *\n * PUBLIC: int cl_ssize(SCR *, int, size_t *, size_t *, int *);\n */\nint\ncl_ssize(SCR *sp, int sigwinch, size_t *rowp, size_t *colp, int *changedp)\n{\n        struct winsize win;\n        size_t col, row;\n        int rval;\n        char *p;\n\n        /* Assume it's changed. */\n        if (changedp != NULL)\n                *changedp = 1;\n\n        /*\n         * !!!\n         * sp may be NULL.\n         *\n         * Get the screen rows and columns.  If the values are wrong, it's\n         * not a big deal -- as soon as the user sets them explicitly the\n         * environment will be set and the screen package will use the new\n         * values.\n         *\n         * Try TIOCGWINSZ.\n         */\n        row = col = 0;\n        if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) {\n                row = win.ws_row;\n                col = win.ws_col;\n        }\n        /* If here because of suspend or a signal, only trust TIOCGWINSZ. */\n        if (sigwinch) {\n                /*\n                 * Somebody didn't get TIOCGWINSZ right, or has suspend\n                 * without window resizing support.  The user just lost,\n                 * but there's nothing we can do.\n                 */\n                if (row == 0 || col == 0) {\n                        if (changedp != NULL)\n                                *changedp = 0;\n                        return (0);\n                }\n\n                /*\n                 * SunOS systems deliver SIGWINCH when windows are uncovered\n                 * as well as when they change size.  In addition, we call\n                 * here when continuing after being suspended since the window\n                 * may have changed size.  Since we don't want to background\n                 * all of the screens just because the window was uncovered,\n                 * ignore the signal if there's no change.\n                 */\n                if (sp != NULL &&\n                    row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) {\n                        if (changedp != NULL)\n                                *changedp = 0;\n                        return (0);\n                }\n\n                if (rowp != NULL)\n                        *rowp = row;\n                if (colp != NULL)\n                        *colp = col;\n                return (0);\n        }\n\n        /*\n         * !!!\n         * If TIOCGWINSZ failed, or had entries of 0, try termcap.  This\n         * routine is called before any termcap or terminal information\n         * has been set up.  If there's no TERM environmental variable set,\n         * let it go, at least ex can run.\n         */\n        if (row == 0 || col == 0) {\n                p = getenv(\"TERM\");\n                if (p == NULL)\n                        goto noterm;\n                if (row == 0) {\n                        if ((rval = tigetnum(\"lines\")) < 0)\n                                msgq(sp, M_SYSERR, \"tigetnum: lines\");\n                        else\n                                row = rval;\n                }\n                if (col == 0) {\n                        if ((rval = tigetnum(\"cols\")) < 0)\n                                msgq(sp, M_SYSERR, \"tigetnum: cols\");\n                        else\n                                col = rval;\n                }\n        }\n\n        /* If nothing else, well, it's probably a VT100. */\nnoterm: if (row == 0)\n                row = 24;\n        if (col == 0)\n                col = 80;\n\n        /*\n         * !!!\n         * POSIX 1003.2 requires the environment to override everything.\n         * Often, people can get nvi to stop messing up their screen by\n         * deleting the LINES and COLUMNS environment variables from their\n         * dot-files.\n         */\n        if ((p = getenv(\"LINES\")) != NULL &&\n            (rval = strtonum(p, 1, INT_MAX, NULL)) > 0)\n                row = rval;\n        if ((p = getenv(\"COLUMNS\")) != NULL &&\n            (rval = strtonum(p, 1, INT_MAX, NULL)) > 0)\n                col = rval;\n\n        if (rowp != NULL)\n                *rowp = row;\n        if (colp != NULL)\n                *colp = col;\n        return (0);\n}\n\n/*\n * cl_putchar --\n *      Function version of putchar, for tputs.\n *\n * PUBLIC: int cl_putchar(int);\n */\nint\ncl_putchar(int ch)\n{\n        return (putchar(ch));\n}\n"
  },
  {
    "path": "cl/extern.h",
    "content": "/*      $OpenBSD: extern.h,v 1.8 2015/08/27 04:37:09 guenther Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)extern.h    8.10 (Berkeley) 7/20/94\n */\n\n#include \"hash.h\"\n\nint      __bt_close(DB *);\nint      __bt_cmp(BTREE *, const DBT *, EPG *);\nint      __bt_defcmp(const DBT *, const DBT *);\nsize_t   __bt_defpfx(const DBT *, const DBT *);\nint      __bt_delete(const DB *, const DBT *, unsigned int);\nint      __bt_dleaf(BTREE *, const DBT *, PAGE *, unsigned int);\nint      __bt_fd(const DB *);\nint      __bt_free(BTREE *, PAGE *);\nint      __bt_get(const DB *, const DBT *, DBT *, unsigned int);\nPAGE    *__bt_new(BTREE *, pgno_t *);\nvoid     __bt_pgin(void *, pgno_t, void *);\nvoid     __bt_pgout(void *, pgno_t, void *);\nint      __bt_put(const DB *dbp, DBT *, const DBT *, unsigned int);\nint      __bt_ret(BTREE *, EPG *, DBT *, DBT *, DBT *, DBT *, int);\nEPG     *__bt_search(BTREE *, const DBT *, int *);\nint      __bt_seq(const DB *, DBT *, DBT *, unsigned int);\nvoid     __bt_setcur(BTREE *, pgno_t, unsigned int);\nint      __bt_split(BTREE *, PAGE *,\n            const DBT *, const DBT *, int, size_t, u_int32_t);\nint      __bt_sync(const DB *, unsigned int);\n\nint      __ovfl_delete(BTREE *, void *);\nint      __ovfl_get(BTREE *, void *, size_t *, void **, size_t *);\nint      __ovfl_put(BTREE *, const DBT *, pgno_t *);\n\nint  __rec_close(DB *);\nint  __rec_delete(const DB *, const DBT *, unsigned int);\nint  __rec_dleaf(BTREE *, PAGE *, u_int32_t);\nint  __rec_fd(const DB *);\nint  __rec_fmap(BTREE *, recno_t);\nint  __rec_fout(BTREE *);\nint  __rec_fpipe(BTREE *, recno_t);\nint  __rec_get(const DB *, const DBT *, DBT *, unsigned int);\nint  __rec_iput(BTREE *, recno_t, const DBT *, unsigned int);\nint  __rec_put(const DB *dbp, DBT *, const DBT *, unsigned int);\nint  __rec_ret(BTREE *, EPG *, recno_t, DBT *, DBT *);\nint  __rec_seq(const DB *, DBT *, DBT *, unsigned int);\nint  __rec_sync(const DB *, unsigned int);\nint  __rec_vmap(BTREE *, recno_t);\nint  __rec_vout(BTREE *);\nint  __rec_vpipe(BTREE *, recno_t);\n\nBUFHEAD *__add_ovflpage(HTAB *, BUFHEAD *);\nint  __addel(HTAB *, BUFHEAD *, const DBT *, const DBT *);\nint  __big_delete(HTAB *, BUFHEAD *);\nint  __big_insert(HTAB *, BUFHEAD *, const DBT *, const DBT *);\nint  __big_keydata(HTAB *, BUFHEAD *, DBT *, DBT *, int);\nint  __big_return(HTAB *, BUFHEAD *, int, DBT *, int);\nint  __big_split(HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *,\n        int, u_int32_t, SPLIT_RETURN *);\nint  __buf_free(HTAB *, int, int);\nvoid     __buf_init(HTAB *, int);\nu_int32_t    __call_hash(HTAB *, char *, int);\nint  __delpair(HTAB *, BUFHEAD *, int);\nint  __expand_table(HTAB *);\nint  __find_bigpair(HTAB *, BUFHEAD *, int, char *, int);\nu_int16_t    __find_last_page(HTAB *, BUFHEAD **);\nvoid     __free_ovflpage(HTAB *, BUFHEAD *);\nBUFHEAD *__get_buf(HTAB *, u_int32_t, BUFHEAD *, int);\nint  __get_page(HTAB *, char *, u_int32_t, int, int, int);\nint  __ibitmap(HTAB *, int, int, int);\nu_int32_t    __log2(u_int32_t);\nint  __put_page(HTAB *, char *, u_int32_t, int, int);\nint  __split_page(HTAB *, u_int32_t, u_int32_t);\n\n#ifdef DEBUG\nvoid     __bt_dnpage(DB *, pgno_t);\nvoid     __bt_dpage(PAGE *);\nvoid     __bt_dump(DB *);\n#endif /* ifdef DEBUG */\n"
  },
  {
    "path": "common/args.h",
    "content": "/*      $OpenBSD: args.h,v 1.5 2016/05/27 09:18:11 martijn Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)args.h      10.2 (Berkeley) 3/6/96\n */\n\n/*\n * Structure for building \"argc/argv\" vector of arguments.\n *\n * !!!\n * All arguments are NULL terminated as well as having an associated length.\n * The argument vector is NOT necessarily NULL terminated.  The proper way\n * to check the number of arguments is to use the argc value in the EXCMDARG\n * structure or to walk the array until an ARGS structure with a length of 0\n * is found.\n */\ntypedef struct _args {\n        CHAR_T  *bp;            /* Argument. */\n        size_t   blen;          /* Buffer length. */\n        size_t   len;           /* Argument length. */\n\n#define A_ALLOCATED     0x01    /* If allocated space. */\n        u_int8_t flags;\n} ARGS;\n"
  },
  {
    "path": "common/common.h",
    "content": "/*      $OpenBSD: common.h,v 1.10 2021/01/26 18:19:43 deraadt Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)common.h    10.13 (Berkeley) 9/25/96\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/time.h>\n#include <bsd_db.h>\n#include <bsd_regex.h>\n\n/*\n * Forward structure declarations.  Not pretty, but the include files\n * are far too interrelated for a clean solution.\n */\n\ntypedef struct _cb              CB;\ntypedef struct _event           EVENT;\ntypedef struct _excmd           EXCMD;\ntypedef struct _exf             EXF;\ntypedef struct _fref            FREF;\ntypedef struct _gs              GS;\ntypedef struct _lmark           LMARK;\ntypedef struct _mark            MARK;\ntypedef struct _msg             MSGS;\ntypedef struct _option          OPTION;\ntypedef struct _optlist         OPTLIST;\ntypedef struct _scr             SCR;\ntypedef struct _script          SCRIPT;\ntypedef struct _seq             SEQ;\ntypedef struct _tag             TAG;\ntypedef struct _tagf            TAGF;\ntypedef struct _tagq            TAGQ;\ntypedef struct _text            TEXT;\n\n/* Autoindent state. */\ntypedef enum { C_NOTSET, C_CARATSET, C_ZEROSET } carat_t;\n\n/* Busy message types. */\ntypedef enum { BUSY_ON = 1, BUSY_OFF, BUSY_UPDATE } busy_t;\n\n/*\n * Routines that return a confirmation return:\n *\n *      CONF_NO         User answered no.\n *      CONF_QUIT       User answered quit, eof or an error.\n *      CONF_YES        User answered yes.\n */\n\ntypedef enum { CONF_NO, CONF_QUIT, CONF_YES } conf_t;\n\n/* Directions. */\ntypedef enum { NOTSET, FORWARD, BACKWARD } dir_t;\n\n/* Line operations. */\ntypedef enum { LINE_APPEND, LINE_DELETE, LINE_INSERT, LINE_RESET } lnop_t;\n\n/* Lock return values. */\ntypedef enum { LOCK_FAILED, LOCK_SUCCESS, LOCK_UNAVAIL } lockr_t;\n\n/* Sequence types. */\ntypedef enum { SEQ_ABBREV, SEQ_COMMAND, SEQ_INPUT } seq_t;\n\n/* Program modes. */\nextern enum pmode { MODE_EX, MODE_VI, MODE_VIEW } pmode;\n\n/*\n * Extensions to POSIX.\n */\n\n#ifndef ACCESSPERMS\n# define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO)\n#endif /* ifndef ACCESSPERMS */\n\n#ifndef ALLPERMS\n# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)\n#endif /* ifndef ALLPERMS */\n\n/*\n * Local includes.\n */\n\n#include \"key.h\"                /* Required by args.h. */\n#include \"args.h\"               /* Required by options.h. */\n#include \"options.h\"            /* Required by screen.h. */\n#include \"msg.h\"                /* Required by gs.h. */\n#include \"cut.h\"                /* Required by gs.h. */\n#include \"seq.h\"                /* Required by screen.h. */\n#include \"util.h\"               /* Required by ex.h. */\n#include \"mark.h\"               /* Required by gs.h. */\n#include \"../ex/ex.h\"           /* Required by gs.h. */\n#include \"gs.h\"                 /* Required by screen.h. */\n#include \"screen.h\"             /* Required by exf.h. */\n#include \"exf.h\"\n#include \"log.h\"\n#include \"mem.h\"\n\n#include \"com_extern.h\"\n"
  },
  {
    "path": "common/cut.c",
    "content": "/*      $OpenBSD: cut.c,v 1.18 2025/07/30 22:19:13 millert Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"common.h\"\n\nstatic void     cb_rotate(SCR *);\n\n/*\n * cut --\n *      Put a range of lines/columns into a TEXT buffer.\n *\n * There are two buffer areas, both found in the global structure.  The first\n * is the linked list of all the buffers the user has named, the second is the\n * unnamed buffer storage.  There is a pointer, too, which is the current\n * default buffer, i.e. it may point to the unnamed buffer or a named buffer\n * depending on into what buffer the last text was cut.  Logically, in both\n * delete and yank operations, if the user names a buffer, the text is cut\n * into it.  If it's a delete of information on more than a single line, the\n * contents of the numbered buffers are rotated up one, the contents of the\n * buffer named '9' are discarded, and the text is cut into the buffer named\n * '1'.  The text is always cut into the unnamed buffer.\n *\n * In all cases, upper-case buffer names are the same as lower-case names,\n * with the exception that they cause the buffer to be appended to instead\n * of replaced.  Note, however, that if text is appended to a buffer, the\n * default buffer only contains the appended text, not the entire contents\n * of the buffer.\n *\n * !!!\n * The contents of the default buffer would disappear after most operations\n * in historic vi.  It's unclear that this is useful, so we don't bother.\n *\n * When users explicitly cut text into the numeric buffers, historic vi became\n * genuinely strange.  I've never been able to figure out what was supposed to\n * happen.  It behaved differently if you deleted text than if you yanked text,\n * and, in the latter case, the text was appended to the buffer instead of\n * replacing the contents.  Hopefully it's not worth getting right, and here\n * we just treat the numeric buffers like any other named buffer.\n *\n * PUBLIC: int cut(SCR *, CHAR_T *, MARK *, MARK *, int);\n */\n\nint\ncut(SCR *sp, CHAR_T *namep, MARK *fm, MARK *tm, int flags)\n{\n        CB *cbp;\n        CHAR_T name = '1';      /* default numeric buffer */\n        recno_t lno;\n        int append, copy_one, copy_def;\n\n        /* Check if the line numbers are out-of-band. */\n        if (fm->lno == OOBLNO || tm->lno == OOBLNO)\n                return (1);\n\n        /*\n         * If the user specified a buffer, put it there.  (This may require\n         * a copy into the numeric buffers.  We do the copy so that we don't\n         * have to reference count and so we don't have to deal with things\n         * like appends to buffers that are used multiple times.)\n         *\n         * Otherwise, if it's supposed to be put in a numeric buffer (usually\n         * a delete) put it there.  The rules for putting things in numeric\n         * buffers were historically a little strange.  There were three cases.\n         *\n         *      1: Some motions are always line mode motions, which means\n         *         that the cut always goes into the numeric buffers.\n         *      2: Some motions aren't line mode motions, e.g. d10w, but\n         *         can cross line boundaries.  For these commands, if the\n         *         cut crosses a line boundary, it goes into the numeric\n         *         buffers.  This includes most of the commands.\n         *      3: Some motions aren't line mode motions, e.g. d`<char>,\n         *         but always go into the numeric buffers, regardless.  This\n         *         was the commands: % ` / ? ( ) N n { } -- and nvi adds ^A.\n         *\n         * Otherwise, put it in the unnamed buffer.\n         */\n\n        append = copy_one = copy_def = 0;\n        if (namep != NULL) {\n                name = *namep;\n                if (LF_ISSET(CUT_NUMREQ) || (LF_ISSET(CUT_NUMOPT) &&\n                    (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno))) {\n                        copy_one = 1;\n                        cb_rotate(sp);\n                }\n                if ((append = isupper(name)) == 1) {\n                        if (!copy_one)\n                                copy_def = 1;\n                        name = tolower(name);\n                }\nnamecb:         CBNAME(sp, cbp, name);\n        } else if (LF_ISSET(CUT_NUMREQ) || (LF_ISSET(CUT_NUMOPT) &&\n            (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno))) {\n                /* Copy into numeric buffer 1. */\n                cb_rotate(sp);\n                goto namecb;\n        } else\n                cbp = &sp->gp->dcb_store;\n\ncopyloop:\n        /*\n         * If this is a new buffer, create it and add it into the list.\n         * Otherwise, if it's not an append, free its current contents.\n         */\n        if (cbp == NULL) {\n                CALLOC_RET(sp, cbp, 1, sizeof(CB));\n                cbp->name = name;\n                TAILQ_INIT(&cbp->textq);\n                LIST_INSERT_HEAD(&sp->gp->cutq, cbp, q);\n        } else if (!append) {\n                text_lfree(&cbp->textq);\n                cbp->len = 0;\n                cbp->flags = 0;\n        }\n\n        /* In line mode, it's pretty easy, just cut the lines. */\n        if (LF_ISSET(CUT_LINEMODE)) {\n                cbp->flags |= CB_LMODE;\n                for (lno = fm->lno; lno <= tm->lno; ++lno)\n                        if (cut_line(sp, lno, 0, CUT_LINE_TO_EOL, cbp))\n                                goto cut_line_err;\n        } else {\n                /*\n                 * Get the first line.  A length of CUT_LINE_TO_EOL causes\n                 * cut_line() to cut from the MARK to the end of the line.\n                 */\n                if (cut_line(sp, fm->lno, fm->cno, fm->lno != tm->lno ?\n                    CUT_LINE_TO_EOL : (tm->cno - fm->cno) + 1, cbp))\n                        goto cut_line_err;\n\n                /* Get the intermediate lines. */\n                for (lno = fm->lno; ++lno < tm->lno;)\n                        if (cut_line(sp, lno, 0, CUT_LINE_TO_EOL, cbp))\n                                goto cut_line_err;\n\n                /* Get the last line. */\n                if (tm->lno != fm->lno &&\n                    cut_line(sp, lno, 0, tm->cno + 1, cbp))\n                        goto cut_line_err;\n        }\n\n        append = 0;             /* Only append to the named buffer. */\n        sp->gp->dcbp = cbp;     /* Repoint the default buffer on each pass. */\n\n        if (copy_one) {         /* Copy into numeric buffer 1. */\n                CBNAME(sp, cbp, name);\n                copy_one = 0;\n                goto copyloop;\n        }\n        if (copy_def) {         /* Copy into the default buffer. */\n                cbp = &sp->gp->dcb_store;\n                copy_def = 0;\n                goto copyloop;\n        }\n        return (0);\n\ncut_line_err:\n        text_lfree(&cbp->textq);\n        cbp->len = 0;\n        cbp->flags = 0;\n\tsp->gp->dcbp = NULL;\n        return (1);\n}\n\n/*\n * cb_rotate --\n *      Rotate the numbered buffers up one.\n */\nstatic void\ncb_rotate(SCR *sp)\n{\n        CB *cbp, *del_cbp;\n\n        del_cbp = NULL;\n        LIST_FOREACH(cbp, &sp->gp->cutq, q)\n                switch(cbp->name) {\n                case '1':\n                        cbp->name = '2';\n                        break;\n                case '2':\n                        cbp->name = '3';\n                        break;\n                case '3':\n                        cbp->name = '4';\n                        break;\n                case '4':\n                        cbp->name = '5';\n                        break;\n                case '5':\n                        cbp->name = '6';\n                        break;\n                case '6':\n                        cbp->name = '7';\n                        break;\n                case '7':\n                        cbp->name = '8';\n                        break;\n                case '8':\n                        cbp->name = '9';\n                        break;\n                case '9':\n                        del_cbp = cbp;\n                        break;\n                }\n        if (del_cbp != NULL) {\n                LIST_REMOVE(del_cbp, q);\n                text_lfree(&del_cbp->textq);\n                free(del_cbp);\n        }\n}\n\n/*\n * cut_line --\n *      Cut a portion of a single line.\n *\n * PUBLIC: int cut_line(SCR *, recno_t, size_t, size_t, CB *);\n */\nint\ncut_line(SCR *sp, recno_t lno, size_t fcno, size_t clen, CB *cbp)\n{\n        TEXT *tp;\n        size_t len;\n        char *p;\n\n        /* Get the line. */\n        if (db_get(sp, lno, DBG_FATAL, &p, &len))\n                return (1);\n\n        /* Create a TEXT structure that can hold the entire line. */\n        if ((tp = text_init(sp, NULL, 0, len)) == NULL)\n                return (1);\n\n        /*\n         * If the line isn't empty and it's not the entire line,\n         * copy the portion we want, and reset the TEXT length.\n         */\n        if (len != 0) {\n                if (clen == CUT_LINE_TO_EOL)\n                        clen = len - fcno;\n                memcpy(tp->lb, p + fcno, clen);\n                tp->len = clen;\n        }\n\n        /* Append to the end of the cut buffer. */\n        TAILQ_INSERT_TAIL(&cbp->textq, tp, q);\n        cbp->len += tp->len;\n\n        return (0);\n}\n\n/*\n * cut_close --\n *      Discard all cut buffers.\n *\n * PUBLIC: void cut_close(GS *);\n */\nvoid\ncut_close(GS *gp)\n{\n        CB *cbp;\n\n        /* Free cut buffer list. */\n        while ((cbp = LIST_FIRST(&gp->cutq)) != NULL) {\n                if (!TAILQ_EMPTY(&cbp->textq))\n                        text_lfree(&cbp->textq);\n                LIST_REMOVE(cbp, q);\n                free(cbp);\n        }\n\n        /* Free default cut storage. */\n        cbp = &gp->dcb_store;\n        if (!TAILQ_EMPTY(&cbp->textq))\n                text_lfree(&cbp->textq);\n}\n\n/*\n * text_init --\n *      Allocate a new TEXT structure.\n *\n * PUBLIC: TEXT *text_init(SCR *, const char *, size_t, size_t);\n */\nTEXT *\ntext_init(SCR *sp, const char *p, size_t len, size_t total_len)\n{\n        TEXT *tp;\n\n        CALLOC(sp, tp, 1, sizeof(TEXT));\n        if (tp == NULL)\n                return (NULL);\n        /* ANSI C doesn't define a call to malloc(3) for 0 bytes. */\n        if ((tp->lb_len = total_len) != 0) {\n                MALLOC(sp, tp->lb, tp->lb_len);\n                if (tp->lb == NULL) {\n                        free(tp);\n                        return (NULL);\n                }\n                if (p != NULL && len != 0)\n                        memcpy(tp->lb, p, len);\n        }\n        tp->len = len;\n        return (tp);\n}\n\n/*\n * text_lfree --\n *      Free a chain of text structures.\n *\n * PUBLIC: void text_lfree(TEXTH *);\n */\nvoid\ntext_lfree(TEXTH *headp)\n{\n        TEXT *tp;\n\n        while ((tp = TAILQ_FIRST(headp))) {\n                TAILQ_REMOVE(headp, tp, q);\n                text_free(tp);\n        }\n}\n\n/*\n * text_free --\n *      Free a text structure.\n *\n * PUBLIC: void text_free(TEXT *);\n */\nvoid\ntext_free(TEXT *tp)\n{\n        free(tp->lb);\n        free(tp);\n}\n"
  },
  {
    "path": "common/cut.h",
    "content": "/*      $OpenBSD: cut.h,v 1.9 2016/05/27 09:18:11 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)cut.h       10.5 (Berkeley) 4/3/96\n */\n\ntypedef struct _texth TEXTH;            /* TEXT list head structure. */\nTAILQ_HEAD(_texth, _text);\n\n/* Cut buffers. */\nstruct _cb {\n        LIST_ENTRY(_cb) q;              /* Linked list of cut buffers. */\n        TEXTH    textq;                 /* Linked list of TEXT structures. */\n        CHAR_T   name;                  /* Cut buffer name. */\n        size_t   len;                   /* Total length of cut text. */\n\n#define CB_LMODE        0x01            /* Cut was in line mode. */\n        u_int8_t flags;\n};\n\n/* Lines/blocks of text. */\nstruct _text {                          /* Text: a linked list of lines. */\n        TAILQ_ENTRY(_text) q;           /* Linked list of text structures. */\n        char    *lb;                    /* Line buffer. */\n        size_t   lb_len;                /* Line buffer length. */\n        size_t   len;                   /* Line length. */\n\n        /* These fields are used by the vi text input routine. */\n        recno_t  lno;                   /* 1-N: file line. */\n        size_t   cno;                   /* 0-N: file character in line. */\n        size_t   ai;                    /* 0-N: autoindent bytes. */\n        size_t   insert;                /* 0-N: bytes to insert (push). */\n        size_t   offset;                /* 0-N: initial, unerasable chars. */\n        size_t   owrite;                /* 0-N: chars to overwrite. */\n        size_t   R_erase;               /* 0-N: 'R' erase count. */\n        size_t   sv_cno;                /* 0-N: Saved line cursor. */\n        size_t   sv_len;                /* 0-N: Saved line length. */\n\n        /*\n         * These fields returns information from the vi text input routine.\n         *\n         * The termination condition.  Note, this field is only valid if the\n         * text input routine returns success.\n         *      TERM_BS:        User backspaced over the prompt.\n         *      TERM_CEDIT:     User entered <edit-char>.\n         *      TERM_CR:        User entered <carriage-return>; no data.\n         *      TERM_ESC:       User entered <escape>; no data.\n         *      TERM_OK:        Data available.\n         *      TERM_SEARCH:    Incremental search.\n         */\n\n        enum {\n            TERM_BS, TERM_CEDIT, TERM_CR, TERM_ESC, TERM_OK, TERM_SEARCH\n        } term;\n};\n\n/*\n * Get named buffer 'name'.\n * Translate upper-case buffer names to lower-case buffer names.\n */\n\n#define CBNAME(sp, cbp, nch) {                                          \\\n        CHAR_T L__name;                                                 \\\n        L__name = isupper(nch) ? tolower(nch) : (nch);                  \\\n        LIST_FOREACH((cbp), &(sp)->gp->cutq, q)                         \\\n                if ((cbp)->name == L__name)                             \\\n                        break;                                          \\\n}\n\n/* Flags to the cut() routine. */\n#define CUT_LINEMODE    0x01            /* Cut in line mode. */\n#define CUT_NUMOPT      0x02            /* Numeric buffer: optional. */\n#define CUT_NUMREQ      0x04            /* Numeric buffer: required. */\n\n/* Special length to cut_line(). */\n#define CUT_LINE_TO_EOL ((size_t) -1)   /* Cut to the end of line. */\n"
  },
  {
    "path": "common/delete.c",
    "content": "/*      $OpenBSD: delete.c,v 1.12 2017/11/26 09:59:41 mestre Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"common.h\"\n\n/*\n * del --\n *      Delete a range of text.\n *\n * PUBLIC: int del(SCR *, MARK *, MARK *, int);\n */\n\nint\ndel(SCR *sp, MARK *fm, MARK *tm, int lmode)\n{\n        recno_t lno;\n        size_t blen, len, nlen, tlen;\n        char *bp, *p;\n        int eof, rval;\n\n        bp = NULL;\n\n        /* Case 1 -- delete in line mode. */\n        if (lmode) {\n                for (lno = tm->lno; lno >= fm->lno; --lno) {\n                        if (db_delete(sp, lno))\n                                return (1);\n                        ++sp->rptlines[L_DELETED];\n                        if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp))\n                                break;\n                }\n                goto done;\n        }\n\n        /*\n         * Case 2 -- delete to EOF.  This is a special case because it's\n         * easier to pick it off than try and find it in the other cases.\n         */\n\n        if (db_last(sp, &lno))\n                return (1);\n        if (tm->lno >= lno) {\n                if (tm->lno == lno) {\n                        if (db_get(sp, lno, DBG_FATAL, &p, &len))\n                                return (1);\n                        eof = tm->cno != -1 && tm->cno >= len ? 1 : 0;\n                } else\n                        eof = 1;\n                if (eof) {\n                        for (lno = tm->lno; lno > fm->lno; --lno) {\n                                if (db_delete(sp, lno))\n                                        return (1);\n                                ++sp->rptlines[L_DELETED];\n                                if (lno %\n                                    INTERRUPT_CHECK == 0 && INTERRUPTED(sp))\n                                        break;\n                        }\n                        if (db_get(sp, fm->lno, DBG_FATAL, &p, &len))\n                                return (1);\n                        GET_SPACE_RET(sp, bp, blen, fm->cno);\n                        if (bp == NULL)\n                                return (1);\n                        memcpy(bp, p, fm->cno);\n                        if (db_set(sp, fm->lno, bp, fm->cno))\n                                return (1);\n                        goto done;\n                }\n        }\n\n        /* Case 3 -- delete within a single line. */\n        if (tm->lno == fm->lno) {\n                if (db_get(sp, fm->lno, DBG_FATAL, &p, &len))\n                        return (1);\n                if (len != 0) {\n                        GET_SPACE_RET(sp, bp, blen, len);\n                        if (bp == NULL)\n                                goto err;\n                        if (fm->cno != 0)\n                                memcpy(bp, p, fm->cno);\n                        memcpy(bp + fm->cno, p + (tm->cno + 1), len - (tm->cno + 1));\n                        if (db_set(sp, fm->lno,\n                            bp, len - ((tm->cno - fm->cno) + 1)))\n                                goto err;\n                        goto done;\n                }\n        }\n\n        /*\n         * Case 4 -- delete over multiple lines.\n         *\n         * Copy the start partial line into place.\n         */\n\n        if ((tlen = fm->cno) != 0) {\n                if (db_get(sp, fm->lno, DBG_FATAL, &p, NULL))\n                        return (1);\n                GET_SPACE_RET(sp, bp, blen, tlen + 256);\n                if (bp == NULL)\n                        return (1);\n                memcpy(bp, p, tlen);\n        }\n\n        /* Copy the end partial line into place. */\n        if (db_get(sp, tm->lno, DBG_FATAL, &p, &len))\n                goto err;\n        if (len != 0 && tm->cno != len - 1) {\n                if (len < tm->cno + 1 || len - (tm->cno + 1) > SIZE_MAX - tlen) {\n                        msgq(sp, M_ERR, \"Line length overflow\");\n                        goto err;\n                }\n                nlen = (len - (tm->cno + 1)) + tlen;\n                if (tlen == 0) {\n                        GET_SPACE_RET(sp, bp, blen, nlen);\n                } else\n                        ADD_SPACE_RET(sp, bp, blen, nlen);\n\n                if (bp == NULL)\n                        goto err;\n                memcpy(bp + tlen, p + (tm->cno + 1), len - (tm->cno + 1));\n                tlen += len - (tm->cno + 1);\n        }\n\n        /* Set the current line. */\n        if (db_set(sp, fm->lno, bp, tlen))\n                goto err;\n\n        /* Delete the last and intermediate lines. */\n        for (lno = tm->lno; lno > fm->lno; --lno) {\n                if (db_delete(sp, lno))\n                        goto err;\n                ++sp->rptlines[L_DELETED];\n                if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp))\n                        break;\n        }\n\ndone:   rval = 0;\n        if (0)\nerr:            rval = 1;\n        if (bp != NULL)\n                FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n"
  },
  {
    "path": "common/exf.c",
    "content": "/*      $OpenBSD: exf.c,v 1.50 2024/02/15 00:55:01 jsg Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#undef open\n\n/*\n * We include <sys/file.h>, because the flock(2) and open(2) #defines\n * were found there on historical systems.  We also include <bsd_fcntl.h>\n * because the open(2) #defines are found there on newer systems.\n */\n#include <sys/file.h>\n\n#include <bitstring.h>\n#include <dirent.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n\n#if defined(__solaris__)\n# define __EXTENSIONS__\n# include <termios.h>\n# include <sys/termios.h>\n#endif /* if defined(__solaris__) */\n\n#include <bsd_string.h>\n#include <time.h>\n#include <bsd_unistd.h>\n\n#include <sys/types.h>\n#include <bsd_db.h>\n\n#include \"common.h\"\n\nstatic int      file_backup(SCR *, char *, char *);\nstatic void     file_cinit(SCR *);\nstatic void     file_comment(SCR *);\nstatic int      file_spath(SCR *, FREF *, struct stat *, int *);\n\n/*\n * file_add --\n *      Insert a file name into the FREF list, if it doesn't already\n *      appear in it.\n *\n * !!!\n * The \"if it doesn't already appear\" changes vi's semantics slightly.  If\n * you do a \"vi foo bar\", and then execute \"next bar baz\", the edit of bar\n * will reflect the line/column of the previous edit session.  Historic nvi\n * did not do this.  The change is a logical extension of the change where\n * vi now remembers the last location in any file that it has ever edited,\n * not just the previously edited file.\n *\n * PUBLIC: FREF *file_add(SCR *, CHAR_T *);\n */\nFREF *\nfile_add(SCR *sp, CHAR_T *name)\n{\n        GS *gp;\n        FREF *frp, *tfrp;\n\n        /*\n         * Return it if it already exists.  Note that we test against the\n         * user's name, whatever that happens to be, including if it's a\n         * temporary file.\n         *\n         * If the user added a file but was unable to initialize it, there\n         * can be file list entries where the name field is NULL.  Discard\n         * them the next time we see them.\n         */\n        gp = sp->gp;\n        if (name != NULL)\n                TAILQ_FOREACH_SAFE(frp, &gp->frefq, q, tfrp) {\n                        if (frp->name == NULL) {\n                                TAILQ_REMOVE(&gp->frefq, frp, q);\n                                free(frp->name);\n                                free(frp);\n                                continue;\n                        }\n                        if (!strcmp(frp->name, name))\n                                return (frp);\n                }\n\n        /* Allocate and initialize the FREF structure. */\n        CALLOC(sp, frp, 1, sizeof(FREF));\n        if (frp == NULL)\n                return (NULL);\n\n        /*\n         * If no file name specified, or if the file name is a request\n         * for something temporary, file_init() will allocate the file\n         * name.  Temporary files are always ignored.\n         */\n        if (name != NULL && strcmp(name, TEMPORARY_FILE_STRING) &&\n            (frp->name = strdup(name)) == NULL) {\n                free(frp);\n                msgq(sp, M_SYSERR, NULL);\n                return (NULL);\n        }\n\n        /* Append into the chain of file names. */\n        TAILQ_INSERT_TAIL(&gp->frefq, frp, q);\n\n        return (frp);\n}\n\n/*\n * file_init --\n *      Start editing a file, based on the FREF structure.  If successful,\n *      let go of any previous file.  Don't release the previous file until\n *      absolutely sure we have the new one.\n *\n * PUBLIC: int file_init(SCR *, FREF *, char *, int);\n */\nint\nfile_init(SCR *sp, FREF *frp, char *rcv_name, int flags)\n{\n        EXF *ep;\n        RECNOINFO oinfo;\n        struct stat sb;\n        size_t psize;\n        int fd, exists, open_err, readonly;\n        char *oname, tname[] = \"/tmp/vi.XXXXXX\";\n\n        open_err = readonly = 0;\n\n        /*\n         * If the file is a recovery file, let the recovery code handle it.\n         * Clear the FR_RECOVER flag first -- the recovery code does set up,\n         * and then calls us!  If the recovery call fails, it's probably\n         * because the named file doesn't exist.  So, move boldly forward,\n         * presuming that there's an error message the user will get to see.\n         */\n        if (F_ISSET(frp, FR_RECOVER)) {\n                F_CLR(frp, FR_RECOVER);\n                if (rcv_read(sp, frp) == 0)\n                        return (0);             /* successful recovery */\n        }\n\n        /*\n         * Required FRP initialization; the only flag we keep is the\n         * cursor information.\n         */\n        F_CLR(frp, ~FR_CURSORSET);\n\n        /*\n         * Required EXF initialization:\n         *      Flush the line caches.\n         *      Default recover mail file fd to -1.\n         *      Set initial EXF flag bits.\n         */\n        CALLOC_RET(sp, ep, 1, sizeof(EXF));\n        ep->c_lno = ep->c_nlines = OOBLNO;\n        ep->rcv_fd = ep->fcntl_fd = -1;\n        F_SET(ep, F_FIRSTMODIFY);\n\n        /*\n         * Scan the user's path to find the file that we're going to\n         * try and open.\n         */\n        if (file_spath(sp, frp, &sb, &exists)) {\n                free(ep);\n                return (1);\n        }\n\n        /*\n         * If no name or backing file, for whatever reason, create a backing\n         * temporary file, saving the temp file name so we can later unlink\n         * it.  If the user never named this file, copy the temporary file name\n         * to the real name (we display that until the user renames it).\n         */\n        oname = frp->name;\n\n        /*\n         * User is editing a named file that doesn't exist yet other than as a\n         * temporary file.\n         */\n        if (!exists && oname != NULL && frp->tname != NULL) {\n                free(ep);\n                return (1);\n        }\n\n        if (LF_ISSET(FS_OPENERR) || oname == NULL || !exists) {\n                /*\n                 * Don't try to create a temporary support file twice.\n                 */\n                if (frp->tname != NULL)\n                        goto err;\n                fd = mkstemp(tname);\n                if (fd == -1 || fstat(fd, &sb) == -1 ||\n                    fchmod(fd, S_IRUSR | S_IWUSR) == -1) {\n                        msgq(sp, M_SYSERR,\n                            \"Unable to create temporary file\");\n                        if (fd != -1) {\n                                close(fd);\n                                (void)unlink(tname);\n                        }\n                        goto err;\n                }\n                (void)close(fd);\n\n                if (frp->name == NULL)\n                        F_SET(frp, FR_TMPFILE);\n                if ((frp->tname = strdup(tname)) == NULL ||\n                    (frp->name == NULL && (frp->name = strdup(tname)) == NULL)) {\n                        free(frp->tname);\n                        frp->tname = NULL;\n                        msgq(sp, M_SYSERR, NULL);\n                        (void)unlink(tname);\n                        goto err;\n                }\n                oname = frp->tname;\n                psize = 1024;\n                if (!LF_ISSET(FS_OPENERR))\n                        F_SET(frp, FR_NEWFILE);\n        } else {\n                /*\n                 * XXX\n                 * A seat of the pants calculation: try to keep the file in\n                 * 15 pages or less.  Don't use a page size larger than 10K\n                 * (vi should have good locality) or smaller than 1K.\n                 */\n                psize = ((sb.st_size / 15) + 1023) / 1024;\n                if (psize >= 8) psize=8<<10;\n                else if (psize >= 4) psize=4<<10;\n                else if (psize >= 2) psize=2<<10;\n                else psize=1<<10;\n\n                if (!S_ISREG(sb.st_mode))\n                        msgq_str(sp, M_ERR, oname,\n                            \"Warning: %s is not a regular file\");\n        }\n\n        /* Save device, inode and modification time. */\n        F_SET(ep, F_DEVSET);\n        ep->mdev = sb.st_dev;\n        ep->minode = sb.st_ino;\n\n        ep->mtim = sb.st_mtim;\n\n        /* Set up recovery. */\n        memset(&oinfo, 0, sizeof(RECNOINFO));\n        oinfo.bval = '\\n';                      /* Always set. */\n        oinfo.psize = psize;\n        oinfo.flags = F_ISSET(sp->gp, G_SNAPSHOT) ? R_SNAPSHOT : 0;\n#ifndef NO_BFNAME\n        if (rcv_name == NULL) {\n                if (!rcv_tmp(sp, ep, frp->name))\n                        oinfo.bfname = ep->rcv_path;\n        } else {\n                if ((ep->rcv_path = strdup(rcv_name)) == NULL) {\n                        msgq(sp, M_SYSERR, NULL);\n                        goto err;\n                }\n                oinfo.bfname = ep->rcv_path;\n                F_SET(ep, F_MODIFIED);\n        }\n#endif /* ifndef NO_BFNAME */\n\n        /* Open a db structure. */\n        if ((ep->db = dbopen(rcv_name == NULL ? oname : NULL,\n            O_NONBLOCK | O_RDONLY,\n            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,\n            DB_RECNO, &oinfo)) == NULL) {\n                msgq_str(sp,\n                    M_SYSERR, rcv_name == NULL ? oname : rcv_name, \"%s\");\n                /*\n                 * !!!\n                 * Historically, vi permitted users to edit files that couldn't\n                 * be read.  This isn't useful for single files from a command\n                 * line, but it's quite useful for \"vi *.c\", since you can skip\n                 * past files that you can't read.\n                 */\n                open_err = 1;\n                goto oerr;\n        }\n\n        /*\n         * Do the remaining things that can cause failure of the new file,\n         * mark and logging initialization.\n         */\n        if (mark_init(sp, ep) || log_init(sp, ep))\n                goto err;\n\n        /*\n         * Set the alternate file name to be the file we're discarding.\n         *\n         * !!!\n         * Temporary files can't become alternate files, so there's no file\n         * name.  This matches historical practice, although it could only\n         * happen in historical vi as the result of the initial command, i.e.\n         * if vi was executed without a file name.\n         */\n        if (LF_ISSET(FS_SETALT))\n                set_alt_name(sp, sp->frp == NULL ||\n                    F_ISSET(sp->frp, FR_TMPFILE) ? NULL : sp->frp->name);\n\n        /*\n         * Close the previous file; if that fails, close the new one and run\n         * for the border.\n         *\n         * !!!\n         * There's a nasty special case.  If the user edits a temporary file,\n         * and then does an \":e! %\", we need to re-initialize the backing\n         * file, but we can't change the name.  (It's worse -- we're dealing\n         * with *names* here, we can't even detect that it happened.)  Set a\n         * flag so that the file_end routine ignores the backing information\n         * of the old file if it happens to be the same as the new one.\n         *\n         * !!!\n         * Side-effect: after the call to file_end(), sp->frp may be NULL.\n         */\n        if (sp->ep != NULL) {\n                F_SET(frp, FR_DONTDELETE);\n                if (file_end(sp, NULL, LF_ISSET(FS_FORCE))) {\n                        (void)file_end(sp, ep, 1);\n                        goto err;\n                }\n                F_CLR(frp, FR_DONTDELETE);\n        }\n\n        /*\n         * Lock the file; if it's a recovery file, it should already be\n         * locked.  Note, we acquire the lock after the previous file\n         * has been ended, so that we don't get an \"already locked\" error\n         * for \":edit!\".\n         *\n         * XXX\n         * While the user can't interrupt us between the open and here,\n         * there's a race between the dbopen() and the lock.  Not much\n         * we can do about it.\n         *\n         * XXX\n         * We don't make a big deal of not being able to lock the file.  As\n         * locking rarely works over NFS, and often fails if the file was\n         * mmap(2)'d, it's far too common to do anything like print an error\n         * message, let alone make the file readonly.  At some future time,\n         * when locking is a little more reliable, this should change to be\n         * an error.\n         */\n        if (rcv_name == NULL && !O_ISSET(sp, O_READONLY))\n                switch (file_lock(sp, oname,\n                    &ep->fcntl_fd, ep->db->fd(ep->db), 0)) {\n                case LOCK_FAILED:\n                        F_SET(frp, FR_UNLOCKED);\n                        break;\n                case LOCK_UNAVAIL:\n                        readonly = 1;\n                        msgq_str(sp, M_INFO, oname,\n                            \"%s already locked, session is read-only\");\n                        break;\n                case LOCK_SUCCESS:\n                        break;\n                }\n\n        /*\n         * Historically, the readonly edit option was set per edit buffer in\n         * vi, unless the -R command-line option was specified or the program\n         * was executed as \"view\".  (Well, to be truthful, if the letter 'w'\n         * occurred anywhere in the program name, but let's not get into that.)\n         * So, the persistent readonly state has to be stored in the screen\n         * structure, and the edit option value toggles with the contents of\n         * the edit buffer.  If the persistent readonly flag is set, set the\n         * readonly edit option.\n         *\n         * Otherwise, try and figure out if a file is readonly.  This is a\n         * dangerous thing to do.  The kernel is the only arbiter of whether\n         * or not a file is writeable, and the best that a user program can\n         * do is guess.  Obvious loopholes are files that are on a file system\n         * mounted readonly (access catches this one on a few systems), or\n         * alternate protection mechanisms, ACL's for example, that we can't\n         * portably check.  Lots of fun, and only here because users whined.\n         *\n         * !!!\n         * Historic vi displayed the readonly message if none of the file\n         * write bits were set, or if an an access(2) call on the path\n         * failed.  This seems reasonable.  If the file is mode 444, root\n         * users may want to know that the owner of the file did not expect\n         * it to be written.\n         *\n         * Historic vi set the readonly bit if no write bits were set for\n         * a file, even if the access call would have succeeded.  This makes\n         * the superuser force the write even when vi expects that it will\n         * succeed.  I'm less supportive of this semantic, but it's historic\n         * practice and the conservative approach to vi'ing files as root.\n         *\n         * It would be nice if there was some way to update this when the user\n         * does a \"^Z; chmod ...\".  The problem is that we'd first have to\n         * distinguish between readonly bits set because of file permissions\n         * and those set for other reasons.  That's not too hard, but deciding\n         * when to reevaluate the permissions is trickier.  An alternative\n         * might be to turn off the readonly bit if the user forces a write\n         * and it succeeds.\n         *\n         * XXX\n         * Access(2) doesn't consider the effective uid/gid values.  This\n         * probably isn't a problem for vi when it's running standalone.\n         */\n        if (readonly || F_ISSET(sp, SC_READONLY) ||\n            (!F_ISSET(frp, FR_NEWFILE) &&\n            (!(sb.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) ||\n            access(frp->name, W_OK))))\n                O_SET(sp, O_READONLY);\n        else\n                O_CLR(sp, O_READONLY);\n\n        /* Switch... */\n        ++ep->refcnt;\n        sp->ep = ep;\n        sp->frp = frp;\n\n        /* Set the initial cursor position, queue initial command. */\n        file_cinit(sp);\n\n        /* Redraw the screen from scratch, schedule a welcome message. */\n        F_SET(sp, SC_SCR_REFORMAT | SC_STATUS);\n\n        if (frp->lno == OOBLNO)\n                F_SET(sp, SC_SCR_TOP);\n\n        return (0);\n\nerr:\n        free(frp->name);\n        frp->name = NULL;\n        if (frp->tname != NULL) {\n                (void)unlink(frp->tname);\n                free(frp->tname);\n                frp->tname = NULL;\n        }\n\noerr:   if (F_ISSET(ep, F_RCV_ON))\n                (void)unlink(ep->rcv_path);\n        free(ep->rcv_path);\n        ep->rcv_path = NULL;\n        if (ep->db != NULL)\n                (void)ep->db->close(ep->db);\n        free(ep);\n\n        return (open_err ?\n            file_init(sp, frp, rcv_name, flags | FS_OPENERR) : 1);\n}\n\n/*\n * file_spath --\n *      Scan the user's path to find the file that we're going to\n *      try and open.\n */\nstatic int\nfile_spath(SCR *sp, FREF *frp, struct stat *sbp, int *existsp)\n{\n        CHAR_T savech;\n        size_t len;\n        int found;\n        char *name, *p, *t, path[PATH_MAX];\n\n        /*\n         * If the name is NULL or an explicit reference (i.e., the first\n         * component is . or ..) ignore the O_PATH option.\n         */\n        name = frp->name;\n        if (name == NULL) {\n                *existsp = 0;\n                return (0);\n        }\n        if (name[0] == '/' || (name[0] == '.' &&\n            (name[1] == '/' || (name[1] == '.' && name[2] == '/')))) {\n                *existsp = !stat(name, sbp);\n                return (0);\n        }\n\n        /* Try . */\n        if (!stat(name, sbp)) {\n                *existsp = 1;\n                return (0);\n        }\n\n        /* Try the O_PATH option values. */\n        for (found = 0, p = t = O_STR(sp, O_PATH);; ++p)\n                if (*p == ':' || *p == '\\0') {\n                        if (t < p - 1) {\n                                savech = *p;\n                                *p = '\\0';\n                                len = snprintf(path,\n                                    sizeof(path), \"%s/%s\", t, name);\n                                if (len >= sizeof(path))\n                                        len = sizeof(path) - 1;\n                                *p = savech;\n                                if (!stat(path, sbp)) {\n                                        found = 1;\n                                        break;\n                                }\n                        }\n                        t = p + 1;\n                        if (*p == '\\0')\n                                break;\n                }\n\n        /* If we found it, build a new pathname and discard the old one. */\n        if (found) {\n                MALLOC_RET(sp, p, len + 1);\n                memcpy(p, path, len + 1);\n                free(frp->name);\n                frp->name = p;\n        }\n        *existsp = found;\n        return (0);\n}\n\n/*\n * file_cinit --\n *      Set up the initial cursor position.\n */\nstatic void\nfile_cinit(SCR *sp)\n{\n        GS *gp;\n        MARK m;\n        size_t len;\n        int nb;\n\n        /* Set some basic defaults. */\n        sp->lno = 1;\n        sp->cno = 0;\n\n        /*\n         * Historically, initial commands (the -c option) weren't executed\n         * until a file was loaded, e.g. \"vi +10 nofile\", followed by an\n         * :edit or :tag command, would execute the +10 on the file loaded\n         * by the subsequent command, (assuming that it existed).  This\n         * applied as well to files loaded using the tag commands, and we\n         * follow that historic practice.  Also, all initial commands were\n         * ex commands and were always executed on the last line of the file.\n         *\n         * Otherwise, if no initial command for this file:\n         *    If in ex mode, move to the last line, first nonblank character.\n         *    If the file has previously been edited, move to the last known\n         *        position, and check it for validity.\n         *    Otherwise, move to the first line, first nonblank.\n         *\n         * This gets called by the file init code, because we may be in a\n         * file of ex commands and we want to execute them from the right\n         * location in the file.\n         */\n        nb = 0;\n        gp = sp->gp;\n        if (gp->C_option != NULL) {\n                if (db_last(sp, &sp->lno))\n                        return;\n                if (sp->lno == 0) {\n                        sp->lno = 1;\n                        sp->cno = 0;\n                }\n                if (ex_run_str(sp,\n                    \"-C option\", gp->C_option, strlen(gp->C_option), 1, 1))\n                        return;\n                gp->C_option = NULL;\n                gp->c_option = NULL;\n        } else if (gp->c_option != NULL && !F_ISSET(sp->frp, FR_NEWFILE)) {\n                if (db_last(sp, &sp->lno))\n                        return;\n                if (sp->lno == 0) {\n                        sp->lno = 1;\n                        sp->cno = 0;\n                }\n                if (ex_run_str(sp,\n                    \"-c option\", gp->c_option, strlen(gp->c_option), 1, 1))\n                        return;\n                gp->c_option = NULL;\n        } else if (F_ISSET(sp, SC_EX)) {\n                if (db_last(sp, &sp->lno))\n                        return;\n                if (sp->lno == 0) {\n                        sp->lno = 1;\n                        sp->cno = 0;\n                        return;\n                }\n                nb = 1;\n        } else {\n                if (F_ISSET(sp->frp, FR_CURSORSET)) {\n                        sp->lno = sp->frp->lno;\n                        sp->cno = sp->frp->cno;\n\n                        /* If returning to a file in vi, center the line. */\n                         F_SET(sp, SC_SCR_CENTER);\n                } else {\n                        if (O_ISSET(sp, O_COMMENT))\n                                file_comment(sp);\n                        else\n                                sp->lno = 1;\n                        nb = 1;\n                }\n                if (db_get(sp, sp->lno, 0, NULL, &len)) {\n                        sp->lno = 1;\n                        sp->cno = 0;\n                        return;\n                }\n                if (!nb && sp->cno > len)\n                        nb = 1;\n        }\n        if (nb) {\n                sp->cno = 0;\n                (void)nonblank(sp, sp->lno, &sp->cno);\n        }\n\n        /*\n         * !!!\n         * The initial column is also the most attractive column.\n         */\n        sp->rcm = sp->cno;\n\n        /*\n         * !!!\n         * Historically, vi initialized the absolute mark, but ex did not.\n         * Which meant, that if the first command in ex mode was \"visual\",\n         * or if an ex command was executed first (e.g. vi +10 file) vi was\n         * entered without the mark being initialized.  For consistency, if\n         * the file isn't empty, we initialize it for everyone, believing\n         * that it can't hurt, and is generally useful.  Not initializing it\n         * if the file is empty is historic practice, although it has always\n         * been possible to set (and use) marks in empty vi files.\n         */\n        m.lno = sp->lno;\n        m.cno = sp->cno;\n        (void)mark_set(sp, ABSMARK1, &m, 0);\n}\n\n/*\n * file_end --\n *      Stop editing a file.\n *\n * PUBLIC: int file_end(SCR *, EXF *, int);\n */\nint\nfile_end(SCR *sp, EXF *ep, int force)\n{\n        FREF *frp;\n\n        /*\n         * !!!\n         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.\n         * (If argument ep is NULL, use sp->ep.)\n         *\n         * If multiply referenced, just decrement the count and return.\n         */\n        if (ep == NULL)\n                ep = sp->ep;\n        if (--ep->refcnt != 0)\n                return (0);\n\n        /*\n         *\n         * Clean up the FREF structure.\n         *\n         * Save the cursor location.\n         *\n         * XXX\n         * It would be cleaner to do this somewhere else, but by the time\n         * ex or vi knows that we're changing files it's already happened.\n         */\n        frp = sp->frp;\n        if (frp == NULL)\n                return (1);\n        frp->lno = sp->lno;\n        frp->cno = sp->cno;\n        F_SET(frp, FR_CURSORSET);\n\n        /*\n         * We may no longer need the temporary backing file, so clean it\n         * up.  We don't need the FREF structure either, if the file was\n         * never named, so lose it.\n         *\n         * !!!\n         * Re: FR_DONTDELETE, see the comment above in file_init().\n         */\n        if (!F_ISSET(frp, FR_DONTDELETE) && frp->tname != NULL) {\n                if (unlink(frp->tname))\n                        msgq_str(sp, M_SYSERR, frp->tname, \"%s: remove\");\n                free(frp->tname);\n                frp->tname = NULL;\n                if (F_ISSET(frp, FR_TMPFILE)) {\n                        TAILQ_REMOVE(&sp->gp->frefq, frp, q);\n                        free(frp->name);\n                        free(frp);\n                        frp = NULL;\n                }\n                sp->frp = NULL;\n        }\n\n        /*\n         * Clean up the EXF structure.\n         *\n         * Close the db structure.\n         */\n        if (ep->db->close != NULL && ep->db->close(ep->db) && !force) {\n                if (frp)\n                    msgq_str(sp, M_SYSERR, frp->name, \"%s: close\");\n                else\n                    msgq(sp, M_SYSERR, \"close\");\n                ++ep->refcnt;\n                return (1);\n        }\n\n        /* COMMITTED TO THE CLOSE.  THERE'S NO GOING BACK... */\n\n        /* Stop logging. */\n        (void)log_end(sp, ep);\n\n        /* Free up any marks. */\n        (void)mark_end(sp, ep);\n\n        /*\n         * Delete recovery files, close the open descriptor, free recovery\n         * memory.  See recover.c for a description of the protocol.\n         *\n         * XXX\n         * Unlink backup file first, we can detect that the recovery file\n         * doesn't reference anything when the user tries to recover it.\n         * There's a race, here, obviously, but it's fairly small.\n         */\n        if (!F_ISSET(ep, F_RCV_NORM)) {\n                if (ep->rcv_path != NULL && unlink(ep->rcv_path))\n                        msgq_str(sp, M_SYSERR, ep->rcv_path, \"%s: remove\");\n                if (ep->rcv_mpath != NULL && unlink(ep->rcv_mpath))\n                        msgq_str(sp, M_SYSERR, ep->rcv_mpath, \"%s: remove\");\n        }\n        if (ep->fcntl_fd != -1)\n                (void)close(ep->fcntl_fd);\n        if (ep->rcv_fd != -1)\n                (void)close(ep->rcv_fd);\n        free(ep->rcv_path);\n        free(ep->rcv_mpath);\n        free(ep);\n        return (0);\n}\n\n/*\n * file_write --\n *      Write the file to disk.  Historic vi had fairly convoluted\n *      semantics for whether or not writes would happen.  That's\n *      why all the flags.\n *\n * PUBLIC: int file_write(SCR *, MARK *, MARK *, char *, int);\n */\nint\nfile_write(SCR *sp, MARK *fm, MARK *tm, char *name, int flags)\n{\n        enum { NEWFILE, OLDFILE } mtype;\n        struct stat sb;\n        EXF *ep;\n        FILE *fp;\n        FREF *frp;\n        MARK from, to;\n        size_t len;\n        unsigned long nlno, nch;\n        int fd, nf, noname, oflags, rval;\n        char *p, *s, *t, buf[PATH_MAX + 64];\n        const char *msgstr;\n\n        ep = sp->ep;\n        frp = sp->frp;\n\n        /*\n         * Writing '%', or naming the current file explicitly, has the\n         * same semantics as writing without a name.\n         */\n        if (name == NULL || !strcmp(name, frp->name)) {\n                noname = 1;\n                name = frp->name;\n        } else\n                noname = 0;\n\n        /* Can't write files marked read-only, unless forced. */\n        if (!LF_ISSET(FS_FORCE) && noname && O_ISSET(sp, O_READONLY)) {\n                msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ?\n                    \"Read-only file, not written; use ! to override\" :\n                    \"Read-only file, not written\");\n                return (1);\n        }\n\n        /* If not forced, not appending, and \"writeany\" not set ... */\n        if (!LF_ISSET(FS_FORCE | FS_APPEND) && !O_ISSET(sp, O_WRITEANY)) {\n                /* Don't overwrite anything but the original file. */\n                if ((!noname || F_ISSET(frp, FR_NAMECHANGE)) &&\n                    !stat(name, &sb)) {\n                        msgq_str(sp, M_ERR, name,\n                            LF_ISSET(FS_POSSIBLE) ?\n                            \"%s exists, not written; use ! to override\" :\n                            \"%s exists, not written\");\n                        return (1);\n                }\n\n                /*\n                 * Don't write part of any existing file.  Only test for the\n                 * original file, the previous test catches anything else.\n                 */\n                if (!LF_ISSET(FS_ALL) && noname && !stat(name, &sb)) {\n                        msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ?\n                            \"Partial file, not written; use ! to override\" :\n                            \"Partial file, not written\");\n                        return (1);\n                }\n        }\n\n        /*\n         * Figure out if the file already exists -- if it doesn't, we display\n         * the \"new file\" message.  The stat might not be necessary, but we\n         * just repeat it because it's easier than hacking the previous tests.\n         * The information is only used for the user message and modification\n         * time test, so we can ignore the obvious race condition.\n         *\n         * One final test.  If we're not forcing or appending the current file,\n         * and we have a saved modification time, object if the file changed\n         * since we last edited or wrote it, and make them force it.\n         */\n        if (stat(name, &sb))\n                mtype = NEWFILE;\n        else {\n                if (noname && !LF_ISSET(FS_FORCE | FS_APPEND) &&\n                    ((F_ISSET(ep, F_DEVSET) &&\n                    (sb.st_dev != ep->mdev || sb.st_ino != ep->minode)) ||\n                    timespeccmp(&sb.st_mtim, &ep->mtim, !=))) {\n                        msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ?\n\"%s: file modified more recently than this copy; use ! to override\" :\n\"%s: file modified more recently than this copy\");\n                        return (1);\n                }\n\n                mtype = OLDFILE;\n        }\n\n        /* Set flags to create, write, and either append or truncate. */\n        oflags = O_CREAT | O_WRONLY |\n            (LF_ISSET(FS_APPEND) ? O_APPEND : O_TRUNC);\n\n        /* Backup the file if requested. */\n        if (!opts_empty(sp, O_BACKUP, 1) &&\n            file_backup(sp, name, O_STR(sp, O_BACKUP)) && !LF_ISSET(FS_FORCE))\n                return (1);\n\n        /* Open the file. */\n        if ((fd = open(name, oflags,\n            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) {\n                msgq_str(sp, M_SYSERR, name, \"%s\");\n                return (1);\n        }\n\n        /* Try and get a lock. */\n        if (!noname && file_lock(sp, NULL, NULL, fd, 0) == LOCK_UNAVAIL)\n                msgq_str(sp, M_ERR, name,\n                    \"%s: write lock was unavailable\");\n\n        /*\n         * Use stdio for buffering.\n         *\n         * XXX\n         * SVR4.2 requires the fdopen mode exactly match the original open\n         * mode, i.e. you have to open with \"a\" if appending.\n         */\n        if ((fp = fdopen(fd, LF_ISSET(FS_APPEND) ? \"a\" : \"w\")) == NULL) {\n                msgq_str(sp, M_SYSERR, name, \"%s\");\n                (void)close(fd);\n                return (1);\n        }\n\n        /* Build fake addresses, if necessary. */\n        if (fm == NULL) {\n                from.lno = 1;\n                from.cno = 0;\n                fm = &from;\n                if (db_last(sp, &to.lno)) {\n                        (void)fclose(fp);\n                        return (1);\n                }\n                to.cno = 0;\n                tm = &to;\n        }\n\n        rval = ex_writefp(sp, name, fp, fm, tm, &nlno, &nch, 0);\n\n        /*\n         * Save the new last modification time -- even if the write fails\n         * we re-init the time.  That way the user can clean up the disk\n         * and rewrite without having to force it.\n         */\n        if (noname) {\n                if (stat(name, &sb))\n                        (void)clock_gettime(CLOCK_REALTIME, &ep->mtim);\n                else {\n                        F_SET(ep, F_DEVSET);\n                        ep->mdev = sb.st_dev;\n                        ep->minode = sb.st_ino;\n\n                        ep->mtim = sb.st_mtim;\n                }\n        }\n\n        /*\n         * If the write failed, complain loudly.  ex_writefp() has already\n         * complained about the actual error, reinforce it if data was lost.\n         */\n        if (rval) {\n                if (!LF_ISSET(FS_APPEND))\n                        msgq_str(sp, M_ERR, name,\n                            \"%s: WARNING: FILE TRUNCATED\");\n                return (1);\n        }\n\n        /*\n         * Once we've actually written the file, it doesn't matter that the\n         * file name was changed -- if it was, we've already whacked it.\n         */\n        F_CLR(frp, FR_NAMECHANGE);\n\n        /*\n         * If wrote the entire file, and it wasn't by appending it to a file,\n         * clear the modified bit.  If the file was written to the original\n         * file name and the file is a temporary, set the \"no exit\" bit.  This\n         * permits the user to write the file and use it in the context of the\n         * filesystem, but still keeps them from discarding their changes by\n         * exiting.\n         */\n        if (LF_ISSET(FS_ALL) && !LF_ISSET(FS_APPEND)) {\n                F_CLR(ep, F_MODIFIED);\n                if (F_ISSET(frp, FR_TMPFILE)) {\n                        if (noname)\n                                F_SET(frp, FR_TMPEXIT);\n                        else\n                                F_CLR(frp, FR_TMPEXIT);\n                }\n        }\n\n        p = msg_print(sp, name, &nf);\n        switch (mtype) {\n        case NEWFILE:\n                len = snprintf(buf, sizeof(buf),\n                    \"%s: new file: %'lu lines, %'lu characters\", p, nlno, nch);\n                if (len >= sizeof(buf))\n                        len = sizeof(buf) - 1;\n                break;\n        case OLDFILE:\n                msgstr = LF_ISSET(FS_APPEND) ?\n                    \"%s: appended: %'lu lines, %'lu characters\" :\n                    \"%s: %'lu lines, %'lu characters\";\n                len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch);\n                if (len >= sizeof(buf))\n                        len = sizeof(buf) - 1;\n                break;\n        default:\n                abort();\n        }\n\n        /*\n         * There's a nasty problem with long path names.  Tags files\n         * can result in long paths and vi will request a continuation key from\n         * the user.  Unfortunately, the user has typed ahead, and chaos will\n         * result.  If we assume that the characters in the filenames only take\n         * a single screen column each, we can trim the filename.\n         */\n        s = buf;\n        if (len >= sp->cols) {\n                for (s = buf, t = buf + strlen(p); s < t &&\n                    (*s != '/' || len >= sp->cols - 3); ++s, --len);\n                if (s == t)\n                        s = buf;\n                else {\n                        *--s = '.';             /* Leading ellipses. */\n                        *--s = '.';\n                        *--s = '.';\n                }\n        }\n        msgq(sp, M_INFO, \"%s\", s);\n        if (nf)\n                FREE_SPACE(sp, p, 0);\n        return (0);\n}\n\n/*\n * file_backup --\n *      Backup the about-to-be-written file.\n *\n * XXX\n * We do the backup by copying the entire file.  It would be nice to do\n * a rename instead, but: (1) both files may not fit and we want to fail\n * before doing the rename; (2) the backup file may not be on the same\n * disk partition as the file being written; (3) there may be optional\n * file information (MACs, DACs, whatever) that we won't get right if we\n * recreate the file.  So, let's not risk it.\n */\nstatic int\nfile_backup(SCR *sp, char *name, char *bname)\n{\n        struct dirent *dp;\n        struct stat sb;\n        DIR *dirp;\n        EXCMD cmd;\n        off_t off;\n        size_t blen;\n        int flags, maxnum, nr, num, nw, rfd, wfd, version;\n        char *bp, *estr, *p, *pct, *slash, *t, *wfname, buf[8192];\n\n        rfd = wfd = -1;\n        (void)rfd;\n        bp = estr = wfname = NULL;\n\n        /*\n         * Open the current file for reading.  Do this first, so that\n         * we don't exec a shell before the most likely failure point.\n         * If it doesn't exist, it's okay, there's just nothing to back\n         * up.\n         */\n        errno = 0;\n        if ((rfd = open(name, O_RDONLY)) < 0) {\n                if (errno == ENOENT)\n                        return (0);\n                estr = name;\n                goto err;\n        }\n\n        /*\n         * If the name starts with an 'N' character, add a version number\n         * to the name.  Strip the leading N from the string passed to the\n         * expansion routines, for no particular reason.  It would be nice\n         * to permit users to put the version number anywhere in the backup\n         * name, but there isn't a special character that we can use in the\n         * name, and giving a new character a special meaning leads to ugly\n         * hacks both here and in the supporting ex routines.\n         *\n         * Shell and file name expand the option's value.\n         */\n        argv_init(sp, &cmd);\n        ex_cinit(&cmd, 0, 0, 0, 0, 0, NULL);\n        if (bname[0] == 'N') {\n                version = 1;\n                ++bname;\n        } else\n                version = 0;\n        if (argv_exp2(sp, &cmd, bname, strlen(bname))) {\n                (void)close(rfd);\n                return (1);\n        }\n\n\n        /*\n         *  0 args: impossible.\n         *  1 args: use it.\n         * >1 args: object, too many args.\n         */\n        if (cmd.argc != 1) {\n                msgq_str(sp, M_ERR, bname,\n                    \"%s expanded into too many file names\");\n                (void)close(rfd);\n                return (1);\n        }\n\n        /*\n         * If appending a version number, read through the directory, looking\n         * for file names that match the name followed by a number.  Make all\n         * of the other % characters in name literal, so the user doesn't get\n         * surprised and sscanf doesn't drop core indirecting through pointers\n         * that don't exist.  If any such files are found, increment its number\n         * by one.\n         */\n        if (version) {\n                GET_SPACE_GOTO(sp, bp, blen, cmd.argv[0]->len * 2 + 50);\n                if (bp == NULL)\n                        goto err;\n                for (t = bp, slash = NULL,\n                    p = cmd.argv[0]->bp; p[0] != '\\0'; *t++ = *p++)\n                        if (p[0] == '%') {\n                                if (p[1] != '%')\n                                        *t++ = '%';\n                        } else if (p[0] == '/')\n                                slash = t;\n                pct = t;\n                *t++ = '%';\n                *t++ = 'd';\n                *t = '\\0';\n\n                if (slash == NULL) {\n                        dirp = opendir(\".\");\n                        p = bp;\n                } else {\n                        *slash = '\\0';\n                        dirp = opendir(bp);\n                        *slash = '/';\n                        p = slash + 1;\n                }\n                if (dirp == NULL) {\n                        estr = cmd.argv[0]->bp;\n                        goto err;\n                }\n\n                for (maxnum = 0; (dp = readdir(dirp)) != NULL;)\n                        if (sscanf(dp->d_name, p, &num) == 1 && num > maxnum)\n                                maxnum = num;\n                (void)closedir(dirp);\n\n                /* Format the backup file name. */\n                (void)snprintf(pct, blen - (pct - bp), \"%d\", maxnum + 1);\n                wfname = bp;\n        } else {\n                bp = NULL;\n                wfname = cmd.argv[0]->bp;\n        }\n\n        /* Open the backup file, avoiding lurkers. */\n        if (stat(wfname, &sb) == 0) {\n                if (!S_ISREG(sb.st_mode)) {\n                        msgq_str(sp, M_ERR, bname,\n                            \"%s: not a regular file\");\n                        goto err;\n                }\n                if (sb.st_uid != getuid()) {\n                        msgq_str(sp, M_ERR, bname, \"%s: not owned by you\");\n                        goto err;\n                }\n                if (sb.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) {\n                        msgq_str(sp, M_ERR, bname,\n                           \"%s: accessible by a user other than the owner\");\n                        goto err;\n                }\n                flags = O_TRUNC;\n        } else\n                flags = O_CREAT | O_EXCL;\n        if ((wfd = open(wfname, flags | O_WRONLY, S_IRUSR | S_IWUSR)) < 0 ||\n            fchmod(wfd, S_IRUSR | S_IWUSR) < 0) {\n                if (wfd != -1) {\n                        close(wfd);\n                        (void)unlink(wfname);\n                }\n                estr = bname;\n                goto err;\n        }\n\n        /* Copy the file's current contents to its backup value. */\n        while ((nr = read(rfd, buf, sizeof(buf))) > 0)\n                for (off = 0; nr != 0; nr -= nw, off += nw)\n                        if ((nw = write(wfd, buf + off, nr)) < 0) {\n                                estr = wfname;\n                                goto err;\n                        }\n        if (nr < 0) {\n                estr = name;\n                goto err;\n        }\n\n        if (close(rfd)) {\n                estr = name;\n                goto err;\n        }\n        if (close(wfd)) {\n                estr = wfname;\n                goto err;\n        }\n        if (bp != NULL)\n                FREE_SPACE(sp, bp, blen);\n        return (0);\n\nalloc_err:\nerr:    if (rfd != -1)\n                (void)close(rfd);\n        if (wfd != -1) {\n                (void)unlink(wfname);\n                (void)close(wfd);\n        }\n        if (estr)\n                msgq_str(sp, M_SYSERR, estr, \"%s\");\n        if (bp != NULL)\n                FREE_SPACE(sp, bp, blen);\n        return (1);\n}\n\n/*\n * file_comment --\n *      Skip the first comment.\n */\nstatic void\nfile_comment(SCR *sp)\n{\n        recno_t lno;\n        size_t len;\n        char *p;\n\n        for (lno = 1; !db_get(sp, lno, 0, &p, &len) && len == 0; ++lno);\n        if (p == NULL)\n                return;\n        if (p[0] == '#') {\n                F_SET(sp, SC_SCR_TOP);\n                while (!db_get(sp, ++lno, 0, &p, &len))\n                        if (len < 1 || p[0] != '#') {\n                                sp->lno = lno;\n                                return;\n                        }\n        } else if (len > 1 && p[0] == '/' && p[1] == '*') {\n                F_SET(sp, SC_SCR_TOP);\n                do {\n                        for (; len > 1; --len, ++p)\n                                if (p[0] == '*' && p[1] == '/') {\n                                        sp->lno = lno;\n                                        return;\n                                }\n                } while (!db_get(sp, ++lno, 0, &p, &len));\n        } else if (len > 1 && p[0] == '/' && p[1] == '/') {\n                F_SET(sp, SC_SCR_TOP);\n                p += 2;\n                len -= 2;\n                do {\n                        for (; len > 1; --len, ++p)\n                                if (p[0] == '/' && p[1] == '/') {\n                                        sp->lno = lno;\n                                        return;\n                                }\n                } while (!db_get(sp, ++lno, 0, &p, &len));\n        }\n}\n\n/*\n * file_m1 --\n *      First modification check routine.  The :next, :prev, :rewind, :tag,\n *      :tagpush, :tagpop, ^^ modifications check.\n *\n * PUBLIC: int file_m1(SCR *, int, int);\n */\nint\nfile_m1(SCR *sp, int force, int flags)\n{\n        EXF *ep;\n\n        ep = sp->ep;\n\n        /* If no file loaded, return no modifications. */\n        if (ep == NULL)\n                return (0);\n\n        /*\n         * If the file has been modified, we'll want to write it back or\n         * fail.  If autowrite is set, we'll write it back automatically,\n         * unless force is also set.  Otherwise, we fail unless forced or\n         * there's another open screen on this file.\n         */\n        if (F_ISSET(ep, F_MODIFIED)) {\n                if (O_ISSET(sp, O_AUTOWRITE)) {\n                        if (!force && file_aw(sp, flags))\n                                return (1);\n                } else if (ep->refcnt <= 1 && !force) {\n                        msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ?\n\"File may be modified since last complete write; write or use ! to override\" :\n\"File may be modified since last complete write; write or use :edit! to override\");\n                        return (1);\n                }\n        }\n\n        return (file_m3(sp, force));\n}\n\n/*\n * file_m2 --\n *      Second modification check routine.  The :edit, :quit, :recover\n *      modifications check.\n *\n * PUBLIC: int file_m2(SCR *, int);\n */\nint\nfile_m2(SCR *sp, int force)\n{\n        EXF *ep;\n\n        ep = sp->ep;\n\n        /* If no file loaded, return no modifications. */\n        if (ep == NULL)\n                return (0);\n\n        /*\n         * If the file has been modified, we'll want to fail, unless forced\n         * or there's another open screen on this file.\n         */\n        if (F_ISSET(ep, F_MODIFIED) && ep->refcnt <= 1 && !force) {\n                msgq(sp, M_ERR,\n\"File may be modified since last complete write; write or use ! to override\");\n                return (1);\n        }\n\n        return (file_m3(sp, force));\n}\n\n/*\n * file_m3 --\n *      Third modification check routine.\n *\n * PUBLIC: int file_m3(SCR *, int);\n */\nint\nfile_m3(SCR *sp, int force)\n{\n        EXF *ep;\n\n        ep = sp->ep;\n\n        /* If no file loaded, return no modifications. */\n        if (ep == NULL)\n                return (0);\n\n        /*\n         * Don't exit while in a temporary files if the file was ever modified.\n         * The problem is that if the user does a \":wq\", we write and quit,\n         * unlinking the temporary file.  Not what the user had in mind at all.\n         * We permit writing to temporary files, so that user maps using file\n         * system names work with temporary files.\n         */\n        if (F_ISSET(sp->frp, FR_TMPEXIT) && ep->refcnt <= 1 && !force) {\n                msgq(sp, M_ERR,\n                    \"File is a temporary; exit will discard modifications\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * file_aw --\n *      Autowrite routine.  If modified, autowrite is set and the readonly bit\n *      is not set, write the file.  A routine so there's a place to put the\n *      comment.\n *\n * PUBLIC: int file_aw(SCR *, int);\n */\nint\nfile_aw(SCR *sp, int flags)\n{\n        if (!F_ISSET(sp->ep, F_MODIFIED))\n                return (0);\n        if (!O_ISSET(sp, O_AUTOWRITE))\n                return (0);\n\n        /*\n         * !!!\n         * Historic 4BSD vi attempted to write the file if autowrite was set,\n         * regardless of the writeability of the file (as defined by the file\n         * readonly flag).  System V changed this as some point, not attempting\n         * autowrite if the file was readonly.  This feels like a bug fix to\n         * me (e.g. the principle of least surprise is violated if readonly is\n         * set and vi writes the file), so I'm compatible with System V.\n         */\n        if (O_ISSET(sp, O_READONLY)) {\n                msgq(sp, M_INFO,\n                    \"File readonly, modifications not auto-written\");\n                return (1);\n        }\n        return (file_write(sp, NULL, NULL, NULL, flags));\n}\n\n/*\n * set_alt_name --\n *      Set the alternate pathname.\n *\n * Set the alternate pathname.  It's a routine because I wanted some place\n * to hang this comment.  The alternate pathname (normally referenced using\n * the special character '#' during file expansion and in the vi ^^ command)\n * is set by almost all ex commands that take file names as arguments.  The\n * rules go something like this:\n *\n *    1: If any ex command takes a file name as an argument (except for the\n *       :next command), the alternate pathname is set to that file name.\n *       This excludes the command \":e\" and \":w !command\" as no file name\n *       was specified.  Note, historically, the :source command did not set\n *       the alternate pathname.  It does in nvi, for consistency.\n *\n *    2: However, if any ex command sets the current pathname, e.g. the\n *       \":e file\" or \":rew\" commands succeed, then the alternate pathname\n *       is set to the previous file's current pathname, if it had one.\n *       This includes the \":file\" command and excludes the \":e\" command.\n *       So, by rule #1 and rule #2, if \":edit foo\" fails, the alternate\n *       pathname will be \"foo\", if it succeeds, the alternate pathname will\n *       be the previous current pathname.  The \":e\" command will not set\n *       the alternate or current pathnames regardless.\n *\n *    3: However, if it's a read or write command with a file argument and\n *       the current pathname has not yet been set, the file name becomes\n *       the current pathname, and the alternate pathname is unchanged.\n *\n * If the user edits a temporary file, there may be times when there is no\n * alternative file name.  A name argument of NULL turns it off.\n *\n * PUBLIC: void set_alt_name(SCR *, char *);\n */\nvoid\nset_alt_name(SCR *sp, char *name)\n{\n        free(sp->alt_name);\n        if (name == NULL)\n                sp->alt_name = NULL;\n        else if ((sp->alt_name = strdup(name)) == NULL)\n                msgq(sp, M_SYSERR, NULL);\n}\n\n/*\n * file_lock --\n *      Get an exclusive lock on a file.\n *\n * PUBLIC: lockr_t file_lock(SCR *, char *, int *, int, int);\n */\nlockr_t\nfile_lock(SCR *sp, char *name, int *fdp, int fd, int iswrite)\n{\n        if (!O_ISSET(sp, O_LOCKFILES))\n                return (LOCK_SUCCESS);\n\n        /* Set close-on-exec flag so locks are not inherited by shell cmd. */\n        if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)\n                msgq_str(sp, M_SYSERR, name, \"%s\");\n\n        /*\n         * !!!\n         * We need to distinguish a lock not being available for the file\n         * from the file system not supporting locking.  Flock is documented\n         * as returning EWOULDBLOCK; add EAGAIN for good measure, and assume\n         * they are the former.  There's no portable way to do this.\n         */\n        errno = 0;\n#ifdef __solaris__\n        return (LOCK_FAILED);\n#else\n        return (flock(fd, LOCK_EX | LOCK_NB) ?\n            errno == EAGAIN || errno == EWOULDBLOCK ? LOCK_UNAVAIL : LOCK_FAILED :\n            LOCK_SUCCESS);\n#endif /* ifdef __solaris__ */\n}\n"
  },
  {
    "path": "common/exf.h",
    "content": "/*      $OpenBSD: exf.h,v 1.6 2022/02/20 19:45:51 tb Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)exf.h       10.7 (Berkeley) 7/9/96\n */\n\n#ifdef _AIX\n# include \"../include/compat.h\"\n# include <sys/queue.h>\n# include <sys/stat.h>\n# include <sys/time.h>\n# include <sys/wait.h>\n# undef open\n#endif /* ifdef _AIX */\n\n/*\n * exf --\n *      The file structure.\n */\n\nstruct _exf {\n        int      refcnt;                /* Reference count. */\n\n                                        /* Underlying database state. */\n        DB      *db;                    /* File db structure. */\n        char    *c_lp;                  /* Cached line. */\n        size_t   c_len;                 /* Cached line length. */\n        recno_t  c_lno;                 /* Cached line number. */\n        recno_t  c_nlines;              /* Cached lines in the file. */\n\n        DB      *log;                   /* Log db structure. */\n        char    *l_lp;                  /* Log buffer. */\n        size_t   l_len;                 /* Log buffer length. */\n        recno_t  l_high;                /* Log last + 1 record number. */\n        recno_t  l_cur;                 /* Log current record number. */\n        MARK     l_cursor;              /* Log cursor position. */\n        dir_t    lundo;                 /* Last undo direction. */\n\n        LIST_HEAD(_markh, _lmark) marks;/* Linked list of file MARK's. */\n\n        dev_t    mdev;                  /* Device. */\n        ino_t    minode;                /* Inode. */\n#ifdef _AIX\n        struct st_timespec mtim;        /* Last modification time. (AIX 7+) */\n#else\n        struct timespec mtim;           /* Last modification time. */\n#endif /* ifdef _AIX */\n        int      fcntl_fd;              /* Fcntl locking fd; see exf.c. */\n\n        /*\n         * Recovery in general, and these fields specifically, are described\n         * in recover.c.\n         */\n#define RCV_PERIOD      120             /* Sync every two minutes. */\n        char    *rcv_path;              /* Recover file name. */\n        char    *rcv_mpath;             /* Recover mail file name. */\n        int      rcv_fd;                /* Locked mail file descriptor. */\n\n#define F_DEVSET        0x001           /* mdev/minode fields initialized. */\n#define F_FIRSTMODIFY   0x002           /* File not yet modified. */\n#define F_MODIFIED      0x004           /* File is currently dirty. */\n#define F_MULTILOCK     0x008           /* Multiple processes running, lock. */\n#define F_NOLOG         0x010           /* Logging turned off. */\n#define F_RCV_NORM      0x020           /* Don't delete recovery files. */\n#define F_RCV_ON        0x040           /* Recovery is possible. */\n#define F_UNDO          0x080           /* No change since last undo. */\n#define F_RCV_SYNC      0x100           /* Recovery file sync needed. */\n        u_int16_t flags;\n};\n\n/* Flags to db_get(). */\n#define DBG_FATAL       0x001   /* If DNE, error message. */\n#define DBG_NOCACHE     0x002   /* Ignore the front-end cache. */\n\n/* Flags to file_init() and file_write(). */\n#define FS_ALL          0x001   /* Write the entire file. */\n#define FS_APPEND       0x002   /* Append to the file. */\n#define FS_FORCE        0x004   /* Force is set. */\n#define FS_OPENERR      0x008   /* Open failed, try it again. */\n#define FS_POSSIBLE     0x010   /* Force could have been set. */\n#define FS_SETALT       0x020   /* Set alternate file name. */\n\n/* Flags to rcv_sync(). */\n#define RCV_EMAIL       0x01    /* Send the user email, IFF file modified. */\n#define RCV_ENDSESSION  0x02    /* End the file session. */\n#define RCV_PRESERVE    0x04    /* Preserve backup file, IFF file modified. */\n#define RCV_SNAPSHOT    0x08    /* Snapshot the recovery, and send email. */\n"
  },
  {
    "path": "common/gs.h",
    "content": "/*      $OpenBSD: gs.h,v 1.18 2016/05/27 09:18:11 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)gs.h        10.34 (Berkeley) 9/24/96\n */\n\n#define TEMPORARY_FILE_STRING   \"/tmp\"  /* Default temporary file name. */\n\n/*\n * File reference structure (FREF).  The structure contains the name of the\n * file, along with the information that follows the name.\n *\n * !!!\n * The read-only bit follows the file name, not the file itself.\n */\n\nstruct _fref {\n        TAILQ_ENTRY(_fref) q;           /* Linked list of file references. */\n        char    *name;                  /* File name.                      */\n        char    *tname;                 /* Backing temporary file name.    */\n\n        recno_t  lno;                   /* 1-N: file cursor line.   */\n        size_t   cno;                   /* 0-N: file cursor column. */\n\n#define FR_CURSORSET    0x0001          /* If lno/cno values valid.          */\n#define FR_DONTDELETE   0x0002          /* Don't delete the temporary file.  */\n#define FR_EXNAMED      0x0004          /* Read/write renamed the file.      */\n#define FR_NAMECHANGE   0x0008          /* If the name changed.              */\n#define FR_NEWFILE      0x0010          /* File doesn't really exist yet.    */\n#define FR_RECOVER      0x0020          /* File is being recovered.          */\n#define FR_TMPEXIT      0x0040          /* Modified temporary file, no exit. */\n#define FR_TMPFILE      0x0080          /* If file has no name.              */\n#define FR_UNLOCKED     0x0100          /* File couldn't be locked.          */\n        u_int16_t flags;\n};\n\n/* Action arguments to scr_exadjust(). */\ntypedef enum { EX_TERM_CE, EX_TERM_SCROLL } exadj_t;\n\n/* Screen attribute arguments to scr_attr(). */\ntypedef enum { SA_ALTERNATE, SA_INVERSE } scr_attr_t;\n\n/* Input method control arguments to scr_imctrl(). */\ntypedef enum { IMCTRL_INIT, IMCTRL_OFF, IMCTRL_ON } imctrl_t;\n\n/* Key type arguments to scr_keyval(). */\ntypedef enum { KEY_VEOF, KEY_VERASE, KEY_VKILL, KEY_VWERASE } scr_keyval_t;\n\n/*\n * GS:\n *\n * Structure that describes global state of the running program.\n */\n\nstruct _gs {\n        int      id;                    /* Last allocated screen id. */\n        TAILQ_HEAD(_dqh, _scr) dq;      /* Displayed screens.        */\n        TAILQ_HEAD(_hqh, _scr) hq;      /* Hidden screens.           */\n\n        SCR     *ccl_sp;                /* Colon command-line screen. */\n\n        void    *cl_private;            /* Curses support private area. */\n\n                                        /* File references. */\n        TAILQ_HEAD(_frefh, _fref) frefq;\n\n#define GO_COLUMNS      0               /* Global options: columns.       */\n#define GO_LINES        1               /* Global options: lines.         */\n#define GO_SECURE       2               /* Global options: secure.        */\n#define GO_TERM         3               /* Global options: terminal type. */\n        OPTION   opts[GO_TERM + 1];\n\n        MSGH     msgq;                  /* User message list.                */\n#define DEFAULT_NOPRINT '\\1'            /* Emergency non-printable character */\n        CHAR_T   noprint;               /* Cached, unprintable character.    */\n\n        char    *tmp_bp;                /* Temporary buffer.      */\n        size_t   tmp_blen;              /* Temporary buffer size. */\n\n        /*\n         * Ex command structures (EXCMD).  Defined here because ex commands\n         * exist outside of any particular screen or file.\n         */\n#define EXCMD_RUNNING(gp)       (LIST_FIRST(&(gp)->ecq)->clen != 0)\n        LIST_HEAD(_excmdh, _excmd) ecq; /* Ex command linked list.         */\n        EXCMD    excmd;                 /* Default ex command structure.   */\n        char     *if_name;              /* Current associated file.        */\n        recno_t   if_lno;               /* Current associated line number. */\n\n        char    *c_option;              /* Ex initial, command-line command. */\n        char    *C_option;              /* Ex initial, command-line command. */\n\n#ifdef DEBUG\n        FILE    *tracefp;               /* Trace file pointer. */\n#endif /* ifdef DEBUG */\n\n        EVENT   *i_event;               /* Array of input events.    */\n        size_t   i_nelem;               /* Number of array elements. */\n        size_t   i_cnt;                 /* Count of events.          */\n        size_t   i_next;                /* Offset of next event.     */\n\n        CB      *dcbp;                  /* Default cut buffer pointer. */\n        CB       dcb_store;             /* Default cut buffer storage. */\n        LIST_HEAD(_cuth, _cb) cutq;     /* Linked list of cut buffers. */\n\n#define MAX_BIT_SEQ     128             /* Max + 1 fast check character. */\n        LIST_HEAD(_seqh, _seq) seqq;    /* Linked list of maps, abbrevs. */\n        bitstr_t bit_decl(seqb, MAX_BIT_SEQ);\n\n#define MAX_FAST_KEY    254             /* Max fast check character.*/\n\n#define KEY_LEN(sp, ch)                                                 \\\n        ((unsigned char)(ch) <= MAX_FAST_KEY ?                          \\\n            (sp)->gp->cname[(unsigned char)(ch)].len :                  \\\n            v_key_len((sp), (ch)))\n\n#define KEY_NAME(sp, ch)                                                \\\n        ((unsigned char)(ch) <= MAX_FAST_KEY ?                          \\\n            (sp)->gp->cname[(unsigned char)(ch)].name :                 \\\n            v_key_name((sp), (ch)))\n        struct {\n                CHAR_T   name[MAX_CHARACTER_COLUMNS + 1];\n                u_int8_t len;\n        } cname[MAX_FAST_KEY + 1];      /* Fast lookup table. */\n\n#define KEY_VAL(sp, ch)                                                 \\\n        ((unsigned char)(ch) <= MAX_FAST_KEY ?                          \\\n            (sp)->gp->special_key[(unsigned char)(ch)] :                \\\n            (unsigned char)(ch) > (sp)->gp->max_special ? 0 :           \\\n            v_key_val((sp),(ch)))\n        CHAR_T   max_special;           /* Max special character. */\n        unsigned char                   /* Fast lookup table.     */\n            special_key[MAX_FAST_KEY + 1];\n\n/* Flags. */\n#define G_ABBREV        0x0001          /* If have abbreviations.      */\n#define G_BELLSCHED     0x0002          /* Bell scheduled.             */\n#define G_INTERRUPTED   0x0004          /* Interrupted.                */\n#define G_RECOVER_SET   0x0008          /* Recover system initialized. */\n#define G_SCRIPTED      0x0010          /* Ex script session.          */\n#define G_SCRWIN        0x0020          /* Scripting windows running.  */\n#define G_SNAPSHOT      0x0040          /* Always snapshot files.      */\n#define G_SRESTART      0x0080          /* Screen restarted.           */\n#define G_TMP_INUSE     0x0100          /* Temporary buffer in use.    */\n        u_int32_t flags;\n\n        /* Screen interface functions... */\n                                        /* Add a string to the screen.       */\n        int     (*scr_addstr)(SCR *, const char *, size_t);\n                                        /* Toggle a screen attribute.        */\n        int     (*scr_attr)(SCR *, scr_attr_t, int);\n                                        /* Terminal baud rate.               */\n        int     (*scr_baud)(SCR *, unsigned long *);\n                                        /* Beep/bell/flash the terminal.     */\n        int     (*scr_bell)(SCR *);\n                                        /* Display a busy message.           */\n        void    (*scr_busy)(SCR *, const char *, busy_t);\n                                        /* Clear to the end of the line.     */\n        int     (*scr_clrtoeol)(SCR *);\n                                        /* Return the cursor location.       */\n        int     (*scr_cursor)(SCR *, size_t *, size_t *);\n                                        /* Delete a line.                    */\n        int     (*scr_deleteln)(SCR *);\n                                        /* Get a keyboard event.             */\n        int     (*scr_event)(SCR *, EVENT *, u_int32_t, int);\n                                        /* Ex: screen adjustment routine.    */\n        int     (*scr_ex_adjust)(SCR *, exadj_t);\n        int     (*scr_fmap)             /* Set a function key.               */\n                           (SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t);\n                                        /* Get terminal key value.           */\n        int     (*scr_keyval)(SCR *, scr_keyval_t, CHAR_T *, int *);\n                                        /* Control the state of input method */\n        void    (*scr_imctrl)(SCR *, imctrl_t);\n                                        /* Insert a line.                    */\n        int     (*scr_insertln)(SCR *);\n                                        /* Handle an option change.          */\n        int     (*scr_optchange)(SCR *, int, char *, unsigned long *);\n                                        /* Move the cursor.                  */\n        int     (*scr_move)(SCR *, size_t, size_t);\n                                        /* Message or ex output.             */\n        void    (*scr_msg)(SCR *, mtype_t, char *, size_t);\n                                        /* Refresh the screen.               */\n        int     (*scr_refresh)(SCR *, int);\n                                        /* Rename the file.                  */\n        int     (*scr_rename)(SCR *, char *, int);\n                                        /* Set the screen type.              */\n        int     (*scr_screen)(SCR *, u_int32_t);\n                                        /* Suspend the editor.               */\n        int     (*scr_suspend)(SCR *, int *);\n                                        /* Print usage message.              */\n        void    (*scr_usage)(void);\n};\n"
  },
  {
    "path": "common/key.c",
    "content": "/*      $OpenBSD: key.c,v 1.19 2022/04/21 17:50:50 millert Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <locale.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"common.h\"\n#include \"../vi/vi.h\"\n\n#define MAXIMUM(a, b)   (((a) > (b)) ? (a) : (b))\n\nstatic int      v_event_append(SCR *, EVENT *);\nstatic int      v_event_grow(SCR *, int);\nstatic int      v_key_cmp(const void *, const void *);\nstatic void     v_keyval(SCR *, int, scr_keyval_t);\nstatic void     v_sync(SCR *, int);\n\n/*\n * !!!\n * Historic vi always used:\n *\n *      ^D: autoindent deletion\n *      ^H: last character deletion\n *      ^W: last word deletion\n *      ^Q: quote the next character (if not used in flow control).\n *      ^V: quote the next character\n *\n * regardless of the user's choices for these characters.  The user's erase\n * and kill characters worked in addition to these characters.  Nvi wires\n * down the above characters, but in addition permits the VEOF, VERASE, VKILL\n * and VWERASE characters described by the user's termios structure.\n *\n * Ex was not consistent with this scheme, as it historically ran in tty\n * cooked mode.  This meant that the scroll command and autoindent erase\n * characters were mapped to the user's EOF character, and the character\n * and word deletion characters were the user's tty character and word\n * deletion characters.  This implementation makes it all consistent, as\n * described above for vi.\n *\n * !!!\n * This means that all screens share a special key set.\n */\nKEYLIST keylist[] = {\n        {K_BACKSLASH,     '\\\\'},        /*  \\ */\n        {K_CARAT,          '^'},        /*  ^ */\n        {K_CNTRLD,      '\\004'},        /* ^D */\n        {K_CNTRLR,      '\\022'},        /* ^R */\n        {K_CNTRLT,      '\\024'},        /* ^T */\n        {K_CNTRLZ,      '\\032'},        /* ^Z */\n        {K_COLON,          ':'},        /*  : */\n        {K_CR,            '\\r'},        /* \\r */\n        {K_ESCAPE,      '\\033'},        /* ^[ */\n        {K_FORMFEED,      '\\f'},        /* \\f */\n        {K_HEXCHAR,     '\\030'},        /* ^X */\n        {K_NL,            '\\n'},        /* \\n */\n        {K_RIGHTBRACE,     '}'},        /*  } */\n        {K_RIGHTPAREN,     ')'},        /*  ) */\n        {K_TAB,           '\\t'},        /* \\t */\n        {K_VERASE,        '\\b'},        /* \\b */\n        {K_VKILL,       '\\025'},        /* ^U */\n        {K_VLNEXT,      '\\021'},        /* ^Q */\n        {K_VLNEXT,      '\\026'},        /* ^V */\n        {K_VWERASE,     '\\027'},        /* ^W */\n        {K_ZERO,           '0'},        /*  0 */\n\n#define ADDITIONAL_CHARACTERS   4\n        {K_NOTUSED, 0},                 /* VEOF, VERASE, VKILL, VWERASE */\n        {K_NOTUSED, 0},\n        {K_NOTUSED, 0},\n        {K_NOTUSED, 0},\n};\nstatic int nkeylist =\n    (sizeof(keylist) / sizeof(keylist[0])) - ADDITIONAL_CHARACTERS;\n\n/*\n * v_key_init --\n *      Initialize the special key lookup table.\n *\n * PUBLIC: int v_key_init(SCR *);\n */\nint\nv_key_init(SCR *sp)\n{\n        unsigned int ch;\n        GS *gp;\n        KEYLIST *kp;\n        int cnt;\n\n        gp = sp->gp;\n\n        /*\n         * XXX\n         * 8-bit only, for now.  Recompilation should get you any 8-bit\n         * character set, as long as NULL isn't a character.\n         */\n        (void)setlocale(LC_ALL, \"\");\n        (void)setlocale(LC_NUMERIC, \"\");\n        v_key_ilookup(sp);\n\n        v_keyval(sp, K_CNTRLD, KEY_VEOF);\n        v_keyval(sp, K_VERASE, KEY_VERASE);\n        v_keyval(sp, K_VKILL, KEY_VKILL);\n        v_keyval(sp, K_VWERASE, KEY_VWERASE);\n\n        /* Sort the special key list. */\n        qsort(keylist, nkeylist, sizeof(keylist[0]), v_key_cmp);\n\n        /* Initialize the fast lookup table. */\n        for (gp->max_special = 0, kp = keylist, cnt = nkeylist; cnt--; ++kp) {\n                if (gp->max_special < kp->value)\n                        gp->max_special = kp->value;\n                if (kp->ch <= MAX_FAST_KEY)\n                        gp->special_key[kp->ch] = kp->value;\n        }\n\n        /* Find a non-printable character to use as a message separator. */\n        for (ch = 1; ch <= MAX_CHAR_T; ++ch)\n                if (!isprint(ch)) {\n                        gp->noprint = ch;\n                        break;\n                }\n        if (ch != gp->noprint) {\n                msgq(sp, M_ERR, \"No non-printable character found\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * v_keyval --\n *      Set key values.\n *\n * We've left some open slots in the keylist table, and if these values exist,\n * we put them into place.  Note, they may reset (or duplicate) values already\n * in the table, so we check for that first.\n */\nstatic void\nv_keyval(SCR *sp, int val, scr_keyval_t name)\n{\n        KEYLIST *kp;\n        CHAR_T ch;\n        int dne;\n\n        /* Get the key's value from the screen. */\n        if (sp->gp->scr_keyval(sp, name, &ch, &dne))\n                return;\n        if (dne)\n                return;\n\n        /* Check for duplication. */\n        for (kp = keylist; kp->value != K_NOTUSED; ++kp)\n                if (kp->ch == ch) {\n                        kp->value = val;\n                        return;\n                }\n\n        /* Add a new entry. */\n        if (kp->value == K_NOTUSED) {\n                keylist[nkeylist].ch = ch;\n                keylist[nkeylist].value = val;\n                ++nkeylist;\n        }\n}\n\n/*\n * v_key_ilookup --\n *      Build the fast-lookup key display array.\n *\n * PUBLIC: void v_key_ilookup(SCR *);\n */\nvoid\nv_key_ilookup(SCR *sp)\n{\n        CHAR_T ch, *p, *t;\n        GS *gp;\n        size_t len;\n\n        for (gp = sp->gp, ch = 0; ch <= MAX_FAST_KEY; ++ch)\n                for (p = gp->cname[ch].name, t = v_key_name(sp, ch),\n                    len = gp->cname[ch].len = sp->clen; len--;)\n                        *p++ = *t++;\n}\n\n/*\n * v_key_len --\n *      Return the length of the string that will display the key.\n *      This routine is the backup for the KEY_LEN() macro.\n *\n * PUBLIC: size_t v_key_len(SCR *, CHAR_T);\n */\nsize_t\nv_key_len(SCR *sp, CHAR_T ch)\n{\n        (void)v_key_name(sp, ch);\n        return (sp->clen);\n}\n\n/*\n * v_key_name --\n *      Return the string that will display the key.  This routine\n *      is the backup for the KEY_NAME() macro.\n *\n * PUBLIC: CHAR_T *v_key_name(SCR *, CHAR_T);\n */\nCHAR_T *\nv_key_name(SCR *sp, CHAR_T ch)\n{\n        static const CHAR_T hexdigit[] = \"0123456789abcdef\";\n        static const CHAR_T octdigit[] = \"01234567\";\n        CHAR_T *chp, mask;\n        size_t len;\n        int cnt, shift;\n\n        /* See if the character was explicitly declared printable or not. */\n        if ((chp = O_STR(sp, O_PRINT)) != NULL)\n                for (; *chp != '\\0'; ++chp)\n                        if (*chp == ch)\n                                goto pr;\n        if ((chp = O_STR(sp, O_NOPRINT)) != NULL)\n                for (; *chp != '\\0'; ++chp)\n                        if (*chp == ch)\n                                goto nopr;\n\n        /*\n         * Historical (ARPA standard) mappings.  Printable characters are left\n         * alone.  Control characters less than 0x20 are represented as '^'\n         * followed by the character offset from the '@' character in the ASCII\n         * character set.  Del (0x7f) is represented as '^' followed by '?'.\n         *\n         * If set O_ALTNOTATION, most control characters less than 0x20 are\n         * displayed using <C-char> notation.  Carriage feed, escape, and\n         * delete are displayed as <Ret>, <Esc>, and <Del>, respectively.\n         *\n         * XXX\n         * The following code depends on the current locale being identical to\n         * the ASCII map from 0x40 to 0x5f (since 0x1f + 0x40 == 0x5f).  I'm\n         * told that this is a reasonable assumption...\n         *\n         * XXX\n         * This code will only work with CHAR_T's that are multiples of 8-bit\n         * bytes.\n         *\n         * XXX\n         * NB: There's an assumption here that all printable characters take\n         * up a single column on the screen.  This is not always correct.\n         */\n        if (isprint(ch)) {\npr:             sp->cname[0] = ch;\n                len = 1;\n                goto done;\n        }\nnopr:   if (iscntrl(ch) && (ch < 0x20 || ch == 0x7f)) {\n            if (O_ISSET(sp, O_ALTNOTATION)) {\n                if (ch == 0x00) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = '@';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x01) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'a';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x02) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'b';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x03) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'c';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x04) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'd';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x05) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'e';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x06) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'f';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x07) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'g';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x08) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'h';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x0A) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'N';\n                    sp->cname[2] = 'L';\n                    sp->cname[3] = '>';\n                    len = 4;\n                } else if (ch == 0x0B) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'k';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x0C) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'l';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x0D) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'R';\n                    sp->cname[2] = 'e';\n                    sp->cname[3] = 't';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x0E) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'n';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x0F) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'o';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x10) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'p';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x11) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'q';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x12) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'r';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x13) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 's';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x14) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 't';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x15) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'u';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x16) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'v';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x17) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'w';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x18) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'x';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x19) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'y';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x1A) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = 'z';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x1B) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'E';\n                    sp->cname[2] = 's';\n                    sp->cname[3] = 'c';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x1C) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = '\\\\';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x1D) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = ']';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x1E) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = '^';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x1F) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'C';\n                    sp->cname[2] = '-';\n                    sp->cname[3] = '_';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else if (ch == 0x7F) {\n                    sp->cname[0] = '<';\n                    sp->cname[1] = 'D';\n                    sp->cname[2] = 'e';\n                    sp->cname[3] = 'l';\n                    sp->cname[4] = '>';\n                    len = 5;\n                } else {\n                    sp->cname[0] = '^';\n                    sp->cname[1] = ch == 0x7f ? '?' : '@' + ch;\n                    len = 2;\n                }\n            } else {\n                sp->cname[0] = '^';\n                sp->cname[1] = ch == 0x7f ? '?' : '@' + ch;\n                len = 2;\n            }\n        } else if (O_ISSET(sp, O_OCTAL)) {\n#define BITS    (sizeof(CHAR_T) * 8)\n#define SHIFT   (BITS - BITS % 3)\n#define TOPMASK (BITS % 3 == 2 ? 3 : 1) << (BITS - BITS % 3)\n                sp->cname[0] = '\\\\';\n                sp->cname[1] = octdigit[(ch & TOPMASK) >> SHIFT];\n                shift = SHIFT - 3;\n                for (len = 2, mask = 7 << (SHIFT - 3),\n                    cnt = BITS / 3; cnt-- > 0; mask >>= 3, shift -= 3)\n                        sp->cname[len++] = octdigit[(ch & mask) >> shift];\n        } else {\n                sp->cname[0] = '\\\\';\n                sp->cname[1] = 'x';\n                for (len = 2, chp = (u_int8_t *)&ch,\n                    cnt = sizeof(CHAR_T); cnt-- > 0; ++chp) {\n                        sp->cname[len++] = hexdigit[(*chp & 0xf0) >> 4];\n                        sp->cname[len++] = hexdigit[*chp & 0x0f];\n                }\n        }\ndone:   sp->cname[sp->clen = len] = '\\0';\n        return (sp->cname);\n}\n\n/*\n * v_key_val --\n *      Fill in the value for a key.  This routine is the backup\n *      for the KEY_VAL() macro.\n *\n * PUBLIC: int v_key_val(SCR *, CHAR_T);\n */\nint\nv_key_val(SCR *sp, CHAR_T ch)\n{\n        KEYLIST k, *kp;\n\n        k.ch = ch;\n        kp = bsearch(&k, keylist, nkeylist, sizeof(keylist[0]), v_key_cmp);\n        return (kp == NULL ? K_NOTUSED : kp->value);\n}\n\n/*\n * v_event_push --\n *      Push events/keys onto the front of the buffer.\n *\n * There is a single input buffer in ex/vi.  Characters are put onto the\n * end of the buffer by the terminal input routines, and pushed onto the\n * front of the buffer by various other functions in ex/vi.  Each key has\n * an associated flag value, which indicates if it has already been quoted,\n * and if it is the result of a mapping or an abbreviation.\n *\n * PUBLIC: int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, unsigned int);\n */\nint\nv_event_push(SCR *sp, EVENT *p_evp, CHAR_T *p_s, size_t nitems, unsigned int flags)\n{\n        EVENT *evp;\n        GS *gp;\n        size_t total;\n\n        /* If we have room, stuff the items into the buffer. */\n        gp = sp->gp;\n        if (nitems <= gp->i_next ||\n            (gp->i_event != NULL && gp->i_cnt == 0 && nitems <= gp->i_nelem)) {\n                if (gp->i_cnt != 0)\n                        gp->i_next -= nitems;\n                goto copy;\n        }\n\n        /*\n         * If there are currently items in the queue, shift them up,\n         * leaving some extra room.  Get enough space plus a little\n         * extra.\n         */\n#define TERM_PUSH_SHIFT 30\n        total = gp->i_cnt + gp->i_next + nitems + TERM_PUSH_SHIFT;\n        if (total >= gp->i_nelem && v_event_grow(sp, MAXIMUM(total, 64)))\n                return (1);\n        if (gp == NULL)\n                return (1);\n        if (gp->i_cnt)\n                MEMMOVE(gp->i_event + TERM_PUSH_SHIFT + nitems,\n                    gp->i_event + gp->i_next, gp->i_cnt);\n        gp->i_next = TERM_PUSH_SHIFT;\n\n        /* Put the new items into the queue. */\ncopy:   gp->i_cnt += nitems;\n        for (evp = gp->i_event + gp->i_next; nitems--; ++evp) {\n                if (p_evp != NULL)\n                        *evp = *p_evp++;\n                else {\n                        if (evp == NULL)\n                            return (1);\n                        evp->e_event = E_CHARACTER;\n                        evp->e_c = *p_s++;\n                        evp->e_value = KEY_VAL(sp, evp->e_c);\n                        F_INIT(&evp->e_ch, flags);\n                }\n        }\n        return (0);\n}\n\n/*\n * v_event_append --\n *      Append events onto the tail of the buffer.\n */\nstatic int\nv_event_append(SCR *sp, EVENT *argp)\n{\n        CHAR_T *s;                      /* Characters. */\n        EVENT *evp;\n        GS *gp;\n        size_t nevents;                 /* Number of events. */\n\n        /* Grow the buffer as necessary. */\n        nevents = argp->e_event == E_STRING ? argp->e_len : 1;\n        gp = sp->gp;\n        if (gp->i_event == NULL ||\n            nevents > gp->i_nelem - (gp->i_next + gp->i_cnt))\n                v_event_grow(sp, MAXIMUM(nevents, 64));\n        evp = gp->i_event + gp->i_next + gp->i_cnt;\n        gp->i_cnt += nevents;\n\n        /* Transform strings of characters into single events. */\n        if (argp == NULL || evp == NULL)\n                return (1);\n        if (argp->e_event == E_STRING)\n                for (s = argp->e_csp; nevents--; ++evp) {\n                        evp->e_event = E_CHARACTER;\n                        evp->e_c = *s++;\n                        evp->e_value = KEY_VAL(sp, evp->e_c);\n                        evp->e_flags = 0;\n                }\n        else\n                *evp = *argp;\n        return (0);\n}\n\n/* Remove events from the queue. */\n#define QREM(len) {                                                     \\\n        if ((gp->i_cnt -= (len)) == 0)                                  \\\n                gp->i_next = 0;                                         \\\n        else                                                            \\\n                gp->i_next += (len);                                    \\\n}\n\n/*\n * v_event_get --\n *      Return the next event.\n *\n * !!!\n * The flag EC_NODIGIT probably needs some explanation.  First, the idea of\n * mapping keys is that one or more keystrokes act like a function key.\n * What's going on is that vi is reading a number, and the character following\n * the number may or may not be mapped (EC_MAPCOMMAND).  For example, if the\n * user is entering the z command, a valid command is \"z40+\", and we don't want\n * to map the '+', i.e. if '+' is mapped to \"xxx\", we don't want to change it\n * into \"z40xxx\".  However, if the user enters \"35x\", we want to put all of the\n * characters through the mapping code.\n *\n * Historical practice is a bit muddled here.  (Surprise!)  It always permitted\n * mapping digits as long as they weren't the first character of the map, e.g.\n * \":map ^A1 xxx\" was okay.  It also permitted the mapping of the digits 1-9\n * (the digit 0 was a special case as it doesn't indicate the start of a count)\n * as the first character of the map, but then ignored those mappings.  While\n * it's probably stupid to map digits, vi isn't your mother.\n *\n * The way this works is that the EC_MAPNODIGIT causes term_key to return the\n * end-of-digit without \"looking\" at the next character, i.e. leaving it as the\n * user entered it.  Presumably, the next term_key call will tell us how the\n * user wants it handled.\n *\n * There is one more complication.  Users might map keys to digits, and, as\n * it's described above, the commands:\n *\n *      :map g 1G\n *      d2g\n *\n * would return the keys \"d2<end-of-digits>1G\", when the user probably wanted\n * \"d21<end-of-digits>G\".  So, if a map starts off with a digit we continue as\n * before, otherwise, we pretend we haven't mapped the character, and return\n * <end-of-digits>.\n *\n * Now that that's out of the way, let's talk about Energizer Bunny macros.\n * It's easy to create macros that expand to a loop, e.g. map x 3x.  It's\n * fairly easy to detect this example, because it's all internal to term_key.\n * If we're expanding a macro and it gets big enough, at some point we can\n * assume it's looping and kill it.  The examples that are tough are the ones\n * where the parser is involved, e.g. map x \"ayyx\"byy.  We do an expansion\n * on 'x', and get \"ayyx\"byy.  We then return the first 4 characters, and then\n * find the looping macro again.  There is no way that we can detect this\n * without doing a full parse of the command, because the character that might\n * cause the loop (in this case 'x') may be a literal character, e.g. the map\n * map x \"ayy\"xyy\"byy is perfectly legal and won't cause a loop.\n *\n * Historic vi tried to detect looping macros by disallowing obvious cases in\n * the map command, maps that that ended with the same letter as they started\n * (which wrongly disallowed \"map x 'x\"), and detecting macros that expanded\n * too many times before keys were returned to the command parser.  It didn't\n * get many (most?) of the tricky cases right, however, and it was certainly\n * possible to create macros that ran forever.  And, even if it did figure out\n * what was going on, the user was usually tossed into ex mode.  Finally, any\n * changes made before vi realized that the macro was recursing were left in\n * place.  We recover gracefully, but the only recourse the user has in an\n * infinite macro loop is to interrupt.\n *\n * !!!\n * It is historic practice that mapping characters to themselves as the first\n * part of the mapped string was legal, and did not cause infinite loops, i.e.\n * \":map! { {^M^T\" and \":map n nz.\" were known to work.  The initial, matching\n * characters were returned instead of being remapped.\n *\n * !!!\n * It is also historic practice that the macro \"map ] ]]^\" caused a single ]\n * keypress to behave as the command ]] (the ^ got the map past the vi check\n * for \"tail recursion\").  Conversely, the mapping \"map n nn^\" went recursive.\n * What happened was that, in the historic vi, maps were expanded as the keys\n * were retrieved, but not all at once and not centrally.  So, the keypress ]\n * pushed ]]^ on the stack, and then the first ] from the stack was passed to\n * the ]] command code.  The ]] command then retrieved a key without entering\n * the mapping code.  This could bite us anytime a user has a map that depends\n * on secondary keys NOT being mapped.  I can't see any possible way to make\n * this work in here without the complete abandonment of Rationality Itself.\n *\n * XXX\n * The final issue is recovery.  It would be possible to undo all of the work\n * that was done by the macro if we entered a record into the log so that we\n * knew when the macro started, and, in fact, this might be worth doing at some\n * point.  Given that this might make the log grow unacceptably (consider that\n * cursor keys are done with maps), for now we leave any changes made in place.\n *\n * PUBLIC: int v_event_get(SCR *, EVENT *, int, u_int32_t);\n */\nint\nv_event_get(SCR *sp, EVENT *argp, int timeout, u_int32_t flags)\n{\n        EVENT *evp, ev;\n        GS *gp;\n        SEQ *qp;\n        int init_nomap, ispartial, istimeout, remap_cnt;\n\n        gp = sp->gp;\n\n        /* If simply checking for interrupts, argp may be NULL. */\n        if (argp == NULL)\n                argp = &ev;\n\nretry:  istimeout = remap_cnt = 0;\n\n        /*\n         * If the queue isn't empty and we're timing out for characters,\n         * return immediately.\n         */\n        if (gp->i_cnt != 0 && LF_ISSET(EC_TIMEOUT))\n                return (0);\n\n        /*\n         * If the queue is empty, we're checking for interrupts, or we're\n         * timing out for characters, get more events.\n         */\n        if (gp->i_cnt == 0 || LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) {\n                /*\n                 * If we're reading new characters, check any scripting\n                 * windows for input.\n                 */\n                if (F_ISSET(gp, G_SCRWIN) && sscr_input(sp))\n                        return (1);\nloop:           if (gp->scr_event(sp, argp,\n                    LF_ISSET(EC_INTERRUPT | EC_QUOTED | EC_RAW), timeout))\n                        return (1);\n                switch (argp->e_event) {\n                case E_ERR:\n                case E_SIGHUP:\n                case E_SIGTERM:\n                        /*\n                         * Fatal conditions cause the file to be synced to\n                         * disk immediately.\n                         */\n                        v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE |\n                            (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL));\n                        return (1);\n                case E_TIMEOUT:\n                        istimeout = 1;\n                        break;\n                case E_INTERRUPT:\n                        /* Set the global interrupt flag. */\n                        F_SET(sp->gp, G_INTERRUPTED);\n\n                        /*\n                         * If the caller was interested in interrupts, return\n                         * immediately.\n                         */\n                        if (LF_ISSET(EC_INTERRUPT))\n                                return (0);\n                        goto append;\n                default:\nappend:                 if (v_event_append(sp, argp))\n                                return (1);\n                        break;\n                }\n        }\n\n        /*\n         * If the caller was only interested in interrupts or timeouts, return\n         * immediately.  (We may have gotten characters, and that's okay, they\n         * were queued up for later use.)\n         */\n        if (LF_ISSET(EC_INTERRUPT | EC_TIMEOUT))\n                return (0);\n\nnewmap: evp = &gp->i_event[gp->i_next];\n\n        /*\n         * If the next event in the queue isn't a character event, return\n         * it, we're done.\n         */\n        if (evp->e_event != E_CHARACTER) {\n                *argp = *evp;\n                QREM(1);\n                return (0);\n        }\n\n        /*\n         * If the key isn't mappable because:\n         *\n         *      + ... the timeout has expired\n         *      + ... it's not a mappable key\n         *      + ... neither the command or input map flags are set\n         *      + ... there are no maps that can apply to it\n         *\n         * return it forthwith.\n         */\n        if (istimeout || F_ISSET(&evp->e_ch, CH_NOMAP) ||\n            !LF_ISSET(EC_MAPCOMMAND | EC_MAPINPUT) ||\n            (evp->e_c < MAX_BIT_SEQ && !bit_test(gp->seqb, evp->e_c)))\n                goto nomap;\n\n        /* Search the map. */\n        qp = seq_find(sp, NULL, evp, NULL, gp->i_cnt,\n            LF_ISSET(EC_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT, &ispartial);\n\n        /*\n         * If get a partial match, get more characters and retry the map.\n         * If time out without further characters, return the characters\n         * unmapped.\n         *\n         * !!!\n         * <escape> characters are a problem.  Cursor keys start with <escape>\n         * characters, so there's almost always a map in place that begins with\n         * an <escape> character.  If we timeout <escape> keys in the same way\n         * that we timeout other keys, the user will get a noticeable pause as\n         * they enter <escape> to terminate input mode.  If key timeout is set\n         * for a slow link, users will get an even longer pause.  Nvi used to\n         * simply timeout <escape> characters at 1/10th of a second, but this\n         * loses over PPP links where the latency is greater than 100Ms.\n         */\n        if (ispartial) {\n                if (O_ISSET(sp, O_TIMEOUT))\n                        timeout = (evp->e_value == K_ESCAPE ?\n                            O_VAL(sp, O_ESCAPETIME) :\n                            O_VAL(sp, O_KEYTIME)) * 100;\n                else\n                        timeout = 0;\n                goto loop;\n        }\n\n        /* If no map, return the character. */\n        if (qp == NULL) {\nnomap:          if (!isdigit(evp->e_c) && LF_ISSET(EC_MAPNODIGIT))\n                        goto not_digit;\n                *argp = *evp;\n                QREM(1);\n                return (0);\n        }\n\n        /*\n         * If looking for the end of a digit string, and the first character\n         * of the map is it, pretend we haven't seen the character.\n         */\n        if (LF_ISSET(EC_MAPNODIGIT) &&\n            qp->output != NULL && !isdigit(qp->output[0])) {\nnot_digit:      argp->e_c = CH_NOT_DIGIT;\n                argp->e_value = K_NOTUSED;\n                argp->e_event = E_CHARACTER;\n                F_INIT(&argp->e_ch, 0);\n                return (0);\n        }\n\n        /* Find out if the initial segments are identical. */\n        if (qp->output != NULL) {\n                init_nomap =\n                    !e_memcmp(qp->output, &gp->i_event[gp->i_next], qp->ilen);\n        }\n\n        /* Delete the mapped characters from the queue. */\n        QREM(qp->ilen);\n\n        /* If keys mapped to nothing, go get more. */\n        if (qp->output == NULL)\n                goto retry;\n\n        /* If remapping characters... */\n        if (O_ISSET(sp, O_REMAP)) {\n                /*\n                 * Periodically check for interrupts.  Always check the first\n                 * time through, because it's possible to set up a map that\n                 * will return a character every time, but will expand to more,\n                 * e.g. \"map! a aaaa\" will always return a 'a', but we'll never\n                 * get anywhere useful.\n                 */\n                if ((++remap_cnt == 1 || remap_cnt % 10 == 0) &&\n                    (gp->scr_event(sp, &ev,\n                    EC_INTERRUPT, 0) || ev.e_event == E_INTERRUPT)) {\n                        F_SET(sp->gp, G_INTERRUPTED);\n                        argp->e_event = E_INTERRUPT;\n                        return (0);\n                }\n\n                /*\n                 * If an initial part of the characters mapped, they are not\n                 * further remapped -- return the first one.  Push the rest\n                 * of the characters, or all of the characters if no initial\n                 * part mapped, back on the queue.\n                 */\n                if (init_nomap) {\n                        if (v_event_push(sp, NULL, qp->output + qp->ilen,\n                            qp->olen - qp->ilen, CH_MAPPED))\n                                return (1);\n                        if (v_event_push(sp, NULL,\n                            qp->output, qp->ilen, CH_NOMAP | CH_MAPPED))\n                                return (1);\n                        evp = &gp->i_event[gp->i_next];\n                        goto nomap;\n                }\n                if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED))\n                        return (1);\n                goto newmap;\n        }\n\n        /* Else, push the characters on the queue and return one. */\n        if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED | CH_NOMAP))\n                return (1);\n\n        goto nomap;\n}\n\n/*\n * v_sync --\n *      Walk the screen lists, sync'ing files to their backup copies.\n */\nstatic void\nv_sync(SCR *sp, int flags)\n{\n        GS *gp;\n\n        gp = sp->gp;\n        TAILQ_FOREACH(sp, &gp->dq, q)\n                rcv_sync(sp, flags);\n        TAILQ_FOREACH(sp, &gp->hq, q)\n                rcv_sync(sp, flags);\n}\n\n/*\n * v_event_err --\n *      Unexpected event.\n *\n * PUBLIC: void v_event_err(SCR *, EVENT *);\n */\nvoid\nv_event_err(SCR *sp, EVENT *evp)\n{\n        switch (evp->e_event) {\n        case E_CHARACTER:\n                msgq(sp, M_ERR, \"Unexpected character event\");\n                break;\n        case E_EOF:\n                msgq(sp, M_ERR, \"Unexpected end-of-file event\");\n                break;\n        case E_INTERRUPT:\n                msgq(sp, M_ERR, \"Unexpected interrupt event\");\n                break;\n        case E_QUIT:\n                msgq(sp, M_ERR, \"Unexpected quit event\");\n                break;\n        case E_REPAINT:\n                msgq(sp, M_ERR, \"Unexpected repaint event\");\n                break;\n        case E_STRING:\n                msgq(sp, M_ERR, \"Unexpected string event\");\n                break;\n        case E_TIMEOUT:\n                msgq(sp, M_ERR, \"Unexpected timeout event\");\n                break;\n        case E_WRESIZE:\n                msgq(sp, M_ERR, \"Unexpected resize event\");\n                break;\n        case E_WRITE:\n                msgq(sp, M_ERR, \"Unexpected write event\");\n                break;\n\n        /*\n         * Theoretically, none of these can occur, as they're handled at the\n         * top editor level.\n         */\n        case E_ERR:\n        case E_SIGHUP:\n        case E_SIGTERM:\n        default:\n                abort();\n        }\n\n        /* Free any allocated memory. */\n        free(evp->e_asp);\n}\n\n/*\n * v_event_flush --\n *      Flush any flagged keys, returning if any keys were flushed.\n *\n * PUBLIC: int v_event_flush(SCR *, unsigned int);\n */\nint\nv_event_flush(SCR *sp, unsigned int flags)\n{\n        GS *gp;\n        int rval;\n\n        for (rval = 0, gp = sp->gp; gp->i_cnt != 0 &&\n            F_ISSET(&gp->i_event[gp->i_next].e_ch, flags); rval = 1)\n                QREM(1);\n        return (rval);\n}\n\n/*\n * v_event_grow --\n *      Grow the terminal queue.\n */\nstatic int\nv_event_grow(SCR *sp, int add)\n{\n        GS *gp;\n        size_t new_nelem, olen;\n\n        gp = sp->gp;\n        new_nelem = gp->i_nelem + add;\n        olen = gp->i_nelem * sizeof(gp->i_event[0]);\n        BINC_RET(sp, gp->i_event, olen, new_nelem * sizeof(gp->i_event[0]));\n        gp->i_nelem = olen / sizeof(gp->i_event[0]);\n        return (0);\n}\n\n/*\n * v_key_cmp --\n *      Compare two keys for sorting.\n */\nstatic int\nv_key_cmp(const void *ap, const void *bp)\n{\n        return (((KEYLIST *)ap)->ch - ((KEYLIST *)bp)->ch);\n}\n"
  },
  {
    "path": "common/key.h",
    "content": "/*      $OpenBSD: key.h,v 1.8 2016/05/27 09:18:11 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)key.h       10.18 (Berkeley) 6/30/96\n */\n\n/*\n * Fundamental character types.\n *\n * CHAR_T       An integral type that can hold any character.\n * MAX_CHAR_T   The maximum value of any character.\n *\n * If no integral type can hold a character, don't even try the port.\n */\n\ntypedef unsigned char   CHAR_T;\n#define MAX_CHAR_T      0xff\n\n/* The maximum number of columns any character can take up on a screen. */\n#define MAX_CHARACTER_COLUMNS   6\n\n/*\n * Event types.\n *\n * The program structure depends on the event loop being able to return\n * E_EOF/E_ERR multiple times -- eventually enough things will end due\n * to the events that vi will reach the command level for the screen, at\n * which point the exit flags will be set and vi will exit.\n */\n\ntypedef enum {\n        E_NOTUSED = 0,                  /* Not set. */\n        E_CHARACTER,                    /* Input character: e_c set. */\n        E_EOF,                          /* End of input (NOT ^D). */\n        E_ERR,                          /* Input error. */\n        E_INTERRUPT,                    /* Interrupt. */\n        E_QUIT,                         /* Quit. */\n        E_REPAINT,                      /* Repaint: e_flno, e_tlno set. */\n        E_SIGHUP,                       /* SIGHUP. */\n        E_SIGTERM,                      /* SIGTERM. */\n        E_STRING,                       /* Input string: e_csp, e_len set. */\n        E_TIMEOUT,                      /* Timeout. */\n        E_WRESIZE,                      /* Window resize. */\n        E_WRITE                         /* Write. */\n} e_event_t;\n\n/*\n * Character values.\n */\n\ntypedef enum {\n        K_NOTUSED = 0,                  /* Not set. */\n        K_BACKSLASH,                    /*  \\ */\n        K_CARAT,                        /*  ^ */\n        K_CNTRLD,                       /* ^D */\n        K_CNTRLR,                       /* ^R */\n        K_CNTRLT,                       /* ^T */\n        K_CNTRLZ,                       /* ^Z */\n        K_COLON,                        /*  : */\n        K_CR,                           /* \\r */\n        K_ESCAPE,                       /* ^[ */\n        K_FORMFEED,                     /* \\f */\n        K_HEXCHAR,                      /* ^X */\n        K_NL,                           /* \\n */\n        K_RIGHTBRACE,                   /*  } */\n        K_RIGHTPAREN,                   /*  ) */\n        K_TAB,                          /* \\t */\n        K_VERASE,                       /* set from tty: default ^H */\n        K_VKILL,                        /* set from tty: default ^U */\n        K_VLNEXT,                       /* set from tty: default ^V */\n        K_VWERASE,                      /* set from tty: default ^W */\n        K_ZERO                          /*  0 */\n} e_key_t;\n\nstruct _event {\n        TAILQ_ENTRY(_event) q;          /* Linked list of events. */\n        e_event_t e_event;              /* Event type. */\n        union {\n                struct {                /* Input character. */\n                        CHAR_T c;       /* Character. */\n                        e_key_t value;  /* Key type. */\n\n#define CH_ABBREVIATED  0x01            /* Character is from an abbreviation. */\n#define CH_MAPPED       0x02            /* Character is from a map. */\n#define CH_NOMAP        0x04            /* Do not map the character. */\n#define CH_QUOTED       0x08            /* Character is already quoted. */\n                        u_int8_t flags;\n                } _e_ch;\n#define e_ch    _u_event._e_ch          /* !!! The structure, not the char. */\n#define e_c     _u_event._e_ch.c\n#define e_value _u_event._e_ch.value\n#define e_flags _u_event._e_ch.flags\n\n                struct {                /* Screen position, size. */\n                        size_t lno1;    /* Line number. */\n                        size_t cno1;    /* Column number. */\n                        size_t lno2;    /* Line number. */\n                        size_t cno2;    /* Column number. */\n                } _e_mark;\n#define e_lno   _u_event._e_mark.lno1   /* Single location. */\n#define e_cno   _u_event._e_mark.cno1\n#define e_flno  _u_event._e_mark.lno1   /* Text region. */\n#define e_fcno  _u_event._e_mark.cno1\n#define e_tlno  _u_event._e_mark.lno2\n#define e_tcno  _u_event._e_mark.cno2\n\n                struct {                /* Input string. */\n                        CHAR_T  *asp;   /* Allocated string. */\n                        CHAR_T  *csp;   /* String. */\n                        size_t   len;   /* String length. */\n                } _e_str;\n#define e_asp   _u_event._e_str.asp\n#define e_csp   _u_event._e_str.csp\n#define e_len   _u_event._e_str.len\n        } _u_event;\n};\n\ntypedef struct _keylist {\n        e_key_t value;                  /* Special value. */\n        CHAR_T ch;                      /* Key. */\n} KEYLIST;\nextern KEYLIST keylist[];\n\n                                        /* Return if more keys in queue. */\n#define KEYS_WAITING(sp)        ((sp)->gp->i_cnt != 0)\n#define MAPPED_KEYS_WAITING(sp)                                            \\\n        (KEYS_WAITING(sp) &&                                               \\\n            F_ISSET(&(sp)->gp->i_event[(sp)->gp->i_next].e_ch, CH_MAPPED))\n\n/* The \"standard\" tab width, for displaying things to users. */\n#define STANDARD_TAB    6\n\n/* Various special characters, messages. */\n#define CH_BSEARCH      '?'             /* Backward search prompt. */\n#define CH_CURSOR       ' '             /* Cursor character. */\n#define CH_ENDMARK      '$'             /* End of a range. */\n#define CH_EXPROMPT     ':'             /* Ex prompt. */\n#define CH_FSEARCH      '/'             /* Forward search prompt. */\n#define CH_HEX          '\\030'          /* Leading hex character. */\n#define CH_LITERAL      '\\026'          /* ASCII ^V. */\n#define CH_NO           'n'             /* No. */\n#define CH_NOT_DIGIT    'a'             /* A non-isdigit() character. */\n#define CH_QUIT         'q'             /* Quit. */\n#define CH_YES          'y'             /* Yes. */\n\n/*\n * Checking for interrupts means that we look at the bit that gets set if the\n * screen code supports asynchronous events, and call back into the event code\n * so that non-asynchronous screens get a chance to post the interrupt.\n *\n * INTERRUPT_CHECK is the number of lines \"operated\" on before checking for\n * interrupts.\n */\n\n#define INTERRUPT_CHECK 100\n\n#define INTERRUPTED(sp)                                                    \\\n        (F_ISSET((sp)->gp, G_INTERRUPTED) ||                               \\\n        (!v_event_get((sp), NULL, 0, EC_INTERRUPT) &&                      \\\n        F_ISSET((sp)->gp, G_INTERRUPTED)))\n\n#define CLR_INTERRUPT(sp)                                                  \\\n        F_CLR((sp)->gp, G_INTERRUPTED)\n\n/* Flags describing types of characters being requested. */\n#define EC_INTERRUPT    0x001           /* Checking for interrupts. */\n#define EC_MAPCOMMAND   0x002           /* Apply the command map. */\n#define EC_MAPINPUT     0x004           /* Apply the input map. */\n#define EC_MAPNODIGIT   0x008           /* Return to a digit. */\n#define EC_QUOTED       0x010           /* Try to quote next character */\n#define EC_RAW          0x020           /* Any next character. XXX: not used. */\n#define EC_TIMEOUT      0x040           /* Timeout to next character. */\n\n/* Flags describing text input special cases. */\n#define TXT_ADDNEWLINE  0x00000001      /* Replay starts on a new line. */\n#define TXT_AICHARS     0x00000002      /* Leading autoindent chars. */\n#define TXT_ALTWERASE   0x00000004      /* Option: altwerase. */\n#define TXT_APPENDEOL   0x00000008      /* Appending after EOL. */\n#define TXT_AUTOINDENT  0x00000010      /* Autoindent set this line. */\n#define TXT_BACKSLASH   0x00000020      /* Backslashes escape characters. */\n#define TXT_BEAUTIFY    0x00000040      /* Only printable characters. */\n#define TXT_BS          0x00000080      /* Backspace returns the buffer. */\n#define TXT_CEDIT       0x00000100      /* Can return TERM_CEDIT. */\n#define TXT_CNTRLD      0x00000200      /* Control-D is a command. */\n#define TXT_CNTRLT      0x00000400      /* Control-T is an indent special. */\n#define TXT_CR          0x00000800      /* CR returns the buffer. */\n#define TXT_DOTTERM     0x00001000      /* Leading '.' terminates the input. */\n#define TXT_EMARK       0x00002000      /* End of replacement mark. */\n#define TXT_EOFCHAR     0x00004000      /* ICANON set, return EOF character. */\n#define TXT_ESCAPE      0x00008000      /* Escape returns the buffer. */\n#define TXT_FILEC       0x00010000      /* Option: filec. */\n#define TXT_INFOLINE    0x00020000      /* Editing the info line. */\n#define TXT_MAPINPUT    0x00040000      /* Apply the input map. */\n#define TXT_NLECHO      0x00080000      /* Echo the newline. */\n#define TXT_NUMBER      0x00100000      /* Number the line. */\n#define TXT_OVERWRITE   0x00200000      /* Overwrite characters. */\n#define TXT_PROMPT      0x00400000      /* Display a prompt. */\n#define TXT_RECORD      0x00800000      /* Record for replay. */\n#define TXT_REPLACE     0x01000000      /* Replace; don't delete overwrite. */\n#define TXT_REPLAY      0x02000000      /* Replay the last input. */\n#define TXT_RESOLVE     0x04000000      /* Resolve the text into the file. */\n#define TXT_SEARCHINCR  0x08000000      /* Incremental search. */\n#define TXT_SHOWMATCH   0x10000000      /* Option: showmatch. */\n#define TXT_TTYWERASE   0x20000000      /* Option: ttywerase. */\n#define TXT_WRAPMARGIN  0x40000000      /* Option: wrapmargin. */\n"
  },
  {
    "path": "common/line.c",
    "content": "/*      $OpenBSD: line.c,v 1.17 2025/07/30 22:19:13 millert Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"common.h\"\n#include \"../vi/vi.h\"\n\nstatic int scr_update(SCR *, recno_t, lnop_t, int);\n\n/*\n * db_eget --\n *      Front-end to db_get, special case handling for empty files.\n *\n * PUBLIC: int db_eget(SCR *, recno_t, char **, size_t *, int *);\n */\n\nint\ndb_eget(SCR *sp, recno_t lno, char **pp, size_t *lenp, int *isemptyp)\n{\n        recno_t l1;\n\n        if (isemptyp != NULL)\n                *isemptyp = 0;\n\n        /* If the line exists, simply return it. */\n        if (!db_get(sp, lno, 0, pp, lenp))\n                return (0);\n\n        /*\n         * If the user asked for line 0 or line 1, i.e. the only possible\n         * line in an empty file, find the last line of the file; db_last\n         * fails loudly.\n         */\n\n        if ((lno == OOBLNO || lno == 1) && db_last(sp, &l1))\n                return (1);\n\n        /* If the file isn't empty, fail loudly. */\n        if ((lno != 0 && lno != 1) || l1 != 0) {\n                db_err(sp, lno);\n                return (1);\n        }\n\n        if (isemptyp != NULL)\n                *isemptyp = 1;\n\n        return (1);\n}\n\n/*\n * db_get --\n *      Look in the text buffers for a line, followed by the cache, followed\n *      by the database.\n *\n * PUBLIC: int db_get(SCR *, recno_t, u_int32_t, char **, size_t *);\n */\n\nint\ndb_get(SCR *sp, recno_t lno, u_int32_t flags, char **pp, size_t *lenp)\n{\n        DBT data, key;\n        EXF *ep;\n        TEXT *tp;\n        recno_t l1, l2;\n\n        /*\n         * The underlying recno stuff handles zero by returning NULL, but\n         * have to have an OOB condition for the look-aside into the input\n         * buffer anyway.\n         */\n\n        if (lno == OOBLNO)\n                goto err1;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                goto err3;\n        }\n\n        if (LF_ISSET(DBG_NOCACHE))\n                goto nocache;\n\n        /*\n         * Look-aside into the TEXT buffers and see if the line we want\n         * is there.\n         */\n\n        if (F_ISSET(sp, SC_TINPUT)) {\n                l1 = TAILQ_FIRST(&sp->tiq)->lno;\n                l2 = TAILQ_LAST(&sp->tiq, _texth)->lno;\n                if (l1 <= lno && l2 >= lno) {\n                        TAILQ_FOREACH(tp, &sp->tiq, q) {\n                                if (tp->lno == lno)\n                                        break;\n                        }\n                        if (lenp != NULL)\n                                *lenp = tp->len;\n                        if (pp != NULL)\n                                *pp = tp->lb;\n                        return (0);\n                }\n\n                /*\n                 * Adjust the line number for the number of lines used\n                 * by the text input buffers.\n                 */\n\n                if (lno > l2)\n                        lno -= l2 - l1;\n        }\n\n        /* Look-aside into the cache, and see if the line we want is there. */\n        if (lno == ep->c_lno) {\n                if (lenp != NULL)\n                        *lenp = ep->c_len;\n                if (pp != NULL)\n                        *pp = ep->c_lp;\n                return (0);\n        }\n        ep->c_lno = OOBLNO;\n\nnocache:\n        /* Get the line from the underlying database. */\n        key.data = &lno;\n        key.size = sizeof(lno);\n        switch (ep->db->get(ep->db, &key, &data, 0)) {\n        case -1:\n                goto err2;\n        case 1:\nerr1:           if (LF_ISSET(DBG_FATAL))\nerr2:                   db_err(sp, lno);\nerr3:           if (lenp != NULL)\n                        *lenp = 0;\n                if (pp != NULL)\n                        *pp = NULL;\n                return (1);\n        }\n\n        /* Reset the cache. */\n        ep->c_lno = lno;\n        ep->c_len = data.size;\n        ep->c_lp = data.data;\n\n        if (lenp != NULL)\n                *lenp = data.size;\n        if (pp != NULL)\n                *pp = ep->c_lp;\n        return (0);\n}\n\n/*\n * db_delete --\n *      Delete a line from the file.\n *\n * PUBLIC: int db_delete(SCR *, recno_t);\n */\n\nint\ndb_delete(SCR *sp, recno_t lno)\n{\n        DBT key;\n        EXF *ep;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                return (1);\n        }\n\n        /* Update marks, @ and global commands. */\n        if (mark_insdel(sp, LINE_DELETE, lno))\n                return (1);\n        if (ex_g_insdel(sp, LINE_DELETE, lno))\n                return (1);\n\n        /* Log change. */\n        log_line(sp, lno, LOG_LINE_DELETE);\n\n        /* Update file. */\n        key.data = &lno;\n        key.size = sizeof(lno);\n        if (ep->db->del(ep->db, &key, 0) == 1) {\n                msgq(sp, M_SYSERR,\n                    \"unable to delete line %'lu\", (unsigned long)lno);\n                return (1);\n        }\n\n        /* Flush the cache, update line count, before screen update. */\n        if (lno <= ep->c_lno)\n                ep->c_lno = OOBLNO;\n        if (ep->c_nlines != OOBLNO)\n                --ep->c_nlines;\n\n        /* File now modified. */\n        if (F_ISSET(ep, F_FIRSTMODIFY))\n                (void)rcv_init(sp);\n        F_SET(ep, F_MODIFIED | F_RCV_SYNC);\n\n        /* Update screen. */\n        return (scr_update(sp, lno, LINE_DELETE, 1));\n}\n\n/*\n * db_append --\n *      Append a line into the file.\n *\n * PUBLIC: int db_append(SCR *, int, recno_t, char *, size_t);\n */\n\nint\ndb_append(SCR *sp, int update, recno_t lno, char *p, size_t len)\n{\n        DBT data, key;\n        EXF *ep;\n        int rval;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                return (1);\n        }\n\n        /* Update file. */\n        key.data = &lno;\n        key.size = sizeof(lno);\n        data.data = p;\n        data.size = len;\n        if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) {\n                msgq(sp, M_SYSERR,\n                    \"unable to append to line %'lu\", (unsigned long)lno);\n                return (1);\n        }\n\n        /* Flush the cache, update line count, before screen update. */\n        if (lno < ep->c_lno)\n                ep->c_lno = OOBLNO;\n        if (ep->c_nlines != OOBLNO)\n                ++ep->c_nlines;\n\n        /* File now dirty. */\n        if (F_ISSET(ep, F_FIRSTMODIFY))\n                (void)rcv_init(sp);\n        F_SET(ep, F_MODIFIED | F_RCV_SYNC);\n\n        /* Log change. */\n        log_line(sp, lno + 1, LOG_LINE_APPEND);\n\n        /* Update marks, @ and global commands. */\n        rval = 0;\n        if (mark_insdel(sp, LINE_INSERT, lno + 1))\n                rval = 1;\n        if (ex_g_insdel(sp, LINE_INSERT, lno + 1))\n                rval = 1;\n\n        /*\n         * Update screen.\n         *\n         * XXX\n         * Nasty hack.  If multiple lines are input by the user, they aren't\n         * committed until an <ESC> is entered.  The problem is the screen was\n         * updated/scrolled as each line was entered.  So, when this routine\n         * is called to copy the new lines from the cut buffer into the file,\n         * it has to know not to update the screen again.\n         */\n\n        return (scr_update(sp, lno, LINE_APPEND, update) || rval);\n}\n\n/*\n * db_insert --\n *      Insert a line into the file.\n *\n * PUBLIC: int db_insert(SCR *, recno_t, char *, size_t);\n */\n\nint\ndb_insert(SCR *sp, recno_t lno, char *p, size_t len)\n{\n        DBT data, key;\n        EXF *ep;\n        int rval;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                return (1);\n        }\n\n        /* Update file. */\n        key.data = &lno;\n        key.size = sizeof(lno);\n        data.data = p;\n        data.size = len;\n        if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) {\n                msgq(sp, M_SYSERR,\n                    \"unable to insert at line %'lu\", (unsigned long)lno);\n                return (1);\n        }\n\n        /* Flush the cache, update line count, before screen update. */\n        if (lno >= ep->c_lno)\n                ep->c_lno = OOBLNO;\n        if (ep->c_nlines != OOBLNO)\n                ++ep->c_nlines;\n\n        /* File now dirty. */\n        if (F_ISSET(ep, F_FIRSTMODIFY))\n                (void)rcv_init(sp);\n        F_SET(ep, F_MODIFIED | F_RCV_SYNC);\n\n        /* Log change. */\n        log_line(sp, lno, LOG_LINE_INSERT);\n\n        /* Update marks, @ and global commands. */\n        rval = 0;\n        if (mark_insdel(sp, LINE_INSERT, lno))\n                rval = 1;\n        if (ex_g_insdel(sp, LINE_INSERT, lno))\n                rval = 1;\n\n        /* Update screen. */\n        return (scr_update(sp, lno, LINE_INSERT, 1) || rval);\n}\n\n/*\n * db_set --\n *      Store a line in the file.\n *\n * PUBLIC: int db_set(SCR *, recno_t, char *, size_t);\n */\n\nint\ndb_set(SCR *sp, recno_t lno, char *p, size_t len)\n{\n        DBT data, key;\n        EXF *ep;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                return (1);\n        }\n\n        /* Log before change. */\n        log_line(sp, lno, LOG_LINE_RESET_B);\n\n        /* Update file. */\n        key.data = &lno;\n        key.size = sizeof(lno);\n        data.data = p;\n        data.size = len;\n        if (ep->db->put(ep->db, &key, &data, 0) == -1) {\n                msgq(sp, M_SYSERR,\n                    \"unable to store line %'lu\", (unsigned long)lno);\n                return (1);\n        }\n\n        /* Flush the cache, before logging or screen update. */\n        if (lno == ep->c_lno)\n                ep->c_lno = OOBLNO;\n\n        /* File now dirty. */\n        if (F_ISSET(ep, F_FIRSTMODIFY))\n                (void)rcv_init(sp);\n        F_SET(ep, F_MODIFIED | F_RCV_SYNC);\n\n        /* Log after change. */\n        log_line(sp, lno, LOG_LINE_RESET_F);\n\n        /* Update screen. */\n        return (scr_update(sp, lno, LINE_RESET, 1));\n}\n\n/*\n * db_exist --\n *      Return if a line exists.\n *\n * PUBLIC: int db_exist(SCR *, recno_t);\n */\n\nint\ndb_exist(SCR *sp, recno_t lno)\n{\n        EXF *ep;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                return (1);\n        }\n\n        if (lno == OOBLNO)\n                return (0);\n\n        /*\n         * Check the last-line number cache.  Adjust the cached line\n         * number for the lines used by the text input buffers.\n         */\n\n        if (ep->c_nlines != OOBLNO)\n                return (lno <= (F_ISSET(sp, SC_TINPUT) ?\n                    ep->c_nlines + (TAILQ_LAST(&sp->tiq, _texth)->lno\n                    - TAILQ_FIRST(&sp->tiq)->lno) : ep->c_nlines));\n\n        /* Go get the line. */\n        return (!db_get(sp, lno, 0, NULL, NULL));\n}\n\n/*\n * db_last --\n *      Return the number of lines in the file.\n *\n * PUBLIC: int db_last(SCR *, recno_t *);\n */\n\nint\ndb_last(SCR *sp, recno_t *lnop)\n{\n        DBT data, key;\n        EXF *ep;\n        recno_t lno;\n\n        /* Check for no underlying file. */\n        if ((ep = sp->ep) == NULL) {\n                ex_emsg(sp, NULL, EXM_NOFILEYET);\n                return (1);\n        }\n\n        /*\n         * Check the last-line number cache.  Adjust the cached line\n         * number for the lines used by the text input buffers.\n         */\n\n        if (ep->c_nlines != OOBLNO) {\n                *lnop = ep->c_nlines;\n                if (F_ISSET(sp, SC_TINPUT))\n                        *lnop += TAILQ_LAST(&sp->tiq, _texth)->lno -\n                            TAILQ_FIRST(&sp->tiq)->lno;\n                return (0);\n        }\n\n        key.data = &lno;\n        key.size = sizeof(lno);\n\n        switch (ep->db->seq(ep->db, &key, &data, R_LAST)) {\n        case -1:\n                msgq(sp, M_SYSERR, \"unable to get last line\");\n                *lnop = 0;\n                return (1);\n        case 1:\n                *lnop = 0;\n                return (0);\n        default:\n                break;\n        }\n\n        /* Fill the cache. */\n        memcpy(&lno, key.data, sizeof(lno));\n        ep->c_nlines = ep->c_lno = lno;\n        ep->c_len = data.size;\n        ep->c_lp = data.data;\n\n        /* Return the value. */\n        *lnop = (F_ISSET(sp, SC_TINPUT) &&\n            TAILQ_LAST(&sp->tiq, _texth)->lno > lno ?\n            TAILQ_LAST(&sp->tiq, _texth)->lno : lno);\n        return (0);\n}\n\n/*\n * db_err --\n *      Report a line error.\n *\n * PUBLIC: void db_err(SCR *, recno_t);\n */\n\nvoid\ndb_err(SCR *sp, recno_t lno)\n{\n        if (lno)\n            msgq(sp, M_ERR,\n                \"Error: unable to retrieve line %'lu\", (unsigned long)lno);\n}\n\n/*\n * scr_update --\n *      Update all of the screens that are backed by the file that\n *      just changed.\n */\n\nstatic int\nscr_update(SCR *sp, recno_t lno, lnop_t op, int current)\n{\n        EXF *ep;\n        SCR *tsp;\n\n        if (F_ISSET(sp, SC_EX))\n                return (0);\n\n        ep = sp->ep;\n        if (ep->refcnt != 1)\n                TAILQ_FOREACH(tsp, &sp->gp->dq, q)\n                        if (sp != tsp && tsp->ep == ep)\n                                if (vs_change(tsp, lno, op))\n                                        return (1);\n        return (current ? vs_change(sp, lno, op) : 0);\n}\n"
  },
  {
    "path": "common/log.c",
    "content": "/*      $OpenBSD: log.c,v 1.12 2017/04/18 01:45:35 deraadt Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <libgen.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stddef.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n\n#include \"common.h\"\n\n#undef open\n\n/*\n * The log consists of records, each containing a type byte and a variable\n * length byte string, as follows:\n *\n *      LOG_CURSOR_INIT         MARK\n *      LOG_CURSOR_END          MARK\n *      LOG_LINE_APPEND         recno_t         char *\n *      LOG_LINE_DELETE         recno_t         char *\n *      LOG_LINE_INSERT         recno_t         char *\n *      LOG_LINE_RESET_F        recno_t         char *\n *      LOG_LINE_RESET_B        recno_t         char *\n *      LOG_MARK                LMARK\n *\n * We do before image physical logging.  This means that the editor layer\n * MAY NOT modify records in place, even if simply deleting or overwriting\n * characters.  Since the smallest unit of logging is a line, we're using\n * up lots of space.  This may eventually have to be reduced, probably by\n * doing logical logging, which is a much cooler database phrase.\n *\n * The implementation of the historic vi 'u' command, using roll-forward and\n * roll-back, is simple.  Each set of changes has a LOG_CURSOR_INIT record,\n * followed by a number of other records, followed by a LOG_CURSOR_END record.\n * LOG_LINE_RESET records come in pairs.  The first is a LOG_LINE_RESET_B\n * record, and is the line before the change.  The second is LOG_LINE_RESET_F,\n * and is the line after the change.  Roll-back is done by backing up to the\n * first LOG_CURSOR_INIT record before a change.  Roll-forward is done in a\n * similar fashion.\n *\n * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END\n * record for a line different from the current one.  It should be noted that\n * this means that a subsequent 'u' command will make a change based on the\n * new position of the log's cursor.  This is okay, and, in fact, historic vi\n * behaved that way.\n */\n\nstatic int      log_cursor1(SCR *, int);\nstatic void     log_err(SCR *, char *, int);\n\n/* Try and restart the log on failure, i.e. if we run out of memory. */\n#define LOG_ERR {                                                       \\\n        log_err(sp, __FILE__, __LINE__);                                \\\n        return (1);                                                     \\\n}\n\n/*\n * log_init --\n *      Initialize the logging subsystem.\n *\n * PUBLIC: int log_init(SCR *, EXF *);\n */\n\nint\nlog_init(SCR *sp, EXF *ep)\n{\n        /*\n         * !!!\n         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.\n         *\n         * Initialize the buffer.  The logging subsystem has its own\n         * buffers because the global ones are almost by definition\n         * going to be in use when the log runs.\n         */\n\n        ep->l_lp = NULL;\n        ep->l_len = 0;\n        ep->l_cursor.lno = 1;           /* XXX Any valid recno. */\n        ep->l_cursor.cno = 0;\n        ep->l_high = ep->l_cur = 1;\n\n        ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR,\n            S_IRUSR | S_IWUSR, DB_RECNO, NULL);\n        if (ep->log == NULL) {\n                msgq(sp, M_SYSERR, \"Log file\");\n                F_SET(ep, F_NOLOG);\n                return (1);\n        }\n\n        return (0);\n}\n\n/*\n * log_end --\n *      Close the logging subsystem.\n *\n * PUBLIC: int log_end(SCR *, EXF *);\n */\n\nint\nlog_end(SCR *sp, EXF *ep)\n{\n        /*\n         * !!!\n         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.\n         */\n\n        if (ep->log != NULL) {\n                (void)(ep->log->close)(ep->log);\n                ep->log = NULL;\n        }\n        free(ep->l_lp);\n        ep->l_lp = NULL;\n        ep->l_len = 0;\n        ep->l_cursor.lno = 1;           /* XXX Any valid recno. */\n        ep->l_cursor.cno = 0;\n        ep->l_high = ep->l_cur = 1;\n        return (0);\n}\n\n/*\n * log_cursor --\n *      Log the current cursor position, starting an event.\n *\n * PUBLIC: int log_cursor(SCR *);\n */\n\nint\nlog_cursor(SCR *sp)\n{\n        EXF *ep;\n\n        ep = sp->ep;\n        if (F_ISSET(ep, F_NOLOG))\n                return (0);\n\n        /*\n         * If any changes were made since the last cursor init,\n         * put out the ending cursor record.\n         */\n\n        if (ep->l_cursor.lno == OOBLNO) {\n                ep->l_cursor.lno = sp->lno;\n                ep->l_cursor.cno = sp->cno;\n                return (log_cursor1(sp, LOG_CURSOR_END));\n        }\n        ep->l_cursor.lno = sp->lno;\n        ep->l_cursor.cno = sp->cno;\n        return (0);\n}\n\n/*\n * log_cursor1 --\n *      Actually push a cursor record out.\n */\n\nstatic int\nlog_cursor1(SCR *sp, int type)\n{\n        DBT data, key;\n        EXF *ep;\n\n        ep = sp->ep;\n        BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(unsigned char) + sizeof(MARK));\n        ep->l_lp[0] = type;\n        memmove(ep->l_lp + sizeof(unsigned char), &ep->l_cursor, sizeof(MARK));\n\n        key.data = &ep->l_cur;\n        key.size = sizeof(recno_t);\n        data.data = ep->l_lp;\n        data.size = sizeof(unsigned char) + sizeof(MARK);\n        if (ep->log->put(ep->log, &key, &data, 0) == -1)\n                LOG_ERR;\n\n        /* Reset high water mark. */\n        ep->l_high = ++ep->l_cur;\n\n        return (0);\n}\n\n/*\n * log_line --\n *      Log a line change.\n *\n * PUBLIC: int log_line(SCR *, recno_t, unsigned int);\n */\n\nint\nlog_line(SCR *sp, recno_t lno, unsigned int action)\n{\n        DBT data, key;\n        EXF *ep;\n        size_t len;\n        char *lp;\n\n        ep = sp->ep;\n        if (F_ISSET(ep, F_NOLOG))\n                return (0);\n\n        /*\n         * XXX\n         *\n         * Kluge for vi.  Clear the EXF undo flag so that the\n         * next 'u' command does a roll-back, regardless.\n         */\n\n        F_CLR(ep, F_UNDO);\n\n        /* Put out one initial cursor record per set of changes. */\n        if (ep->l_cursor.lno != OOBLNO) {\n                if (log_cursor1(sp, LOG_CURSOR_INIT))\n                        return (1);\n                ep->l_cursor.lno = OOBLNO;\n        }\n\n        /*\n         * Put out the changes.  If it's a LOG_LINE_RESET_B call, it's a\n         * special case, avoid the caches.  Also, if it fails and it's\n         * line 1, it just means that the user started with an empty file,\n         * so fake an empty length line.\n         */\n\n        if (action == LOG_LINE_RESET_B) {\n                if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) {\n                        if (lno != 1) {\n                                db_err(sp, lno);\n                                return (1);\n                        }\n                        len = 0;\n                        lp = \"\";\n                }\n        } else\n                if (db_get(sp, lno, DBG_FATAL, &lp, &len))\n                        return (1);\n        BINC_RET(sp,\n            ep->l_lp, ep->l_len, len + sizeof(unsigned char) + sizeof(recno_t));\n        ep->l_lp[0] = action;\n        memmove(ep->l_lp + sizeof(unsigned char), &lno, sizeof(recno_t));\n        memmove(ep->l_lp + sizeof(unsigned char) + sizeof(recno_t), lp, len);\n\n        key.data = &ep->l_cur;\n        key.size = sizeof(recno_t);\n        data.data = ep->l_lp;\n        data.size = len + sizeof(unsigned char) + sizeof(recno_t);\n        if (ep->log->put(ep->log, &key, &data, 0) == -1)\n                LOG_ERR;\n\n        /* Reset high water mark. */\n        ep->l_high = ++ep->l_cur;\n\n        return (0);\n}\n\n/*\n * log_mark --\n *      Log a mark position.  For the log to work, we assume that there\n *      aren't any operations that just put out a log record -- this\n *      would mean that undo operations would only reset marks, and not\n *      cause any other change.\n *\n * PUBLIC: int log_mark(SCR *, LMARK *);\n */\n\nint\nlog_mark(SCR *sp, LMARK *lmp)\n{\n        DBT data, key;\n        EXF *ep;\n\n        ep = sp->ep;\n        if (F_ISSET(ep, F_NOLOG))\n                return (0);\n\n        /* Put out one initial cursor record per set of changes. */\n        if (ep->l_cursor.lno != OOBLNO) {\n                if (log_cursor1(sp, LOG_CURSOR_INIT))\n                        return (1);\n                ep->l_cursor.lno = OOBLNO;\n        }\n\n        BINC_RET(sp, ep->l_lp,\n            ep->l_len, sizeof(unsigned char) + sizeof(LMARK));\n        ep->l_lp[0] = LOG_MARK;\n        memmove(ep->l_lp + sizeof(unsigned char), lmp, sizeof(LMARK));\n\n        key.data = &ep->l_cur;\n        key.size = sizeof(recno_t);\n        data.data = ep->l_lp;\n        data.size = sizeof(unsigned char) + sizeof(LMARK);\n        if (ep->log->put(ep->log, &key, &data, 0) == -1)\n                LOG_ERR;\n\n        /* Reset high water mark. */\n        ep->l_high = ++ep->l_cur;\n        return (0);\n}\n\n/*\n * Log_backward --\n *      Roll the log backward one operation.\n *\n * PUBLIC: int log_backward(SCR *, MARK *);\n */\n\nint\nlog_backward(SCR *sp, MARK *rp)\n{\n        DBT key, data;\n        EXF *ep;\n        LMARK lm;\n        MARK m;\n        recno_t lno;\n        int didop;\n        unsigned char *p;\n\n        ep = sp->ep;\n        if (F_ISSET(ep, F_NOLOG)) {\n                msgq(sp, M_ERR,\n                    \"Logging not being performed, undo not possible\");\n                return (1);\n        }\n\n        if (ep->l_cur == 1) {\n                msgq(sp, M_BERR, \"No changes to undo\");\n                return (1);\n        }\n\n        F_SET(ep, F_NOLOG);             /* Turn off logging. */\n\n        key.data = &ep->l_cur;          /* Initialize db request. */\n        key.size = sizeof(recno_t);\n        for (didop = 0;;) {\n                --ep->l_cur;\n                if (ep->log->get(ep->log, &key, &data, 0))\n                        LOG_ERR;\n                switch (*(p = (unsigned char *)data.data)) {\n                case LOG_CURSOR_INIT:\n                        if (didop) {\n                                memmove(rp, p + sizeof(unsigned char), sizeof(MARK));\n                                F_CLR(ep, F_NOLOG);\n                                return (0);\n                        }\n                        break;\n                case LOG_CURSOR_END:\n                        break;\n                case LOG_LINE_APPEND:\n                case LOG_LINE_INSERT:\n                        didop = 1;\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (db_delete(sp, lno))\n                                goto err;\n                        ++sp->rptlines[L_DELETED];\n                        break;\n                case LOG_LINE_DELETE:\n                        didop = 1;\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (db_insert(sp, lno, p + sizeof(unsigned char) +\n                            sizeof(recno_t), data.size - sizeof(unsigned char) -\n                            sizeof(recno_t)))\n                                goto err;\n                        ++sp->rptlines[L_ADDED];\n                        break;\n                case LOG_LINE_RESET_F:\n                        break;\n                case LOG_LINE_RESET_B:\n                        didop = 1;\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (db_set(sp, lno, p + sizeof(unsigned char) +\n                            sizeof(recno_t), data.size - sizeof(unsigned char) -\n                            sizeof(recno_t)))\n                                goto err;\n                        if (sp->rptlchange != lno) {\n                                sp->rptlchange = lno;\n                                ++sp->rptlines[L_CHANGED];\n                        }\n                        break;\n                case LOG_MARK:\n                        didop = 1;\n                        memmove(&lm, p + sizeof(unsigned char), sizeof(LMARK));\n                        m.lno = lm.lno;\n                        m.cno = lm.cno;\n                        if (mark_set(sp, lm.name, &m, 0))\n                                goto err;\n                        break;\n                default:\n                        abort();\n                }\n        }\n\nerr:    F_CLR(ep, F_NOLOG);\n        return (1);\n}\n\n/*\n * Log_setline --\n *      Reset the line to its original appearance.\n *\n * XXX\n * There's a bug in this code due to our not logging cursor movements\n * unless a change was made.  If you do a change, move off the line,\n * then move back on and do a 'U', the line will be restored to the way\n * it was before the original change.\n *\n * PUBLIC: int log_setline(SCR *);\n */\n\nint\nlog_setline(SCR *sp)\n{\n        DBT key, data;\n        EXF *ep;\n        LMARK lm;\n        MARK m;\n        recno_t lno;\n        unsigned char *p;\n\n        ep = sp->ep;\n        if (F_ISSET(ep, F_NOLOG)) {\n                msgq(sp, M_ERR,\n                    \"Logging not being performed, undo not possible\");\n                return (1);\n        }\n\n        if (ep->l_cur == 1)\n                return (1);\n\n        F_SET(ep, F_NOLOG);             /* Turn off logging. */\n\n        key.data = &ep->l_cur;          /* Initialize db request. */\n        key.size = sizeof(recno_t);\n\n        for (;;) {\n                --ep->l_cur;\n                if (ep->log->get(ep->log, &key, &data, 0))\n                        LOG_ERR;\n                switch (*(p = (unsigned char *)data.data)) {\n                case LOG_CURSOR_INIT:\n                        memmove(&m, p + sizeof(unsigned char), sizeof(MARK));\n                        if (m.lno != sp->lno || ep->l_cur == 1) {\n                                F_CLR(ep, F_NOLOG);\n                                return (0);\n                        }\n                        break;\n                case LOG_CURSOR_END:\n                        memmove(&m, p + sizeof(unsigned char), sizeof(MARK));\n                        if (m.lno != sp->lno) {\n                                ++ep->l_cur;\n                                F_CLR(ep, F_NOLOG);\n                                return (0);\n                        }\n                        break;\n                case LOG_LINE_APPEND:\n                case LOG_LINE_INSERT:\n                case LOG_LINE_DELETE:\n                case LOG_LINE_RESET_F:\n                        break;\n                case LOG_LINE_RESET_B:\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (lno == sp->lno &&\n                            db_set(sp, lno, p + sizeof(unsigned char) +\n                            sizeof(recno_t), data.size - sizeof(unsigned char) -\n                            sizeof(recno_t)))\n                                goto err;\n                        if (sp->rptlchange != lno) {\n                                sp->rptlchange = lno;\n                                ++sp->rptlines[L_CHANGED];\n                        }\n                        break;\n                case LOG_MARK:\n                        memmove(&lm, p + sizeof(unsigned char), sizeof(LMARK));\n                        m.lno = lm.lno;\n                        m.cno = lm.cno;\n                        if (mark_set(sp, lm.name, &m, 0))\n                                goto err;\n                        break;\n                default:\n                        abort();\n                }\n        }\n\nerr:    F_CLR(ep, F_NOLOG);\n        return (1);\n}\n\n/*\n * Log_forward --\n *      Roll the log forward one operation.\n *\n * PUBLIC: int log_forward(SCR *, MARK *);\n */\n\nint\nlog_forward(SCR *sp, MARK *rp)\n{\n        DBT key, data;\n        EXF *ep;\n        LMARK lm;\n        MARK m;\n        recno_t lno;\n        int didop;\n        unsigned char *p;\n\n        ep = sp->ep;\n        if (F_ISSET(ep, F_NOLOG)) {\n                msgq(sp, M_ERR,\n            \"Logging not being performed, roll-forward not possible\");\n                return (1);\n        }\n\n        if (ep->l_cur == ep->l_high) {\n                msgq(sp, M_BERR, \"No changes to re-do\");\n                return (1);\n        }\n\n        F_SET(ep, F_NOLOG);             /* Turn off logging. */\n\n        key.data = &ep->l_cur;          /* Initialize db request. */\n        key.size = sizeof(recno_t);\n        for (didop = 0;;) {\n                ++ep->l_cur;\n                if (ep->log->get(ep->log, &key, &data, 0))\n                        LOG_ERR;\n                switch (*(p = (unsigned char *)data.data)) {\n                case LOG_CURSOR_END:\n                        if (didop) {\n                                ++ep->l_cur;\n                                memmove(rp, p + sizeof(unsigned char), sizeof(MARK));\n                                F_CLR(ep, F_NOLOG);\n                                return (0);\n                        }\n                        break;\n                case LOG_CURSOR_INIT:\n                        break;\n                case LOG_LINE_APPEND:\n                case LOG_LINE_INSERT:\n                        didop = 1;\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (db_insert(sp, lno, p + sizeof(unsigned char) +\n                            sizeof(recno_t), data.size - sizeof(unsigned char) -\n                            sizeof(recno_t)))\n                                goto err;\n                        ++sp->rptlines[L_ADDED];\n                        break;\n                case LOG_LINE_DELETE:\n                        didop = 1;\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (db_delete(sp, lno))\n                                goto err;\n                        ++sp->rptlines[L_DELETED];\n                        break;\n                case LOG_LINE_RESET_B:\n                        break;\n                case LOG_LINE_RESET_F:\n                        didop = 1;\n                        memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t));\n                        if (db_set(sp, lno, p + sizeof(unsigned char) +\n                            sizeof(recno_t), data.size - sizeof(unsigned char) -\n                            sizeof(recno_t)))\n                                goto err;\n                        if (sp->rptlchange != lno) {\n                                sp->rptlchange = lno;\n                                ++sp->rptlines[L_CHANGED];\n                        }\n                        break;\n                case LOG_MARK:\n                        didop = 1;\n                        memmove(&lm, p + sizeof(unsigned char), sizeof(LMARK));\n                        m.lno = lm.lno;\n                        m.cno = lm.cno;\n                        if (mark_set(sp, lm.name, &m, 0))\n                                goto err;\n                        break;\n                default:\n                        abort();\n                }\n        }\n\nerr:    F_CLR(ep, F_NOLOG);\n        return (1);\n}\n\n/*\n * log_err --\n *      Try and restart the log on failure, i.e. if we run out of memory.\n */\n\nstatic void\nlog_err(SCR *sp, char *file, int line)\n{\n        EXF *ep;\n\n        msgq(sp, M_SYSERR, \"%s/%d: log put error\", openbsd_basename(file), line);\n        ep = sp->ep;\n        (void)ep->log->close(ep->log);\n        if (!log_init(sp, ep))\n                msgq(sp, M_ERR, \"Log restarted\");\n}\n"
  },
  {
    "path": "common/log.h",
    "content": "/*      $OpenBSD: log.h,v 1.3 2001/01/29 01:58:30 niklas Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)log.h       10.2 (Berkeley) 3/6/96\n */\n\n#define LOG_NOTYPE              0\n#define LOG_CURSOR_INIT         1\n#define LOG_CURSOR_END          2\n#define LOG_LINE_APPEND         3\n#define LOG_LINE_DELETE         4\n#define LOG_LINE_INSERT         5\n#define LOG_LINE_RESET_F        6\n#define LOG_LINE_RESET_B        7\n#define LOG_MARK                8\n"
  },
  {
    "path": "common/main.c",
    "content": "/*      $OpenBSD: main.c,v 1.43 2021/10/24 21:24:17 deraadt Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <bsd_err.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <paths.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"errc.h\"\n\n#include \"common.h\"\n#include \"../vi/vi.h\"\n\n#undef open\n\n#ifdef DEBUG\nstatic void      attach(GS *);\n#endif /* ifdef DEBUG */\nstatic int       v_obsolete(char *[]);\n\nenum pmode pmode;\n\n/*\n * editor --\n *      Main editor routine.\n *\n * PUBLIC: int editor(GS *, int, char *[]);\n */\n\nint\neditor(GS *gp, int argc, char *argv[])\n{\n        extern int openbsd_optind;\n        extern char *openbsd_optarg;\n        const char *p;\n        EVENT ev;\n        FREF *frp;\n        SCR *sp;\n        size_t len;\n        unsigned int flags;\n        int ch, flagchk, secure, startup, readonly, rval, silent;\n        char *tag_f, *wsizearg, path[256];\n\n        static const char *optstr[3] = {\n#ifdef DEBUG\n                \"c:C:D:FlRrSsT:t:vw:\",\n                \"c:C:D:eFlRrST:t:w:\",\n                \"c:C:D:eFlrST:t:w:\"\n#else\n                \"c:C:FlRrSst:vw:\",\n                \"c:C:eFlRrSt:w:\",\n                \"c:C:eFlrSt:w:\"\n#endif /* ifdef DEBUG */\n        };\n\n        if (openbsd_pledge(\n            \"stdio rpath wpath cpath fattr flock getpw tty proc exec\",\n            NULL) == -1) {\n                perror(\"pledge\");\n                goto err;\n        }\n\n        /* Initialize the busy routine, if not defined by the screen. */\n        if (gp->scr_busy == NULL)\n                gp->scr_busy = vs_busy;\n        /* Initialize the message routine, if not defined by the screen. */\n        if (gp->scr_msg == NULL)\n                gp->scr_msg = vs_msg;\n\n        /* Common global structure initialization. */\n        TAILQ_INIT(&gp->dq);\n        TAILQ_INIT(&gp->hq);\n        LIST_INIT(&gp->ecq);\n        LIST_INSERT_HEAD(&gp->ecq, &gp->excmd, q);\n        gp->noprint = DEFAULT_NOPRINT;\n\n        /* Structures shared by screens so stored in the GS structure. */\n        TAILQ_INIT(&gp->frefq);\n        TAILQ_INIT(&gp->dcb_store.textq);\n        LIST_INIT(&gp->cutq);\n        LIST_INIT(&gp->seqq);\n\n        /* Set initial screen type and mode based on the program name. */\n        readonly = 0;\n        if (!strcmp(bsd_getprogname(), \"ex\")   ||\n            !strcmp(bsd_getprogname(), \"nex\")  ||\n            !strcmp(bsd_getprogname(), \"oex\")  ||\n            !strcmp(bsd_getprogname(), \"obex\") ||\n            !strcmp(bsd_getprogname(), \"openex\"))\n                LF_INIT(SC_EX);\n        else {\n                /* Nview, view are readonly. */\n                if (!strcmp(bsd_getprogname(), \"view\")   ||\n                    !strcmp(bsd_getprogname(), \"nview\")  ||\n                    !strcmp(bsd_getprogname(), \"oview\")  ||\n                    !strcmp(bsd_getprogname(), \"obview\") ||\n                    !strcmp(bsd_getprogname(), \"openview\"))\n                        readonly = 1;\n\n                /* Vi is the default. */\n                LF_INIT(SC_VI);\n        }\n\n        /* Convert old-style arguments into new-style ones. */\n        if (v_obsolete(argv))\n                return (1);\n\n        /* Parse the arguments. */\n        flagchk = '\\0';\n        tag_f = wsizearg = NULL;\n        secure = silent = 0;\n        startup = 1;\n\n        /* Set the file snapshot flag. */\n        F_SET(gp, G_SNAPSHOT);\n\n        pmode = MODE_EX;\n        if (!strcmp(bsd_getprogname(), \"ex\")   ||\n            !strcmp(bsd_getprogname(), \"nex\")  ||\n            !strcmp(bsd_getprogname(), \"oex\")  ||\n            !strcmp(bsd_getprogname(), \"obex\") ||\n            !strcmp(bsd_getprogname(), \"openex\"))\n                pmode = MODE_EX;\n        else if (!strcmp(bsd_getprogname(), \"vi\")   ||\n                 !strcmp(bsd_getprogname(), \"nvi\")  ||\n                 !strcmp(bsd_getprogname(), \"ovi\")  ||\n                 !strcmp(bsd_getprogname(), \"obvi\") ||\n                 !strcmp(bsd_getprogname(), \"openvi\"))\n                     pmode = MODE_VI;\n        else if (!strcmp(bsd_getprogname(), \"view\")   ||\n                 !strcmp(bsd_getprogname(), \"nview\")  ||\n                 !strcmp(bsd_getprogname(), \"oview\")  ||\n                 !strcmp(bsd_getprogname(), \"obview\") ||\n                 !strcmp(bsd_getprogname(), \"openview\"))\n                    pmode = MODE_VIEW;\n\n        while ((ch = openbsd_getopt(argc, argv, optstr[pmode])) != -1)\n                switch (ch) {\n                case 'C':               /* Run the command. */\n\n                        /*\n                         * XXX\n                         * Should we support multiple -C options?\n                         */\n\n                        if (gp->c_option != NULL || gp->C_option != NULL) {\n                                openbsd_warnx(\"only one -c or -C command may be specified.\");\n                                return (1);\n                        }\n                        gp->C_option = openbsd_optarg;\n                        break;\n                case 'c':               /* Run the command. */\n\n                        /*\n                         * XXX\n                         * We should support multiple -c options.\n                         */\n\n                        if (gp->C_option != NULL || gp->c_option != NULL) {\n                                openbsd_warnx(\"only one -c or -C command may be specified.\");\n                                return (1);\n                        }\n                        gp->c_option = openbsd_optarg;\n                        break;\n#ifdef DEBUG\n                case 'D':\n                        switch (openbsd_optarg[0]) {\n                        case 's':\n                                startup = 0;\n                                break;\n                        case 'w':\n                                attach(gp);\n                                break;\n                        default:\n                                openbsd_warnx(\"-D requires s or w argument.\");\n                                return (1);\n                        }\n                        break;\n#endif /* ifdef DEBUG */\n                case 'e':               /* Ex mode. */\n                        LF_CLR(SC_VI);\n                        LF_SET(SC_EX);\n                        break;\n                case 'F':               /* No snapshot. */\n                        F_CLR(gp, G_SNAPSHOT);\n                        break;\n                case 'R':               /* Readonly. */\n                        readonly = 1;\n                        break;\n                case 'r':               /* Recover. */\n                        if (flagchk == 't') {\n                                openbsd_warnx(\n                                    \"only one of -r and -t may be specified.\");\n                                return (1);\n                        }\n                        flagchk = 'r';\n                        break;\n                case 'S':\n                        secure = 1;\n                        break;\n                case 's':\n                        silent = 1;\n                        break;\n#ifdef DEBUG\n                case 'T':               /* Trace. */\n                        if ((gp->tracefp = fopen(openbsd_optarg, \"w\")) == NULL) {\n                                openbsd_warn(\"%s\", openbsd_optarg);\n                                goto err;\n                        }\n                        (void)fprintf(gp->tracefp,\n                            \"\\n===\\ntrace: open %s\\n\", openbsd_optarg);\n                        fflush(gp->tracefp);\n                        break;\n#endif /* ifdef DEBUG */\n                case 't':               /* Tag. */\n                        if (flagchk == 'r') {\n                                openbsd_warnx(\n                                    \"only one of -r and -t may be specified.\");\n                                return (1);\n                        }\n                        if (flagchk == 't') {\n                                openbsd_warnx(\"only one tag file may be specified.\");\n                                return (1);\n                        }\n                        flagchk = 't';\n                        tag_f = openbsd_optarg;\n                        break;\n                case 'v':               /* Vi mode. */\n                        LF_CLR(SC_EX);\n                        LF_SET(SC_VI);\n                        break;\n                case 'w':\n                        wsizearg = openbsd_optarg;\n                        break;\n                case '?':\n                default:\n                        (void)gp->scr_usage();\n                        return (1);\n                }\n        argc -= openbsd_optind;\n        (void)argc;\n        argv += openbsd_optind;\n        (void)argv;\n\n        if (secure)\n                if (openbsd_pledge(\n                    \"stdio rpath wpath cpath fattr flock getpw tty\",\n                    NULL) == -1) {\n                        perror(\"pledge\");\n                        goto err;\n                }\n\n        /*\n         * -s option is only meaningful to ex.\n         *\n         * If not reading from a terminal, it's like -s was specified.\n         */\n\n        if (silent && !LF_ISSET(SC_EX)) {\n                openbsd_warnx(\"-s option is only applicable to ex.\");\n                goto err;\n        }\n        if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED))\n                silent = 1;\n\n        /*\n         * Build and initialize the first/current screen.  This is a bit\n         * tricky.  If an error is returned, we may or may not have a\n         * screen structure.  If we have a screen structure, put it on a\n         * display queue so that the error messages get displayed.\n         *\n         * !!!\n         * Everything we do until we go interactive is done in ex mode.\n         */\n\n        if (screen_init(gp, NULL, &sp)) {\n                if (sp != NULL)\n                        TAILQ_INSERT_HEAD(&gp->dq, sp, q);\n                goto err;\n        }\n        F_SET(sp, SC_EX);\n        TAILQ_INSERT_HEAD(&gp->dq, sp, q);\n\n        if (v_key_init(sp))             /* Special key initialization. */\n                goto err;\n\n        { int oargs[5], *oargp = oargs;\n        if (readonly)                   /* Command-line options. */\n                *oargp++ = O_READONLY;\n        if (secure)\n                *oargp++ = O_SECURE;\n        *oargp = -1;                    /* Options initialization. */\n        if (opts_init(sp, oargs))\n                goto err;\n        }\n        if (wsizearg != NULL) {\n                ARGS *av[2], a, b;\n                (void)snprintf(path, sizeof(path), \"window=%s\", wsizearg);\n                a.bp  = (CHAR_T *)path;\n                a.len = strlen(path);\n                b.bp  = NULL;\n                b.len = 0;\n                av[0] = &a;\n                av[1] = &b;\n                (void)opts_set(sp, av, NULL);\n        }\n        if (silent) {                   /* Ex batch mode option values. */\n                O_CLR(sp, O_AUTOPRINT);\n                O_CLR(sp, O_PROMPT);\n                O_CLR(sp, O_VERBOSE);\n                O_CLR(sp, O_WARN);\n                F_SET(sp, SC_EX_SILENT);\n        }\n\n        sp->rows = O_VAL(sp, O_LINES);  /* Make ex formatting work. */\n        sp->cols = O_VAL(sp, O_COLUMNS);\n\n        if (!silent && startup) {       /* Read EXINIT, exrc files. */\n                if (ex_exrc(sp))\n                        goto err;\n                if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {\n                        if (screen_end(sp))\n                                goto err;\n                        goto done;\n                }\n        }\n\n        /*\n         * List recovery files if -r specified without file arguments.\n         * Note, options must be initialized and startup information\n         * read before doing this.\n         */\n\n        if (flagchk == 'r' && argv[0] == NULL) {\n                if (rcv_list(sp))\n                        goto err;\n                if (screen_end(sp))\n                        goto err;\n                goto done;\n        }\n\n        /*\n         * !!!\n         * Initialize the default ^D, ^U scrolling value here, after the\n         * user has had every opportunity to set the window option.\n         *\n         * It's historic practice that changing the value of the window\n         * option did not alter the default scrolling value, only giving\n         * a count to ^D/^U did that.\n         */\n\n        sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2;\n\n        /*\n         * If we don't have a command-line option, switch into the right\n         * editor now, so that we position default files correctly, and\n         * so that any tags file file-already-locked messages are in the\n         * vi screen, not the ex screen.\n         *\n         * XXX\n         * If we have a command-line option, the error message can end\n         * up in the wrong place, but I think that the combination is\n         * unlikely.\n         */\n\n        if (gp->c_option == NULL) {\n                F_CLR(sp, SC_EX | SC_VI);\n                F_SET(sp, LF_ISSET(SC_EX | SC_VI));\n        }\n\n        /* Open a tag file if specified. */\n        if (tag_f != NULL && ex_tag_first(sp, tag_f))\n                goto err;\n\n        /*\n         * Append any remaining arguments as file names.  Files are recovery\n         * files if -r specified.  If the tag option or ex startup commands\n         * loaded a file, then any file arguments are going to come after it.\n         */\n\n        if (*argv != NULL) {\n                if (sp->frp != NULL) {\n                        size_t l;\n                        /* Cheat -- we know we have an extra argv slot. */\n                        l = strlen(sp->frp->name) + 1;\n                        if ((*--argv = malloc(l)) == NULL) {\n                                openbsd_warn(NULL);\n                                goto err;\n                        }\n                        (void)openbsd_strlcpy(*argv, sp->frp->name, l);\n                }\n                sp->argv = sp->cargv = argv;\n                F_SET(sp, SC_ARGNOFREE);\n                if (flagchk == 'r')\n                        F_SET(sp, SC_ARGRECOVER);\n        }\n\n        /*\n         * If the ex startup commands and or/the tag option haven't already\n         * created a file, create one.  If no command-line files were given,\n         * use a temporary file.\n         */\n\n        if (sp->frp == NULL) {\n                if (sp->argv == NULL) {\n                        if ((frp = file_add(sp, NULL)) == NULL)\n                                goto err;\n                } else  {\n                        if ((frp = file_add(sp, (CHAR_T *)sp->argv[0])) == NULL)\n                                goto err;\n                        if (F_ISSET(sp, SC_ARGRECOVER))\n                                F_SET(frp, FR_RECOVER);\n                }\n\n                if (file_init(sp, frp, NULL, 0))\n                        goto err;\n                if (EXCMD_RUNNING(gp)) {\n                        (void)ex_cmd(sp);\n                        if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {\n                                if (screen_end(sp))\n                                        goto err;\n                                goto done;\n                        }\n                }\n        }\n\n        /*\n         * Check to see if we need to wait for ex.  If SC_SCR_EX is set, ex\n         * was forced to initialize the screen during startup.  We'd like to\n         * wait for a single character from the user, but we can't because\n         * we're not in raw mode.  We can't switch to raw mode because the\n         * vi initialization will switch to xterm's alternate screen, causing\n         * us to lose the messages we're pausing to make sure the user read.\n         * So, wait for a complete line.\n         */\n\n        if (F_ISSET(sp, SC_SCR_EX)) {\n                p = msg_cmsg(sp, CMSG_CONT_R, &len);\n                (void)!write(STDOUT_FILENO, p, len);\n                    for (;;) {\n                        if (v_event_get(sp, &ev, 0, 0))\n                                goto err;\n                        if (ev.e_event  == E_INTERRUPT ||\n                            (ev.e_event == E_CHARACTER &&\n                            (ev.e_value == K_CR || ev.e_value == K_NL)))\n                                break;\n                        (void)gp->scr_bell(sp);\n                }\n        }\n\n        /* Switch into the right editor, regardless. */\n        F_CLR(sp, SC_EX | SC_VI);\n        F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT);\n\n        /*\n         * Main edit loop.  Vi handles split screens itself, we only return\n         * here when switching editor modes or restarting the screen.\n         */\n\n        while (sp != NULL)\n                if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp))\n                        goto err;\n\ndone:   rval = 0;\n        if (0)\nerr:            rval = 1;\n\n        /* Clean out the global structure. */\n        v_end(gp);\n\n        return (rval);\n}\n\n/*\n * v_end --\n *      End the program, discarding screens and most of the global area.\n *\n * PUBLIC: void v_end(GS *);\n */\n\nvoid\nv_end(GS *gp)\n{\n        MSGS *mp;\n        SCR *sp;\n\n        /* If there are any remaining screens, kill them off. */\n        if (gp->ccl_sp != NULL) {\n                (void)file_end(gp->ccl_sp, NULL, 1);\n                (void)screen_end(gp->ccl_sp);\n        }\n        while ((sp = TAILQ_FIRST(&gp->dq)))\n                (void)screen_end(sp);   /* Removes sp from the queue. */\n        while ((sp = TAILQ_FIRST(&gp->hq)))\n                (void)screen_end(sp);   /* Removes sp from the queue. */\n\n#if defined(DEBUG) || defined(PURIFY)\n        { FREF *frp;\n                /* Free FREF's. */\n                while ((frp = TAILQ_FIRST(&gp->frefq))) {\n                        TAILQ_REMOVE(&gp->frefq, frp, q);\n                        free(frp->name);\n                        free(frp->tname);\n                        free(frp);\n                }\n        }\n\n        /* Free key input queue. */\n        free(gp->i_event);\n\n        /* Free cut buffers. */\n        cut_close(gp);\n\n        /* Free map sequences. */\n        seq_close(gp);\n\n        /* Free default buffer storage. */\n        (void)text_lfree(&gp->dcb_store.textq);\n#endif /* if defined(DEBUG) || defined(PURIFY) */\n\n        /* Ring the bell if scheduled. */\n        if (F_ISSET(gp, G_BELLSCHED))\n                (void)fprintf(stderr, \"\\07\");           /* \\a */\n\n        /*\n         * Flush any remaining messages.  If a message is here, it's almost\n         * certainly the message about the event that killed us (although\n         * it's possible that the user is sourcing a file that exits from the\n         * editor).\n         */\n\n        while ((mp = LIST_FIRST(&gp->msgq)) != NULL) {\n                (void)fprintf(stderr, \"%s%.*s\",\n                    mp->mtype == M_ERR ? \"ex/vi: \" : \"\", (int)mp->len, mp->buf);\n                LIST_REMOVE(mp, q);\n#if defined(DEBUG) || defined(PURIFY)\n                free(mp->buf);\n                free(mp);\n#endif /* if defined(DEBUG) || defined(PURIFY) */\n        }\n\n#if defined(DEBUG) || defined(PURIFY)\n        /* Free any temporary space. */\n        free(gp->tmp_bp);\n\n# if defined(DEBUG)\n        /* Close debugging file descriptor. */\n        if (gp->tracefp != NULL)\n                (void)fclose(gp->tracefp);\n# endif /* if defined(DEBUG) */\n#endif /* if defined(DEBUG) || defined(PURIFY) */\n}\n\n/*\n * v_obsolete --\n *      Convert historic arguments into something\n *      openbsd_getopt(3) will like.\n */\n\nstatic int\nv_obsolete(char *argv[])\n{\n        size_t len;\n        char *p;\n\n        /*\n         * Translate old style arguments into something openbsd_getopt\n         * will like. Make sure it's not text space memory, because\n         * ex modifies the strings.\n         *      Change \"+\" into \"-c$\".\n         *      Change \"+<anything else>\" into \"-c<anything else>\".\n         *      Change \"-\" into \"-s\"\n         *      The c, T, t and w options take arguments so they can't be\n         *          special arguments.\n         *\n         * Stop if we find \"--\" as an argument, the user may want to edit\n         * a file named \"+foo\".\n         */\n\n        while (*++argv && strcmp(argv[0], \"--\"))\n                if (argv[0][0] == '+') {\n                        if (argv[0][1] == '\\0') {\n                                argv[0] = strdup(\"-c$\");\n                                if (argv[0] == NULL)\n                                        goto nomem;\n                        } else  {\n                                p = argv[0];\n                                len = strlen(argv[0]);\n                                if ((argv[0] = malloc(len + 2)) == NULL)\n                                        goto nomem;\n                                argv[0][0] = '-';\n                                argv[0][1] = 'c';\n                                (void)openbsd_strlcpy(argv[0] + 2, p + 1, len);\n                        }\n                } else if (argv[0][0] == '-') {\n                        if (argv[0][1] == '\\0') {\n                                argv[0] = strdup(\"-s\");\n                                if (argv[0] == NULL) {\nnomem:                                  openbsd_warn(NULL);\n                                        return (1);\n                                }\n                        } else\n                                if ((argv[0][1] == 'c' || argv[0][1] == 'T' ||\n                                    argv[0][1] == 't' || argv[0][1] == 'w') &&\n                                    argv[0][2] == '\\0')\n                                        ++argv;\n                }\n        return (0);\n}\n\n#ifdef DEBUG\nstatic void\nattach(GS *gp)\n{\n        int fd;\n        char ch;\n\n        if ((fd = open(_PATH_TTY, O_RDONLY)) < 0) {\n                openbsd_warn(\"%s\", _PATH_TTY);\n                return;\n        }\n\n        (void)printf(\"process %ld waiting, enter <CR> to continue: \",\n            (long)getpid());\n        (void)fflush(stdout);\n\n        do {\n                if (read(fd, &ch, 1) != 1) {\n                        (void)close(fd);\n                        return;\n                }\n        } while (ch != '\\n' && ch != '\\r');\n        (void)close(fd);\n}\n#endif /* ifdef DEBUG */\n"
  },
  {
    "path": "common/mark.c",
    "content": "/*      $OpenBSD: mark.c,v 1.14 2016/05/27 09:18:11 martijn Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"common.h\"\n\nstatic LMARK *mark_find(SCR *, CHAR_T);\n\n/*\n * Marks are maintained in a key sorted doubly linked list.  We can't\n * use arrays because we have no idea how big an index key could be.\n * The underlying assumption is that users don't have more than, say,\n * 10 marks at any one time, so this will be is fast enough.\n *\n * Marks are fixed, and modifications to the line don't update the mark's\n * position in the line.  This can be hard.  If you add text to the line,\n * place a mark in that text, undo the addition and use ` to move to the\n * mark, the location will have disappeared.  It's tempting to try to adjust\n * the mark with the changes in the line, but this is hard to do, especially\n * if we've given the line to v_ntext.c:v_ntext() for editing.  Historic vi\n * would move to the first non-blank on the line when the mark location was\n * past the end of the line.  This can be complicated by deleting to a mark\n * that has disappeared using the ` command.  Historic vi treated this as\n * a line-mode motion and deleted the line.  This implementation complains to\n * the user.\n *\n * In historic vi, marks returned if the operation was undone, unless the\n * mark had been subsequently reset.  Tricky.  This is hard to start with,\n * but in the presence of repeated undo it gets nasty.  When a line is\n * deleted, we delete (and log) any marks on that line.  An undo will create\n * the mark.  Any mark creations are noted as to whether the user created\n * it or if it was created by an undo.  The former cannot be reset by another\n * undo, but the latter may.\n *\n * All of these routines translate ABSMARK2 to ABSMARK1.  Setting either of\n * the absolute mark locations sets both, so that \"m'\" and \"m`\" work like\n * they, ah, for lack of a better word, \"should\".\n */\n\n/*\n * mark_init --\n *      Set up the marks.\n *\n * PUBLIC: int mark_init(SCR *, EXF *);\n */\n\nint\nmark_init(SCR *sp, EXF *ep)\n{\n\n        /*\n         * !!!\n         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.\n         *\n         * Set up the marks.\n         */\n\n        LIST_INIT(&ep->marks);\n        return (0);\n}\n\n/*\n * mark_end --\n *      Free up the marks.\n *\n * PUBLIC: int mark_end(SCR *, EXF *);\n */\n\nint\nmark_end(SCR *sp, EXF *ep)\n{\n        LMARK *lmp;\n\n        /*\n         * !!!\n         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.\n         */\n\n        while ((lmp = LIST_FIRST(&ep->marks)) != NULL) {\n                LIST_REMOVE(lmp, q);\n                free(lmp);\n        }\n        return (0);\n}\n\n/*\n * mark_get --\n *      Get the location referenced by a mark.\n *\n * PUBLIC: int mark_get(SCR *, CHAR_T, MARK *, mtype_t);\n */\n\nint\nmark_get(SCR *sp, CHAR_T key, MARK *mp, mtype_t mtype)\n{\n        LMARK *lmp;\n\n        if (key == ABSMARK2)\n                key = ABSMARK1;\n\n        lmp = mark_find(sp, key);\n        if (lmp == NULL || lmp->name != key) {\n                msgq(sp, mtype, \"Mark %s: not set\", KEY_NAME(sp, key));\n                return (1);\n        }\n        if (F_ISSET(lmp, MARK_DELETED)) {\n                msgq(sp, mtype,\n                    \"Mark %s: the line was deleted\", KEY_NAME(sp, key));\n                return (1);\n        }\n\n        /*\n         * !!!\n         * The absolute mark is initialized to lno 1/cno 0, and historically\n         * you could use it in an empty file.  Make such a mark always work.\n         */\n\n        if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) {\n                msgq(sp, mtype,\n                    \"Mark %s: cursor position no longer exists\",\n                    KEY_NAME(sp, key));\n                return (1);\n        }\n        mp->lno = lmp->lno;\n        mp->cno = lmp->cno;\n        return (0);\n}\n\n/*\n * mark_set --\n *      Set the location referenced by a mark.\n *\n * PUBLIC: int mark_set(SCR *, CHAR_T, MARK *, int);\n */\n\nint\nmark_set(SCR *sp, CHAR_T key, MARK *value, int userset)\n{\n        LMARK *lmp, *lmt;\n\n        if (key == ABSMARK2)\n                key = ABSMARK1;\n\n        /*\n         * The rules are simple.  If the user is setting a mark (if it's a\n         * new mark this is always true), it always happens.  If not, it's\n         * an undo, and we set it if it's not already set or if it was set\n         * by a previous undo.\n         */\n\n        lmp = mark_find(sp, key);\n        if (lmp == NULL || lmp->name != key) {\n                MALLOC_RET(sp, lmt, sizeof(LMARK));\n                if (lmp == NULL) {\n                        LIST_INSERT_HEAD(&sp->ep->marks, lmt, q);\n                } else\n                        LIST_INSERT_AFTER(lmp, lmt, q);\n                lmp = lmt;\n        } else if (!userset &&\n            !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET))\n                return (0);\n\n        lmp->lno = value->lno;\n        lmp->cno = value->cno;\n        lmp->name = key;\n        lmp->flags = userset ? MARK_USERSET : 0;\n        return (0);\n}\n\n/*\n * mark_find --\n *      Find the requested mark, or, the slot immediately before\n *      where it would go.\n */\n\nstatic LMARK *\nmark_find(SCR *sp, CHAR_T key)\n{\n        LMARK *lmp, *lastlmp;\n\n        /*\n         * Return the requested mark or the slot immediately before\n         * where it should go.\n         */\n\n        for (lastlmp = NULL, lmp = LIST_FIRST(&sp->ep->marks);\n            lmp != NULL; lastlmp = lmp, lmp = LIST_NEXT(lmp, q))\n                if (lmp->name >= key)\n                        return (lmp->name == key ? lmp : lastlmp);\n        return (lastlmp);\n}\n\n/*\n * mark_insdel --\n *      Update the marks based on an insertion or deletion.\n *\n * PUBLIC: int mark_insdel(SCR *, lnop_t, recno_t);\n */\n\nint\nmark_insdel(SCR *sp, lnop_t op, recno_t lno)\n{\n        LMARK *lmp;\n        recno_t lline;\n\n        switch (op) {\n        case LINE_APPEND:\n                /* All insert/append operations are done as inserts. */\n                abort();\n        case LINE_DELETE:\n                LIST_FOREACH(lmp, &sp->ep->marks, q)\n                        if (lmp->lno >= lno) {\n                                if (lmp->lno == lno) {\n                                        F_SET(lmp, MARK_DELETED);\n                                        (void)log_mark(sp, lmp);\n                                } else\n                                        --lmp->lno;\n                        }\n                break;\n        case LINE_INSERT:\n\n                /*\n                 * XXX\n                 * Very nasty special case.  If the file was empty, then we're\n                 * adding the first line, which is a replacement.  So, we don't\n                 * modify the marks.  This is a hack to make:\n                 *\n                 *      mz:r!echo foo<carriage-return>'z\n                 *\n                 * work, i.e. historically you could mark the \"line\" in an empty\n                 * file and replace it, and continue to use the mark.  Insane,\n                 * well, yes, I know, but someone complained.\n                 *\n                 * Check for line #2 before going to the end of the file.\n                 */\n\n                if (!db_exist(sp, 2)) {\n                        if (db_last(sp, &lline))\n                                return (1);\n                        if (lline == 1)\n                                return (0);\n                }\n\n                LIST_FOREACH(lmp, &sp->ep->marks, q)\n                        if (lmp->lno >= lno)\n                                ++lmp->lno;\n                break;\n        case LINE_RESET:\n                break;\n        }\n        return (0);\n}\n"
  },
  {
    "path": "common/mark.h",
    "content": "/*      $OpenBSD: mark.h,v 1.5 2016/05/27 09:18:11 martijn Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)mark.h      10.3 (Berkeley) 3/6/96\n */\n\n/*\n * The MARK and LMARK structures define positions in the file.  There are\n * two structures because the mark subroutines are the only places where\n * anything cares about something other than line and column.\n *\n * Because of the different interfaces used by the db(3) package, curses,\n * and users, the line number is 1 based and the column number is 0 based.\n * Additionally, it is known that the out-of-band line number is less than\n * any legal line number.  The line number is of type recno_t, as that's\n * the underlying type of the database.  The column number is of type size_t,\n * guaranteeing that we can malloc a line.\n */\n\nstruct _mark {\n#define OOBLNO          0               /* Out-of-band line number. */\n        recno_t  lno;                   /* Line number.             */\n        size_t   cno;                   /* Column number.           */\n};\n\nstruct _lmark {\n        LIST_ENTRY(_lmark) q;           /* Linked list of marks. */\n        recno_t  lno;                   /* Line number.          */\n        size_t   cno;                   /* Column number.        */\n        CHAR_T   name;                  /* Mark name.            */\n\n#define MARK_DELETED    0x01            /* Mark was deleted.   */\n#define MARK_USERSET    0x02            /* User set this mark. */\n        u_int8_t flags;\n};\n\n#define ABSMARK1        '\\''            /* Absolute mark name. */\n#define ABSMARK2        '`'             /* Absolute mark name. */\n"
  },
  {
    "path": "common/mem.h",
    "content": "/*      $OpenBSD: mem.h,v 1.10 2017/06/24 16:30:47 martijn Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)mem.h       10.7 (Berkeley) 3/30/96\n */\n\n/*\n * Increase the size of a malloc'd buffer.  Two versions, one that\n * returns, one that jumps to an error label.\n */\n\n#define BINC_GOTO(sp, lp, llen, nlen) {                                 \\\n        void *L__bincp;                                                 \\\n        if ((nlen) > (llen)) {                                          \\\n                if ((L__bincp = binc((sp), (lp), &(llen), (nlen)))      \\\n                    == NULL)                                            \\\n                        goto alloc_err;                                 \\\n                /*                                                      \\\n                 * !!!                                                  \\\n                 * Possible pointer conversion.                         \\\n                 */                                                     \\\n                (lp) = L__bincp;                                        \\\n        }                                                               \\\n}\n\n#define BINC_RET(sp, lp, llen, nlen) {                                  \\\n        void *L__bincp;                                                 \\\n        if ((nlen) > (llen)) {                                          \\\n                if ((L__bincp = binc((sp), (lp), &(llen), (nlen)))      \\\n                    == NULL)                                            \\\n                        return (1);                                     \\\n                /*                                                      \\\n                 * !!!                                                  \\\n                 * Possible pointer conversion.                         \\\n                 */                                                     \\\n                (lp) = L__bincp;                                        \\\n        }                                                               \\\n}\n\n/*\n * Get some temporary space, preferably from the global temporary buffer,\n * from a malloc'd buffer otherwise.  Two versions, one that returns, one\n * that jumps to an error label.\n */\n\n#define GET_SPACE_GOTO(sp, bp, blen, nlen) {                            \\\n        GS *L__gp = (sp) == NULL ? NULL : (sp)->gp;                     \\\n        if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) {             \\\n                (bp) = NULL;                                            \\\n                (blen) = 0;                                             \\\n                BINC_GOTO((sp), (bp), (blen), (nlen));                  \\\n        } else {                                                        \\\n                BINC_GOTO((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen));\\\n                (bp) = L__gp->tmp_bp;                                   \\\n                (blen) = L__gp->tmp_blen;                               \\\n                F_SET(L__gp, G_TMP_INUSE);                              \\\n        }                                                               \\\n}\n\n#define GET_SPACE_RET(sp, bp, blen, nlen) {                             \\\n        GS *L__gp = (sp) == NULL ? NULL : (sp)->gp;                     \\\n        if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) {             \\\n                (bp) = NULL;                                            \\\n                (blen) = 0;                                             \\\n                BINC_RET((sp), (bp), (blen), (nlen));                   \\\n        } else {                                                        \\\n                BINC_RET((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen)); \\\n                (bp) = L__gp->tmp_bp;                                   \\\n                (blen) = L__gp->tmp_blen;                               \\\n                F_SET(L__gp, G_TMP_INUSE);                              \\\n        }                                                               \\\n}\n\n/*\n * Add space to a GET_SPACE returned buffer.  Two versions, one that\n * returns, one that jumps to an error label.\n */\n\n#define ADD_SPACE_GOTO(sp, bp, blen, nlen) {                            \\\n        GS *L__gp = (sp) == NULL ? NULL : (sp)->gp;                     \\\n        if (L__gp != NULL && (bp) == L__gp->tmp_bp) {                   \\\n                F_CLR(L__gp, G_TMP_INUSE);                              \\\n                BINC_GOTO((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen));\\\n                (bp) = L__gp->tmp_bp;                                   \\\n                (blen) = L__gp->tmp_blen;                               \\\n                F_SET(L__gp, G_TMP_INUSE);                              \\\n        } else                                                          \\\n                BINC_GOTO((sp), (bp), (blen), (nlen));                  \\\n}\n\n#define ADD_SPACE_RET(sp, bp, blen, nlen) {                             \\\n        GS *L__gp = (sp) == NULL ? NULL : (sp)->gp;                     \\\n        if (L__gp != NULL && (bp) == L__gp->tmp_bp) {                   \\\n                F_CLR(L__gp, G_TMP_INUSE);                              \\\n                BINC_RET((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen)); \\\n                (bp) = L__gp->tmp_bp;                                   \\\n                (blen) = L__gp->tmp_blen;                               \\\n                F_SET(L__gp, G_TMP_INUSE);                              \\\n        } else                                                          \\\n                BINC_RET((sp), (bp), (blen), (nlen));                   \\\n}\n\n/* Free a GET_SPACE returned buffer. */\n\n#define FREE_SPACE(sp, bp, blen) {                                      \\\n        GS *L__gp = (sp) == NULL ? NULL : (sp)->gp;                     \\\n        if (L__gp != NULL && (bp) == L__gp->tmp_bp)                     \\\n                F_CLR(L__gp, G_TMP_INUSE);                              \\\n        else                                                            \\\n                free(bp);                                               \\\n}\n\n/*\n * Malloc a buffer, casting the return pointer.  Various versions.\n */\n\n#define CALLOC(sp, p, nmemb, size) {                                    \\\n        if (((p) = calloc((nmemb), (size))) == NULL)                    \\\n                msgq((sp), M_SYSERR, NULL);                             \\\n}\n\n#define CALLOC_GOTO(sp, p, nmemb, size) {                               \\\n        if (((p) = calloc((nmemb), (size))) == NULL)                    \\\n                goto alloc_err;                                         \\\n}\n\n#define CALLOC_RET(sp, p, nmemb, size) {                                \\\n        if (((p) = calloc((nmemb), (size))) == NULL) {                  \\\n                msgq((sp), M_SYSERR, NULL);                             \\\n                return (1);                                             \\\n        }                                                               \\\n}\n\n#define MALLOC(sp, p, size) {                                           \\\n        if (((p) = malloc(size)) == NULL)                               \\\n                msgq((sp), M_SYSERR, NULL);                             \\\n}\n\n#define MALLOC_GOTO(sp, p, size) {                                      \\\n        if (((p) = malloc(size)) == NULL)                               \\\n                goto alloc_err;                                         \\\n}\n\n#define MALLOC_RET(sp, p, size) {                                       \\\n        if (((p) = malloc(size)) == NULL) {                             \\\n                msgq((sp), M_SYSERR, NULL);                             \\\n                return (1);                                             \\\n        }                                                               \\\n}\n\n#define REALLOC(sp, p, size) {                                          \\\n        void *tmpp;                                                     \\\n        if (((tmpp) = (realloc((p), (size)))) == NULL) {                \\\n                msgq((sp), M_SYSERR, NULL);                             \\\n                free(p);                                                \\\n        }                                                               \\\n        p = tmpp;                                                       \\\n}\n\n#define REALLOCARRAY(sp, p, nelem, size) {                              \\\n        void *tmpp;                                                     \\\n        if (((tmpp) =                                                   \\\n            (openbsd_reallocarray((p), (nelem), (size)))) == NULL) {    \\\n                msgq((sp), M_SYSERR, NULL);                             \\\n                free(p);                                                \\\n        }                                                               \\\n        p = tmpp;                                                       \\\n}\n\n/*\n * Versions of memmove(3) and memset(3) that use the size of the\n * initial pointer to figure out how much memory to manipulate.\n */\n\n#define MEMMOVE(p, t, len)      memmove((p),     (t), (len) * sizeof(*(p)))\n#define MEMSET(p, value, len)   memset( (p), (value), (len) * sizeof(*(p)))\n"
  },
  {
    "path": "common/msg.c",
    "content": "/*      $OpenBSD: msg.c,v 1.28 2022/12/26 19:16:03 jmc Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * msgq --\n *      Display a message.\n *\n * PUBLIC: void msgq(SCR *, mtype_t, const char *, ...);\n */\n\nvoid\nmsgq(SCR *sp, mtype_t mt, const char *fmt, ...)\n{\n        static int reenter;             /* STATIC: Re-entrancy check. */\n        GS *gp;\n        size_t blen, len, mlen, nlen;\n        const char *p;\n        char *bp, *mp;\n        va_list ap;\n\n        /*\n         * !!!\n         * It's possible to enter msg when there's no screen to hold the\n         * message.  If sp is NULL, ignore the special cases and put the\n         * message out to stderr.\n         */\n\n        if (sp == NULL) {\n                gp = NULL;\n                if (mt == M_BERR)\n                        mt = M_ERR;\n                else if (mt == M_VINFO)\n                        mt = M_INFO;\n        } else {\n                gp = sp->gp;\n                switch (mt) {\n                case M_BERR:\n                        if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) {\n                                F_SET(gp, G_BELLSCHED);\n                                return;\n                        }\n                        mt = M_ERR;\n                        break;\n                case M_XINFO:\n                        mt = M_INFO;\n                        break;\n                case M_VINFO:\n                        if (!O_ISSET(sp, O_VERBOSE))\n                                return;\n                        mt = M_INFO;\n                        /* FALLTHROUGH */\n                case M_INFO:\n                        if (F_ISSET(sp, SC_EX_SILENT))\n                                return;\n                        break;\n                case M_ERR:\n                case M_SYSERR:\n                        break;\n                default:\n                        abort();\n                }\n        }\n\n        /*\n         * It's possible to reenter msg when it allocates space.  We're\n         * probably dead anyway, but there's no reason to drop core.\n         *\n         * XXX\n         * Yes, there's a race, but it should only be two instructions.\n         */\n\n        if (reenter++)\n                return;\n\n        /* Get space for the message. */\n        nlen = 1024;\n        if (0) {\nretry:          FREE_SPACE(sp, bp, blen);\n                nlen *= 2;\n        }\n        bp = NULL;\n        blen = 0;\n        GET_SPACE_GOTO(sp, bp, blen, nlen);\n\n        /*\n         * Error prefix.\n         *\n         * mp:   pointer to the current next character to be written\n         * mlen: length of the already written characters\n         * blen: total length of the buffer\n         */\n\n#define REM     (blen - mlen)\n        mp = bp;\n        mlen = 0;\n\n        if (mp == NULL)\n                return;\n\n        if (mt == M_SYSERR) {\n                p = \"Error: \";\n                len = strlen(p);\n                if (REM < len)\n                        goto retry;\n                memcpy(mp, p, len);\n                mp += len;\n                mlen += len;\n        }\n\n        /*\n         * If we're running an ex command that the user didn't enter, display\n         * the file name and line number prefix.\n         */\n\n        if ((mt == M_ERR || mt == M_SYSERR) &&\n            sp != NULL && gp != NULL && gp->if_name != NULL) {\n                for (p = gp->if_name; *p != '\\0'; ++p) {\n                        len = snprintf(mp, REM, \"%s\", KEY_NAME(sp, *p));\n                        mp += len;\n                        if ((mlen += len) > blen)\n                                goto retry;\n                }\n                len = snprintf(mp, REM, \", %d: \", gp->if_lno);\n                mp += len;\n                if ((mlen += len) > blen)\n                        goto retry;\n        }\n\n        /* If nothing to format, we're done. */\n        if (fmt == NULL) {\n                len = 0;\n                goto nofmt;\n        }\n\n        /* Format the arguments into the string. */\n        va_start(ap, fmt);\n        len = vsnprintf(mp, REM, fmt, ap);\n        va_end(ap);\n        if (len >= nlen)\n                goto retry;\n\nnofmt:  mp += len;\n        if ((mlen += len) > blen)\n                goto retry;\n        if (mt == M_SYSERR) {\n                len = snprintf(mp, REM, \": %s\", strerror(errno));\n                mp += len;\n                if ((mlen += len) > blen)\n                        goto retry;\n                mt = M_ERR;\n        }\n\n        /* Add trailing newline. */\n        if ((mlen += 1) > blen)\n                goto retry;\n        *mp = '\\n';\n\n        if (sp != NULL)\n                (void)ex_fflush(sp);\n        if (gp != NULL)\n                gp->scr_msg(sp, mt, bp, mlen);\n        else\n                (void)fprintf(stderr, \"%.*s\", (int)mlen, bp);\n\n        /* Cleanup. */\n        FREE_SPACE(sp, bp, blen);\nalloc_err:\n        reenter = 0;\n}\n\n/*\n * msgq_str --\n *      Display a message with an embedded string.\n *\n * PUBLIC: void msgq_str(SCR *, mtype_t, char *, char *);\n */\n\nvoid\nmsgq_str(SCR *sp, mtype_t mtype, char *str, char *fmt)\n{\n        int nf, sv_errno;\n        char *p;\n\n        if (str == NULL) {\n                msgq(sp, mtype, fmt);\n                return;\n        }\n\n        sv_errno = errno;\n        p = msg_print(sp, str, &nf);\n        errno = sv_errno;\n        msgq(sp, mtype, fmt, p);\n        if (nf)\n                FREE_SPACE(sp, p, 0);\n}\n\n/*\n * mod_rpt --\n *      Report on the lines that changed.\n *\n * !!!\n * Historic vi documentation (USD:15-8) claimed that \"The editor will also\n * always tell you when a change you make affects text which you cannot see.\"\n * This wasn't true -- edit a large file and do \"100d|1\".  We don't implement\n * this semantic since it requires tracking each line that changes during a\n * command instead of just keeping count.\n *\n * Line counts weren't right in historic vi, either.  For example, given the\n * file:\n *      abc\n *      def\n * the command 2d}, from the 'b' would report that two lines were deleted,\n * not one.\n *\n * PUBLIC: void mod_rpt(SCR *);\n */\n\nvoid\nmod_rpt(SCR *sp)\n{\n        static char * const action[] = {\n                \"added\",\n                \"changed\",\n                \"deleted\",\n                \"joined\",\n                \"moved\",\n                \"shifted\",\n                \"yanked\",\n        };\n        static char * const lines[] = {\n                \"line\",\n                \"lines\",\n        };\n        recno_t total;\n        unsigned long rptval;\n        int first, cnt;\n        size_t blen, len, tlen;\n        const char *t;\n        char * const *ap;\n        char *bp, *p;\n\n        /* Change reports are turned off in batch mode. */\n        if (F_ISSET(sp, SC_EX_SILENT))\n                return;\n\n        /* Reset changing line number. */\n        sp->rptlchange = OOBLNO;\n\n        /*\n         * Don't build a message if not enough changed.\n         *\n         * !!!\n         * And now, a vi clone test.  Historically, vi reported if the number\n         * of changed lines was > than the value, not >=, unless it was a yank\n         * command, which used >=.  No lie.  Furthermore, an action was never\n         * reported for a single line action.  This is consistent for actions\n         * other than yank, but yank didn't report single line actions even if\n         * the report edit option was set to 1.  In addition, setting report to\n         * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an\n         * unknown reason (this bug was fixed in System III/V at some point).\n         * I got complaints, so nvi conforms to System III/V historic practice\n         * except that we report a yank of 1 line if report is set to 1.\n         */\n\n#define ARSIZE(a)       sizeof(a) / sizeof (*a)\n#define MAXNUM          25\n        rptval = O_VAL(sp, O_REPORT);\n        for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt)\n                total += sp->rptlines[cnt];\n        if (total == 0)\n                return;\n        if (total <= rptval && sp->rptlines[L_YANKED] < rptval) {\n                for (cnt = 0; cnt < ARSIZE(action); ++cnt)\n                        sp->rptlines[cnt] = 0;\n                return;\n        }\n\n        /* Build and display the message. */\n        GET_SPACE_GOTO(sp, bp, blen, sizeof(action) * MAXNUM + 1);\n        for (p = bp, first = 1, tlen = 0,\n            ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt)\n                if (sp->rptlines[cnt] != 0) {\n                        if (first)\n                                first = 0;\n                        else {\n                                *p++ = ';';\n                                *p++ = ' ';\n                                tlen += 2;\n                        }\n                        len = snprintf(p, MAXNUM, \"%u \", sp->rptlines[cnt]);\n                        p += len;\n                        tlen += len;\n                        t = lines[sp->rptlines[cnt] == 1 ? 0 : 1];\n                        len = strlen(t);\n                        memcpy(p, t, len);\n                        p += len;\n                        tlen += len;\n                        *p++ = ' ';\n                        ++tlen;\n                        len = strlen(*ap);\n                        memcpy(p, *ap, len);\n                        p += len;\n                        tlen += len;\n                        sp->rptlines[cnt] = 0;\n                }\n\n        /* Add trailing newline. */\n        *p = '\\n';\n        ++tlen;\n\n        (void)ex_fflush(sp);\n        sp->gp->scr_msg(sp, M_INFO, bp, tlen);\n\n        FREE_SPACE(sp, bp, blen);\nalloc_err:\n        return;\n\n#undef ARSIZE\n#undef MAXNUM\n}\n\n/*\n * msgq_status --\n *      Report on the file's status.\n *\n * PUBLIC: void msgq_status(SCR *, recno_t, unsigned int);\n */\n\nvoid\nmsgq_status(SCR *sp, recno_t lno, unsigned int flags)\n{\n        recno_t last;\n        size_t blen, len;\n        int cnt, needsep;\n        const char *t;\n        char **ap, *bp, *np, *p, *s, *ep;\n\n        /* Get sufficient memory. */\n        len = strlen(sp->frp->name);\n        GET_SPACE_GOTO(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128);\n        p = bp;\n        ep = bp + blen;\n\n        if (p == NULL)\n                return;\n\n        /* Copy in the filename. */\n        for (t = sp->frp->name; *t != '\\0'; ++t) {\n                len = KEY_LEN(sp, *t);\n                memcpy(p, KEY_NAME(sp, *t), len);\n                p += len;\n        }\n        np = p;\n        *p++ = ':';\n        *p++ = ' ';\n\n        /* Copy in the argument count. */\n        if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) {\n                for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt);\n                if (cnt > 1) {\n                        (void)snprintf(p, ep - p, \"%'d files to edit\", cnt);\n                        p += strlen(p);\n                        *p++ = ':';\n                        *p++ = ' ';\n                }\n                F_CLR(sp, SC_STATUS_CNT);\n        }\n\n        /*\n         * See nvi/exf.c:file_init() for a description of how and when the\n         * read-only bit is set.\n         *\n         * !!!\n         * The historic display for \"name changed\" was \"[Not edited]\".\n         */\n\n        needsep = 0;\n        if (F_ISSET(sp->frp, FR_NEWFILE)) {\n                F_CLR(sp->frp, FR_NEWFILE);\n                len = strlen(\"new file\");\n                memcpy(p, \"new file\", len);\n                p += len;\n                needsep = 1;\n        } else {\n                if (F_ISSET(sp->frp, FR_NAMECHANGE)) {\n                        len = strlen(\"name changed\");\n                        memcpy(p, \"name changed\", len);\n                        p += len;\n                        needsep = 1;\n                }\n                if (needsep) {\n                        *p++ = ',';\n                        *p++ = ' ';\n                }\n                t = (F_ISSET(sp->ep, F_MODIFIED)) ? \"modified\" : \"unmodified\";\n                len = strlen(t);\n                memcpy(p, t, len);\n                p += len;\n                needsep = 1;\n        }\n#ifndef _AIX\n        if (F_ISSET(sp->frp, FR_UNLOCKED)) {\n                if (needsep) {\n                        *p++ = ',';\n                        *p++ = ' ';\n                }\n                len = strlen(\"UNLOCKED\");\n                memcpy(p, \"UNLOCKED\", len);\n                p += len;\n                needsep = 1;\n        }\n#endif /* ifndef _AIX */\n        if (O_ISSET(sp, O_READONLY)) {\n                if (needsep) {\n                        *p++ = ',';\n                        *p++ = ' ';\n                }\n                len = strlen(\"readonly\");\n                memcpy(p, \"readonly\", len);\n                p += len;\n                needsep = 1;\n        }\n        if (needsep) {\n                *p++ = ':';\n                *p++ = ' ';\n        }\n        if (LF_ISSET(MSTAT_SHOWLAST)) {\n                if (db_last(sp, &last))\n                        last = 0;\n                if (last == 0) {\n                        char* mtfilestr = \"empty file\";\n                        len = strlen(mtfilestr);\n                        memcpy(p, mtfilestr, len);\n                        p += len;\n                } else {\n                        (void)snprintf(p, ep - p, \"line %'lu of %'lu [%lu%%]\",\n                            (unsigned long)lno, (unsigned long)last,\n                            (unsigned long)(lno * 100) / last);\n                        p += strlen(p);\n                }\n        } else {\n                if (db_last(sp, &last))\n                        last = 0;\n                (void)snprintf(p, ep - p, \"line %'lu\", (unsigned long)lno);\n                p += strlen(p);\n                if (last) {\n                        (void)snprintf(p, ep - p, \" of %'lu\", (unsigned long)last);\n                        p += strlen(p);\n                }\n        }\n#ifdef DEBUG\n        (void)snprintf(p, ep - p, \" (pid %ld)\", (long)getpid());\n        p += strlen(p);\n#endif /* ifdef DEBUG */\n        *p++ = '\\n';\n        len = p - bp;\n\n        /*\n         * There's a nasty problem with long path names.  Tags files\n         * can result in long paths and vi will request a continuation key from\n         * the user as soon as it starts the screen.  Unfortunately, the user\n         * has already typed ahead, and chaos results.  If we assume that the\n         * characters in the filenames and informational messages only take a\n         * single screen column each, we can trim the filename.\n         *\n         * XXX\n         * Status lines get put up at fairly awkward times.  For example, when\n         * you do a filter read (e.g., :read ! echo foo) in the top screen of a\n         * split screen, we have to repaint the status lines for all the screens\n         * below the top screen.  We don't want users having to enter continue\n         * characters for those screens.  Make it really hard to screw this up.\n         */\n\n        s = bp;\n        if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) {\n                for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s);\n                if (s == np) {\n                        s = p - (sp->cols - 5);\n                        *--s = ' ';\n                }\n                *--s = '.';\n                *--s = '.';\n                *--s = '.';\n                len = p - s;\n        }\n\n        /* Flush any waiting ex messages. */\n        (void)ex_fflush(sp);\n\n        sp->gp->scr_msg(sp, M_INFO, s, len);\n\n        FREE_SPACE(sp, bp, blen);\nalloc_err:\n        return;\n}\n\n/*\n * msg_cont --\n *      Return common continuation messages.\n *\n * PUBLIC: const char *msg_cmsg(SCR *, cmsg_t, size_t *);\n */\n\nconst char *\nmsg_cmsg(SCR *sp, cmsg_t which, size_t *lenp)\n{\n        const char *s;\n        switch (which) {\n        case CMSG_CONF:\n                s = \"Confirm? [y/n/q]\";\n                break;\n        case CMSG_CONT:\n                s = \"Press any key to continue: \";\n                break;\n        case CMSG_CONT_EX:\n                s = \"Press any key to continue [: to enter more ex commands]: \";\n                break;\n        case CMSG_CONT_R:\n                s = \"Press Enter to continue: \";\n                break;\n        case CMSG_CONT_S:\n                s = \" Cont?\";\n                break;\n        case CMSG_CONT_Q:\n                s = \"Press any key to continue [q to quit]: \";\n                break;\n        default:\n                abort();\n        }\n        *lenp = strlen(s);\n        return s;\n}\n\n/*\n * msg_print --\n *      Return a printable version of a string, in allocated memory.\n *\n * PUBLIC: char *msg_print(SCR *, const char *, int *);\n */\n\nchar *\nmsg_print(SCR *sp, const char *s, int *needfree)\n{\n        size_t blen, nlen;\n        const char *cp;\n        char *bp, *ep, *p, *t;\n\n        *needfree = 0;\n\n        for (cp = s; *cp != '\\0'; ++cp)\n                if (!isprint(*cp))\n                        break;\n        if (*cp == '\\0')\n                return ((char *)s);     /* SAFE: needfree set to 0. */\n\n        nlen = 0;\n        if (0) {\nretry:          if (sp == NULL)\n                        free(bp);\n                else\n                        FREE_SPACE(sp, bp, blen);\n                *needfree = 0;\n        }\n        nlen += 256;\n        if (sp == NULL) {\n                if ((bp = malloc(nlen)) == NULL)\n                        goto alloc_err;\n                blen = 0;\n        } else\n                GET_SPACE_GOTO(sp, bp, blen, nlen);\n        if (0) {\nalloc_err:      return (\"\");\n        }\n        *needfree = 1;\n\n        for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\\0' && p < ep; ++cp)\n                for (t = KEY_NAME(sp, *cp); *t != '\\0' && p < ep; *p++ = *t++);\n        if (p == ep)\n                goto retry;\n        *p = '\\0';\n        return (bp);\n}\n"
  },
  {
    "path": "common/msg.h",
    "content": "/*      $OpenBSD: msg.h,v 1.3 2001/01/29 01:58:31 niklas Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)msg.h       10.10 (Berkeley) 5/10/96\n */\n\n/*\n * Common messages (continuation or confirmation).\n */\n\ntypedef enum {\n        CMSG_CONF, CMSG_CONT, CMSG_CONT_EX,\n        CMSG_CONT_R, CMSG_CONT_S, CMSG_CONT_Q } cmsg_t;\n\n/*\n * Message types.\n *\n * !!!\n * In historical vi, O_VERBOSE didn't exist, and O_TERSE made the error\n * messages shorter.  In this implementation, O_TERSE has no effect and\n * O_VERBOSE results in informational displays about common errors, for\n * naive users.\n *\n * M_NONE       Display to the user, no reformatting, no nothing.\n *\n * M_BERR       Error: M_ERR if O_VERBOSE, else bell.\n * M_ERR        Error: Display in inverse video.\n * M_INFO        Info: Display in normal video, except in silent mode.\n * M_SYSERR     Error: M_ERR, using strerror(3) message.\n * M_VINFO       Info: M_INFO if O_VERBOSE, else ignore.\n * M_XINFO       Info: Like M_INFO, but ignoring silent mode setting.\n *\n * The underlying message display routines only need to know about M_NONE,\n * M_ERR and M_INFO -- all the other message types are converted into one\n * of them by the message routines.\n */\n\ntypedef enum {\n        M_NONE = 1, M_BERR, M_ERR, M_INFO,\n        M_SYSERR, M_VINFO, M_XINFO } mtype_t;\n\n/*\n * There are major problems with error messages being generated by routines\n * preparing the screen to display error messages.  It's possible for the\n * editor to generate messages before we have a screen in which to display\n * them, or during the transition between ex (and vi startup) and a true vi.\n * There's a queue in the global area to hold them.\n *\n * If SC_EX/SC_VI is set, that's the mode that the editor is in.  If the flag\n * S_SCREEN_READY is set, that means that the screen is prepared to display\n * messages.\n */\n\ntypedef struct _msgh MSGH;      /* MSGS list head structure. */\nLIST_HEAD(_msgh, _msg);\nstruct _msg {\n        LIST_ENTRY(_msg) q;     /* Linked list of messages. */\n        mtype_t  mtype;         /* Message type: M_NONE, M_ERR, M_INFO. */\n        char    *buf;           /* Message buffer. */\n        size_t   len;           /* Message length. */\n};\n\n/* Flags to msgq_status(). */\n#define MSTAT_SHOWLAST  0x01    /* Show the line number of the last line. */\n#define MSTAT_TRUNCATE  0x02    /* Truncate the file name if it's too long. */\n"
  },
  {
    "path": "common/options.awk",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n#         The Regents of the University of California\n# Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n#         Keith Bostic\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n\nBEGIN {\n        printf(\"enum {\\n\");\n        first = 1;\n}\n/^\\/\\* O_[0-9A-Z_]*/ {\n        printf(\"\\t%s%s,\\n\", $2, first ? \" = 0\" : \"\");\n        first = 0;\n        next;\n}\nEND {\n        printf(\"\\tO_OPTIONCOUNT\\n};\\n\");\n}\n"
  },
  {
    "path": "common/options.c",
    "content": "/*      $OpenBSD: options.c,v 1.30 2024/02/12 16:42:42 job Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <paths.h>\n#include <stdio.h>\n#include <bsd_err.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"errc.h\"\n#include \"common.h\"\n#include \"../vi/vi.h\"\n#include \"pathnames.h\"\n\n#undef open\n\nstatic int               opts_abbcmp(const void *, const void *);\nstatic int               opts_cmp(const void *, const void *);\nstatic int               opts_print(SCR *, OPTLIST const *);\n\nint f_imctrl (SCR *, OPTION *, char *, unsigned long *);\n\n/*\n * O'Reilly noted options and abbreviations are from \"Learning the VI Editor\",\n * Fifth Edition, May 1992.  There's no way of knowing what systems they are\n * actually from.\n *\n * HPUX noted options and abbreviations are from \"The Ultimate Guide to the\n * VI and EX Text Editors\", 1990.\n */\n\nOPTLIST const optlist[] = {\n/* O_ALTNOTATION    Nvi2 */\n        {\"altnotation\", f_print,        OPT_0BOOL,      OPT_EARLYSET},\n/* O_ALTWERASE    4.4BSD */\n        {\"altwerase\",   f_altwerase,    OPT_0BOOL,      0},\n/* O_AUTOINDENT     4BSD */\n        {\"autoindent\",  NULL,           OPT_0BOOL,      0},\n/* O_AUTOPRINT      4BSD */\n        {\"autoprint\",   NULL,           OPT_1BOOL,      0},\n/* O_AUTOWRITE      4BSD */\n        {\"autowrite\",   NULL,           OPT_0BOOL,      0},\n/* O_BACKUP       4.4BSD */\n        {\"backup\",      NULL,           OPT_STR,        0},\n/* O_BEAUTIFY       4BSD */\n        {\"beautify\",    NULL,           OPT_0BOOL,      0},\n/* O_BSERASE      OpenVi */\n        {\"bserase\",     NULL,           OPT_0BOOL,      0},\n/* O_CDPATH       4.4BSD */\n        {\"cdpath\",      NULL,           OPT_STR,        0},\n/* O_CEDIT        4.4BSD */\n        {\"cedit\",       NULL,           OPT_STR,        0},\n/* O_COLUMNS      4.4BSD */\n        {\"columns\",     f_columns,      OPT_NUM,        OPT_NOSAVE},\n/* O_COMMENT      4.4BSD */\n        {\"comment\",     NULL,           OPT_0BOOL,      0},\n/* O_EDCOMPATIBLE   4BSD */\n        {\"edcompatible\",NULL,           OPT_0BOOL,      0},\n/* O_ESCAPETIME   4.4BSD */\n        {\"escapetime\",  NULL,           OPT_NUM,        0},\n/* O_ERRORBELLS     4BSD */\n        {\"errorbells\",  NULL,           OPT_0BOOL,      0},\n/* O_EXPANDTAB      NetBSD 5.0 */\n        {\"expandtab\",   NULL,           OPT_0BOOL,      0},\n/* O_EXRC       System V (undocumented) */\n        {\"exrc\",        NULL,           OPT_0BOOL,      0},\n/* O_EXTENDED     4.4BSD */\n        {\"extended\",    f_recompile,    OPT_0BOOL,      0},\n/* O_FILEC        4.4BSD */\n        {\"filec\",       NULL,           OPT_STR,        0},\n/* O_FLASH          HPUX */\n        {\"flash\",       NULL,           OPT_0BOOL,      0},\n/* O_HARDTABS       4BSD */\n        {\"hardtabs\",    NULL,           OPT_NUM,        0},\n/* O_ICLOWER      4.4BSD */\n        {\"iclower\",     f_recompile,    OPT_0BOOL,      0},\n/* O_IGNORECASE     4BSD */\n        {\"ignorecase\",  f_recompile,    OPT_0BOOL,      0},\n/* O_IMCTRL  nvi-m17n-nb */\n        {\"imctrl\",      f_imctrl,       OPT_0BOOL,      0},\n/* O_IMKEY   nvi-m17n-nb */\n        {\"imkey\",       NULL,           OPT_STR,        0},\n/* O_KEYTIME      4.4BSD */\n        {\"keytime\",     NULL,           OPT_NUM,        0},\n/* O_LEFTRIGHT    4.4BSD */\n        {\"leftright\",   f_reformat,     OPT_0BOOL,      0},\n/* O_LINES        4.4BSD */\n        {\"lines\",       f_lines,        OPT_NUM,        OPT_NOSAVE},\n/* O_LIST           4BSD */\n        {\"list\",        f_reformat,     OPT_0BOOL,      0},\n/* O_LOCKFILES    4.4BSD */\n        {\"lock\",        NULL,           OPT_1BOOL,      0},\n/* O_MAGIC          4BSD */\n        {\"magic\",       NULL,           OPT_1BOOL,      0},\n/* O_MATCHTIME    4.4BSD */\n        {\"matchtime\",   NULL,           OPT_NUM,        0},\n/* O_MESG           4BSD */\n        {\"mesg\",        NULL,           OPT_1BOOL,      0},\n/* O_NOPRINT      4.4BSD */\n        {\"noprint\",     f_print,        OPT_STR,        OPT_EARLYSET},\n/* O_NUMBER         4BSD */\n        {\"number\",      f_reformat,     OPT_0BOOL,      0},\n/* O_OCTAL        4.4BSD */\n        {\"octal\",       f_print,        OPT_0BOOL,      OPT_EARLYSET},\n/* O_OPEN           4BSD */\n        {\"open\",        NULL,           OPT_1BOOL,      0},\n/* O_PARAGRAPHS     4BSD */\n        {\"paragraphs\",  f_paragraph,    OPT_STR,        0},\n/* O_PATH         4.4BSD */\n        {\"path\",        NULL,           OPT_STR,        0},\n/* O_PRINT        4.4BSD */\n        {\"print\",       f_print,        OPT_STR,        OPT_EARLYSET},\n/* O_PROMPT         4BSD */\n        {\"prompt\",      NULL,           OPT_1BOOL,      0},\n/* O_READONLY       4BSD (undocumented) */\n        {\"readonly\",    f_readonly,     OPT_0BOOL,      OPT_ALWAYS},\n/* O_RECDIR       4.4BSD */\n        {\"recdir\",      NULL,           OPT_STR,        0},\n/* O_REMAP          4BSD */\n        {\"remap\",       NULL,           OPT_1BOOL,      0},\n/* O_REPORT         4BSD */\n        {\"report\",      NULL,           OPT_NUM,        0},\n/* O_RULER        4.4BSD */\n        {\"ruler\",       NULL,           OPT_0BOOL,      0},\n/* O_SCROLL         4BSD */\n        {\"scroll\",      NULL,           OPT_NUM,        0},\n/* O_SEARCHINCR   4.4BSD */\n        {\"searchincr\",  NULL,           OPT_0BOOL,      0},\n/* O_SECTIONS       4BSD */\n        {\"sections\",    f_section,      OPT_STR,        0},\n/* O_SECURE       4.4BSD */\n        {\"secure\",      f_secure,       OPT_0BOOL,      OPT_NOUNSET},\n/* O_SHELL          4BSD */\n        {\"shell\",       NULL,           OPT_STR,        0},\n/* O_SHELLMETA    4.4BSD */\n        {\"shellmeta\",   NULL,           OPT_STR,        0},\n/* O_SHIFTWIDTH     4BSD */\n        {\"shiftwidth\",  NULL,           OPT_NUM,        OPT_NOZERO},\n/* O_SHOWMATCH      4BSD */\n        {\"showmatch\",   NULL,           OPT_0BOOL,      0},\n/* O_SHOWFILENAME */\n        {\"showfilename\",NULL,           OPT_0BOOL,      0},\n/* O_SHOWMODE     4.4BSD */\n        {\"showmode\",    NULL,           OPT_0BOOL,      0},\n/* O_SIDESCROLL   4.4BSD */\n        {\"sidescroll\",  NULL,           OPT_NUM,        OPT_NOZERO},\n/* O_TABSTOP        4BSD */\n        {\"tabstop\",     f_reformat,     OPT_NUM,        OPT_NOZERO},\n/* O_TAGLENGTH      4BSD */\n        {\"taglength\",   NULL,           OPT_NUM,        0},\n/* O_TAGS           4BSD */\n        {\"tags\",        NULL,           OPT_STR,        0},\n/* O_TERM           4BSD\n *      !!!\n *      By default, the historic vi always displayed information about two\n *      options, redraw and term.  Term seems sufficient.\n */\n        {\"term\",        NULL,           OPT_STR,        OPT_ADISP|OPT_NOSAVE},\n/* O_TERSE          4BSD */\n        {\"terse\",       NULL,           OPT_0BOOL,      0},\n/* O_TILDEOP      4.4BSD */\n        {\"tildeop\",     NULL,           OPT_0BOOL,      0},\n/* O_TIMEOUT        4BSD (undocumented) */\n        {\"timeout\",     NULL,           OPT_1BOOL,      0},\n/* O_TTYWERASE    4.4BSD */\n        {\"ttywerase\",   f_ttywerase,    OPT_0BOOL,      0},\n/* O_VERBOSE      4.4BSD */\n        {\"verbose\",     NULL,           OPT_0BOOL,      0},\n/* O_VISIBLETAB   OpenVi */\n        {\"visibletab\",  f_reformat,     OPT_0BOOL,      0},\n/* O_W1200          4BSD */\n        {\"w1200\",       f_w1200,        OPT_NUM,        OPT_NDISP|OPT_NOSAVE},\n/* O_W300           4BSD */\n        {\"w300\",        f_w300,         OPT_NUM,        OPT_NDISP|OPT_NOSAVE},\n/* O_W9600          4BSD */\n        {\"w9600\",       f_w9600,        OPT_NUM,        OPT_NDISP|OPT_NOSAVE},\n/* O_WARN           4BSD */\n        {\"warn\",        NULL,           OPT_1BOOL,      0},\n/* O_WINDOW         4BSD */\n        {\"window\",      f_window,       OPT_NUM,        OPT_NOZERO},\n/* O_WINDOWNAME     4BSD */\n        {\"windowname\",  NULL,           OPT_0BOOL,      0},\n/* O_WRAPLEN      4.4BSD */\n        {\"wraplen\",     NULL,           OPT_NUM,        0},\n/* O_WRAPMARGIN     4BSD */\n        {\"wrapmargin\",  NULL,           OPT_NUM,        0},\n/* O_WRAPSCAN       4BSD */\n        {\"wrapscan\",    NULL,           OPT_1BOOL,      0},\n/* O_WRITEANY       4BSD */\n        {\"writeany\",    NULL,           OPT_0BOOL,      0},\n        {NULL,          NULL,           255,            0},\n};\n\ntypedef struct abbrev {\n        char *name;\n        int offset;\n} OABBREV;\n\nstatic OABBREV const abbrev[] = {\n        {\"ai\",          O_AUTOINDENT},          /*     4BSD */\n        {\"an\",          O_ALTNOTATION},         /*     Nvi2 */\n        {\"ap\",          O_AUTOPRINT},           /*     4BSD */\n        {\"aw\",          O_AUTOWRITE},           /*     4BSD */\n        {\"bf\",          O_BEAUTIFY},            /*     4BSD */\n        {\"bse\",         O_BSERASE},             /*   OpenVi */\n        {\"co\",          O_COLUMNS},             /*   4.4BSD */\n        {\"eb\",          O_ERRORBELLS},          /*     4BSD */\n        {\"ed\",          O_EDCOMPATIBLE},        /*     4BSD */\n        {\"et\",          O_EXPANDTAB},           /* NetBSD 5.0 */\n        {\"ex\",          O_EXRC},                /* System V (undocumented) */\n        {\"ht\",          O_HARDTABS},            /*     4BSD */\n        {\"ic\",          O_IGNORECASE},          /*     4BSD */\n        {\"li\",          O_LINES},               /*   4.4BSD */\n        {\"nu\",          O_NUMBER},              /*     4BSD */\n        {\"para\",        O_PARAGRAPHS},          /*     4BSD */\n        {\"ro\",          O_READONLY},            /*     4BSD (undocumented) */\n        {\"scr\",         O_SCROLL},              /*     4BSD (undocumented) */\n        {\"sect\",        O_SECTIONS},            /* O'Reilly */\n        {\"sh\",          O_SHELL},               /*     4BSD */\n        {\"sm\",          O_SHOWMATCH},           /*     4BSD */\n        {\"smd\",         O_SHOWMODE},            /*     4BSD */\n        {\"sw\",          O_SHIFTWIDTH},          /*     4BSD */\n        {\"tag\",         O_TAGS},                /*     4BSD (undocumented) */\n        {\"tl\",          O_TAGLENGTH},           /*     4BSD */\n        {\"to\",          O_TIMEOUT},             /*     4BSD (undocumented) */\n        {\"ts\",          O_TABSTOP},             /*     4BSD */\n        {\"tty\",         O_TERM},                /*     4BSD (undocumented) */\n        {\"ttytype\",     O_TERM},                /*     4BSD (undocumented) */\n        {\"vt\",          O_VISIBLETAB},          /*   OpenVi */\n        {\"w\",           O_WINDOW},              /* O'Reilly */\n        {\"wa\",          O_WRITEANY},            /*     4BSD */\n        {\"wi\",          O_WINDOW},              /*     4BSD (undocumented) */\n        {\"wl\",          O_WRAPLEN},             /*   4.4BSD */\n        {\"wm\",          O_WRAPMARGIN},          /*     4BSD */\n        {\"ws\",          O_WRAPSCAN},            /*     4BSD */\n        {NULL,          255},\n};\n\n/*\n * opts_init --\n *      Initialize some of the options.\n *\n * PUBLIC: int opts_init(SCR *, int *);\n */\n\nint\nopts_init(SCR *sp, int *oargs)\n{\n        ARGS *argv[2], a, b;\n        OPTLIST const *op;\n        unsigned long v;\n        int optindx;\n        char *s, b1[1024];\n\n        a.bp = b1;\n        b.bp = NULL;\n        a.len = b.len = 0;\n        argv[0] = &a;\n        argv[1] = &b;\n\n        /* Set numeric and string default values. */\n#define OI_b1(indx) {                                                   \\\n        a.len = strlen(b1);                                             \\\n        if (opts_set(sp, argv, NULL)) {                                 \\\n                optindx = indx;                                         \\\n                goto err;                                               \\\n        }                                                               \\\n}\n#define OI(indx, str) {                                                 \\\n        (void)openbsd_strlcpy(b1, (str), sizeof(b1));                   \\\n        OI_b1(indx);                                                    \\\n}\n\n        /*\n         * Indirect global options to global space.  Specifically, set up\n         * terminal, lines, columns first, they're used by other options.\n         * Note, don't set the flags until we've set up the indirection.\n         */\n\n        if (o_set(sp, O_TERM, 0, NULL, GO_TERM)) {\n                optindx = O_TERM;\n                goto err;\n        }\n        F_SET(&sp->opts[O_TERM], OPT_GLOBAL);\n        if (o_set(sp, O_LINES, 0, NULL, GO_LINES)) {\n                optindx = O_LINES;\n                goto err;\n        }\n        F_SET(&sp->opts[O_LINES], OPT_GLOBAL);\n        if (o_set(sp, O_COLUMNS, 0, NULL, GO_COLUMNS)) {\n                optindx = O_COLUMNS;\n                goto err;\n        }\n        F_SET(&sp->opts[O_COLUMNS], OPT_GLOBAL);\n        if (o_set(sp, O_SECURE, 0, NULL, GO_SECURE)) {\n                optindx = O_SECURE;\n                goto err;\n        }\n        F_SET(&sp->opts[O_SECURE], OPT_GLOBAL);\n\n        /* Initialize string values. */\n        (void)snprintf(b1, sizeof(b1),\n            \"cdpath=%s\", (s = getenv(\"CDPATH\")) == NULL ? \":\" : s);\n        OI_b1(O_CDPATH);\n        OI(O_ESCAPETIME, \"escapetime=2\");\n        OI(O_FILEC, \"filec=\\t\");\n        OI(O_KEYTIME, \"keytime=6\");\n        OI(O_MATCHTIME, \"matchtime=7\");\n        OI(O_REPORT, \"report=5\");\n        OI(O_PARAGRAPHS, \"paragraphs=IPLPPPQPP LIpplpipbpBlBdPpLpIt\");\n        OI(O_PATH, \"path=\");\n        (void)snprintf(b1, sizeof(b1), \"recdir=%s\", _PATH_PRESERVE);\n        OI_b1(O_RECDIR);\n        OI(O_SECTIONS, \"sections=NHSHH HUnhshShSs\");\n        (void)snprintf(b1, sizeof(b1),\n            \"shell=%s\", (s = getenv(\"SHELL\")) == NULL ? _PATH_BSHELL : s);\n        OI_b1(O_SHELL);\n        OI(O_SHELLMETA, \"shellmeta=~{[*?$`'\\\"\\\\\");\n        OI(O_SHIFTWIDTH, \"shiftwidth=8\");\n        OI(O_SIDESCROLL, \"sidescroll=16\");\n        OI(O_TABSTOP, \"tabstop=8\");\n        (void)snprintf(b1, sizeof(b1), \"tags=%s\", _PATH_TAGS);\n        OI_b1(O_TAGS);\n        OI(O_IMKEY, \"imkey=/?aioAIO\");\n\n        /*\n         * XXX\n         * Initialize O_SCROLL here, after term; initializing term should\n         * have created a LINES/COLUMNS value.\n         */\n\n        if ((v = (O_VAL(sp, O_LINES) - 1) / 2) == 0)\n                v = 1;\n        (void)snprintf(b1, sizeof(b1), \"scroll=%ld\", v);\n        OI_b1(O_SCROLL);\n\n        /*\n         * The default window option values are:\n         *              8 if baud rate <=  600\n         *             16 if baud rate <= 1200\n         *      LINES - 1 if baud rate  > 1200\n         *\n         * Note, the windows option code will correct any too-large value\n         * or when the O_LINES value is 1.\n         */\n\n        if (sp->gp->scr_baud(sp, &v))\n                return (1);\n        if (v <= 600)\n                v = 8;\n        else if (v <= 1200)\n                v = 16;\n        else\n                v = O_VAL(sp, O_LINES) - 1;\n        (void)snprintf(b1, sizeof(b1), \"window=%lu\", v);\n        OI_b1(O_WINDOW);\n\n        /*\n         * Set boolean default values, and copy all settings into the default\n         * information.  OS_NOFREE is set, we're copying, not replacing.\n         */\n\n        for (op = optlist, optindx = 0; op->name != NULL; ++op, ++optindx)\n                switch (op->type) {\n                case OPT_0BOOL:\n                        break;\n                case OPT_1BOOL:\n                        O_SET(sp, optindx);\n                        O_D_SET(sp, optindx);\n                        break;\n                case OPT_NUM:\n                        o_set(sp, optindx, OS_DEF, NULL, O_VAL(sp, optindx));\n                        break;\n                case OPT_STR:\n                        if (O_STR(sp, optindx) != NULL && o_set(sp, optindx,\n                            OS_DEF | OS_NOFREE | OS_STRDUP, O_STR(sp, optindx), 0))\n                                goto err;\n                        break;\n                default:\n                        abort();\n                }\n\n        /*\n         * !!!\n         * Some options can be initialized by the command name or the\n         * command-line arguments.  They don't set the default values,\n         * it's historic practice.\n         */\n\n        for (; *oargs != -1; ++oargs)\n                OI(*oargs, optlist[*oargs].name);\n        return (0);\n#undef OI\n#undef OI_b1\n\nerr:    msgq(sp, M_ERR,\n            \"Unable to set default %s option\", optlist[optindx].name);\n        return (1);\n}\n\n/*\n * opts_set --\n *      Change the values of one or more options.\n *\n * PUBLIC: int opts_set(SCR *, ARGS *[], char *);\n */\n\nint\nopts_set(SCR *sp, ARGS *argv[], char *usage)\n{\n        enum optdisp disp;\n        enum nresult nret;\n        OPTLIST const *op;\n        OPTION *spo;\n        unsigned long value, turnoff;\n        int ch, equals, nf, nf2, offset, qmark, rval;\n        char *endp, *name, *p, *sep, *t;\n\n        disp = NO_DISPLAY;\n        for (rval = 0; argv[0]->len != 0; ++argv) {\n\n                /*\n                 * The historic vi dumped the options for each occurrence of\n                 * \"all\" in the set list.  Puhleeze.\n                 */\n\n                if (!strcmp(argv[0]->bp, \"all\")) {\n                        disp = ALL_DISPLAY;\n                        continue;\n                }\n\n                /* Find equals sign or question mark. */\n                for (sep = NULL, equals = qmark = 0,\n                    p = name = argv[0]->bp; (ch = *p) != '\\0'; ++p)\n                        if (ch == '=' || ch == '?') {\n                                if (p == name) {\n                                        if (usage != NULL)\n                                                msgq(sp, M_ERR,\n                                                    \"Usage: %s\", usage);\n                                        return (1);\n                                }\n                                sep = p;\n                                if (ch == '=')\n                                        equals = 1;\n                                else\n                                        qmark = 1;\n                                break;\n                        }\n\n                turnoff = 0;\n                op = NULL;\n                if (sep != NULL)\n                        *sep++ = '\\0';\n\n                /* Search for the name, then name without any leading \"no\". */\n                if ((op = opts_search(name)) == NULL &&\n                    name[0] == 'n' && name[1] == 'o') {\n                        turnoff = 1;\n                        name += 2;\n                        op = opts_search(name);\n                }\n                if (op == NULL) {\n                        opts_nomatch(sp, name);\n                        rval = 1;\n                        continue;\n                }\n\n                /* Find current option values. */\n                offset = op - optlist;\n                spo = sp->opts + offset;\n\n                /*\n                 * !!!\n                 * Historically, the question mark could be a separate\n                 * argument.\n                 */\n\n                if (!equals && !qmark &&\n                    argv[1]->len == 1 && argv[1]->bp[0] == '?') {\n                        ++argv;\n                        qmark = 1;\n                }\n\n                /* Set name, value. */\n                switch (op->type) {\n                case OPT_0BOOL:\n                case OPT_1BOOL:\n                        /* Some options may not be reset. */\n                        if (F_ISSET(op, OPT_NOUNSET) && turnoff) {\n                                msgq_str(sp, M_ERR, name,\n                            \"set: the %s option may not be turned off\");\n                                rval = 1;\n                                break;\n                        }\n\n                        if (equals) {\n                                msgq_str(sp, M_ERR, name,\n                            \"set: [no]%s option doesn't take a value\");\n                                rval = 1;\n                                break;\n                        }\n                        if (qmark) {\n                                if (!disp)\n                                        disp = SELECT_DISPLAY;\n                                F_SET(spo, OPT_SELECTED);\n                                break;\n                        }\n\n                        /*\n                         * Do nothing if the value is unchanged, the underlying\n                         * functions can be expensive.\n                         */\n\n                        if (!F_ISSET(op, OPT_ALWAYS)) {\n                                if (turnoff) {\n                                        if (!O_ISSET(sp, offset))\n                                                break;\n                                } else {\n                                        if (O_ISSET(sp, offset))\n                                                break;\n                                }\n                        }\n\n                        if (F_ISSET(op, OPT_EARLYSET)) {\n                            /* Set the value. */\n                            if (turnoff)\n                                O_CLR(sp, offset);\n                            else\n                                O_SET(sp, offset);\n                        }\n\n                        /* Report to subsystems. */\n                        if ((op->func != NULL &&\n                            op->func(sp, spo, NULL, &turnoff)) ||\n                            ex_optchange(sp, offset, NULL, &turnoff) ||\n                            v_optchange(sp, offset, NULL, &turnoff) ||\n                            sp->gp->scr_optchange(sp, offset, NULL, &turnoff)) {\n                                rval = 1;\n                                break;\n                        }\n\n                        if (!F_ISSET(op, OPT_EARLYSET)) {\n                            /* Set the value. */\n                            if (turnoff)\n                                O_CLR(sp, offset);\n                            else\n                                O_SET(sp, offset);\n                        }\n                        break;\n                case OPT_NUM:\n                        if (turnoff) {\n                                msgq_str(sp, M_ERR, name,\n                                    \"set: %s option isn't a boolean\");\n                                rval = 1;\n                                break;\n                        }\n                        if (qmark || !equals) {\n                                if (!disp)\n                                        disp = SELECT_DISPLAY;\n                                F_SET(spo, OPT_SELECTED);\n                                break;\n                        }\n\n                        if (!isdigit(sep[0]))\n                                goto badnum;\n                        if ((nret =\n                            nget_uslong(&value, sep, &endp, 10)) != NUM_OK) {\n                                p = msg_print(sp, name, &nf);\n                                t = msg_print(sp, sep, &nf2);\n                                switch (nret) {\n                                case NUM_ERR:\n                                        msgq(sp, M_SYSERR,\n                                            \"set: %s option: %s\", p, t);\n                                        break;\n                                case NUM_OVER:\n                                        msgq(sp, M_ERR,\n                            \"set: %s option: %s: value overflow\", p, t);\n                                        break;\n                                case NUM_OK:\n                                case NUM_UNDER:\n                                        abort();\n                                }\n                                if (nf)\n                                        FREE_SPACE(sp, p, 0);\n                                if (nf2)\n                                        FREE_SPACE(sp, t, 0);\n                                rval = 1;\n                                break;\n                        }\n                        if (*endp && !isblank(*endp)) {\nbadnum:                         p = msg_print(sp, name, &nf);\n                                t = msg_print(sp, sep, &nf2);\n                                msgq(sp, M_ERR,\n                    \"set: %s option: %s is an illegal number\", p, t);\n                                if (nf)\n                                        FREE_SPACE(sp, p, 0);\n                                if (nf2)\n                                        FREE_SPACE(sp, t, 0);\n                                rval = 1;\n                                break;\n                        }\n\n                        /* Some options may never be set to zero. */\n                        if (F_ISSET(op, OPT_NOZERO) && value == 0) {\n                                msgq_str(sp, M_ERR, name,\n                            \"set: the %s option may never be set to 0\");\n                                rval = 1;\n                                break;\n                        }\n\n                        /*\n                         * Do nothing if the value is unchanged, the underlying\n                         * functions can be expensive.\n                         */\n\n                        if (!F_ISSET(op, OPT_ALWAYS) &&\n                            O_VAL(sp, offset) == value)\n                                break;\n\n                        if (F_ISSET(op, OPT_EARLYSET)) {\n                            /* Set the value. */\n                            if (o_set(sp, offset, 0, NULL, value)) {\n                                rval = 1;\n                                break;\n                            }\n                        }\n\n                        /* Report to subsystems. */\n                        if ((op->func != NULL &&\n                            op->func(sp, spo, sep, &value)) ||\n                            ex_optchange(sp, offset, sep, &value) ||\n                            v_optchange(sp, offset, sep, &value) ||\n                            sp->gp->scr_optchange(sp, offset, sep, &value)) {\n                                rval = 1;\n                                break;\n                        }\n\n                        if (!F_ISSET(op, OPT_EARLYSET)) {\n                            /* Set the value. */\n                            if (o_set(sp, offset, 0, NULL, value))\n                                rval = 1;\n                        }\n                        break;\n                case OPT_STR:\n                        if (turnoff) {\n                                msgq_str(sp, M_ERR, name,\n                                    \"set: %s option isn't a boolean\");\n                                rval = 1;\n                                break;\n                        }\n                        if (qmark || !equals) {\n                                if (!disp)\n                                        disp = SELECT_DISPLAY;\n                                F_SET(spo, OPT_SELECTED);\n                                break;\n                        }\n\n                        /*\n                         * Do nothing if the value is unchanged, the underlying\n                         * functions can be expensive.\n                         */\n\n                        if (!F_ISSET(op, OPT_ALWAYS) &&\n                            O_STR(sp, offset) != NULL &&\n                            !strcmp(O_STR(sp, offset), sep))\n                                break;\n\n                        if (F_ISSET(op, OPT_EARLYSET)) {\n                            /* Set the value. */\n                            if (o_set(sp, offset, OS_STRDUP, sep, 0)) {\n                                rval = 1;\n                                break;\n                            }\n                        }\n\n                        /* Report to subsystems. */\n                        if ((op->func != NULL &&\n                            op->func(sp, spo, sep, NULL)) ||\n                            ex_optchange(sp, offset, sep, NULL) ||\n                            v_optchange(sp, offset, sep, NULL) ||\n                            sp->gp->scr_optchange(sp, offset, sep, NULL)) {\n                                rval = 1;\n                                break;\n                        }\n\n                        if (!F_ISSET(op, OPT_EARLYSET)) {\n                            /* Set the value. */\n                            if (o_set(sp, offset, OS_STRDUP, sep, 0))\n                                rval = 1;\n                        }\n                        break;\n                default:\n                        abort();\n                }\n        }\n        if (disp != NO_DISPLAY)\n                opts_dump(sp, disp);\n        return (rval);\n}\n\n/*\n * o_set --\n *      Set an option's value.\n *\n * PUBLIC: int o_set(SCR *, int, unsigned int, char *, unsigned long);\n */\n\nint\no_set(SCR *sp, int opt, unsigned int flags, char *str, unsigned long val)\n{\n        OPTION *op;\n\n        /* Set a pointer to the options area. */\n        op = F_ISSET(&sp->opts[opt], OPT_GLOBAL) ?\n            &sp->gp->opts[sp->opts[opt].o_cur.val] : &sp->opts[opt];\n\n        /* Copy the string, if requested. */\n        if (LF_ISSET(OS_STRDUP) && (str = strdup(str)) == NULL) {\n                msgq(sp, M_SYSERR, NULL);\n                return (1);\n        }\n\n        /* Free the previous string, if requested, and set the value. */\n        if (LF_ISSET(OS_DEF))\n                if (LF_ISSET(OS_STR | OS_STRDUP)) {\n                        if (!LF_ISSET(OS_NOFREE) && op->o_def.str != NULL)\n                                free(op->o_def.str);\n                        op->o_def.str = str;\n                } else\n                        op->o_def.val = val;\n        else\n                if (LF_ISSET(OS_STR | OS_STRDUP)) {\n                        if (!LF_ISSET(OS_NOFREE) && op->o_cur.str != NULL)\n                                free(op->o_cur.str);\n                        op->o_cur.str = str;\n                } else\n                        op->o_cur.val = val;\n        return (0);\n}\n\n/*\n * opts_empty --\n *      Return 1 if the string option is invalid, 0 if it's OK.\n *\n * PUBLIC: int opts_empty(SCR *, int, int);\n */\n\nint\nopts_empty(SCR *sp, int off, int silent)\n{\n        char *p;\n\n        if ((p = O_STR(sp, off)) == NULL || p[0] == '\\0') {\n                if (!silent)\n                        msgq_str(sp, M_ERR, optlist[off].name,\n                            \"No %s edit option specified\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * opts_dump --\n *      List the current values of selected options.\n *\n * PUBLIC: void opts_dump(SCR *, enum optdisp);\n */\n\nvoid\nopts_dump(SCR *sp, enum optdisp type)\n{\n        OPTLIST const *op;\n        int base, b_num, cnt, col, colwidth, curlen, s_num;\n        int numcols, numrows, row;\n        int b_op[O_OPTIONCOUNT], s_op[O_OPTIONCOUNT];\n        char nbuf[20];\n\n        /*\n         * Options are output in two groups -- those that fit in a column and\n         * those that don't.  Output is done on 6 character \"tab\" boundaries\n         * for no particular reason.  (Since we don't output tab characters,\n         * we can ignore the terminal's tab settings.)  Ignore the user's tab\n         * setting because we have no idea how reasonable it is.\n         *\n         * Find a column width we can live with, testing from 10 columns to 1.\n         */\n\n        for (numcols = 10; numcols > 1; --numcols) {\n                colwidth = sp->cols / numcols & ~(STANDARD_TAB - 1);\n                if (colwidth >= 10) {\n                        colwidth =\n                            (colwidth + STANDARD_TAB) & ~(STANDARD_TAB - 1);\n                        numcols = sp->cols / colwidth;\n                        break;\n                }\n                colwidth = 0;\n        }\n\n        /*\n         * Get the set of options to list, entering them into\n         * the column list or the overflow list.\n         */\n\n        for (b_num = s_num = 0, op = optlist; op->name != NULL; ++op) {\n                cnt = op - optlist;\n\n                /* If OPT_NDISP set, it's never displayed. */\n                if (F_ISSET(op, OPT_NDISP))\n                        continue;\n\n                switch (type) {\n                case ALL_DISPLAY:               /* Display all. */\n                        break;\n                case CHANGED_DISPLAY:           /* Display changed. */\n                        /* If OPT_ADISP set, it's always \"changed\". */\n                        if (F_ISSET(op, OPT_ADISP))\n                                break;\n                        switch (op->type) {\n                        case OPT_0BOOL:\n                        case OPT_1BOOL:\n                        case OPT_NUM:\n                                if (O_VAL(sp, cnt) == O_D_VAL(sp, cnt))\n                                        continue;\n                                break;\n                        case OPT_STR:\n                                if (O_STR(sp, cnt) == O_D_STR(sp, cnt) ||\n                                    (O_D_STR(sp, cnt) != NULL &&\n                                    !strcmp(O_STR(sp, cnt), O_D_STR(sp, cnt))))\n                                        continue;\n                                break;\n                        }\n                        break;\n                case SELECT_DISPLAY:            /* Display selected. */\n                        if (!F_ISSET(&sp->opts[cnt], OPT_SELECTED))\n                                continue;\n                        break;\n                default:\n                case NO_DISPLAY:\n                        abort();\n                }\n                F_CLR(&sp->opts[cnt], OPT_SELECTED);\n\n                curlen = strlen(op->name);\n                switch (op->type) {\n                case OPT_0BOOL:\n                case OPT_1BOOL:\n                        if (!O_ISSET(sp, cnt))\n                                curlen += 2;\n                        break;\n                case OPT_NUM:\n                        (void)snprintf(nbuf,\n                            sizeof(nbuf), \"%ld\", O_VAL(sp, cnt));\n                        curlen += strlen(nbuf);\n                        break;\n                case OPT_STR:\n                        if (O_STR(sp, cnt) != NULL)\n                                curlen += strlen(O_STR(sp, cnt));\n                        curlen += 3;\n                        break;\n                }\n                /* Offset by 2 so there's a gap. */\n                if (curlen <= colwidth - 2)\n                        s_op[s_num++] = cnt;\n                else\n                        b_op[b_num++] = cnt;\n        }\n\n        if (s_num > 0) {\n                /* Figure out the number of rows. */\n                if ((s_num > 0) && (numcols > 0) && (s_num > numcols)) {\n                        numrows = s_num / numcols;\n                        if (s_num % numcols)\n                                ++numrows;\n                } else\n                        numrows = 1;\n\n                /* Display the options in sorted order. */\n                for (row = 0; row < numrows;) {\n                        for (base = row, col = 0; col < numcols; ++col) {\n                                cnt = opts_print(sp, &optlist[s_op[base]]);\n                                if ((base += numrows) >= s_num)\n                                        break;\n                                (void)ex_printf(sp, \"%*s\",\n                                    (int)(colwidth - cnt), \"\");\n                        }\n                        if (++row < numrows || b_num)\n                                (void)ex_puts(sp, \"\\n\");\n                }\n        }\n\n        for (row = 0; row < b_num;) {\n                (void)opts_print(sp, &optlist[b_op[row]]);\n                if (++row < b_num)\n                        (void)ex_puts(sp, \"\\n\");\n        }\n        (void)ex_puts(sp, \"\\n\");\n}\n\n/*\n * opts_print --\n *      Print out an option.\n */\n\nstatic int\nopts_print(SCR *sp, OPTLIST const *op)\n{\n        int curlen, offset;\n\n        curlen = 0;\n        offset = op - optlist;\n        switch (op->type) {\n        case OPT_0BOOL:\n        case OPT_1BOOL:\n                curlen += ex_printf(sp,\n                    \"%s%s\", O_ISSET(sp, offset) ? \"\" : \"no\", op->name);\n                break;\n        case OPT_NUM:\n                curlen += ex_printf(sp, \"%s=%ld\", op->name, O_VAL(sp, offset));\n                break;\n        case OPT_STR:\n                curlen += ex_printf(sp, \"%s=\\\"%s\\\"\", op->name,\n                    O_STR(sp, offset) == NULL ? \"\" : O_STR(sp, offset));\n                break;\n        }\n        return (curlen);\n}\n\n/*\n * opts_save --\n *      Write the current configuration to a file.\n *\n * PUBLIC: int opts_save(SCR *, FILE *);\n */\n\nint\nopts_save(SCR *sp, FILE *fp)\n{\n        OPTLIST const *op;\n        int ch, cnt;\n        char *p;\n\n        for (op = optlist; op->name != NULL; ++op) {\n                if (F_ISSET(op, OPT_NOSAVE))\n                        continue;\n                cnt = op - optlist;\n                switch (op->type) {\n                case OPT_0BOOL:\n                case OPT_1BOOL:\n                        if (O_ISSET(sp, cnt))\n                                (void)fprintf(fp, \"set %s\\n\", op->name);\n                        else\n                                (void)fprintf(fp, \"set no%s\\n\", op->name);\n                        break;\n                case OPT_NUM:\n                        (void)fprintf(fp,\n                            \"set %s=%-3ld\\n\", op->name, O_VAL(sp, cnt));\n                        break;\n                case OPT_STR:\n                        if (O_STR(sp, cnt) == NULL)\n                                break;\n                        (void)fprintf(fp, \"set \");\n                        for (p = op->name; (ch = *p) != '\\0'; ++p) {\n                                if (isblank(ch) || ch == '\\\\')\n                                        (void)putc('\\\\', fp);\n                                (void)putc(ch, fp);\n                        }\n                        (void)putc('=', fp);\n                        for (p = O_STR(sp, cnt); (ch = *p) != '\\0'; ++p) {\n                                if (isblank(ch) || ch == '\\\\')\n                                        (void)putc('\\\\', fp);\n                                (void)putc(ch, fp);\n                        }\n                        (void)putc('\\n', fp);\n                        break;\n                }\n                if (ferror(fp)) {\n                        msgq(sp, M_SYSERR, NULL);\n                        return (1);\n                }\n        }\n        return (0);\n}\n\n/*\n * opts_search --\n *      Search for an option.\n *\n * PUBLIC: OPTLIST const *opts_search(char *);\n */\n\nOPTLIST const *\nopts_search(char *name)\n{\n        OPTLIST const *op, *found;\n        OABBREV atmp, *ap;\n        OPTLIST otmp;\n        size_t len;\n\n        /* Check list of abbreviations. */\n        atmp.name = name;\n        if ((ap = bsearch(&atmp, abbrev, sizeof(abbrev) / sizeof(OABBREV) - 1,\n            sizeof(OABBREV), opts_abbcmp)) != NULL)\n                return (optlist + ap->offset);\n\n        /* Check list of options. */\n        otmp.name = name;\n        if ((op = bsearch(&otmp, optlist, sizeof(optlist) / sizeof(OPTLIST) - 1,\n            sizeof(OPTLIST), opts_cmp)) != NULL)\n                return (op);\n\n        /*\n         * Check to see if the name is the prefix of one (and only one)\n         * option.  If so, return the option.\n         */\n\n        len = strlen(name);\n        for (found = NULL, op = optlist; op->name != NULL; ++op) {\n                if (op->name[0] < name[0])\n                        continue;\n                if (op->name[0] > name[0])\n                        break;\n                if (!memcmp(op->name, name, len)) {\n                        if (found != NULL)\n                                return (NULL);\n                        found = op;\n                }\n        }\n        return (found);\n}\n\n/*\n * opts_nomatch --\n *      Standard nomatch error message for options.\n *\n * PUBLIC: void opts_nomatch(SCR *, char *);\n */\n\nvoid\nopts_nomatch(SCR *sp, char *name)\n{\n        msgq_str(sp, M_ERR, name,\n            \"set: no %s option: 'set all' gives all option values\");\n}\n\nstatic int\nopts_abbcmp(const void *a, const void *b)\n{\n        return(strcmp(((OABBREV *)a)->name, ((OABBREV *)b)->name));\n}\n\nstatic int\nopts_cmp(const void *a, const void *b)\n{\n        return(strcmp(((OPTLIST *)a)->name, ((OPTLIST *)b)->name));\n}\n\n/*\n * opts_copy --\n *      Copy a screen's OPTION array.\n *\n * PUBLIC: int opts_copy(SCR *, SCR *);\n */\n\nint\nopts_copy(SCR *orig, SCR *sp)\n{\n        int cnt, rval;\n\n        /* Copy most everything without change. */\n        memcpy(sp->opts, orig->opts, sizeof(orig->opts));\n\n        /* Copy the string edit options. */\n        for (cnt = rval = 0; cnt < O_OPTIONCOUNT; ++cnt) {\n                if (optlist[cnt].type != OPT_STR ||\n                    F_ISSET(&optlist[cnt], OPT_GLOBAL))\n                        continue;\n                /*\n                 * If never set, or already failed, NULL out the entries --\n                 * have to continue after failure, otherwise would have two\n                 * screens referencing the same memory.\n                 */\n\n                if (rval || O_STR(sp, cnt) == NULL) {\n                        o_set(sp, cnt, OS_NOFREE | OS_STR, NULL, 0);\n                        o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0);\n                        continue;\n                }\n\n                /* Copy the current string. */\n                if (o_set(sp, cnt, OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) {\n                        o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0);\n                        goto nomem;\n                }\n\n                /* Copy the default string. */\n                if (O_D_STR(sp, cnt) != NULL && o_set(sp, cnt,\n                    OS_DEF | OS_NOFREE | OS_STRDUP, O_D_STR(sp, cnt), 0)) {\nnomem:                  msgq(orig, M_SYSERR, NULL);\n                        rval = 1;\n                }\n        }\n        return (rval);\n}\n\n/*\n * opts_free --\n *      Free all option strings\n *\n * PUBLIC: void opts_free(SCR *);\n */\n\nvoid\nopts_free(SCR *sp)\n{\n        int cnt;\n\n        for (cnt = 0; cnt < O_OPTIONCOUNT; ++cnt) {\n                if (optlist[cnt].type != OPT_STR ||\n                    F_ISSET(&optlist[cnt], OPT_GLOBAL))\n                        continue;\n                free(O_STR(sp, cnt));\n                free(O_D_STR(sp, cnt));\n        }\n}\n"
  },
  {
    "path": "common/options.h",
    "content": "/*      $OpenBSD: options.h,v 1.9 2017/07/03 07:01:14 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)options.h   10.19 (Berkeley) 10/10/96\n */\n\n/*\n * Edit option information.  Historically, if you set a boolean or numeric\n * edit option value to its \"default\" value, it didn't show up in the :set\n * display, i.e. it wasn't considered \"changed\".  String edit options would\n * show up as changed, regardless.  We maintain a parallel set of values\n * which are the default values and never consider an edit option changed\n * if it was reset to the default value.\n *\n * Macros to retrieve boolean, integral and string option values, and to\n * set, clear and test boolean option values.  Some options (secure, lines,\n * columns, terminal type) are global in scope, and are therefore stored\n * in the global area.  The offset in the global options array is stored\n * in the screen's value field.  This is set up when the options are first\n * initialized.\n */\n\n#define O_V(sp, o, fld)                                                 \\\n        (F_ISSET(&(sp)->opts[(o)], OPT_GLOBAL) ?                        \\\n            (sp)->gp->opts[(sp)->opts[(o)].o_cur.val].fld :             \\\n            (sp)->opts[(o)].fld)\n\n/* Global option macros. */\n#define OG_CLR(gp, o)           ((gp)->opts[(o)].o_cur.val) = 0\n#define OG_SET(gp, o)           ((gp)->opts[(o)].o_cur.val) = 1\n#define OG_STR(gp, o)           ((gp)->opts[(o)].o_cur.str)\n#define OG_VAL(gp, o)           ((gp)->opts[(o)].o_cur.val)\n#define OG_ISSET(gp, o)         OG_VAL((gp), (o))\n\n#define OG_D_STR(gp, o)         ((gp)->opts[(o)].o_def.str)\n#define OG_D_VAL(gp, o)         ((gp)->opts[(o)].o_def.val)\n\n/*\n * Flags to o_set(); need explicit OS_STR as can be setting the value to\n * NULL.\n */\n\n#define OS_DEF          0x01            /* Set the default value.            */\n#define OS_NOFREE       0x02            /* Don't free the old string.        */\n#define OS_STR          0x04            /* Set to string argument.           */\n#define OS_STRDUP       0x08            /* Copy then set to string argument. */\n\nstruct _option {\n        union {\n                unsigned long   val;    /* Value or boolean. */\n                char           *str;    /* String.           */\n        } o_cur;\n#define O_CLR(sp, o)            o_set((sp), (o), 0, NULL, 0)\n#define O_SET(sp, o)            o_set((sp), (o), 0, NULL, 1)\n#define O_STR(sp, o)            O_V((sp), (o), o_cur.str)\n#define O_VAL(sp, o)            O_V((sp), (o), o_cur.val)\n#define O_ISSET(sp, o)          O_VAL((sp), (o))\n\n        union {\n                unsigned long   val;    /* Value or boolean. */\n                char           *str;    /* String.           */\n        } o_def;\n#define O_D_CLR(sp, o)          o_set((sp), (o), OS_DEF, NULL, 0)\n#define O_D_SET(sp, o)          o_set((sp), (o), OS_DEF, NULL, 1)\n#define O_D_STR(sp, o)          O_V((sp), (o), o_def.str)\n#define O_D_VAL(sp, o)          O_V((sp), (o), o_def.val)\n#define O_D_ISSET(sp, o)        O_D_VAL((sp), (o))\n\n#define OPT_GLOBAL      0x01            /* Option is global.     */\n#define OPT_SELECTED    0x02            /* Selected for display. */\n        u_int8_t flags;\n};\n\n/* List of option names, associated update functions and information. */\nstruct _optlist {\n        char    *name;                  /* Name.            */\n                                        /* Change function. */\n        int     (*func)(SCR *, OPTION *, char *, unsigned long *);\n                                        /* Type of object.  */\n        enum { OPT_0BOOL, OPT_1BOOL, OPT_NUM, OPT_STR } type;\n\n#define OPT_ADISP       0x001           /* Always display the option.        */\n#define OPT_ALWAYS      0x002           /* Always call the support function. */\n#define OPT_NDISP       0x004           /* Never display the option.         */\n#define OPT_NOSAVE      0x008           /* Mkexrc command doesn't save.      */\n#define OPT_NOUNSET     0x020           /* Option may not be unset.          */\n#define OPT_NOZERO      0x040           /* Option may not be set to 0.       */\n#define OPT_EARLYSET    0x080           /* Func called after value is set    */\n        u_int8_t flags;\n};\n\n/* Option argument to opts_dump(). */\nenum optdisp { NO_DISPLAY, ALL_DISPLAY, CHANGED_DISPLAY, SELECT_DISPLAY };\n\n/* Options array. */\nextern OPTLIST const optlist[];\n\n#ifdef O_PATH\n# undef O_PATH   /* bits/fcntl-linux.h may have defined O_PATH. */\n#endif /* ifdef O_PATH */\n\n#include \"options_def.h\"\n"
  },
  {
    "path": "common/options_f.c",
    "content": "/*      $OpenBSD: options_f.c,v 1.13 2019/05/21 09:24:58 martijn Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"common.h\"\n\n#undef open\n\n/*\n * PUBLIC: int f_altwerase(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_altwerase(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (!*valp)\n                O_CLR(sp, O_TTYWERASE);\n        return (0);\n}\n\n/*\n * PUBLIC: int f_columns(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_columns(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        /* Validate the number. */\n        if (*valp < MINIMUM_SCREEN_COLS) {\n                msgq(sp, M_ERR, \"Screen columns too small, less than %d\",\n                    MINIMUM_SCREEN_COLS);\n                return (1);\n        }\n\n        /*\n         * !!!\n         * It's not uncommon for allocation of huge chunks of memory to cause\n         * core dumps on various systems.  So, we prune out numbers that are\n         * \"obviously\" wrong.  Vi will not work correctly if it has the wrong\n         * number of lines/columns for the screen, but at least we don't drop\n         * core.\n         */\n\n#define MAXIMUM_SCREEN_COLS     3640\n        if (*valp > MAXIMUM_SCREEN_COLS) {\n                msgq(sp, M_ERR, \"Screen columns too large, greater than %d\",\n                    MAXIMUM_SCREEN_COLS);\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * PUBLIC: int f_lines(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_lines(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        /* Validate the number. */\n        if (*valp < MINIMUM_SCREEN_ROWS) {\n                msgq(sp, M_ERR, \"Screen lines too small, less than %d\",\n                    MINIMUM_SCREEN_ROWS);\n                return (1);\n        }\n\n        /*\n         * !!!\n         * It's not uncommon for allocation of huge chunks of memory to cause\n         * core dumps on various systems.  So, we prune out numbers that are\n         * \"obviously\" wrong.  Vi will not work correctly if it has the wrong\n         * number of lines/columns for the screen, but at least we don't drop\n         * core.\n         */\n\n#define MAXIMUM_SCREEN_ROWS     2048\n        if (*valp > MAXIMUM_SCREEN_ROWS) {\n                msgq(sp, M_ERR, \"Screen lines too large, greater than %d\",\n                    MAXIMUM_SCREEN_ROWS);\n                return (1);\n        }\n\n        /*\n         * Set the value, and the related scroll value.  If no window\n         * value set, set a new default window.\n         */\n\n        o_set(sp, O_LINES, 0, NULL, *valp);\n        if (*valp == 1) {\n                sp->defscroll = 1;\n\n                if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) ||\n                    O_VAL(sp, O_WINDOW) > *valp) {\n                        o_set(sp, O_WINDOW, 0, NULL, 1);\n                        o_set(sp, O_WINDOW, OS_DEF, NULL, 1);\n                }\n        } else {\n                sp->defscroll = (*valp - 1) / 2;\n\n                if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) ||\n                    O_VAL(sp, O_WINDOW) > *valp) {\n                        o_set(sp, O_WINDOW, 0, NULL, *valp - 1);\n                        o_set(sp, O_WINDOW, OS_DEF, NULL, *valp - 1);\n                }\n        }\n        return (0);\n}\n\n/*\n * PUBLIC: int f_paragraph(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_paragraph(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (strlen(str) & 1) {\n                msgq(sp, M_ERR,\n                    \"The paragraph option must be in two character groups\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * PUBLIC: int f_print(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_print(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        /* Reinitialize the key fast lookup table. */\n        v_key_ilookup(sp);\n\n        /* Reformat the screen. */\n        F_SET(sp, SC_SCR_REFORMAT);\n        return (0);\n}\n\n/*\n * PUBLIC: int f_readonly(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_readonly(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        /*\n         * !!!\n         * See the comment in exf.c.\n         */\n        if (*valp)\n                F_CLR(sp, SC_READONLY);\n        else\n                F_SET(sp, SC_READONLY);\n        return (0);\n}\n\n/*\n * PUBLIC: int f_recompile(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_recompile(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (F_ISSET(sp, SC_RE_SEARCH)) {\n                regfree(&sp->re_c);\n                F_CLR(sp, SC_RE_SEARCH);\n        }\n        if (F_ISSET(sp, SC_RE_SUBST)) {\n                regfree(&sp->subre_c);\n                F_CLR(sp, SC_RE_SUBST);\n        }\n        return (0);\n}\n\n/*\n * PUBLIC: int f_reformat(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_reformat(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        F_SET(sp, SC_SCR_REFORMAT);\n        return (0);\n}\n\n/*\n * PUBLIC: int f_section(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_section(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (strlen(str) & 1) {\n                msgq(sp, M_ERR,\n                    \"The section option must be in two character groups\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * PUBLIC: int f_secure(SCR *, OPTION *, char *, unsigned long *)\n */\n\nint\nf_secure(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (openbsd_pledge(\n            \"stdio rpath wpath cpath fattr flock getpw tty\", NULL) == -1) {\n                msgq(sp, M_ERR, \"pledge failed\");\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * PUBLIC: int f_ttywerase(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_ttywerase(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (!*valp)\n                O_CLR(sp, O_ALTWERASE);\n        return (0);\n}\n\n/*\n * PUBLIC: int f_w300(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_w300(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        unsigned long v;\n\n        /* Historical behavior for w300 was < 1200. */\n        if (sp->gp->scr_baud(sp, &v))\n                return (1);\n        if (v >= 1200)\n                return (0);\n\n        return (f_window(sp, op, str, valp));\n}\n\n/*\n * PUBLIC: int f_w1200(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_w1200(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        unsigned long v;\n\n        /* Historical behavior for w1200 was == 1200. */\n        if (sp->gp->scr_baud(sp, &v))\n                return (1);\n        if (v < 1200 || v > 4800)\n                return (0);\n\n        return (f_window(sp, op, str, valp));\n}\n\n/*\n * PUBLIC: int f_w9600(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_w9600(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        unsigned long v;\n\n        /* Historical behavior for w9600 was > 1200. */\n        if (sp->gp->scr_baud(sp, &v))\n                return (1);\n        if (v <= 4800)\n                return (0);\n\n        return (f_window(sp, op, str, valp));\n}\n\n/*\n * PUBLIC: int f_window(SCR *, OPTION *, char *, unsigned long *);\n */\n\nint\nf_window(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n        if (*valp >= O_VAL(sp, O_LINES) - 1 &&\n            (*valp = O_VAL(sp, O_LINES) - 1) == 0)\n                *valp = 1;\n        return (0);\n}\n\n/*\n * PUBLIC: int f_imctrl __P((SCR *, OPTION *, char *, unsigned long *));\n */\n\nint\nf_imctrl(SCR *sp, OPTION *op, char *str, unsigned long *valp)\n{\n\n        if (*valp)\n                sp->gp->scr_imctrl(sp, IMCTRL_INIT);\n        return (0);\n}\n"
  },
  {
    "path": "common/put.c",
    "content": "/*      $OpenBSD: put.c,v 1.17 2025/08/23 21:02:10 millert Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"common.h\"\n\n/*\n * put --\n *      Put text buffer contents into the file.\n *\n * PUBLIC: int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int, int)\n */\n\nint\nput(SCR *sp, CB *cbp, CHAR_T *namep, MARK *cp, MARK *rp, int append, int cnt)\n{\n        CHAR_T name;\n        TEXT *ltp, *tp;\n        recno_t lno;\n        size_t blen, clen, len;\n        int rval, i, isempty;;\n        char *bp, *p, *t;\n\n        if (cbp == NULL) {\n                if (namep == NULL) {\n                        cbp = sp->gp->dcbp;\n                        if (cbp == NULL) {\n                                msgq(sp, M_ERR,\n                                    \"The default buffer is empty\");\n                                return (1);\n                        }\n                } else {\n                        name = *namep;\n                        CBNAME(sp, cbp, name);\n                        if (cbp == NULL) {\n                                msgq(sp, M_ERR, \"Buffer %s is empty\",\n                                    KEY_NAME(sp, name));\n                                return (1);\n                        }\n                }\n        }\n        tp = TAILQ_FIRST(&cbp->textq);\n\n        /*\n         * It's possible to do a put into an empty file, meaning that the cut\n         * buffer simply becomes the file.  It's a special case so that we can\n         * ignore it in general.\n         *\n         * !!!\n         * Historically, pasting into a file with no lines in vi would preserve\n         * the single blank line.  This is surely a result of the fact that the\n         * historic vi couldn't deal with a file that had no lines in it.  This\n         * implementation treats that as a bug, and does not retain the blank\n         * line.\n         *\n         * Historical practice is that the cursor ends at the first character\n         * in the file.\n         */\n\n        if (cp->lno == 1) {\n                if (db_last(sp, &lno))\n                        return (1);\n                if (lno == 0 && F_ISSET(cbp, CB_LMODE)) {\n                        for (i = cnt; i > 0; i--) {\n                                for (; tp; ++lno, ++sp->rptlines[L_ADDED],\n                                    tp = TAILQ_NEXT(tp, q))\n                                        if (db_append(sp, 1, lno, tp->lb,\n                                            tp->len))\n                                                return (1);\n                                tp = TAILQ_FIRST(&cbp->textq);\n                        }\n                        rp->lno = 1;\n                        rp->cno = 0;\n                        return (0);\n                }\n        }\n\n        /* If a line mode buffer, append each new line into the file. */\n        if (F_ISSET(cbp, CB_LMODE)) {\n                lno = append ? cp->lno : cp->lno - 1;\n                rp->lno = lno + 1;\n                for (i = cnt; i > 0; i--) {\n                        for (; tp;\n                            ++lno, ++sp->rptlines[L_ADDED],\n                            tp = TAILQ_NEXT(tp, q))\n                                if (db_append(sp, 1, lno, tp->lb, tp->len))\n                                        return (1);\n                        tp = TAILQ_FIRST(&cbp->textq);\n                }\n                rp->cno = 0;\n                (void)nonblank(sp, rp->lno, &rp->cno);\n                return (0);\n        }\n\n        /*\n         * If buffer was cut in character mode, replace the current line with\n         * one built from the portion of the first line to the left of the\n         * split plus the first line in the CB.  Append each intermediate line\n         * in the CB.  Append a line built from the portion of the first line\n         * to the right of the split plus the last line in the CB.\n         *\n         * Get the first line.\n         */\n\n        lno = cp->lno;\n        if (db_eget(sp, lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n        }\n\n        GET_SPACE_RET(sp, bp, blen, tp->len + len + 1);\n        t = bp;\n\n        if (bp == NULL)\n                return (1);\n\n        /* Original line, left of the split. */\n        if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) {\n                memcpy(bp, p, clen);\n                p += clen;\n                t += clen;\n        }\n\n        if (t == NULL)\n                return (1);\n\n        /* First line from the CB. */\n        if (tp->len != 0) {\n                for (i = cnt; i > 0; i--) {\n                        memcpy(t, tp->lb, tp->len);\n                        t += tp->len;\n                }\n        }\n\n        /* Calculate length left in the original line. */\n        clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0));\n\n        /*\n         * !!!\n         * In the historical 4BSD version of vi, character mode puts within\n         * a single line have two cursor behaviors: if the put is from the\n         * unnamed buffer, the cursor moves to the character inserted which\n         * appears last in the file.  If the put is from a named buffer,\n         * the cursor moves to the character inserted which appears first\n         * in the file.  In System III/V, it was changed at some point and\n         * the cursor always moves to the first character.  In both versions\n         * of vi, character mode puts that cross line boundaries leave the\n         * cursor on the first character.  Nvi implements the System III/V\n         * behavior, and expect POSIX.2 to do so as well.\n         */\n\n        rp->lno = lno;\n        rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0);\n\n        /*\n         * If no more lines in the CB, append the rest of the original\n         * line and quit.  Otherwise, build the last line before doing\n         * the intermediate lines, because the line changes will lose\n         * the cached line.\n         */\n\n        if (TAILQ_NEXT(tp, q) == NULL) {\n                if (clen > 0) {\n                        memcpy(t, p, clen);\n                        t += clen;\n                }\n                if (db_set(sp, lno, bp, t - bp))\n                        goto err;\n                if (sp->rptlchange != lno) {\n                        sp->rptlchange = lno;\n                        ++sp->rptlines[L_CHANGED];\n                }\n        } else {\n\n                /*\n                 * Have to build both the first and last lines of the\n                 * put before doing any sets or we'll lose the cached\n                 * line.  Build both the first and last lines in the\n                 * same buffer, so we don't have to have another buffer\n                 * floating around.\n                 *\n                 * Last part of original line; check for space, reset\n                 * the pointer into the buffer.\n                 */\n\n                ltp = TAILQ_LAST(&cbp->textq, _texth);\n                len = t - bp;\n                ADD_SPACE_RET(sp, bp, blen, ltp->len + clen);\n                t = bp + len;\n\n                /* Add in last part of the CB. */\n                memcpy(t, ltp->lb, ltp->len);\n                if (clen)\n                        memcpy(t + ltp->len, p, clen);\n                clen += ltp->len;\n\n                /*\n                 * Now: bp points to the first character of the first\n                 * line, t points to the last character of the last\n                 * line, t - bp is the length of the first line, and\n                 * clen is the length of the last.  Just figured you'd\n                 * want to know.\n                 *\n                 * Output the line replacing the original line.\n                 */\n\n                if (db_set(sp, lno, bp, t - bp))\n                        goto err;\n                if (sp->rptlchange != lno) {\n                        sp->rptlchange = lno;\n                        ++sp->rptlines[L_CHANGED];\n                }\n\n                /* Output any intermediate lines in the CB. */\n                for (tp = TAILQ_NEXT(tp, q); TAILQ_NEXT(tp, q);\n                    ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q))\n                        if (db_append(sp, 1, lno, tp->lb, tp->len))\n                                goto err;\n\n                if (db_append(sp, 1, lno, t, clen))\n                        goto err;\n                ++sp->rptlines[L_ADDED];\n        }\n        rval = 0;\n\n        if (0)\nerr:            rval = 1;\n\n        FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n"
  },
  {
    "path": "common/recover.c",
    "content": "/*      $OpenBSD: recover.c,v 1.32 2022/02/20 19:45:51 tb Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/wait.h>\n\n/*\n * We include <sys/file.h>, because the open #defines were found there\n * on historical systems.  We also include <bsd_fcntl.h> because the open(2)\n * #defines are found there on newer systems.\n */\n\n#include <sys/file.h>\n\n#include <stddef.h>\n#include <stdint.h>\n#include <bitstring.h>\n#ifdef __solaris__\n# define _XPG7\n#endif /* ifdef __solaris__ */\n#include <dirent.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <paths.h>\n#include <pwd.h>\n#include <stdio.h>\n#include <bsd_err.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <time.h>\n#include <bsd_unistd.h>\n\n#include \"errc.h\"\n#include \"common.h\"\n#include \"pathnames.h\"\n\n#undef open\n\n/*\n * Recovery code.\n *\n * The basic scheme is as follows:  In the EXF structure, we maintain full\n * paths of a b+tree file and a mail recovery file.  The former is the file\n * used as backing store by the DB package.  The latter is the file that\n * contains an email message to be sent to the user if we crash.  The two\n * simple states of recovery are:\n *\n *      + first starting the edit session:\n *              the b+tree file exists and is mode 700, the mail recovery\n *              file doesn't exist.\n *      + after the file has been modified:\n *              the b+tree file exists and is mode 600, the mail recovery\n *              file exists, and is exclusively locked.\n *\n * In the EXF structure we maintain a file descriptor that is the locked\n * file descriptor for the mail recovery file.  NOTE: we sometimes have to\n * do locking with fcntl(2).  This is a problem because if you close(2) any\n * file descriptor associated with the file, ALL of the locks go away.  Be\n * sure to remember that if you have to modify the recovery code.  (It has\n * been rhetorically asked of what the designers could have been thinking\n * when they did that interface.  The answer is simple: they weren't.)\n *\n * To find out if a recovery file/backing file pair are in use, try to get\n * a lock on the recovery file.\n *\n * To find out if a backing file can be deleted at boot time, check for an\n * owner execute bit.  (Yes, I know it's ugly, but it's either that or put\n * special stuff into the backing file itself, or correlate the files at\n * boot time, neither of which looks like fun.)  Note also that there's a\n * window between when the file is created and the X bit is set.  It's small,\n * but it's there.  To fix the window, check for 0 length files as well.\n *\n * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,\n * this DOES NOT mean that any initialization has been done, only that we\n * haven't yet failed at setting up or doing recovery.\n *\n * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.\n * If that bit is not set when ending a file session:\n *      If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,\n *      they are unlink(2)'d, and free(3)'d.\n *      If the EXF file descriptor (rcv_fd) is not -1, it is closed.\n *\n * The backing b+tree file is set up when a file is first edited, so that\n * the DB package can use it for on-disk caching and/or to snapshot the\n * file.  When the file is first modified, the mail recovery file is created,\n * the backing file permissions are updated, the file is sync(2)'d to disk,\n * and the timer is started.  Then, at RCV_PERIOD second intervals, the\n * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which\n * means that the data structures (SCR, EXF, the underlying tree structures)\n * must be consistent when the signal arrives.\n *\n * The recovery mail file contains normal mail headers, with two additions,\n * which occur in THIS order, as the FIRST TWO headers:\n *\n *      X-vi-recover-file: file_name\n *      X-vi-recover-path: recover_path\n *\n * Since newlines delimit the headers, this means that file names cannot have\n * newlines in them, but that's probably okay.  As these files aren't intended\n * to be long-lived, changing their format won't be too painful.\n *\n * Btree files are named \"vi.XXXX\" and recovery files are named \"recover.XXXX\".\n */\n\n#define VI_FHEADER      \"X-vi-recover-file: \"\n#define VI_PHEADER      \"X-vi-recover-path: \"\n\nint rcv_copy(SCR *, int, char *);\nvoid rcv_email(SCR *, int);\nint rcv_mailfile(SCR *, int, char *);\nchar *rcv_gets(char *, size_t, int);\n\nint      rcv_mktemp(SCR *, char *, char *, int);\nint      rcv_openat(SCR *, int, const char *, int *);\n\n/*\n * rcv_tmp --\n *      Build a file name that will be used as the recovery file.\n *\n * PUBLIC: int rcv_tmp(SCR *, EXF *, char *);\n */\n\nint\nrcv_tmp(SCR *sp, EXF *ep, char *name)\n{\n        struct stat sb;\n        static int warned = 0;\n        int fd;\n        char *dp, *p, path[PATH_MAX];\n\n        /*\n         * !!!\n         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.\n         */\n\n        if (opts_empty(sp, O_RECDIR, 0))\n                goto err;\n\n        dp = O_STR(sp, O_RECDIR);\n        if (stat(dp, &sb)) {\n          if (errno == ENOENT)\n            goto err_quiet;\n                if (!warned) {\n                        warned = 1;\n                        msgq(sp, M_SYSERR, \"%s\", dp);\n                        goto err;\n                }\n                return 1;\n        }\n\n        /* Newlines delimit the mail messages. */\n        for (p = name; *p; ++p)\n                if (*p == '\\n') {\n                        msgq(sp, M_ERR,\n                    \"Files with newlines in the name are unrecoverable\");\n                        goto err;\n                }\n\n        (void)snprintf(path, sizeof(path), \"%s/vi.XXXXXX\", dp);\n        if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)\n                goto err;\n        (void)close(fd);\n\n        if ((ep->rcv_path = strdup(path)) == NULL) {\n                msgq(sp, M_SYSERR, NULL);\n                (void)unlink(path);\nerr:            msgq(sp, M_ERR,\n                    \"Modifications not recoverable if the session fails\");\nerr_quiet:\n                return (1);\n        }\n\n        /* We believe the file is recoverable. */\n        F_SET(ep, F_RCV_ON);\n        return (0);\n}\n\n/*\n * rcv_init --\n *      Force the file to be snapshotted for recovery.\n *\n * PUBLIC: int rcv_init(SCR *);\n */\n\nint\nrcv_init(SCR *sp)\n{\n        EXF *ep;\n        recno_t lno;\n\n        ep = sp->ep;\n\n        /* Only do this once. */\n        F_CLR(ep, F_FIRSTMODIFY);\n\n        /* If we already know the file isn't recoverable, we're done. */\n        if (!F_ISSET(ep, F_RCV_ON))\n                return (0);\n\n        /* Turn off recoverability until we figure out if this will work. */\n        F_CLR(ep, F_RCV_ON);\n\n        /* Test if we're recovering a file, not editing one. */\n        if (ep->rcv_mpath == NULL) {\n                /* Build a file to mail to the user. */\n                if (rcv_mailfile(sp, 0, NULL))\n                        goto err;\n\n                /* Force a read of the entire file. */\n                if (db_last(sp, &lno))\n                        goto err;\n\n                /* Turn on a busy message, and sync it to backing store. */\n                sp->gp->scr_busy(sp,\n                    \"Copying file for recovery...\", BUSY_ON);\n                if (ep->db->sync(ep->db, R_RECNOSYNC)) {\n                        msgq_str(sp, M_SYSERR, ep->rcv_path,\n                            \"Preservation failed: %s\");\n                        sp->gp->scr_busy(sp, NULL, BUSY_OFF);\n                        goto err;\n                }\n                sp->gp->scr_busy(sp, NULL, BUSY_OFF);\n        }\n\n        /* Turn off the owner execute bit. */\n        (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);\n\n        /* We believe the file is recoverable. */\n        F_SET(ep, F_RCV_ON);\n        return (0);\n\nerr:    msgq(sp, M_ERR,\n            \"Modifications not recoverable if the session fails\");\n        return (1);\n}\n\n/*\n * rcv_sync --\n *      Sync the file, optionally:\n *              flagging the backup file to be preserved\n *              snapshotting the backup file and send email to the user\n *              sending email to the user if the file was modified\n *              ending the file session\n *\n * PUBLIC: int rcv_sync(SCR *, unsigned int);\n */\n\nint\nrcv_sync(SCR *sp, unsigned int flags)\n{\n        EXF *ep;\n        int fd, rval;\n        char *dp, buf[1024];\n\n        /* Make sure that there's something to recover/sync. */\n        ep = sp->ep;\n        if (ep == NULL || !F_ISSET(ep, F_RCV_ON))\n                return (0);\n\n        /* Sync the file if it's been modified. */\n        if (F_ISSET(ep, F_MODIFIED)) {\n                /* Clear recovery sync flag. */\n                F_CLR(ep, F_RCV_SYNC);\n                if (ep->db->sync(ep->db, R_RECNOSYNC)) {\n                        F_CLR(ep, F_RCV_ON | F_RCV_NORM);\n                        msgq_str(sp, M_SYSERR,\n                            ep->rcv_path, \"File backup failed: %s\");\n                        return (1);\n                }\n\n                /* REQUEST: don't remove backing file on exit. */\n                if (LF_ISSET(RCV_PRESERVE))\n                        F_SET(ep, F_RCV_NORM);\n\n                /* REQUEST: send email. */\n                if (LF_ISSET(RCV_EMAIL))\n                        rcv_email(sp, ep->rcv_fd);\n        }\n\n        /*\n         * !!!\n         * Each time the user exec's :preserve, we have to snapshot all of\n         * the recovery information, i.e. it's like the user re-edited the\n         * file.  We copy the DB(3) backing file, and then create a new mail\n         * recovery file, it's simpler than exiting and reopening all of the\n         * underlying files.\n         *\n         * REQUEST: snapshot the file.\n         */\n\n        rval = 0;\n        if (LF_ISSET(RCV_SNAPSHOT)) {\n                if (opts_empty(sp, O_RECDIR, 0))\n                        goto err;\n                dp = O_STR(sp, O_RECDIR);\n                (void)snprintf(buf, sizeof(buf), \"%s/vi.XXXXXX\", dp);\n                if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)\n                        goto err;\n                sp->gp->scr_busy(sp,\n                    \"Copying file for recovery...\", BUSY_ON);\n                if (rcv_copy(sp, fd, ep->rcv_path) ||\n                    close(fd) || rcv_mailfile(sp, 1, buf)) {\n                        (void)unlink(buf);\n                        (void)close(fd);\n                        rval = 1;\n                }\n                sp->gp->scr_busy(sp, NULL, BUSY_OFF);\n        }\n        if (0) {\nerr:            rval = 1;\n        }\n\n        /* REQUEST: end the file session. */\n        if (LF_ISSET(RCV_ENDSESSION))\n                F_SET(sp, SC_EXIT_FORCE);\n        return (rval);\n}\n\n/*\n * rcv_mailfile --\n *      Build the file to mail to the user.\n */\n\nint\nrcv_mailfile(SCR *sp, int issync, char *cp_path)\n{\n        EXF *ep;\n        GS *gp;\n        struct passwd *pw;\n        size_t len;\n        time_t now;\n        uid_t uid;\n        int fd;\n        char *dp, *p, *t, buf[4096], mpath[PATH_MAX];\n        char *t1, *t2, *t3;\n        char host[HOST_NAME_MAX+1];\n\n        gp = sp->gp;\n        (void)gp;\n        if ((pw = getpwuid(uid = getuid())) == NULL) {\n                msgq(sp, M_ERR,\n                    \"Information on user id %u not found\", uid);\n                return (1);\n        }\n\n        if (opts_empty(sp, O_RECDIR, 0))\n                return (1);\n        dp = O_STR(sp, O_RECDIR);\n        (void)snprintf(mpath, sizeof(mpath), \"%s/recover.XXXXXX\", dp);\n        if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)\n                return (1);\n\n        /*\n         * XXX\n         * We keep an open lock on the file so that the recover option can\n         * distinguish between files that are live and those that need to\n         * be recovered.  There's an obvious window between the mkstemp call\n         * and the lock, but it's pretty small.\n         */\n\n        ep = sp->ep;\n        if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)\n                msgq(sp, M_SYSERR, \"Unable to lock recovery file\");\n        if (!issync) {\n                /* Save the recover file descriptor, and mail path. */\n                ep->rcv_fd = fd;\n                if ((ep->rcv_mpath = strdup(mpath)) == NULL) {\n                        msgq(sp, M_SYSERR, NULL);\n                        goto err;\n                }\n                cp_path = ep->rcv_path;\n        }\n\n        /*\n         * XXX\n         * We can't use stdio(3) here.  The problem is that we may be using\n         * fcntl(2), so if ANY file descriptor into the file is closed, the\n         * lock is lost.  So, we could never close the FILE *, even if we\n         * dup'd the fd first.\n         */\n\n        t = sp->frp->name;\n        if ((p = strrchr(t, '/')) == NULL)\n                p = t;\n        else\n                ++p;\n        (void)time(&now);\n        (void)gethostname(host, sizeof(host));\n        len = snprintf(buf, sizeof(buf),\n            \"%s%s\\n%s%s\\n%s\\n%s\\n%s%s\\n%s%s\\n%s\\n%s\\n\\n\",\n            VI_FHEADER, t,                      /* Non-standard. */\n            VI_PHEADER, cp_path,                /* Non-standard. */\n            \"Reply-To: root\",\n            \"From: root (OpenVi recovery program)\",\n            \"To: \", pw->pw_name,\n            \"Subject: OpenVi saved the file \", p,\n            \"Precedence: bulk\",                 /* For vacation(1). */\n            \"Auto-Submitted: auto-generated\");\n        if (len > sizeof(buf) - 1)\n                goto lerr;\n        if (write(fd, buf, len) != len)\n                goto werr;\n\n        len = snprintf(buf, sizeof(buf),\n            \"%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\\n\\n\",\n            \"On \", ctime(&now), \", the user \", pw->pw_name,\n            \" was editing a file named \", t, \" on the machine \",\n            host, \", when it was saved for recovery. \",\n            \"You can recover most, if not all, of the changes \",\n            \"to this file using the -r option to \", bsd_getprogname(), \":\\n\\n\\t\",\n            bsd_getprogname(), \" -r \", t);\n        if (len > sizeof(buf) - 1) {\nlerr:           msgq(sp, M_ERR, \"Recovery file buffer overrun\");\n                goto err;\n        }\n\n        /*\n         * Format the message.  (Yes, I know it's silly.)\n         * Requires that the message end in a <newline>.\n         */\n\n#define FMTCOLS 60\n        for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {\n                /* Check for a short length. */\n                if (len <= FMTCOLS) {\n                        t2 = t1 + (len - 1);\n                        goto wout;\n                }\n\n                /* Check for a required <newline>. */\n                t2 = strchr(t1, '\\n');\n                if (t2 - t1 <= FMTCOLS)\n                        goto wout;\n\n                /* Find the closest space, if any. */\n                for (t3 = t2; t2 > t1; --t2)\n                        if (*t2 == ' ') {\n                                if (t2 - t1 <= FMTCOLS)\n                                        goto wout;\n                                t3 = t2;\n                        }\n                t2 = t3;\n\n                /* t2 points to the last character to display. */\nwout:           *t2++ = '\\n';\n\n                /* t2 points one after the last character to display. */\n                if (write(fd, t1, t2 - t1) != t2 - t1)\n                        goto werr;\n        }\n\n        if (issync) {\n                rcv_email(sp, fd);\n                if (close(fd)) {\nwerr:                   msgq(sp, M_SYSERR, \"Recovery file\");\n                        goto err;\n                }\n        }\n        return (0);\n\nerr:    if (!issync)\n                ep->rcv_fd = -1;\n        if (fd != -1)\n                (void)close(fd);\n        return (1);\n}\n\n/*\n * rcv_openat --\n *      Open a recovery file in the specified dir and lock it.\n *\n * PUBLIC: int rcv_openat(SCR *, int, const char *, int *)\n */\n\nint\nrcv_openat(SCR *sp, int dfd, const char *name, int *locked)\n{\n        struct stat sb;\n        int fd, dummy;\n\n        /*\n         * If it's readable, it's recoverable.\n         * Note: file_lock() sets the close on exec flag for us.\n         */\n\n        fd = openat(dfd, name, O_RDONLY|O_NOFOLLOW|O_NONBLOCK);\n        if (fd == -1)\n                goto bad;\n\n        /*\n         * Real vi recovery files are created with mode 0600.\n         * If not a regular file or the mode has changed, skip it.\n         */\n\n        if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode) ||\n            (sb.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR))\n                goto bad;\n\n        if (locked == NULL)\n                locked = &dummy;\n        switch ((*locked = file_lock(sp, NULL, NULL, fd, 0))) {\n        case LOCK_FAILED:\n\n                /*\n                 * XXX\n                 * Assume that a lock can't be acquired, but that we\n                 * should permit recovery anyway.  If this is wrong,\n                 * and someone else is using the file, we're going to\n                 * die horribly.\n                 */\n\n                break;\n        case LOCK_SUCCESS:\n                break;\n        case LOCK_UNAVAIL:\n                /* If it's locked, it's live. */\n                goto bad;\n        }\n        return fd;\nbad:\n        if (fd != -1)\n                close(fd);\n        return -1;\n}\n\n/*\n *      people making love\n *      never exactly the same\n *      just like a snowflake\n *\n * rcv_list --\n *      List the files that can be recovered by this user.\n *\n * PUBLIC: int rcv_list(SCR *);\n */\n\nint\nrcv_list(SCR *sp)\n{\n        struct dirent *dp;\n        struct stat sb;\n        DIR *dirp;\n        int fd;\n        FILE *fp;\n        int found;\n        char *p, *t, file[PATH_MAX], path[PATH_MAX];\n\n        /* Open the recovery directory for reading. */\n        if (opts_empty(sp, O_RECDIR, 0))\n                return (1);\n        p = O_STR(sp, O_RECDIR);\n        if ((dirp = opendir(p)) == NULL) {\n                msgq_str(sp, M_SYSERR, p, \"recdir: %s\");\n                return (1);\n        }\n\n        /* Read the directory. */\n        for (found = 0; (dp = readdir(dirp)) != NULL;) {\n                if (strncmp(dp->d_name, \"recover.\", 8))\n                        continue;\n\n                if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, NULL)) == -1)\n                        continue;\n\n                /* Check the headers. */\n                if ((fp = fdopen(fd, \"r\")) == NULL) {\n                        close(fd);\n                        continue;\n                }\n                if (fgets(file, sizeof(file), fp) == NULL ||\n                    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||\n                    (p = strchr(file, '\\n')) == NULL ||\n                    fgets(path, sizeof(path), fp) == NULL ||\n                    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||\n                    (t = strchr(path, '\\n')) == NULL) {\n                        msgq_str(sp, M_ERR, dp->d_name,\n                            \"%s: malformed recovery file\");\n                        goto next;\n                }\n                *p = *t = '\\0';\n\n                /*\n                 * If the file doesn't exist, it's an orphaned recovery file,\n                 * toss it.\n                 *\n                 * XXX\n                 * This can occur if the backup file was deleted and we crashed\n                 * before deleting the email file.\n                 */\n\n                errno = 0;\n                if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&\n                    errno == ENOENT) {\n                        (void)unlinkat(dirfd(dirp), dp->d_name, 0);\n                        goto next;\n                }\n\n                /* Get the last modification time and display. */\n                (void)fstat(fd, &sb);\n                (void)printf(\"%.24s: %s\\n\",\n                    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);\n                found = 1;\n\n                /* Close, discarding lock. */\nnext:           (void)fclose(fp);\n        }\n        if (found == 0)\n                (void)printf(\"%s: No files to recover\\n\", bsd_getprogname());\n        (void)closedir(dirp);\n        return (0);\n}\n\n/*\n * rcv_read --\n *      Start a recovered file as the file to edit.\n *\n * PUBLIC: int rcv_read(SCR *, FREF *);\n */\n\nint\nrcv_read(SCR *sp, FREF *frp)\n{\n        struct dirent *dp;\n        struct stat sb;\n        DIR *dirp;\n        EXF *ep;\n#ifdef _AIX\n        struct st_timespec rec_mtim;\n#else\n        struct timespec rec_mtim;\n#endif /* ifdef _AIX */\n        int fd, found, lck, requested, sv_fd;\n        char *name, *p, *t, *rp, *recp, *pathp;\n        char file[PATH_MAX], path[PATH_MAX], recpath[PATH_MAX];\n\n        if (opts_empty(sp, O_RECDIR, 0))\n                return (1);\n        rp = O_STR(sp, O_RECDIR);\n        if ((dirp = opendir(rp)) == NULL) {\n                msgq_str(sp, M_SYSERR, rp, \"%s\");\n                return (1);\n        }\n\n        name = frp->name;\n        sv_fd = -1;\n        rec_mtim.tv_sec = rec_mtim.tv_nsec = 0;\n        recp = pathp = NULL;\n        for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {\n                if (strncmp(dp->d_name, \"recover.\", 8))\n                        continue;\n                if ((size_t)snprintf(recpath, sizeof(recpath), \"%s/%s\",\n                    rp, dp->d_name) >= sizeof(recpath))\n                        continue;\n\n                if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, &lck)) == -1)\n                        continue;\n\n                /* Check the headers. */\n                if (rcv_gets(file, sizeof(file), fd) == NULL ||\n                    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||\n                    (p = strchr(file, '\\n')) == NULL ||\n                    rcv_gets(path, sizeof(path), fd) == NULL ||\n                    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||\n                    (t = strchr(path, '\\n')) == NULL) {\n                        msgq_str(sp, M_ERR, recpath,\n                            \"%s: malformed recovery file\");\n                        goto next;\n                }\n                *p = *t = '\\0';\n                ++found;\n\n                /*\n                 * If the file doesn't exist, it's an orphaned recovery file,\n                 * toss it.\n                 *\n                 * XXX\n                 * This can occur if the backup file was deleted and we crashed\n                 * before deleting the email file.\n                 */\n\n                errno = 0;\n                if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&\n                    errno == ENOENT) {\n                        (void)unlink(dp->d_name);\n                        goto next;\n                }\n\n                /* Check the file name. */\n                if (strcmp(file + sizeof(VI_FHEADER) - 1, name))\n                        goto next;\n\n                ++requested;\n\n                /*\n                 * If we've found more than one, take the most recent.\n                 */\n\n                (void)fstat(fd, &sb);\n                if (recp == NULL ||\n                    timespeccmp(&rec_mtim, &sb.st_mtim, <)) {\n                        p = recp;\n                        t = pathp;\n                        if ((recp = strdup(recpath)) == NULL) {\n                                msgq(sp, M_SYSERR, NULL);\n                                recp = p;\n                                goto next;\n                        }\n                        if ((pathp = strdup(path)) == NULL) {\n                                msgq(sp, M_SYSERR, NULL);\n                                free(recp);\n                                recp = p;\n                                pathp = t;\n                                goto next;\n                        }\n                        if (p != NULL) {\n                                free(p);\n                                free(t);\n                        }\n                        rec_mtim = sb.st_mtim;\n                        if (sv_fd != -1)\n                                (void)close(sv_fd);\n                        sv_fd = fd;\n                } else\nnext:                   (void)close(fd);\n        }\n        (void)closedir(dirp);\n\n        if (recp == NULL) {\n                msgq_str(sp, M_INFO, name,\n                    \"No files named %s, readable by you, to recover\");\n                return (1);\n        }\n        if (found) {\n                if (requested > 1)\n                        msgq(sp, M_INFO,\n            \"There are older versions of this file for you to recover\");\n                if (found > requested)\n                        msgq(sp, M_INFO,\n                            \"There are other files for you to recover\");\n        }\n\n        /*\n         * Create the FREF structure, start the btree file.\n         *\n         * XXX\n         * file_init() is going to set ep->rcv_path.\n         */\n\n        if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {\n                free(recp);\n                free(pathp);\n                (void)close(sv_fd);\n                return (1);\n        }\n\n        /*\n         * We keep an open lock on the file so that the recover option can\n         * distinguish between files that are live and those that need to\n         * be recovered.  The lock is already acquired, just copy it.\n         */\n\n        ep = sp->ep;\n        ep->rcv_mpath = recp;\n        ep->rcv_fd = sv_fd;\n        if (lck != LOCK_SUCCESS)\n                F_SET(frp, FR_UNLOCKED);\n\n        /* We believe the file is recoverable. */\n        F_SET(ep, F_RCV_ON);\n        return (0);\n}\n\n/*\n * rcv_copy --\n *      Copy a recovery file.\n */\n\nint\nrcv_copy(SCR *sp, int wfd, char *fname)\n{\n        int nr, nw, off, rfd;\n        char buf[8 * 1024];\n\n        if ((rfd = open(fname, O_RDONLY)) == -1)\n                goto err;\n        while ((nr = read(rfd, buf, sizeof(buf))) > 0)\n                for (off = 0; nr; nr -= nw, off += nw)\n                        if ((nw = write(wfd, buf + off, nr)) < 0)\n                                goto err;\n        if (nr == 0)\n                return (0);\n\nerr:    msgq_str(sp, M_SYSERR, fname, \"%s\");\n        return (1);\n}\n\n/*\n * rcv_gets --\n *      Fgets(3) for a file descriptor.\n */\n\nchar *\nrcv_gets(char *buf, size_t len, int fd)\n{\n        int nr;\n        char *p;\n\n        if ((nr = read(fd, buf, len - 1)) == -1)\n                return (NULL);\n        buf[nr] = '\\0';\n        if ((p = strchr(buf, '\\n')) == NULL)\n                return (NULL);\n        (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);\n        return (buf);\n}\n\n/*\n * rcv_mktemp --\n *      Paranoid make temporary file routine.\n */\n\nint\nrcv_mktemp(SCR *sp, char *path, char *dname, int perms)\n{\n        int fd;\n\n        /*\n         * !!!\n         * We expect mkstemp(3) to set the permissions correctly.  On\n         * historic System V systems, mkstemp didn't.  Do it here, on\n         * GP's.  This also protects us from users with stupid umasks.\n         *\n         * XXX\n         * The variable perms should really be a mode_t.\n         */\n\n        if ((fd = mkstemp(path)) == -1 || fchmod(fd, perms) == -1) {\n                msgq_str(sp, M_SYSERR, dname, \"%s\");\n                if (fd != -1) {\n                        close(fd);\n                        unlink(path);\n                        fd = -1;\n                }\n        }\n        return (fd);\n}\n\n/*\n * rcv_email --\n *      Send email.\n */\n\nvoid\nrcv_email(SCR *sp, int fd)\n{\n        struct stat sb;\n        pid_t pid;\n\n        /*\n         * In secure mode, our pledge(2) includes neither \"proc\"\n         * nor \"exec\".  So simply skip sending the mail.\n         * Later vi -r still works because rcv_mailfile()\n         * already did all the necessary setup.\n         */\n\n        if (O_ISSET(sp, O_SECURE))\n                return;\n\n        if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb) == -1)\n                msgq_str(sp, M_SYSERR,\n                    _PATH_SENDMAIL, \"not sending email: %s\");\n        else {\n\n                /*\n                 * !!!\n                 * If you need to port this to a system that doesn't have\n                 * sendmail, the -t flag causes sendmail to read the message\n                 * for the recipients instead of specifying them some other\n                 * way.\n                 */\n\n                switch (pid = fork()) {\n                case -1:                /* Error. */\n                        msgq(sp, M_SYSERR, \"fork\");\n                        break;\n                case 0:                 /* Sendmail. */\n                        if (lseek(fd, 0, SEEK_SET) == -1) {\n                                msgq(sp, M_SYSERR, \"lseek\");\n                                _exit(127);\n                        }\n                        if (fd != STDIN_FILENO) {\n                                if (dup2(fd, STDIN_FILENO) == -1) {\n                                        msgq(sp, M_SYSERR, \"dup2\");\n                                        _exit(127);\n                                }\n                                close(fd);\n                        }\n                        execl(_PATH_SENDMAIL, \"sendmail\", \"-t\", (char *)NULL);\n                        msgq(sp, M_SYSERR, _PATH_SENDMAIL);\n                        _exit(127);\n                default:                /* Parent. */\n                        while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)\n                                continue;\n                        break;\n                }\n\n        }\n}\n"
  },
  {
    "path": "common/screen.c",
    "content": "/*      $OpenBSD: screen.c,v 1.14 2017/04/18 01:45:35 deraadt Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * screen_init --\n *      Do the default initialization of an SCR structure.\n *\n * PUBLIC: int screen_init(GS *, SCR *, SCR **);\n */\n\nint\nscreen_init(GS *gp, SCR *orig, SCR **spp)\n{\n        SCR *sp;\n        size_t len;\n\n        *spp = NULL;\n        CALLOC_RET(orig, sp, 1, sizeof(SCR));\n        *spp = sp;\n\n/* INITIALIZED AT SCREEN CREATE. */\n        sp->id = ++gp->id;\n        sp->refcnt = 1;\n\n        sp->gp = gp;                            /* All ref the GS structure. */\n\n        sp->ccnt = 2;                           /* Anything > 1 */\n\n        /*\n         * XXX\n         * sp->defscroll is initialized by the opts_init() code because\n         * we don't have the option information yet.\n         */\n\n        TAILQ_INIT(&sp->tiq);\n\n/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */\n        if (orig == NULL) {\n                sp->searchdir = NOTSET;\n        } else {\n                /* Alternate file name. */\n                if (orig->alt_name != NULL &&\n                    (sp->alt_name = strdup(orig->alt_name)) == NULL)\n                        goto mem;\n\n                /* Last executed at buffer. */\n                if (F_ISSET(orig, SC_AT_SET)) {\n                        F_SET(sp, SC_AT_SET);\n                        sp->at_lbuf = orig->at_lbuf;\n                }\n\n                /* Retain searching/substitution information. */\n                sp->searchdir = orig->searchdir == NOTSET ? NOTSET : FORWARD;\n                if (orig->re != NULL && (sp->re =\n                    v_strdup(sp, orig->re, orig->re_len)) == NULL)\n                        goto mem;\n                sp->re_len = orig->re_len;\n                if (orig->subre != NULL && (sp->subre =\n                    v_strdup(sp, orig->subre, orig->subre_len)) == NULL)\n                        goto mem;\n                sp->subre_len = orig->subre_len;\n                if (orig->repl != NULL && (sp->repl =\n                    v_strdup(sp, orig->repl, orig->repl_len)) == NULL)\n                        goto mem;\n                sp->repl_len = orig->repl_len;\n                if (orig->newl_len) {\n                        len = orig->newl_len * sizeof(size_t);\n                        MALLOC(sp, sp->newl, len);\n                        if (sp->newl == NULL) {\nmem:                            msgq(orig, M_SYSERR, NULL);\n                                goto err;\n                        }\n                        sp->newl_len = orig->newl_len;\n                        sp->newl_cnt = orig->newl_cnt;\n                        memcpy(sp->newl, orig->newl, len);\n                }\n\n                if (opts_copy(orig, sp))\n                        goto err;\n\n                F_SET(sp, F_ISSET(orig, SC_EX | SC_VI));\n        }\n\n        if (ex_screen_copy(orig, sp))           /* Ex. */\n                goto err;\n        if (v_screen_copy(orig, sp))            /* Vi. */\n                goto err;\n\n        *spp = sp;\n        return (0);\n\nerr:    screen_end(sp);\n        return (1);\n}\n\n/*\n * screen_end --\n *      Release a screen, no matter what had (and had not) been\n *      initialized.\n *\n * PUBLIC: int screen_end(SCR *);\n */\n\nint\nscreen_end(SCR *sp)\n{\n        int rval;\n        SCR *tsp;\n\n        /* If multiply referenced, just decrement the count and return. */\n         if (--sp->refcnt != 0)\n                 return (0);\n\n        /*\n         * Remove the screen from the displayed and hidden queues.\n         *\n         * If a created screen failed during initialization, it may not\n         * be linked into a queue.\n         */\n\n        TAILQ_FOREACH(tsp, &sp->gp->dq, q) {\n                if (tsp == sp) {\n                        TAILQ_REMOVE(&sp->gp->dq, sp, q);\n                        break;\n                }\n        }\n        TAILQ_FOREACH(tsp, &sp->gp->hq, q) {\n                if (tsp == sp) {\n                        TAILQ_REMOVE(&sp->gp->hq, sp, q);\n                        break;\n                }\n        }\n\n        /* The screen is no longer real. */\n        F_CLR(sp, SC_SCR_EX | SC_SCR_VI);\n\n        rval = 0;\n        if (v_screen_end(sp))                   /* End vi. */\n                rval = 1;\n        if (ex_screen_end(sp))                  /* End ex. */\n                rval = 1;\n\n        /* Free file names. */\n        { char **ap;\n                if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) {\n                        for (ap = sp->argv; *ap != NULL; ++ap)\n                                free(*ap);\n                        free(sp->argv);\n                }\n        }\n\n        /* Free any text input. */\n        if (TAILQ_FIRST(&sp->tiq) != NULL)\n                text_lfree(&sp->tiq);\n\n        /* Free alternate file name. */\n        free(sp->alt_name);\n\n        /* Free up search information. */\n        free(sp->re);\n        if (F_ISSET(sp, SC_RE_SEARCH))\n                regfree(&sp->re_c);\n        free(sp->subre);\n        if (F_ISSET(sp, SC_RE_SUBST))\n                regfree(&sp->subre_c);\n        free(sp->repl);\n        free(sp->newl);\n\n        /* Free all the options */\n        opts_free(sp);\n\n        /* Free the screen itself. */\n        free(sp);\n\n        return (rval);\n}\n\n/*\n * screen_next --\n *      Return the next screen in the queue.\n *\n * PUBLIC: SCR *screen_next(SCR *);\n */\n\nSCR *\nscreen_next(SCR *sp)\n{\n        GS *gp;\n        SCR *next;\n\n        /* Try the display queue, without returning the current screen. */\n        gp = sp->gp;\n        TAILQ_FOREACH(next, &gp->dq, q)\n                if (next != sp)\n                        return (next);\n\n        /* Try the hidden queue; if found, move screen to the display queue. */\n        if (!TAILQ_EMPTY(&gp->hq)) {\n                next = TAILQ_FIRST(&gp->hq);\n                TAILQ_REMOVE(&gp->hq, next, q);\n                TAILQ_INSERT_HEAD(&gp->dq, next, q);\n                return (next);\n        }\n        return (NULL);\n}\n"
  },
  {
    "path": "common/screen.h",
    "content": "/*      $OpenBSD: screen.h,v 1.10 2016/05/27 09:18:11 martijn Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)screen.h    10.24 (Berkeley) 7/19/96\n */\n\n/*\n * There are minimum values that vi has to have to display a screen.  The row\n * minimum is fixed at 1 (the svi code can share a line between the text line\n * and the colon command/message line).  Column calculation is a lot trickier.\n * For example, you have to have enough columns to display the line number,\n * not to mention guaranteeing that tabstop and shiftwidth values are smaller\n * than the current column value.  It's simpler to have a fixed value and not\n * worry about it.\n */\n\n#define MINIMUM_SCREEN_ROWS      1\n#define MINIMUM_SCREEN_COLS     20\n\n/*\n * SCR --\n *      The screen structure.  To the extent possible, all screen information\n *      is stored in the various private areas.  The only information here\n *      is used by global routines or is shared by too many screens.\n */\n\nstruct _scr {\n/* INITIALIZED AT SCREEN CREATE. */\n        TAILQ_ENTRY(_scr) q;            /* Screens. */\n\n        int      id;                    /* Screen id #. */\n        int      refcnt;                /* Reference count. */\n\n        GS      *gp;                    /* Pointer to global area. */\n        SCR     *nextdisp;              /* Next display screen. */\n        SCR     *ccl_parent;            /* Colon command-line parent screen. */\n        EXF     *ep;                    /* Screen's current EXF structure. */\n\n        FREF    *frp;                   /* FREF being edited. */\n        char    **argv;                 /* NULL terminated file name array. */\n        char    **cargv;                /* Current file name. */\n\n        unsigned long   ccnt;                  /* Command count. */\n        unsigned long   q_ccnt;                /* Quit or ZZ command count. */\n\n                                        /* Screen's: */\n        size_t   rows;                  /* 1-N: number of rows. */\n        size_t   cols;                  /* 1-N: number of columns. */\n        size_t   t_rows;                /* 1-N: cur number of text rows. */\n        size_t   t_maxrows;             /* 1-N: max number of text rows. */\n        size_t   t_minrows;             /* 1-N: min number of text rows. */\n        size_t   woff;                  /* 0-N: screen offset in frame. */\n\n                                        /* Cursor's: */\n        recno_t  lno;                   /* 1-N: file line. */\n        size_t   cno;                   /* 0-N: file character in line. */\n\n        size_t   rcm;                   /* Vi: 0-N: Most attractive column. */\n\n#define L_ADDED         0               /* Added lines. */\n#define L_CHANGED       1               /* Changed lines. */\n#define L_DELETED       2               /* Deleted lines. */\n#define L_JOINED        3               /* Joined lines. */\n#define L_MOVED         4               /* Moved lines. */\n#define L_SHIFT         5               /* Shift lines. */\n#define L_YANKED        6               /* Yanked lines. */\n        recno_t  rptlchange;            /* Ex/vi: last L_CHANGED lno. */\n        recno_t  rptlines[L_YANKED + 1];/* Ex/vi: lines changed by last op. */\n\n        TEXTH    tiq;                   /* Ex/vi: text input queue. */\n\n        SCRIPT  *script;                /* Vi: script mode information .*/\n\n        recno_t  defscroll;             /* Vi: ^D, ^U scroll information. */\n\n                                        /* Display character. */\n        CHAR_T   cname[MAX_CHARACTER_COLUMNS + 1];\n        size_t   clen;                  /* Length of display character. */\n\n        enum {                          /* Vi editor mode. */\n            SM_APPEND = 0, SM_CHANGE, SM_COMMAND, SM_INSERT,\n            SM_REPLACE } showmode;\n\n        void    *ex_private;            /* Ex private area. */\n        void    *vi_private;            /* Vi private area. */\n\n/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */\n        char    *alt_name;              /* Ex/vi: alternate file name. */\n\n        CHAR_T   at_lbuf;               /* Ex/vi: Last executed at buffer. */\n\n                                        /* Ex/vi: re_compile flags. */\n#define RE_C_SEARCH     0x0002          /* Compile search replacement. */\n#define RE_C_SILENT     0x0004          /* No error messages. */\n#define RE_C_SUBST      0x0008          /* Compile substitute replacement. */\n#define RE_C_TAG        0x0010          /* Compile ctag pattern. */\n\n#define RE_WSTART       \"[[:<:]]\"       /* Ex/vi: not-in-word search pattern. */\n#define RE_WSTOP        \"[[:>:]]\"\n                                        /* Ex/vi: flags to search routines. */\n#define SEARCH_EOL      0x0002          /* Offset past EOL is okay. */\n#define SEARCH_FILE     0x0004          /* Search the entire file. */\n#define SEARCH_INCR     0x0008          /* Search incrementally. */\n#define SEARCH_MSG      0x0010          /* Display search messages. */\n#define SEARCH_PARSE    0x0020          /* Parse the search pattern. */\n#define SEARCH_SET      0x0040          /* Set search direction. */\n#define SEARCH_TAG      0x0080          /* Search for a tag pattern. */\n#define SEARCH_WMSG     0x0100          /* Display search-wrapped messages. */\n\n                                        /* Ex/vi: RE information. */\n        dir_t    searchdir;             /* Last file search direction. */\n        regex_t  re_c;                  /* Search RE: compiled form. */\n        char    *re;                    /* Search RE: uncompiled form. */\n        size_t   re_len;                /* Search RE: uncompiled length. */\n        regex_t  subre_c;               /* Substitute RE: compiled form. */\n        char    *subre;                 /* Substitute RE: uncompiled form. */\n        size_t   subre_len;             /* Substitute RE: uncompiled length). */\n        char    *repl;                  /* Substitute replacement. */\n        size_t   repl_len;              /* Substitute replacement length.*/\n        size_t  *newl;                  /* Newline offset array. */\n        size_t   newl_len;              /* Newline array size. */\n        size_t   newl_cnt;              /* Newlines in replacement. */\n        u_int8_t c_suffix;              /* Edcompatible 'c' suffix value. */\n        u_int8_t g_suffix;              /* Edcompatible 'g' suffix value. */\n\n        OPTION   opts[O_OPTIONCOUNT];   /* Ex/vi: Options. */\n\n/*\n * Screen flags.\n *\n * Editor screens.\n */\n\n#define SC_EX           0x00000001      /* Ex editor. */\n#define SC_VI           0x00000002      /* Vi editor. */\n\n/*\n * Screen formatting flags, first major, then minor.\n *\n * SC_SCR_EX\n *      Ex screen, i.e. cooked mode.\n * SC_SCR_VI\n *      Vi screen, i.e. raw mode.\n * SC_SCR_EXWROTE\n *      The editor had to write on the screen behind curses' back, and we can't\n *      let curses change anything until the user agrees, e.g. entering the\n *      commands :!utility followed by :set.  We have to switch back into the\n *      vi \"editor\" to read the user's command input, but we can't touch the\n *      rest of the screen because it's known to be wrong.\n * SC_SCR_REFORMAT\n *      The expected presentation of the lines on the screen have changed,\n *      requiring that the intended screen lines be recalculated.  Implies\n *      SC_SCR_REDRAW.\n * SC_SCR_REDRAW\n *      The screen doesn't correctly represent the file; repaint it.  Note,\n *      setting SC_SCR_REDRAW in the current window causes *all* windows to\n *      be repainted.\n * SC_SCR_CENTER\n *      If the current line isn't already on the screen, center it.\n * SC_SCR_TOP\n *      If the current line isn't already on the screen, put it at the to@.\n */\n\n#define SC_SCR_EX       0x00000004      /* Screen is in ex mode. */\n#define SC_SCR_VI       0x00000008      /* Screen is in vi mode. */\n#define SC_SCR_EXWROTE  0x00000010      /* Ex overwrite: see comment above. */\n#define SC_SCR_REFORMAT 0x00000020      /* Reformat (refresh). */\n#define SC_SCR_REDRAW   0x00000040      /* Refresh. */\n\n#define SC_SCR_CENTER   0x00000080      /* Center the line if not visible. */\n#define SC_SCR_TOP      0x00000100      /* Top the line if not visible. */\n\n/* Screen/file changes. */\n#define SC_EXIT         0x00000200      /* Exiting (not forced). */\n#define SC_EXIT_FORCE   0x00000400      /* Exiting (forced). */\n#define SC_FSWITCH      0x00000800      /* Switch underlying files. */\n#define SC_SSWITCH      0x00001000      /* Switch screens. */\n\n#define SC_ARGNOFREE    0x00002000      /* Argument list wasn't allocated. */\n#define SC_ARGRECOVER   0x00004000      /* Argument list is recovery files. */\n#define SC_AT_SET       0x00008000      /* Last at buffer set. */\n#define SC_COMEDIT      0x00010000      /* Colon command-line edit window. */\n#define SC_EX_GLOBAL    0x00020000      /* Ex: executing a global command. */\n#define SC_EX_SILENT    0x00040000      /* Ex: batch script. */\n#define SC_EX_WAIT_NO   0x00080000      /* Ex: don't wait for the user. */\n#define SC_EX_WAIT_YES  0x00100000      /* Ex:    do wait for the user. */\n#define SC_READONLY     0x00200000      /* Persistent readonly state. */\n#define SC_RE_SEARCH    0x00400000      /* Search RE has been compiled. */\n#define SC_RE_SUBST     0x00800000      /* Substitute RE has been compiled. */\n#define SC_SCRIPT       0x01000000      /* Shell script window. */\n#define SC_STATUS       0x02000000      /* Welcome message. */\n#define SC_STATUS_CNT   0x04000000      /* Welcome message plus file count. */\n#define SC_TINPUT       0x08000000      /* Doing text input. */\n#define SC_TINPUT_INFO  0x10000000      /* Doing text input on info line. */\n        u_int32_t flags;\n};\n"
  },
  {
    "path": "common/search.c",
    "content": "/*      $OpenBSD: search.c,v 1.14 2022/12/10 16:06:18 millert Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"common.h\"\n\ntypedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t;\n\nstatic void     search_msg(SCR *, smsg_t);\nstatic int      search_init(SCR *, dir_t, char *, size_t, char **, unsigned int);\n\n/*\n * search_init --\n *      Set up a search.\n */\n\nstatic int\nsearch_init(SCR *sp, dir_t dir, char *ptrn, size_t plen, char **epp,\n    unsigned int flags)\n{\n        recno_t lno;\n        int delim;\n        char *p, *t;\n\n        /* If the file is empty, it's a fast search. */\n        if (sp->lno <= 1) {\n                if (db_last(sp, &lno))\n                        return (1);\n                if (lno == 0) {\n                        if (LF_ISSET(SEARCH_MSG))\n                                search_msg(sp, S_EMPTY);\n                        return (1);\n                }\n        }\n\n        if (LF_ISSET(SEARCH_PARSE)) {           /* Parse the string. */\n\n                /*\n                 * Use the saved pattern if no pattern specified, or if only\n                 * one or two delimiter characters specified.\n                 *\n                 * !!!\n                 * Historically, only the pattern itself was saved, vi didn't\n                 * preserve addressing or delta information.\n                 */\n\n                if (ptrn == NULL)\n                        goto prev;\n                if (plen == 1) {\n                        if (epp != NULL)\n                                *epp = ptrn + 1;\n                        goto prev;\n                }\n                if (ptrn[0] == ptrn[1]) {\n                        if (epp != NULL)\n                                *epp = ptrn + 2;\n\n                        /* Complain if we don't have a previous pattern. */\nprev:                   if (sp->re == NULL) {\n                                search_msg(sp, S_NOPREV);\n                                return (1);\n                        }\n                        /* Re-compile the search pattern if necessary. */\n                        if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,\n                            sp->re, sp->re_len, NULL, NULL, &sp->re_c,\n                            RE_C_SEARCH |\n                            (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT)))\n                                return (1);\n\n                        /* Set the search direction. */\n                        if (LF_ISSET(SEARCH_SET))\n                                sp->searchdir = dir;\n                        return (0);\n                }\n\n                /*\n                 * Set the delimiter, and move forward to the terminating\n                 * delimiter, handling escaped delimiters.\n                 *\n                 * QUOTING NOTE:\n                 * Only discard an escape character if it escapes a delimiter.\n                 */\n\n                for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) {\n                        if (--plen == 0 || p[0] == delim) {\n                                if (plen != 0)\n                                        ++p;\n                                break;\n                        }\n                        if (plen > 1 && p[0] == '\\\\') {\n                                if (p[1] == delim) {\n                                        ++p;\n                                        --plen;\n                                } else if (p[1] == '\\\\') {\n                                        *t++ = *p++;\n                                        --plen;\n                                }\n                        }\n                }\n                if (epp != NULL)\n                        *epp = p;\n\n                plen = t - ptrn;\n        }\n\n        /* Compile the RE. */\n        if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c,\n            RE_C_SEARCH |\n            (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT) |\n            (LF_ISSET(SEARCH_TAG) ? RE_C_TAG : 0)))\n                return (1);\n\n        /* Set the search direction. */\n        if (LF_ISSET(SEARCH_SET))\n                sp->searchdir = dir;\n\n        return (0);\n}\n\n/*\n * f_search --\n *      Do a forward search.\n *\n * PUBLIC: int f_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int);\n */\n\nint\nf_search(SCR *sp, MARK *fm, MARK *rm, char *ptrn, size_t plen, char **eptrn,\n    unsigned int flags)\n{\n        busy_t btype;\n        recno_t lno;\n        regmatch_t match[1];\n        size_t coff, len;\n        int cnt, eval, rval, wrapped = 0;\n        char *l;\n\n        if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags))\n                return (1);\n\n        if (LF_ISSET(SEARCH_FILE)) {\n                lno = 1;\n                coff = 0;\n        } else {\n                if (db_get(sp, fm->lno, DBG_FATAL, &l, &len))\n                        return (1);\n                lno = fm->lno;\n\n                /*\n                 * If doing incremental search, start searching at the previous\n                 * column, so that we search a minimal distance and still match\n                 * special patterns, e.g., \\< for beginning of a word.\n                 *\n                 * Otherwise, start searching immediately after the cursor.  If\n                 * at the end of the line, start searching on the next line.\n                 * This is incompatible (read bug fix) with the historic vi --\n                 * searches for the '$' pattern never moved forward, and the\n                 * \"-t foo\" didn't work if the 'f' was the first character in\n                 * the file.\n                 */\n\n                if (LF_ISSET(SEARCH_INCR)) {\n                        if ((coff = fm->cno) != 0)\n                                --coff;\n                } else if (fm->cno + 1 >= len) {\n                        coff = 0;\n                        lno = fm->lno + 1;\n                        if (db_get(sp, lno, 0, &l, &len)) {\n                                if (!O_ISSET(sp, O_WRAPSCAN)) {\n                                        if (LF_ISSET(SEARCH_MSG))\n                                                search_msg(sp, S_EOF);\n                                        return (1);\n                                }\n                                lno = 1;\n                                wrapped = 1;\n                        }\n                } else\n                        coff = fm->cno + 1;\n        }\n\n        btype = BUSY_ON;\n        for (cnt = INTERRUPT_CHECK, rval = 1;; ++lno, coff = 0) {\n                if (cnt-- == 0) {\n                        if (INTERRUPTED(sp))\n                                break;\n                        if (LF_ISSET(SEARCH_MSG)) {\n                                search_busy(sp, btype);\n                                btype = BUSY_UPDATE;\n                        }\n                        cnt = INTERRUPT_CHECK;\n                }\n                if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) {\n                        if (wrapped) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        search_msg(sp, S_NOTFOUND);\n                                break;\n                        }\n                        if (!O_ISSET(sp, O_WRAPSCAN)) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        search_msg(sp, S_EOF);\n                                break;\n                        }\n                        lno = 0;\n                        wrapped = 1;\n                        continue;\n                }\n\n                /* If already at EOL, just keep going. */\n                if (len != 0 && coff == len)\n                        continue;\n\n                /* Set the termination. */\n                match[0].rm_so = coff;\n                match[0].rm_eo = len;\n\n                /* Search the line. */\n                eval = regexec(&sp->re_c, l, 1, match,\n                    (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND);\n                if (eval == REG_NOMATCH)\n                        continue;\n                if (eval != 0) {\n                        if (LF_ISSET(SEARCH_MSG))\n                                re_error(sp, eval, &sp->re_c);\n                        else\n                                (void)sp->gp->scr_bell(sp);\n                        break;\n                }\n\n                /* Warn if the search wrapped. */\n                if (wrapped && LF_ISSET(SEARCH_WMSG))\n                        search_msg(sp, S_WRAP);\n\n                rm->lno = lno;\n                rm->cno = match[0].rm_so;\n\n                /*\n                 * If a change command, it's possible to move beyond the end\n                 * of a line.  Historic vi generally got this wrong (e.g. try\n                 * \"c?$<cr>\").  Not all that sure this gets it right, there\n                 * are lots of strange cases.\n                 */\n\n                if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len)\n                        rm->cno = len != 0 ? len - 1 : 0;\n\n                rval = 0;\n                break;\n        }\n\n        if (LF_ISSET(SEARCH_MSG))\n                search_busy(sp, BUSY_OFF);\n        return (rval);\n}\n\n/*\n * b_search --\n *      Do a backward search.\n *\n * PUBLIC: int b_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int);\n */\n\nint\nb_search(SCR *sp, MARK *fm, MARK *rm, char *ptrn, size_t plen, char **eptrn,\n    unsigned int flags)\n{\n        busy_t btype;\n        recno_t lno;\n        regmatch_t match[1];\n        size_t coff, last, len;\n        int cnt, eval, rval, wrapped;\n        char *l;\n\n        if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags))\n                return (1);\n\n        /*\n         * If doing incremental search, set the \"starting\" position past the\n         * current column, so that we search a minimal distance and still\n         * match special patterns, e.g., \\> for the end of a word.  This is\n         * safe when the cursor is at the end of a line because we only use\n         * it for comparison with the location of the match.\n         *\n         * Otherwise, start searching immediately before the cursor.  If in\n         * the first column, start search on the previous line.\n         */\n\n        if (LF_ISSET(SEARCH_INCR)) {\n                lno = fm->lno;\n                coff = fm->cno + 1;\n        } else {\n                if (fm->cno == 0) {\n                        if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        search_msg(sp, S_SOF);\n                                return (1);\n                        }\n                        lno = fm->lno - 1;\n                } else\n                        lno = fm->lno;\n                coff = fm->cno;\n        }\n\n        btype = BUSY_ON;\n        for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) {\n                if (cnt-- == 0) {\n                        if (INTERRUPTED(sp))\n                                break;\n                        if (LF_ISSET(SEARCH_MSG)) {\n                                search_busy(sp, btype);\n                                btype = BUSY_UPDATE;\n                        }\n                        cnt = INTERRUPT_CHECK;\n                }\n                if ((wrapped && lno < fm->lno) || lno == 0) {\n                        if (wrapped) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        search_msg(sp, S_NOTFOUND);\n                                break;\n                        }\n                        if (!O_ISSET(sp, O_WRAPSCAN)) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        search_msg(sp, S_SOF);\n                                break;\n                        }\n                        if (db_last(sp, &lno))\n                                break;\n                        if (lno == 0) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        search_msg(sp, S_EMPTY);\n                                break;\n                        }\n                        ++lno;\n                        wrapped = 1;\n                        continue;\n                }\n\n                if (db_get(sp, lno, 0, &l, &len))\n                        break;\n\n                /* Set the termination. */\n                match[0].rm_so = 0;\n                match[0].rm_eo = len;\n\n                /* Search the line. */\n                eval = regexec(&sp->re_c, l, 1, match,\n                    (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND);\n                if (eval == REG_NOMATCH)\n                        continue;\n                if (eval != 0) {\n                        if (LF_ISSET(SEARCH_MSG))\n                                re_error(sp, eval, &sp->re_c);\n                        else\n                                (void)sp->gp->scr_bell(sp);\n                        break;\n                }\n\n                /* Check for a match starting past the cursor. */\n                if (coff != 0 && match[0].rm_so >= coff)\n                        continue;\n\n                /* Warn if the search wrapped. */\n                if (wrapped && LF_ISSET(SEARCH_WMSG))\n                        search_msg(sp, S_WRAP);\n\n                /*\n                 * We now have the first match on the line.  Step through the\n                 * line character by character until find the last acceptable\n                 * match.  This is painful, we need a better interface to regex\n                 * to make this work.\n                 */\n\n                for (;;) {\n                        last = match[0].rm_so++;\n                        if (match[0].rm_so >= len)\n                                break;\n                        match[0].rm_eo = len;\n                        eval = regexec(&sp->re_c, l, 1, match,\n                            (match[0].rm_so == 0 ? 0 : REG_NOTBOL) |\n                            REG_STARTEND);\n                        if (eval == REG_NOMATCH)\n                                break;\n                        if (eval != 0) {\n                                if (LF_ISSET(SEARCH_MSG))\n                                        re_error(sp, eval, &sp->re_c);\n                                else\n                                        (void)sp->gp->scr_bell(sp);\n                                goto err;\n                        }\n                        if (coff && match[0].rm_so >= coff)\n                                break;\n                }\n                rm->lno = lno;\n\n                /* See comment in f_search(). */\n                if (!LF_ISSET(SEARCH_EOL) && last >= len)\n                        rm->cno = len != 0 ? len - 1 : 0;\n                else\n                        rm->cno = last;\n                rval = 0;\n                break;\n        }\n\nerr:    if (LF_ISSET(SEARCH_MSG))\n                search_busy(sp, BUSY_OFF);\n        return (rval);\n}\n\n/*\n * search_msg --\n *      Display one of the search messages.\n */\n\nstatic void\nsearch_msg(SCR *sp, smsg_t msg)\n{\n        switch (msg) {\n        case S_EMPTY:\n                msgq(sp, M_ERR, \"File empty; nothing to search\");\n                break;\n        case S_EOF:\n                msgq(sp, M_ERR,\n                    \"Reached end-of-file without finding the pattern\");\n                break;\n        case S_NOPREV:\n                msgq(sp, M_ERR, \"No previous search pattern\");\n                break;\n        case S_NOTFOUND:\n                msgq(sp, M_ERR, \"Pattern not found\");\n                break;\n        case S_SOF:\n                msgq(sp, M_ERR,\n                    \"Reached top-of-file without finding the pattern\");\n                break;\n        case S_WRAP:\n                msgq(sp, M_ERR, \"Search wrapped\");\n                break;\n        default:\n                abort();\n        }\n}\n\n/*\n * search_busy --\n *      Put up the busy searching message.\n *\n * PUBLIC: void search_busy(SCR *, busy_t);\n */\n\nvoid\nsearch_busy(SCR *sp, busy_t btype)\n{\n        sp->gp->scr_busy(sp, \"Searching...\", btype);\n}\n"
  },
  {
    "path": "common/seq.c",
    "content": "/*      $OpenBSD: seq.c,v 1.14 2017/04/18 01:45:35 deraadt Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"common.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\n/*\n * seq_set --\n *      Internal version to enter a sequence.\n *\n * PUBLIC: int seq_set(SCR *, CHAR_T *,\n * PUBLIC:    size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int);\n */\n\nint\nseq_set(SCR *sp, CHAR_T *name, size_t nlen, CHAR_T *input, size_t ilen,\n    CHAR_T *output, size_t olen, seq_t stype, int flags)\n{\n        CHAR_T *p;\n        SEQ *lastqp, *qp;\n        int sv_errno;\n\n        /*\n         * An input string must always be present.  The output string\n         * can be NULL, when set internally, that's how we throw away\n         * input.\n         *\n         * Just replace the output field if the string already set.\n         */\n\n        if ((qp =\n            seq_find(sp, &lastqp, NULL, input, ilen, stype, NULL)) != NULL) {\n                if (LF_ISSET(SEQ_NOOVERWRITE))\n                        return (0);\n                if (output == NULL || olen == 0) {\n                        p = NULL;\n                        olen = 0;\n                } else if ((p = v_strdup(sp, output, olen)) == NULL) {\n                        sv_errno = errno;\n                        goto mem1;\n                }\n                free(qp->output);\n                qp->olen = olen;\n                qp->output = p;\n                return (0);\n        }\n\n        /* Allocate and initialize SEQ structure. */\n        CALLOC(sp, qp, 1, sizeof(SEQ));\n        if (qp == NULL) {\n                sv_errno = errno;\n                goto mem1;\n        }\n\n        /* Name. */\n        if (name == NULL || nlen == 0)\n                qp->name = NULL;\n        else if ((qp->name = v_strdup(sp, name, nlen)) == NULL) {\n                sv_errno = errno;\n                goto mem2;\n        }\n        qp->nlen = nlen;\n\n        /* Input. */\n        if ((qp->input = v_strdup(sp, input, ilen)) == NULL) {\n                sv_errno = errno;\n                goto mem3;\n        }\n        qp->ilen = ilen;\n\n        /* Output. */\n        if (output == NULL) {\n                qp->output = NULL;\n                olen = 0;\n        } else if ((qp->output = v_strdup(sp, output, olen)) == NULL) {\n                sv_errno = errno;\n                free(qp->input);\nmem3:           free(qp->name);\nmem2:           free(qp);\nmem1:           errno = sv_errno;\n                msgq(sp, M_SYSERR, NULL);\n                return (1);\n        }\n        qp->olen = olen;\n\n        /* Type, flags. */\n        qp->stype = stype;\n        qp->flags = flags;\n\n        /* Link into the chain. */\n        if (lastqp == NULL) {\n                LIST_INSERT_HEAD(&sp->gp->seqq, qp, q);\n        } else {\n                LIST_INSERT_AFTER(lastqp, qp, q);\n        }\n\n        /* Set the fast lookup bit. */\n        if (qp->input[0] < MAX_BIT_SEQ)\n                bit_set(sp->gp->seqb, qp->input[0]);\n\n        return (0);\n}\n\n/*\n * seq_delete --\n *      Delete a sequence.\n *\n * PUBLIC: int seq_delete(SCR *, CHAR_T *, size_t, seq_t);\n */\n\nint\nseq_delete(SCR *sp, CHAR_T *input, size_t ilen, seq_t stype)\n{\n        SEQ *qp;\n\n        if ((qp = seq_find(sp, NULL, NULL, input, ilen, stype, NULL)) == NULL)\n                return (1);\n        return (seq_mdel(qp));\n}\n\n/*\n * seq_mdel --\n *      Delete a map entry, without lookup.\n *\n * PUBLIC: int seq_mdel(SEQ *);\n */\n\nint\nseq_mdel(SEQ *qp)\n{\n        LIST_REMOVE(qp, q);\n        free(qp->name);\n        free(qp->input);\n        free(qp->output);\n        free(qp);\n        return (0);\n}\n\n/*\n * seq_find --\n *      Search the sequence list for a match to a buffer, if ispartial\n *      isn't NULL, partial matches count.\n *\n * PUBLIC: SEQ *seq_find\n * PUBLIC:(SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *);\n */\n\nSEQ *\nseq_find(SCR *sp, SEQ **lastqp, EVENT *e_input, CHAR_T *c_input, size_t ilen,\n    seq_t stype, int *ispartialp)\n{\n        SEQ *lqp, *qp;\n        int diff;\n\n        /*\n         * Ispartialp is a location where we return if there was a\n         * partial match, i.e. if the string were extended it might\n         * match something.\n         *\n         * XXX\n         * Overload the meaning of ispartialp; only the terminal key\n         * search doesn't want the search limited to complete matches,\n         * i.e. ilen may be longer than the match.\n         */\n\n        if (ispartialp != NULL)\n                *ispartialp = 0;\n        for (lqp = NULL, qp = LIST_FIRST(&sp->gp->seqq);\n            qp != NULL; lqp = qp, qp = LIST_NEXT(qp, q)) {\n\n                /*\n                 * Fast checks on the first character and type, and then\n                 * a real comparison.\n                 */\n\n                if (e_input == NULL) {\n                        if (qp->input[0] > c_input[0])\n                                break;\n                        if (qp->input[0] < c_input[0] ||\n                            qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP))\n                                continue;\n                        diff = memcmp(qp->input, c_input, MINIMUM(qp->ilen, ilen));\n                } else {\n                        if (qp->input[0] > e_input->e_c)\n                                break;\n                        if (qp->input[0] < e_input->e_c ||\n                            qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP))\n                                continue;\n                        diff =\n                            e_memcmp(qp->input, e_input, MINIMUM(qp->ilen, ilen));\n                }\n                if (diff > 0)\n                        break;\n                if (diff < 0)\n                        continue;\n\n                /*\n                 * If the entry is the same length as the string, return a\n                 * match.  If the entry is shorter than the string, return a\n                 * match if called from the terminal key routine.  Otherwise,\n                 * keep searching for a complete match.\n                 */\n\n                if (qp->ilen <= ilen) {\n                        if (qp->ilen == ilen || ispartialp != NULL) {\n                                if (lastqp != NULL)\n                                        *lastqp = lqp;\n                                return (qp);\n                        }\n                        continue;\n                }\n\n                /*\n                 * If the entry longer than the string, return partial match\n                 * if called from the terminal key routine.  Otherwise, no\n                 * match.\n                 */\n\n                if (ispartialp != NULL)\n                        *ispartialp = 1;\n                break;\n        }\n        if (lastqp != NULL)\n                *lastqp = lqp;\n        return (NULL);\n}\n\n/*\n * seq_close --\n *      Discard all sequences.\n *\n * PUBLIC: void seq_close(GS *);\n */\n\nvoid\nseq_close(GS *gp)\n{\n        SEQ *qp;\n\n        while ((qp = LIST_FIRST(&gp->seqq)) != NULL) {\n                free(qp->name);\n                free(qp->input);\n                free(qp->output);\n                LIST_REMOVE(qp, q);\n                free(qp);\n        }\n}\n\n/*\n * seq_dump --\n *      Display the sequence entries of a specified type.\n *\n * PUBLIC: int seq_dump(SCR *, seq_t, int);\n */\n\nint\nseq_dump(SCR *sp, seq_t stype, int isname)\n{\n        CHAR_T *p;\n        GS *gp;\n        SEQ *qp;\n        int cnt, len, olen;\n\n        cnt = 0;\n        gp = sp->gp;\n        LIST_FOREACH(qp, &gp->seqq, q) {\n                if (stype != qp->stype || F_ISSET(qp, SEQ_FUNCMAP))\n                        continue;\n                ++cnt;\n                for (p = qp->input,\n                    olen = qp->ilen, len = 0; olen > 0; --olen, ++p)\n                        len += ex_puts(sp, KEY_NAME(sp, *p));\n                for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;)\n                        len -= ex_puts(sp, \" \");\n\n                if (qp->output != NULL)\n                        for (p = qp->output,\n                            olen = qp->olen, len = 0; olen > 0; --olen, ++p)\n                                len += ex_puts(sp, KEY_NAME(sp, *p));\n                else\n                        len = 0;\n\n                if (isname && qp->name != NULL) {\n                        for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;)\n                                len -= ex_puts(sp, \" \");\n                        for (p = qp->name,\n                            olen = qp->nlen; olen > 0; --olen, ++p)\n                                (void)ex_puts(sp, KEY_NAME(sp, *p));\n                }\n                (void)ex_puts(sp, \"\\n\");\n        }\n        return (cnt);\n}\n\n/*\n * seq_save --\n *      Save the sequence entries to a file.\n *\n * PUBLIC: int seq_save(SCR *, FILE *, char *, seq_t);\n */\n\nint\nseq_save(SCR *sp, FILE *fp, char *prefix, seq_t stype)\n{\n        CHAR_T *p;\n        SEQ *qp;\n        size_t olen;\n        int ch;\n\n        /* Write a sequence command for all keys the user defined. */\n        LIST_FOREACH(qp, &sp->gp->seqq, q) {\n                if (stype != qp->stype || !F_ISSET(qp, SEQ_USERDEF))\n                        continue;\n                if (prefix)\n                        (void)fprintf(fp, \"%s\", prefix);\n                for (p = qp->input, olen = qp->ilen; olen > 0; --olen) {\n                        ch = *p++;\n                        if (ch == CH_LITERAL || ch == '|' ||\n                            isblank(ch) || KEY_VAL(sp, ch) == K_NL)\n                                (void)putc(CH_LITERAL, fp);\n                        (void)putc(ch, fp);\n                }\n                (void)putc(' ', fp);\n                if (qp->output != NULL)\n                        for (p = qp->output,\n                            olen = qp->olen; olen > 0; --olen) {\n                                ch = *p++;\n                                if (ch == CH_LITERAL || ch == '|' ||\n                                    KEY_VAL(sp, ch) == K_NL)\n                                        (void)putc(CH_LITERAL, fp);\n                                (void)putc(ch, fp);\n                        }\n                (void)putc('\\n', fp);\n        }\n        return (0);\n}\n\n/*\n * e_memcmp --\n *      Compare a string of EVENT's to a string of CHAR_T's.\n *\n * PUBLIC: int e_memcmp(CHAR_T *, EVENT *, size_t);\n */\n\nint\ne_memcmp(CHAR_T *p1, EVENT *ep, size_t n)\n{\n        if (n != 0) {\n                do {\n                        if (*p1++ != ep->e_c)\n                                return (*--p1 - ep->e_c);\n                        ++ep;\n                } while (--n != 0);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "common/seq.h",
    "content": "/*      $OpenBSD: seq.h,v 1.5 2016/05/27 09:18:11 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)seq.h       10.3 (Berkeley) 3/6/96\n */\n\n/*\n * Map and abbreviation structures.\n *\n * The map structure is doubly linked list, sorted by input string and by\n * input length within the string.  (The latter is necessary so that short\n * matches will happen before long matches when the list is searched.)\n * Additionally, there is a bitmap which has bits set if there are entries\n * starting with the corresponding character.  This keeps us from walking\n * the list unless it's necessary.\n *\n * The name and the output fields of a SEQ can be empty, i.e. NULL.\n * Only the input field is required.\n *\n * XXX\n * The fast-lookup bits are never turned off -- users don't usually unmap\n * things, though, so it's probably not a big deal.\n */\n\nstruct _seq {\n        LIST_ENTRY(_seq) q;             /* Linked list of all sequences. */\n        seq_t    stype;                 /* Sequence type.                */\n        CHAR_T  *name;                  /* Sequence name (if any).       */\n        size_t   nlen;                  /* Name length.                  */\n        CHAR_T  *input;                 /* Sequence input keys.          */\n        size_t   ilen;                  /* Input keys length.            */\n        CHAR_T  *output;                /* Sequence output keys.         */\n        size_t   olen;                  /* Output keys length.           */\n\n#define SEQ_FUNCMAP     0x01            /* If unresolved function key.   */\n#define SEQ_NOOVERWRITE 0x02            /* Don't replace existing entry. */\n#define SEQ_SCREEN      0x04            /* If screen specific.           */\n#define SEQ_USERDEF     0x08            /* If user defined.              */\n        u_int8_t flags;\n};\n"
  },
  {
    "path": "common/util.c",
    "content": "/*      $OpenBSD: util.c,v 1.17 2018/07/11 06:39:23 martijn Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"common.h\"\n\n#define MAXIMUM(a, b)   (((a) > (b)) ? (a) : (b))\n\n/*\n * binc --\n *      Increase the size of a buffer.\n *\n * PUBLIC: void *binc(SCR *, void *, size_t *, size_t);\n */\n\nvoid *\nbinc(SCR *sp, void *bp, size_t *bsizep, size_t min)\n{\n        size_t csize;\n\n        /* If already larger than the minimum, just return. */\n        if (min && *bsizep >= min)\n                return (bp);\n\n        csize = *bsizep + MAXIMUM(min, 256);\n        REALLOC(sp, bp, csize);\n\n        if (bp == NULL) {\n                *bsizep = 0;\n                return (NULL);\n        }\n\n        /*\n         * Memory is guaranteed to be zero-filled, various parts of\n         * nvi depend on this.\n         */\n\n        memset((char *)bp + *bsizep, 0, csize - *bsizep);\n        *bsizep = csize;\n        return (bp);\n}\n\n/*\n * nonblank --\n *      Set the column number of the first non-blank character\n *      including or after the starting column.  On error, set\n *      the column to 0, it's safest.\n *\n * PUBLIC: int nonblank(SCR *, recno_t, size_t *);\n */\n\nint\nnonblank(SCR *sp, recno_t lno, size_t *cnop)\n{\n        char *p;\n        size_t cnt, len, off;\n        int isempty;\n\n        /* Default. */\n        off = *cnop;\n        *cnop = 0;\n\n        /* Get the line, succeeding in an empty file. */\n        if (db_eget(sp, lno, &p, &len, &isempty))\n                return (!isempty);\n\n        /* Set the offset. */\n        if (len == 0 || off >= len)\n                return (0);\n\n        for (cnt = off, p = &p[off],\n            len -= off; len && isblank(*p); ++cnt, ++p, --len);\n\n        /* Set the return. */\n        *cnop = len ? cnt : cnt - 1;\n        return (0);\n}\n\n/*\n * v_strdup --\n *      Strdup for wide character strings with an associated length.\n *\n * PUBLIC: CHAR_T *v_strdup(SCR *, const CHAR_T *, size_t);\n */\n\nCHAR_T *\nv_strdup(SCR *sp, const CHAR_T *str, size_t len)\n{\n        CHAR_T *copy;\n\n        MALLOC(sp, copy, len + 1);\n        if (copy == NULL)\n                return (NULL);\n        memcpy(copy, str, len * sizeof(CHAR_T));\n        copy[len] = '\\0';\n        return (copy);\n}\n\n/*\n * nget_uslong --\n *      Get an unsigned long, checking for overflow.\n *\n * PUBLIC: enum nresult nget_uslong(unsigned long *, const char *, char **, int);\n */\n\nenum nresult\nnget_uslong(unsigned long *valp, const char *p, char **endp, int base)\n{\n        errno = 0;\n        *valp = strtoul(p, endp, base);\n        if (errno == 0)\n                return (NUM_OK);\n        if (errno == ERANGE && *valp == ULONG_MAX)\n                return (NUM_OVER);\n        return (NUM_ERR);\n}\n\n/*\n * nget_slong --\n *      Convert a signed long, checking for overflow and underflow.\n *\n * PUBLIC: enum nresult nget_slong(long *, const char *, char **, int);\n */\n\nenum nresult\nnget_slong(long *valp, const char *p, char **endp, int base)\n{\n        errno = 0;\n        *valp = strtol(p, endp, base);\n        if (errno == 0)\n                return (NUM_OK);\n        if (errno == ERANGE) {\n                if (*valp == LONG_MAX)\n                        return (NUM_OVER);\n                if (*valp == LONG_MIN)\n                        return (NUM_UNDER);\n        }\n        return (NUM_ERR);\n}\n\n#ifdef DEBUG\n# include <stdarg.h>\n\n/*\n * TRACE --\n *      debugging trace routine.\n *\n * PUBLIC: void TRACE(SCR *, const char *, ...);\n */\n\nvoid\nTRACE(SCR *sp, const char *fmt, ...)\n{\n        FILE *tfp;\n        va_list ap;\n\n        if ((tfp = sp->gp->tracefp) == NULL)\n                return;\n        va_start(ap, fmt);\n        (void)vfprintf(tfp, fmt, ap);\n        fflush(tfp);\n        va_end(ap);\n\n        (void)fflush(tfp);\n}\n#endif /* ifdef DEBUG */\n"
  },
  {
    "path": "db/btree/bt_close.c",
    "content": "/*      $OpenBSD: bt_close.c,v 1.11 2021/10/24 10:05:22 jsg Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic int bt_meta(BTREE *);\n\n/*\n * BT_CLOSE -- Close a btree.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__bt_close(DB *dbp)\n{\n        BTREE *t;\n        int fd;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* Sync the tree. */\n        if (__bt_sync(dbp, 0) == RET_ERROR)\n                return (RET_ERROR);\n\n        /* Close the memory pool. */\n        if (mpool_close(t->bt_mp) == RET_ERROR)\n                return (RET_ERROR);\n\n        /* Free random memory. */\n        if (t->bt_cursor.key.data != NULL) {\n                free(t->bt_cursor.key.data);\n                t->bt_cursor.key.size = 0;\n                t->bt_cursor.key.data = NULL;\n        }\n        if (t->bt_rkey.data) {\n                free(t->bt_rkey.data);\n                t->bt_rkey.size = 0;\n                t->bt_rkey.data = NULL;\n        }\n        if (t->bt_rdata.data) {\n                free(t->bt_rdata.data);\n                t->bt_rdata.size = 0;\n                t->bt_rdata.data = NULL;\n        }\n\n        fd = t->bt_fd;\n        free(t);\n        free(dbp);\n        return (close(fd) ? RET_ERROR : RET_SUCCESS);\n}\n\n/*\n * BT_SYNC -- sync the btree to disk.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nint\n__bt_sync(const DB *dbp, unsigned int flags)\n{\n        BTREE *t;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* Sync doesn't currently take any flags. */\n        if (flags != 0) {\n                errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        if (F_ISSET(t, B_INMEM | B_RDONLY) || !F_ISSET(t, B_MODIFIED))\n                return (RET_SUCCESS);\n\n        if (F_ISSET(t, B_METADIRTY) && bt_meta(t) == RET_ERROR)\n                return (RET_ERROR);\n\n        if ((status = mpool_sync(t->bt_mp)) == RET_SUCCESS)\n                F_CLR(t, B_MODIFIED);\n\n        return (status);\n}\n\n/*\n * BT_META -- write the tree meta data to disk.\n *\n * Parameters:\n *      t:      tree\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nstatic int\nbt_meta(BTREE *t)\n{\n        BTMETA m;\n        void *p;\n\n        if ((p = mpool_get(t->bt_mp, P_META, 0)) == NULL)\n                return (RET_ERROR);\n\n        /* Fill in metadata. */\n        m.magic = BTREEMAGIC;\n        m.version = BTREEVERSION;\n        m.psize = t->bt_psize;\n        m.free = t->bt_free;\n        m.nrecs = t->bt_nrecs;\n        m.flags = F_ISSET(t, SAVEMETA);\n\n        memmove(p, &m, sizeof(BTMETA));\n        mpool_put(t->bt_mp, p, MPOOL_DIRTY);\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/btree/bt_conv.c",
    "content": "/*      $OpenBSD: bt_conv.c,v 1.10 2015/01/16 16:48:51 deraadt Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <stdio.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic void mswap(PAGE *);\n\n/*\n * __BT_BPGIN, __BT_BPGOUT --\n *      Convert host-specific number layout to/from the host-independent\n *      format stored on disk.\n *\n * Parameters:\n *      t:      tree\n *      pg:     page number\n *      h:      page to convert\n */\n\nvoid\n__bt_pgin(void *t, pgno_t pg, void *pp)\n{\n        PAGE *h;\n        indx_t i, top;\n        unsigned char flags;\n        char *p;\n\n        if (!F_ISSET(((BTREE *)t), B_NEEDSWAP))\n                return;\n        if (pg == P_META) {\n                mswap(pp);\n                return;\n        }\n\n        h = pp;\n        M_32_SWAP(h->pgno);\n        M_32_SWAP(h->prevpg);\n        M_32_SWAP(h->nextpg);\n        M_32_SWAP(h->flags);\n        M_16_SWAP(h->lower);\n        M_16_SWAP(h->upper);\n\n        top = NEXTINDEX(h);\n        if ((h->flags & P_TYPE) == P_BINTERNAL)\n                for (i = 0; i < top; i++) {\n                        M_16_SWAP(h->linp[i]);\n                        p = (char *)GETBINTERNAL(h, i);\n                        P_32_SWAP(p);\n                        p += sizeof(u_int32_t);\n                        P_32_SWAP(p);\n                        p += sizeof(pgno_t);\n                        if (*(unsigned char *)p & P_BIGKEY) {\n                                p += sizeof(unsigned char);\n                                P_32_SWAP(p);\n                                p += sizeof(pgno_t);\n                                P_32_SWAP(p);\n                        }\n                }\n        else if ((h->flags & P_TYPE) == P_BLEAF)\n                for (i = 0; i < top; i++) {\n                        M_16_SWAP(h->linp[i]);\n                        p = (char *)GETBLEAF(h, i);\n                        P_32_SWAP(p);\n                        p += sizeof(u_int32_t);\n                        P_32_SWAP(p);\n                        p += sizeof(u_int32_t);\n                        flags = *(unsigned char *)p;\n                        if (flags & (P_BIGKEY | P_BIGDATA)) {\n                                p += sizeof(unsigned char);\n                                if (flags & P_BIGKEY) {\n                                        P_32_SWAP(p);\n                                        p += sizeof(pgno_t);\n                                        P_32_SWAP(p);\n                                }\n                                if (flags & P_BIGDATA) {\n                                        p += sizeof(u_int32_t);\n                                        P_32_SWAP(p);\n                                        p += sizeof(pgno_t);\n                                        P_32_SWAP(p);\n                                }\n                        }\n                }\n}\n\nvoid\n__bt_pgout(void *t, pgno_t pg, void *pp)\n{\n        PAGE *h;\n        indx_t i, top;\n        unsigned char flags;\n        char *p;\n\n        if (!F_ISSET(((BTREE *)t), B_NEEDSWAP))\n                return;\n        if (pg == P_META) {\n                mswap(pp);\n                return;\n        }\n\n        h = pp;\n        top = NEXTINDEX(h);\n        if ((h->flags & P_TYPE) == P_BINTERNAL)\n                for (i = 0; i < top; i++) {\n                        p = (char *)GETBINTERNAL(h, i);\n                        P_32_SWAP(p);\n                        p += sizeof(u_int32_t);\n                        P_32_SWAP(p);\n                        p += sizeof(pgno_t);\n                        if (*(unsigned char *)p & P_BIGKEY) {\n                                p += sizeof(unsigned char);\n                                P_32_SWAP(p);\n                                p += sizeof(pgno_t);\n                                P_32_SWAP(p);\n                        }\n                        M_16_SWAP(h->linp[i]);\n                }\n        else if ((h->flags & P_TYPE) == P_BLEAF)\n                for (i = 0; i < top; i++) {\n                        p = (char *)GETBLEAF(h, i);\n                        P_32_SWAP(p);\n                        p += sizeof(u_int32_t);\n                        P_32_SWAP(p);\n                        p += sizeof(u_int32_t);\n                        flags = *(unsigned char *)p;\n                        if (flags & (P_BIGKEY | P_BIGDATA)) {\n                                p += sizeof(unsigned char);\n                                if (flags & P_BIGKEY) {\n                                        P_32_SWAP(p);\n                                        p += sizeof(pgno_t);\n                                        P_32_SWAP(p);\n                                }\n                                if (flags & P_BIGDATA) {\n                                        p += sizeof(u_int32_t);\n                                        P_32_SWAP(p);\n                                        p += sizeof(pgno_t);\n                                        P_32_SWAP(p);\n                                }\n                        }\n                        M_16_SWAP(h->linp[i]);\n                }\n\n        M_32_SWAP(h->pgno);\n        M_32_SWAP(h->prevpg);\n        M_32_SWAP(h->nextpg);\n        M_32_SWAP(h->flags);\n        M_16_SWAP(h->lower);\n        M_16_SWAP(h->upper);\n}\n\n/*\n * MSWAP -- Actually swap the bytes on the meta page.\n *\n * Parameters:\n *      p:      page to convert\n */\n\nstatic void\nmswap(PAGE *pg)\n{\n        char *p;\n\n        p = (char *)pg;\n        P_32_SWAP(p);             /* magic   */\n        p += sizeof(u_int32_t);\n        P_32_SWAP(p);             /* version */\n        p += sizeof(u_int32_t);\n        P_32_SWAP(p);             /* psize   */\n        p += sizeof(u_int32_t);\n        P_32_SWAP(p);             /* free    */\n        p += sizeof(u_int32_t);\n        P_32_SWAP(p);             /* nrecs   */\n        p += sizeof(u_int32_t);\n        P_32_SWAP(p);             /* flags   */\n        p += sizeof(u_int32_t);\n        (void)p;\n}\n"
  },
  {
    "path": "db/btree/bt_debug.c",
    "content": "/*      $OpenBSD: bt_debug.c,v 1.10 2015/01/16 16:48:51 deraadt Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\n#ifdef DEBUG\n\n/*\n * BT_DUMP -- Dump the tree\n *\n * Parameters:\n *      dbp:    pointer to the DB\n */\n\nvoid\n__bt_dump(DB *dbp)\n{\n        BTREE *t;\n        PAGE *h;\n        pgno_t i;\n        char *sep;\n\n        t = dbp->internal;\n        (void)fprintf(stderr, \"%s: pgsz %u\",\n            F_ISSET(t, B_INMEM) ? \"memory\" : \"disk\", t->bt_psize);\n        if (F_ISSET(t, R_RECNO))\n                (void)fprintf(stderr, \" keys %u\", t->bt_nrecs);\n# undef X\n# define X(flag, name) \\\n        if (F_ISSET(t, flag)) { \\\n                (void)fprintf(stderr, \"%s%s\", sep, name); \\\n                sep = \", \"; \\\n        }\n        if (t->flags != 0) {\n                sep = \" flags (\";\n                X(R_FIXLEN,     \"FIXLEN\");\n                X(B_INMEM,      \"INMEM\");\n                X(B_NODUPS,     \"NODUPS\");\n                X(B_RDONLY,     \"RDONLY\");\n                X(R_RECNO,      \"RECNO\");\n                X(B_METADIRTY,\"METADIRTY\");\n                (void)fprintf(stderr, \")\\n\");\n        }\n# undef X\n\n        for (i = P_ROOT;\n            (h = mpool_get(t->bt_mp, i, MPOOL_IGNOREPIN)) != NULL; ++i)\n                __bt_dpage(h);\n}\n\n/*\n * BT_DMPAGE -- Dump the meta page\n *\n * Parameters:\n *      h:      pointer to the PAGE\n */\n\nvoid\n__bt_dmpage(PAGE *h)\n{\n        BTMETA *m;\n        char *sep;\n\n        m = (BTMETA *)h;\n        (void)fprintf(stderr, \"magic %x\\n\", m->magic);\n        (void)fprintf(stderr, \"version %u\\n\", m->version);\n        (void)fprintf(stderr, \"psize %u\\n\", m->psize);\n        (void)fprintf(stderr, \"free %u\\n\", m->free);\n        (void)fprintf(stderr, \"nrecs %u\\n\", m->nrecs);\n        (void)fprintf(stderr, \"flags %u\", m->flags);\n# undef X\n# define X(flag, name) \\\n        if (m->flags & flag) { \\\n                (void)fprintf(stderr, \"%s%s\", sep, name); \\\n                sep = \", \"; \\\n        }\n        if (m->flags) {\n                sep = \" (\";\n                X(B_NODUPS,     \"NODUPS\");\n                X(R_RECNO,      \"RECNO\");\n                (void)fprintf(stderr, \")\");\n        }\n}\n\n/*\n * BT_DNPAGE -- Dump the page\n *\n * Parameters:\n *      n:      page number to dump.\n */\n\nvoid\n__bt_dnpage(DB *dbp, pgno_t pgno)\n{\n        BTREE *t;\n        PAGE *h;\n\n        t = dbp->internal;\n        if ((h = mpool_get(t->bt_mp, pgno, MPOOL_IGNOREPIN)) != NULL)\n                __bt_dpage(h);\n}\n\n/*\n * BT_DPAGE -- Dump the page\n *\n * Parameters:\n *      h:      pointer to the PAGE\n */\n\nvoid\n__bt_dpage(PAGE *h)\n{\n        BINTERNAL *bi;\n        BLEAF *bl;\n        RINTERNAL *ri;\n        RLEAF *rl;\n        indx_t cur, top;\n        char *sep;\n\n        (void)fprintf(stderr, \"    page %u: (\", h->pgno);\n# undef X\n# define X(flag, name) \\\n        if (h->flags & flag) { \\\n                (void)fprintf(stderr, \"%s%s\", sep, name); \\\n                sep = \", \"; \\\n        }\n        sep = \"\";\n        X(P_BINTERNAL,  \"BINTERNAL\")            /* types */\n        X(P_BLEAF,      \"BLEAF\")\n        X(P_RINTERNAL,  \"RINTERNAL\")            /* types */\n        X(P_RLEAF,      \"RLEAF\")\n        X(P_OVERFLOW,   \"OVERFLOW\")\n        X(P_PRESERVE,   \"PRESERVE\");\n        (void)fprintf(stderr, \")\\n\");\n# undef X\n\n        (void)fprintf(stderr, \"\\tprev %2u next %2u\", h->prevpg, h->nextpg);\n        if (h->flags & P_OVERFLOW)\n                return;\n\n        top = NEXTINDEX(h);\n        (void)fprintf(stderr, \" lower %3d upper %3d nextind %d\\n\",\n            h->lower, h->upper, top);\n        for (cur = 0; cur < top; cur++) {\n                (void)fprintf(stderr, \"\\t[%03d] %4d \", cur, h->linp[cur]);\n                switch (h->flags & P_TYPE) {\n                case P_BINTERNAL:\n                        bi = GETBINTERNAL(h, cur);\n                        (void)fprintf(stderr,\n                            \"size %03d pgno %03d\", bi->ksize, bi->pgno);\n                        if (bi->flags & P_BIGKEY)\n                                (void)fprintf(stderr, \" (indirect)\");\n                        else if (bi->ksize)\n                                (void)fprintf(stderr,\n                                    \" {%.*s}\", (int)bi->ksize, bi->bytes);\n                        break;\n                case P_RINTERNAL:\n                        ri = GETRINTERNAL(h, cur);\n                        (void)fprintf(stderr, \"entries %03d pgno %03d\",\n                                ri->nrecs, ri->pgno);\n                        break;\n                case P_BLEAF:\n                        bl = GETBLEAF(h, cur);\n                        if (bl->flags & P_BIGKEY)\n                                (void)fprintf(stderr,\n                                    \"big key page %u size %u/\",\n                                    *(pgno_t *)bl->bytes,\n                                    *(u_int32_t *)(bl->bytes + sizeof(pgno_t)));\n                        else if (bl->ksize)\n                                (void)fprintf(stderr, \"%s/\", bl->bytes);\n                        if (bl->flags & P_BIGDATA)\n                                (void)fprintf(stderr,\n                                    \"big data page %u size %u\",\n                                    *(pgno_t *)(bl->bytes + bl->ksize),\n                                    *(u_int32_t *)(bl->bytes + bl->ksize +\n                                    sizeof(pgno_t)));\n                        else if (bl->dsize)\n                                (void)fprintf(stderr, \"%.*s\",\n                                    (int)bl->dsize, bl->bytes + bl->ksize);\n                        break;\n                case P_RLEAF:\n                        rl = GETRLEAF(h, cur);\n                        if (rl->flags & P_BIGDATA)\n                                (void)fprintf(stderr,\n                                    \"big data page %u size %u\",\n                                    *(pgno_t *)rl->bytes,\n                                    *(u_int32_t *)(rl->bytes + sizeof(pgno_t)));\n                        else if (rl->dsize)\n                                (void)fprintf(stderr,\n                                    \"%.*s\", (int)rl->dsize, rl->bytes);\n                        break;\n                }\n                (void)fprintf(stderr, \"\\n\");\n        }\n}\n#endif /* ifdef DEBUG */\n\n#ifdef STATISTICS\n\n/*\n * BT_STAT -- Gather/print the tree statistics\n *\n * Parameters:\n *      dbp:    pointer to the DB\n */\n\nvoid\n__bt_stat(DB *dbp)\n{\n        extern unsigned long bt_cache_hit, bt_cache_miss, bt_pfxsaved, bt_rootsplit;\n        extern unsigned long bt_sortsplit, bt_split;\n        BTREE *t;\n        PAGE *h;\n        pgno_t i, pcont, pinternal, pleaf;\n        unsigned long ifree, lfree, nkeys;\n        int levels;\n\n        t = dbp->internal;\n        pcont = pinternal = pleaf = 0;\n        nkeys = ifree = lfree = 0;\n        for (i = P_ROOT;\n            (h = mpool_get(t->bt_mp, i, MPOOL_IGNOREPIN)) != NULL; ++i)\n                switch (h->flags & P_TYPE) {\n                case P_BINTERNAL:\n                case P_RINTERNAL:\n                        ++pinternal;\n                        ifree += h->upper - h->lower;\n                        break;\n                case P_BLEAF:\n                case P_RLEAF:\n                        ++pleaf;\n                        lfree += h->upper - h->lower;\n                        nkeys += NEXTINDEX(h);\n                        break;\n                case P_OVERFLOW:\n                        ++pcont;\n                        break;\n                }\n\n        /* Count the levels of the tree. */\n        for (i = P_ROOT, levels = 0 ;; ++levels) {\n                h = mpool_get(t->bt_mp, i, MPOOL_IGNOREPIN);\n                if (h->flags & (P_BLEAF|P_RLEAF)) {\n                        if (levels == 0)\n                                levels = 1;\n                        break;\n                }\n                i = F_ISSET(t, R_RECNO) ?\n                    GETRINTERNAL(h, 0)->pgno :\n                    GETBINTERNAL(h, 0)->pgno;\n        }\n\n        (void)fprintf(stderr, \"%d level%s with %lu keys\",\n            levels, levels == 1 ? \"\" : \"s\", nkeys);\n        if (F_ISSET(t, R_RECNO))\n                (void)fprintf(stderr, \" (%u header count)\", t->bt_nrecs);\n        (void)fprintf(stderr,\n            \"\\n%u pages (leaf %u, internal %u, overflow %u)\\n\",\n            pinternal + pleaf + pcont, pleaf, pinternal, pcont);\n        (void)fprintf(stderr, \"%lu cache hits, %lu cache misses\\n\",\n            bt_cache_hit, bt_cache_miss);\n        (void)fprintf(stderr, \"%lu splits (%lu root splits, %lu sort splits)\\n\",\n            bt_split, bt_rootsplit, bt_sortsplit);\n        pleaf *= t->bt_psize - BTDATAOFF;\n        if (pleaf)\n                (void)fprintf(stderr,\n                    \"%.0f%% leaf fill (%lu bytes used, %lu bytes free)\\n\",\n                    ((double)(pleaf - lfree) / pleaf) * 100,\n                    pleaf - lfree, lfree);\n        pinternal *= t->bt_psize - BTDATAOFF;\n        if (pinternal)\n                (void)fprintf(stderr,\n                    \"%.0f%% internal fill (%lu bytes used, %lu bytes free\\n\",\n                    ((double)(pinternal - ifree) / pinternal) * 100,\n                    pinternal - ifree, ifree);\n        if (bt_pfxsaved)\n                (void)fprintf(stderr, \"prefix checking removed %lu bytes.\\n\",\n                    bt_pfxsaved);\n}\n#endif /* ifdef DEBUG */\n"
  },
  {
    "path": "db/btree/bt_delete.c",
    "content": "/*      $OpenBSD: bt_delete.c,v 1.11 2005/08/05 13:02:59 espie Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic int __bt_bdelete(BTREE *, const DBT *);\nstatic int __bt_curdel(BTREE *, const DBT *, PAGE *, unsigned int);\nstatic int __bt_pdelete(BTREE *, PAGE *);\nstatic int __bt_relink(BTREE *, PAGE *);\nstatic int __bt_stkacq(BTREE *, PAGE **, CURSOR *);\n\n/*\n * __bt_delete\n *      Delete the item(s) referenced by a key.\n *\n * Return RET_SPECIAL if the key is not found.\n */\n\nint\n__bt_delete(const DB *dbp, const DBT *key, unsigned int flags)\n{\n        BTREE *t;\n        CURSOR *c;\n        PAGE *h;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* Check for change to a read-only tree. */\n        if (F_ISSET(t, B_RDONLY)) {\n                errno = EPERM;\n                return (RET_ERROR);\n        }\n\n        switch (flags) {\n        case 0:\n                status = __bt_bdelete(t, key);\n                break;\n        case R_CURSOR:\n\n                /*\n                 * If flags is R_CURSOR, delete the cursor.  Must already\n                 * have started a scan and not have already deleted it.\n                 */\n\n                c = &t->bt_cursor;\n                if (F_ISSET(c, CURS_INIT)) {\n                        if (F_ISSET(c, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE))\n                                return (RET_SPECIAL);\n                        if ((h = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL)\n                                return (RET_ERROR);\n\n                        /*\n                         * If the page is about to be emptied, we'll need to\n                         * delete it, which means we have to acquire a stack.\n                         */\n\n                        if (NEXTINDEX(h) == 1)\n                                if (__bt_stkacq(t, &h, &t->bt_cursor))\n                                        return (RET_ERROR);\n\n                        status = __bt_dleaf(t, NULL, h, c->pg.index);\n\n                        if (NEXTINDEX(h) == 0 && status == RET_SUCCESS) {\n                                if (__bt_pdelete(t, h))\n                                        return (RET_ERROR);\n                        } else\n                                mpool_put(t->bt_mp,\n                                    h, status == RET_SUCCESS ? MPOOL_DIRTY : 0);\n                        break;\n                }\n                /* FALLTHROUGH */\n        default:\n                errno = EINVAL;\n                return (RET_ERROR);\n        }\n        if (status == RET_SUCCESS)\n                F_SET(t, B_MODIFIED);\n        return (status);\n}\n\n/*\n * __bt_stkacq --\n *      Acquire a stack so we can delete a cursor entry.\n *\n * Parameters:\n *        t:    tree\n *       hp:    pointer to current, pinned PAGE pointer\n *        c:    pointer to the cursor\n *\n * Returns:\n *      0 on success, 1 on failure\n */\n\nstatic int\n__bt_stkacq(BTREE *t, PAGE **hp, CURSOR *c)\n{\n        BINTERNAL *bi;\n        EPG *e;\n        EPGNO *parent;\n        PAGE *h;\n        indx_t idx;\n        pgno_t pgno;\n        recno_t nextpg, prevpg;\n        int exact, level;\n\n        /*\n         * Find the first occurrence of the key in the tree.  Toss the\n         * currently locked page so we don't hit an already-locked page.\n         */\n\n        h = *hp;\n        mpool_put(t->bt_mp, h, 0);\n        if ((e = __bt_search(t, &c->key, &exact)) == NULL)\n                return (1);\n        h = e->page;\n\n        /* See if we got it in one shot. */\n        if (h->pgno == c->pg.pgno)\n                goto ret;\n\n        /*\n         * Move right, looking for the page.  At each move we have to move\n         * up the stack until we don't have to move to the next page.  If\n         * we have to change pages at an internal level, we have to fix the\n         * stack back up.\n         */\n\n        while (h->pgno != c->pg.pgno) {\n                if ((nextpg = h->nextpg) == P_INVALID)\n                        break;\n                mpool_put(t->bt_mp, h, 0);\n\n                /* Move up the stack. */\n                for (level = 0; (parent = BT_POP(t)) != NULL; ++level) {\n                        /* Get the parent page. */\n                        if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL)\n                                return (1);\n\n                        /* Move to the next index. */\n                        if (parent->index != NEXTINDEX(h) - 1) {\n                                idx = parent->index + 1;\n                                BT_PUSH(t, h->pgno, idx);\n                                break;\n                        }\n                        mpool_put(t->bt_mp, h, 0);\n                }\n\n                /* Restore the stack. */\n                while (level--) {\n                        /* Push the next level down onto the stack. */\n                        bi = GETBINTERNAL(h, idx);\n                        pgno = bi->pgno;\n                        BT_PUSH(t, pgno, 0);\n\n                        /* Lose the currently pinned page. */\n                        mpool_put(t->bt_mp, h, 0);\n\n                        /* Get the next level down. */\n                        if ((h = mpool_get(t->bt_mp, pgno, 0)) == NULL)\n                                return (1);\n                        idx = 0;\n                }\n                mpool_put(t->bt_mp, h, 0);\n                if ((h = mpool_get(t->bt_mp, nextpg, 0)) == NULL)\n                        return (1);\n        }\n\n        if (h->pgno == c->pg.pgno)\n                goto ret;\n\n        /* Reacquire the original stack. */\n        mpool_put(t->bt_mp, h, 0);\n        if ((e = __bt_search(t, &c->key, &exact)) == NULL)\n                return (1);\n        h = e->page;\n\n        /*\n         * Move left, looking for the page.  At each move we have to move\n         * up the stack until we don't have to change pages to move to the\n         * next page.  If we have to change pages at an internal level, we\n         * have to fix the stack back up.\n         */\n\n        while (h->pgno != c->pg.pgno) {\n                if ((prevpg = h->prevpg) == P_INVALID)\n                        break;\n                mpool_put(t->bt_mp, h, 0);\n\n                /* Move up the stack. */\n                for (level = 0; (parent = BT_POP(t)) != NULL; ++level) {\n                        /* Get the parent page. */\n                        if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL)\n                                return (1);\n\n                        /* Move to the next index. */\n                        if (parent->index != 0) {\n                                idx = parent->index - 1;\n                                BT_PUSH(t, h->pgno, idx);\n                                break;\n                        }\n                        mpool_put(t->bt_mp, h, 0);\n                }\n\n                /* Restore the stack. */\n                while (level--) {\n                        /* Push the next level down onto the stack. */\n                        bi = GETBINTERNAL(h, idx);\n                        pgno = bi->pgno;\n\n                        /* Lose the currently pinned page. */\n                        mpool_put(t->bt_mp, h, 0);\n\n                        /* Get the next level down. */\n                        if ((h = mpool_get(t->bt_mp, pgno, 0)) == NULL)\n                                return (1);\n\n                        idx = NEXTINDEX(h) - 1;\n                        BT_PUSH(t, pgno, idx);\n                }\n                mpool_put(t->bt_mp, h, 0);\n                if ((h = mpool_get(t->bt_mp, prevpg, 0)) == NULL)\n                        return (1);\n        }\n\nret:    mpool_put(t->bt_mp, h, 0);\n        return ((*hp = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL);\n}\n\n/*\n * __bt_bdelete --\n *      Delete all key/data pairs matching the specified key.\n *\n * Parameters:\n *        t:    tree\n *      key:    key to delete\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found.\n */\n\nstatic int\n__bt_bdelete(BTREE *t, const DBT *key)\n{\n        EPG *e;\n        PAGE *h;\n        int deleted, exact, redo;\n\n        deleted = 0;\n\n        /* Find any matching record; __bt_search pins the page. */\nloop:   if ((e = __bt_search(t, key, &exact)) == NULL)\n                return (deleted ? RET_SUCCESS : RET_ERROR);\n        if (!exact) {\n                mpool_put(t->bt_mp, e->page, 0);\n                return (deleted ? RET_SUCCESS : RET_SPECIAL);\n        }\n\n        /*\n         * Delete forward, then delete backward, from the found key.  If\n         * there are duplicates and we reach either side of the page, do\n         * the key search again, so that we get them all.\n         */\n\n        redo = 0;\n        h = e->page;\n        do {\n                if (__bt_dleaf(t, key, h, e->index)) {\n                        mpool_put(t->bt_mp, h, 0);\n                        return (RET_ERROR);\n                }\n                if (F_ISSET(t, B_NODUPS)) {\n                        if (NEXTINDEX(h) == 0) {\n                                if (__bt_pdelete(t, h))\n                                        return (RET_ERROR);\n                        } else\n                                mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                        return (RET_SUCCESS);\n                }\n                deleted = 1;\n        } while (e->index < NEXTINDEX(h) && __bt_cmp(t, key, e) == 0);\n\n        /* Check for right-hand edge of the page. */\n        if (e->index == NEXTINDEX(h))\n                redo = 1;\n\n        /* Delete from the key to the beginning of the page. */\n        while (e->index-- > 0) {\n                if (__bt_cmp(t, key, e) != 0)\n                        break;\n                if (__bt_dleaf(t, key, h, e->index) == RET_ERROR) {\n                        mpool_put(t->bt_mp, h, 0);\n                        return (RET_ERROR);\n                }\n                if (e->index == 0)\n                        redo = 1;\n        }\n\n        /* Check for an empty page. */\n        if (NEXTINDEX(h) == 0) {\n                if (__bt_pdelete(t, h))\n                        return (RET_ERROR);\n                goto loop;\n        }\n\n        /* Put the page. */\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n\n        if (redo)\n                goto loop;\n        return (RET_SUCCESS);\n}\n\n/*\n * __bt_pdelete --\n *      Delete a single page from the tree.\n *\n * Parameters:\n *      t:      tree\n *      h:      leaf page\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n *\n * Side-effects:\n *      mpool_put's the page\n */\n\nstatic int\n__bt_pdelete(BTREE *t, PAGE *h)\n{\n        BINTERNAL *bi;\n        PAGE *pg;\n        EPGNO *parent;\n        indx_t cnt, idx, *ip, offset;\n        u_int32_t nksize;\n        char *from;\n\n        /*\n         * Walk the parent page stack -- a LIFO stack of the pages that were\n         * traversed when we searched for the page where the delete occurred.\n         * Each stack entry is a page number and a page index offset.  The\n         * offset is for the page traversed on the search.  We've just deleted\n         * a page, so we have to delete the key from the parent page.\n         *\n         * If the delete from the parent page makes it empty, this process may\n         * continue all the way up the tree.  We stop if we reach the root page\n         * (which is never deleted, it's just not worth the effort) or if the\n         * delete does not empty the page.\n         */\n\n        while ((parent = BT_POP(t)) != NULL) {\n                /* Get the parent page. */\n                if ((pg = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL)\n                        return (RET_ERROR);\n\n                idx = parent->index;\n                bi = GETBINTERNAL(pg, idx);\n\n                /* Free any overflow pages. */\n                if (bi->flags & P_BIGKEY &&\n                    __ovfl_delete(t, bi->bytes) == RET_ERROR) {\n                        mpool_put(t->bt_mp, pg, 0);\n                        return (RET_ERROR);\n                }\n\n                /*\n                 * Free the parent if it has only the one key and it's not the\n                 * root page. If it's the rootpage, turn it back into an empty\n                 * leaf page.\n                 */\n\n                if (NEXTINDEX(pg) == 1) {\n                        if (pg->pgno == P_ROOT) {\n                                pg->lower = BTDATAOFF;\n                                pg->upper = t->bt_psize;\n                                pg->flags = P_BLEAF;\n                        } else {\n                                if (__bt_relink(t, pg) || __bt_free(t, pg))\n                                        return (RET_ERROR);\n                                continue;\n                        }\n                } else {\n                        /* Pack remaining key items at the end of the page. */\n                        nksize = NBINTERNAL(bi->ksize);\n                        from = (char *)pg + pg->upper;\n                        memmove(from + nksize, from, (char *)bi - from);\n                        pg->upper += nksize;\n\n                        /* Adjust indices' offsets, shift the indices down. */\n                        offset = pg->linp[idx];\n                        for (cnt = idx, ip = &pg->linp[0]; cnt--; ++ip)\n                                if (ip[0] < offset)\n                                        ip[0] += nksize;\n                        for (cnt = NEXTINDEX(pg) - idx; --cnt; ++ip)\n                                ip[0] = ip[1] < offset ? ip[1] + nksize : ip[1];\n                        pg->lower -= sizeof(indx_t);\n                }\n\n                mpool_put(t->bt_mp, pg, MPOOL_DIRTY);\n                break;\n        }\n\n        /* Free the leaf page, as long as it wasn't the root. */\n        if (h->pgno == P_ROOT) {\n                mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                return (RET_SUCCESS);\n        }\n        return (__bt_relink(t, h) || __bt_free(t, h));\n}\n\n/*\n * __bt_dleaf --\n *      Delete a single record from a leaf page.\n *\n * Parameters:\n *      t:      tree\n *    key:      referenced key\n *      h:      page\n *      idx:    index on page to delete\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nint\n__bt_dleaf(BTREE *t, const DBT *key, PAGE *h, unsigned int idx)\n{\n        BLEAF *bl;\n        indx_t cnt, *ip, offset;\n        u_int32_t nbytes;\n        void *to;\n        char *from;\n\n        /* If this record is referenced by the cursor, delete the cursor. */\n        if (F_ISSET(&t->bt_cursor, CURS_INIT) &&\n            !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) &&\n            t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index == idx &&\n            __bt_curdel(t, key, h, idx))\n                return (RET_ERROR);\n\n        /* If the entry uses overflow pages, make them available for reuse. */\n        to = bl = GETBLEAF(h, idx);\n        if (bl->flags & P_BIGKEY && __ovfl_delete(t, bl->bytes) == RET_ERROR)\n                return (RET_ERROR);\n        if (bl->flags & P_BIGDATA &&\n            __ovfl_delete(t, bl->bytes + bl->ksize) == RET_ERROR)\n                return (RET_ERROR);\n\n        /* Pack the remaining key/data items at the end of the page. */\n        nbytes = NBLEAF(bl);\n        from = (char *)h + h->upper;\n        memmove(from + nbytes, from, (char *)to - from);\n        h->upper += nbytes;\n\n        /* Adjust the indices' offsets, shift the indices down. */\n        offset = h->linp[idx];\n        for (cnt = idx, ip = &h->linp[0]; cnt--; ++ip)\n                if (ip[0] < offset)\n                        ip[0] += nbytes;\n        for (cnt = NEXTINDEX(h) - idx; --cnt; ++ip)\n                ip[0] = ip[1] < offset ? ip[1] + nbytes : ip[1];\n        h->lower -= sizeof(indx_t);\n\n        /* If the cursor is on this page, adjust it as necessary. */\n        if (F_ISSET(&t->bt_cursor, CURS_INIT) &&\n            !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) &&\n            t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index > idx)\n                --t->bt_cursor.pg.index;\n\n        return (RET_SUCCESS);\n}\n\n/*\n * __bt_curdel --\n *      Delete the cursor.\n *\n * Parameters:\n *      t:      tree\n *    key:      referenced key (or NULL)\n *      h:      page\n *    idx:      index on page to delete\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nstatic int\n__bt_curdel(BTREE *t, const DBT *key, PAGE *h, unsigned int idx)\n{\n        CURSOR *c;\n        EPG e;\n        PAGE *pg;\n        int curcopy, status;\n\n        /*\n         * If there are duplicates, move forward or backward to one.\n         * Otherwise, copy the key into the cursor area.\n         */\n\n        c = &t->bt_cursor;\n        F_CLR(c, CURS_AFTER | CURS_BEFORE | CURS_ACQUIRE);\n\n        curcopy = 0;\n        if (!F_ISSET(t, B_NODUPS)) {\n\n                /*\n                 * We're going to have to do comparisons.  If we weren't\n                 * provided a copy of the key, i.e. the user is deleting\n                 * the current cursor position, get one.\n                 */\n\n                if (key == NULL) {\n                        e.page = h;\n                        e.index = idx;\n                        if ((status = __bt_ret(t, &e,\n                            &c->key, &c->key, NULL, NULL, 1)) != RET_SUCCESS)\n                                return (status);\n                        curcopy = 1;\n                        key = &c->key;\n                }\n                /* Check previous key, if not at the beginning of the page. */\n                if (idx > 0) {\n                        e.page = h;\n                        e.index = idx - 1;\n                        if (__bt_cmp(t, key, &e) == 0) {\n                                F_SET(c, CURS_BEFORE);\n                                goto dup2;\n                        }\n                }\n                /* Check next key, if not at the end of the page. */\n                if (idx < NEXTINDEX(h) - 1) {\n                        e.page = h;\n                        e.index = idx + 1;\n                        if (__bt_cmp(t, key, &e) == 0) {\n                                F_SET(c, CURS_AFTER);\n                                goto dup2;\n                        }\n                }\n                /* Check previous key if at the beginning of the page. */\n                if (idx == 0 && h->prevpg != P_INVALID) {\n                        if ((pg = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL)\n                                return (RET_ERROR);\n                        e.page = pg;\n                        e.index = NEXTINDEX(pg) - 1;\n                        if (__bt_cmp(t, key, &e) == 0) {\n                                F_SET(c, CURS_BEFORE);\n                                goto dup1;\n                        }\n                        mpool_put(t->bt_mp, pg, 0);\n                }\n                /* Check next key if at the end of the page. */\n                if (idx == NEXTINDEX(h) - 1 && h->nextpg != P_INVALID) {\n                        if ((pg = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL)\n                                return (RET_ERROR);\n                        e.page = pg;\n                        e.index = 0;\n                        if (__bt_cmp(t, key, &e) == 0) {\n                                F_SET(c, CURS_AFTER);\ndup1:                           mpool_put(t->bt_mp, pg, 0);\ndup2:                           c->pg.pgno = e.page->pgno;\n                                c->pg.index = e.index;\n                                return (RET_SUCCESS);\n                        }\n                        mpool_put(t->bt_mp, pg, 0);\n                }\n        }\n        e.page = h;\n        e.index = idx;\n        if (curcopy || (status =\n            __bt_ret(t, &e, &c->key, &c->key, NULL, NULL, 1)) == RET_SUCCESS) {\n                F_SET(c, CURS_ACQUIRE);\n                return (RET_SUCCESS);\n        }\n        return (status);\n}\n\n/*\n * __bt_relink --\n *      Link around a deleted page.\n *\n * Parameters:\n *      t:      tree\n *      h:      page to be deleted\n */\n\nstatic int\n__bt_relink(BTREE *t, PAGE *h)\n{\n        PAGE *pg;\n\n        if (h->nextpg != P_INVALID) {\n                if ((pg = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL)\n                        return (RET_ERROR);\n                pg->prevpg = h->prevpg;\n                mpool_put(t->bt_mp, pg, MPOOL_DIRTY);\n        }\n        if (h->prevpg != P_INVALID) {\n                if ((pg = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL)\n                        return (RET_ERROR);\n                pg->nextpg = h->nextpg;\n                mpool_put(t->bt_mp, pg, MPOOL_DIRTY);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "db/btree/bt_get.c",
    "content": "/*      $OpenBSD: bt_get.c,v 1.8 2005/08/05 13:02:59 espie Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stddef.h>\n#include <stdio.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\n/*\n * __BT_GET -- Get a record from the btree.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key to find\n *      data:   data to return\n *      flag:   currently unused\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found.\n */\n\nint\n__bt_get(const DB *dbp, const DBT *key, DBT *data, unsigned int flags)\n{\n        BTREE *t;\n        EPG *e;\n        int exact, status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* Get currently doesn't take any flags. */\n        if (flags) {\n                errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        if ((e = __bt_search(t, key, &exact)) == NULL)\n                return (RET_ERROR);\n        if (!exact) {\n                mpool_put(t->bt_mp, e->page, 0);\n                return (RET_SPECIAL);\n        }\n\n        status = __bt_ret(t, e, NULL, NULL, data, &t->bt_rdata, 0);\n\n        /*\n         * If the user is doing concurrent access, we copied the\n         * key/data, toss the page.\n         */\n\n        if (F_ISSET(t, B_DB_LOCK))\n                mpool_put(t->bt_mp, e->page, 0);\n        else\n                t->bt_pinned = e->page;\n        return (status);\n}\n"
  },
  {
    "path": "db/btree/bt_open.c",
    "content": "/*      $OpenBSD: bt_open.c,v 1.19 2015/12/28 22:08:18 mmcc Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n/*\n * Implementation of btree access method for 4.4BSD.\n *\n * The design here was originally based on that of the btree access method\n * used in the Postgres database system at UC Berkeley.  This implementation\n * is wholly independent of the Postgres code.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/stat.h>\n\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#ifndef EFTYPE\n# define EFTYPE ENOTSUP\n#endif /* ifndef EFTYPE */\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\n#undef open\n\n#ifdef DEBUG\n# undef MINPSIZE\n# define MINPSIZE       128\n#endif /* ifdef DEBUG */\n\n#if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) )\n# define BIG_ENDIAN     4321\n# define LITTLE_ENDIAN  1234\n#endif /* if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) */\n\nstatic int byteorder(void);\nstatic int nroot(BTREE *);\nstatic int tmp(void);\n\n/*\n * __BT_OPEN -- Open a btree.\n *\n * Creates and fills a DB struct, and calls the routine that actually\n * opens the btree.\n *\n * Parameters:\n *      fname:  filename (NULL for in-memory trees)\n *      flags:  open flag bits\n *      mode:   open permission bits\n *      b:      BTREEINFO pointer\n *\n * Returns:\n *      NULL on failure, pointer to DB on success.\n *\n */\n\nDB *\n__bt_open(const char *fname, int flags, int mode, const BTREEINFO *openinfo,\n    int dflags)\n{\n        struct stat sb;\n        BTMETA m;\n        BTREE *t;\n        BTREEINFO b;\n        DB *dbp;\n        pgno_t ncache;\n        ssize_t nr;\n        int machine_lorder, saved_errno;\n\n        t = NULL;\n\n        /*\n         * Intention is to make sure all of the user's selections are okay\n         * here and then use them without checking.  Can't be complete, since\n         * we don't know the right page size, lorder or flags until the backing\n         * file is opened.  Also, the file's page size can cause the cachesize\n         * to change.\n         */\n\n        machine_lorder = byteorder();\n        if (openinfo) {\n                b = *openinfo;\n\n                /* Flags: R_DUP. */\n                if (b.flags & ~(R_DUP))\n                        goto einval;\n\n                /*\n                 * Page size must be indx_t aligned and >= MINPSIZE.  Default\n                 * page size is set farther on, based on the underlying file\n                 * transfer size.\n                 */\n\n                if (b.psize &&\n                    (b.psize < MINPSIZE || b.psize > MAX_PAGE_OFFSET + 1 ||\n                    b.psize & (sizeof(indx_t) - 1)))\n                        goto einval;\n\n                /* Minimum number of keys per page; absolute minimum is 2. */\n                if (b.minkeypage) {\n                        if (b.minkeypage < 2)\n                                goto einval;\n                } else\n                        b.minkeypage = DEFMINKEYPAGE;\n\n                /* If no comparison, use default comparison and prefix. */\n                if (b.compare == NULL) {\n                        b.compare = __bt_defcmp;\n                        if (b.prefix == NULL)\n                                b.prefix = __bt_defpfx;\n                }\n\n                if (b.lorder == 0)\n                        b.lorder = machine_lorder;\n        } else {\n                b.compare = __bt_defcmp;\n                b.cachesize = 0;\n                b.flags = 0;\n                b.lorder = machine_lorder;\n                b.minkeypage = DEFMINKEYPAGE;\n                b.prefix = __bt_defpfx;\n                b.psize = 0;\n        }\n\n        /* Check for the ubiquitous PDP-11. */\n        if (b.lorder != BIG_ENDIAN && b.lorder != LITTLE_ENDIAN)\n                goto einval;\n\n        /* Allocate and initialize DB and BTREE structures. */\n        if ((t = calloc(1, sizeof(BTREE))) == NULL)\n                goto err;\n        t->bt_fd = -1;                  /* Don't close unopened fd on error. */\n        t->bt_lorder = b.lorder;\n        t->bt_order = NOT;\n        t->bt_cmp = b.compare;\n        t->bt_pfx = b.prefix;\n        t->bt_rfd = -1;\n\n        if ((t->bt_dbp = dbp = calloc(1, sizeof(DB))) == NULL)\n                goto err;\n        if (t->bt_lorder != machine_lorder)\n                F_SET(t, B_NEEDSWAP);\n\n        dbp->type = DB_BTREE;\n        dbp->internal = t;\n        dbp->close = __bt_close;\n        dbp->del = __bt_delete;\n        dbp->fd = __bt_fd;\n        dbp->get = __bt_get;\n        dbp->put = __bt_put;\n        dbp->seq = __bt_seq;\n        dbp->sync = __bt_sync;\n\n        /*\n         * If no file name was supplied, this is an in-memory btree and we\n         * open a backing temporary file.  Otherwise, it's a disk-based tree.\n         */\n\n        if (fname) {\n                switch (flags & O_ACCMODE) {\n                case O_RDONLY:\n                        F_SET(t, B_RDONLY);\n                        break;\n                case O_RDWR:\n                        break;\n                case O_WRONLY:\n                default:\n                        goto einval;\n                }\n\n                if ((t->bt_fd = open(fname, flags | O_CLOEXEC, mode)) < 0)\n                        goto err;\n\n        } else {\n                if ((flags & O_ACCMODE) != O_RDWR)\n                        goto einval;\n                if ((t->bt_fd = tmp()) == -1)\n                        goto err;\n                F_SET(t, B_INMEM);\n        }\n\n        if (fstat(t->bt_fd, &sb))\n                goto err;\n        if (sb.st_size) {\n                if ((nr = read(t->bt_fd, &m, sizeof(BTMETA))) < 0)\n                        goto err;\n                if (nr != sizeof(BTMETA))\n                        goto eftype;\n\n                /*\n                 * Read in the meta-data.  This can change the notion of what\n                 * the lorder, page size and flags are, and, when the page size\n                 * changes, the cachesize value can change too.  If the user\n                 * specified the wrong byte order for an existing database, we\n                 * don't bother to return an error, we just clear the NEEDSWAP\n                 * bit.\n                 */\n\n                if (m.magic == BTREEMAGIC)\n                        F_CLR(t, B_NEEDSWAP);\n                else {\n                        F_SET(t, B_NEEDSWAP);\n                        M_32_SWAP(m.magic);\n                        M_32_SWAP(m.version);\n                        M_32_SWAP(m.psize);\n                        M_32_SWAP(m.free);\n                        M_32_SWAP(m.nrecs);\n                        M_32_SWAP(m.flags);\n                }\n                if (m.magic != BTREEMAGIC || m.version != BTREEVERSION)\n                        goto eftype;\n                if (m.psize < MINPSIZE || m.psize > MAX_PAGE_OFFSET + 1 ||\n                    m.psize & (sizeof(indx_t) - 1))\n                        goto eftype;\n                if (m.flags & ~SAVEMETA)\n                        goto eftype;\n                b.psize = m.psize;\n                F_SET(t, m.flags);\n                t->bt_free = m.free;\n                t->bt_nrecs = m.nrecs;\n        } else {\n\n                /*\n                 * Set the page size to the best value for I/O to this file.\n                 * Don't overflow the page offset type.\n                 */\n\n                if (b.psize == 0) {\n                        b.psize = sb.st_blksize;\n                        if (b.psize < MINPSIZE)\n                                b.psize = MINPSIZE;\n                        if (b.psize > MAX_PAGE_OFFSET + 1)\n                                b.psize = MAX_PAGE_OFFSET + 1;\n                }\n\n                /* Set flag if duplicates permitted. */\n                if (!(b.flags & R_DUP))\n                        F_SET(t, B_NODUPS);\n\n                t->bt_free = P_INVALID;\n                t->bt_nrecs = 0;\n                F_SET(t, B_METADIRTY);\n        }\n\n        t->bt_psize = b.psize;\n\n        /* Set the cache size; must be a multiple of the page size. */\n        if (b.cachesize && b.cachesize & (b.psize - 1))\n                b.cachesize += (~b.cachesize & (b.psize - 1)) + 1;\n        if (b.cachesize < b.psize * MINCACHE)\n                b.cachesize = b.psize * MINCACHE;\n\n        /* Calculate number of pages to cache. */\n        ncache = (b.cachesize + t->bt_psize - 1) / t->bt_psize;\n\n        /*\n         * The btree data structure requires that at least two keys can fit on\n         * a page, but other than that there's no fixed requirement.  The user\n         * specified a minimum number per page, and we translated that into the\n         * number of bytes a key/data pair can use before being placed on an\n         * overflow page.  This calculation includes the page header, the size\n         * of the index referencing the leaf item and the size of the leaf item\n         * structure.  Also, don't let the user specify a minkeypage such that\n         * a key/data pair won't fit even if both key and data are on overflow\n         * pages.\n         */\n\n        t->bt_ovflsize = (t->bt_psize - BTDATAOFF) / b.minkeypage -\n            (sizeof(indx_t) + NBLEAFDBT(0, 0));\n        if (t->bt_ovflsize < NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t))\n                t->bt_ovflsize =\n                    NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t);\n\n        /* Initialize the buffer pool. */\n        if ((t->bt_mp =\n            mpool_open(NULL, t->bt_fd, t->bt_psize, ncache)) == NULL)\n                goto err;\n        if (!F_ISSET(t, B_INMEM))\n                mpool_filter(t->bt_mp, __bt_pgin, __bt_pgout, t);\n\n        /* Create a root page if new tree. */\n        if (nroot(t) == RET_ERROR)\n                goto err;\n\n        /* Global flags. */\n        if (dflags & DB_LOCK)\n                F_SET(t, B_DB_LOCK);\n        if (dflags & DB_SHMEM)\n                F_SET(t, B_DB_SHMEM);\n        if (dflags & DB_TXN)\n                F_SET(t, B_DB_TXN);\n\n        return (dbp);\n\neinval: errno = EINVAL;\n        goto err;\n\neftype: errno = EFTYPE;\n        goto err;\n\nerr:    saved_errno = errno;\n        if (t) {\n                free(t->bt_dbp);\n                if (t->bt_fd != -1)\n                        (void)close(t->bt_fd);\n                free(t);\n        }\n        errno = saved_errno;\n        return (NULL);\n}\n\n/*\n * NROOT -- Create the root of a new tree.\n *\n * Parameters:\n *      t:      tree\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nstatic int\nnroot(BTREE *t)\n{\n        PAGE *meta, *root;\n        pgno_t npg;\n\n        if ((root = mpool_get(t->bt_mp, 1, 0)) != NULL) {\n                if (root->lower == 0 &&\n                    root->pgno == 0 &&\n                    root->linp[0] == 0) {\n                        mpool_delete(t->bt_mp, root);\n                        errno = EINVAL;\n                } else {\n                        mpool_put(t->bt_mp, root, 0);\n                        return (RET_SUCCESS);\n                }\n        }\n        if (errno != EINVAL)            /* It's OK to not exist. */\n                return (RET_ERROR);\n        errno = 0;\n\n        if ((meta = mpool_new(t->bt_mp, &npg, MPOOL_PAGE_NEXT)) == NULL)\n                return (RET_ERROR);\n\n        if ((root = mpool_new(t->bt_mp, &npg, MPOOL_PAGE_NEXT)) == NULL)\n                return (RET_ERROR);\n\n        if (npg != P_ROOT)\n                return (RET_ERROR);\n        root->pgno = npg;\n        root->prevpg = root->nextpg = P_INVALID;\n        root->lower = BTDATAOFF;\n        root->upper = t->bt_psize;\n        root->flags = P_BLEAF;\n        memset(meta, 0, t->bt_psize);\n        mpool_put(t->bt_mp, meta, MPOOL_DIRTY);\n        mpool_put(t->bt_mp, root, MPOOL_DIRTY);\n        return (RET_SUCCESS);\n}\n\nstatic int\ntmp(void)\n{\n        sigset_t set, oset;\n        int fd, len;\n        char *envtmp = NULL;\n        char path[PATH_MAX];\n\n        if (issetugid() == 0)\n                envtmp = getenv(\"TMPDIR\");\n        len = snprintf(path,\n            sizeof(path), \"%s/bt.XXXXXX\", envtmp ? envtmp : \"/tmp\");\n        if (len < 0 || len >= sizeof(path)) {\n                errno = ENAMETOOLONG;\n                return(-1);\n        }\n\n        (void)sigfillset(&set);\n        (void)sigprocmask(SIG_BLOCK, &set, &oset);\n#if defined(_AIX) || defined(__solaris__) || defined(__managarm__)\n        if ((fd = mkstemp(path)) != -1)\n#else\n        if ((fd = mkostemp(path, O_CLOEXEC)) != -1)\n#endif /* if defined(_AIX) || defined(__solaris__) */\n                (void)unlink(path);\n        (void)sigprocmask(SIG_SETMASK, &oset, NULL);\n        return(fd);\n}\n\nstatic int\nbyteorder(void)\n{\n        u_int32_t x;\n        unsigned char *p;\n\n        x = 0x01020304;\n        p = (unsigned char *)&x;\n        switch (*p) {\n        case 1:\n                return (BIG_ENDIAN);\n        case 4:\n                return (LITTLE_ENDIAN);\n        default:\n                return (0);\n        }\n}\n\nint\n__bt_fd(const DB *dbp)\n{\n        BTREE *t;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* In-memory database can't have a file descriptor. */\n        if (F_ISSET(t, B_INMEM)) {\n                errno = ENOENT;\n                return (-1);\n        }\n        return (t->bt_fd);\n}\n"
  },
  {
    "path": "db/btree/bt_overflow.c",
    "content": "/*      $OpenBSD: bt_overflow.c,v 1.11 2015/01/16 16:48:51 deraadt Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\n/*\n * Big key/data code.\n *\n * Big key and data entries are stored on linked lists of pages.  The initial\n * reference is byte string stored with the key or data and is the page number\n * and size.  The actual record is stored in a chain of pages linked by the\n * nextpg field of the PAGE header.\n *\n * The first page of the chain has a special property.  If the record is used\n * by an internal page, it cannot be deleted and the P_PRESERVE bit will be set\n * in the header.\n *\n * XXX\n * A single DBT is written to each chain, so a lot of space on the last page\n * is wasted.  This is a fairly major bug for some data sets.\n */\n\n/*\n * __OVFL_GET -- Get an overflow key/data item.\n *\n * Parameters:\n *      t:      tree\n *      p:      pointer to { pgno_t, u_int32_t }\n *      buf:    storage address\n *      bufsz:  storage size\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__ovfl_get(BTREE *t, void *p, size_t *ssz, void **buf, size_t *bufsz)\n{\n        PAGE *h;\n        pgno_t pg;\n        size_t nb, plen;\n        u_int32_t sz;\n        void *tp;\n\n        memmove(&pg, p, sizeof(pgno_t));\n        memmove(&sz, (char *)p + sizeof(pgno_t), sizeof(u_int32_t));\n        *ssz = sz;\n\n#ifdef DEBUG\n        if (pg == P_INVALID || sz == 0)\n                abort();\n#endif /* ifdef DEBUG */\n        /* Make the buffer bigger as necessary. */\n        if (*bufsz < sz) {\n                tp = realloc(*buf, sz);\n                if (tp == NULL)\n                        return (RET_ERROR);\n                *buf = tp;\n                *bufsz = sz;\n        }\n\n        /*\n         * Step through the linked list of pages, copying the data on each one\n         * into the buffer.  Never copy more than the data's length.\n         */\n\n        plen = t->bt_psize - BTDATAOFF;\n        for (p = *buf;; p = (char *)p + nb, pg = h->nextpg) {\n                if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                        return (RET_ERROR);\n\n                nb = MINIMUM(sz, plen);\n                memmove(p, (char *)h + BTDATAOFF, nb);\n                mpool_put(t->bt_mp, h, 0);\n\n                if ((sz -= nb) == 0)\n                        break;\n        }\n        return (RET_SUCCESS);\n}\n\n/*\n * __OVFL_PUT -- Store an overflow key/data item.\n *\n * Parameters:\n *      t:      tree\n *      data:   DBT to store\n *      pgno:   storage page number\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__ovfl_put(BTREE *t, const DBT *dbt, pgno_t *pg)\n{\n        PAGE *h, *last;\n        void *p;\n        pgno_t npg;\n        size_t nb, plen;\n        u_int32_t sz;\n\n        /*\n         * Allocate pages and copy the key/data record into them.  Store the\n         * number of the first page in the chain.\n         */\n\n        plen = t->bt_psize - BTDATAOFF;\n        for (last = NULL, p = dbt->data, sz = dbt->size;;\n            p = (char *)p + plen, last = h) {\n                if ((h = __bt_new(t, &npg)) == NULL)\n                        return (RET_ERROR);\n\n                h->pgno = npg;\n                h->nextpg = h->prevpg = P_INVALID;\n                h->flags = P_OVERFLOW;\n                h->lower = h->upper = 0;\n\n                nb = MINIMUM(sz, plen);\n                memmove((char *)h + BTDATAOFF, p, nb);\n\n                if (last) {\n                        last->nextpg = h->pgno;\n                        mpool_put(t->bt_mp, last, MPOOL_DIRTY);\n                } else\n                        *pg = h->pgno;\n\n                if ((sz -= nb) == 0) {\n                        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                        break;\n                }\n        }\n        return (RET_SUCCESS);\n}\n\n/*\n * __OVFL_DELETE -- Delete an overflow chain.\n *\n * Parameters:\n *      t:      tree\n *      p:      pointer to { pgno_t, u_int32_t }\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__ovfl_delete(BTREE *t, void *p)\n{\n        PAGE *h;\n        pgno_t pg;\n        size_t plen;\n        u_int32_t sz;\n\n        memmove(&pg, p, sizeof(pgno_t));\n        memmove(&sz, (char *)p + sizeof(pgno_t), sizeof(u_int32_t));\n\n#ifdef DEBUG\n        if (pg == P_INVALID || sz == 0)\n                abort();\n#endif /* ifdef DEBUG */\n        if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                return (RET_ERROR);\n\n        /* Don't delete chains used by internal pages. */\n        if (h->flags & P_PRESERVE) {\n                mpool_put(t->bt_mp, h, 0);\n                return (RET_SUCCESS);\n        }\n\n        /* Step through the chain, calling the free routine for each page. */\n        for (plen = t->bt_psize - BTDATAOFF;; sz -= plen) {\n                pg = h->nextpg;\n                __bt_free(t, h);\n                if (sz <= plen)\n                        break;\n                if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                        return (RET_ERROR);\n        }\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/btree/bt_page.c",
    "content": "/*      $OpenBSD: bt_page.c,v 1.9 2005/08/05 13:02:59 espie Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <stdio.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\n/*\n * __bt_free --\n *      Put a page on the freelist.\n *\n * Parameters:\n *      t:      tree\n *      h:      page to free\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n *\n * Side-effect:\n *      mpool_put's the page.\n */\n\nint\n__bt_free(BTREE *t, PAGE *h)\n{\n        /* Insert the page at the head of the free list. */\n        h->prevpg = P_INVALID;\n        h->nextpg = t->bt_free;\n        t->bt_free = h->pgno;\n        F_SET(t, B_METADIRTY);\n\n        /* Make sure the page gets written back. */\n        return (mpool_put(t->bt_mp, h, MPOOL_DIRTY));\n}\n\n/*\n * __bt_new --\n *      Get a new page, preferably from the freelist.\n *\n * Parameters:\n *      t:      tree\n *      npg:    storage for page number.\n *\n * Returns:\n *      Pointer to a page, NULL on error.\n */\n\nPAGE *\n__bt_new(BTREE *t, pgno_t *npg)\n{\n        PAGE *h;\n\n        if (t->bt_free != P_INVALID &&\n            (h = mpool_get(t->bt_mp, t->bt_free, 0)) != NULL) {\n                *npg = t->bt_free;\n                t->bt_free = h->nextpg;\n                F_SET(t, B_METADIRTY);\n                return (h);\n        }\n        return (mpool_new(t->bt_mp, npg, MPOOL_PAGE_NEXT));\n}\n"
  },
  {
    "path": "db/btree/bt_put.c",
    "content": "/*      $OpenBSD: bt_put.c,v 1.13 2005/08/05 13:02:59 espie Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic EPG *bt_fast(BTREE *, const DBT *, const DBT *, int *);\n\n/*\n * __BT_PUT -- Add a btree item to the tree.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key\n *      data:   data\n *      flag:   R_NOOVERWRITE\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key is already in the\n *      tree and R_NOOVERWRITE specified.\n */\n\nint\n__bt_put(const DB *dbp, DBT *key, const DBT *data, unsigned int flags)\n{\n        BTREE *t;\n        DBT tkey, tdata;\n        EPG *e;\n        PAGE *h;\n        indx_t idx, nxtindex;\n        pgno_t pg;\n        u_int32_t nbytes, size32;\n        int dflags, exact, status;\n        char *dest, db[NOVFLSIZE], kb[NOVFLSIZE];\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* Check for change to a read-only tree. */\n        if (F_ISSET(t, B_RDONLY)) {\n                errno = EPERM;\n                return (RET_ERROR);\n        }\n\n        switch (flags) {\n        case 0:\n        case R_NOOVERWRITE:\n                break;\n        case R_CURSOR:\n\n                /*\n                 * If flags is R_CURSOR, put the cursor.  Must already\n                 * have started a scan and not have already deleted it.\n                 */\n\n                if (F_ISSET(&t->bt_cursor, CURS_INIT) &&\n                    !F_ISSET(&t->bt_cursor,\n                        CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE))\n                        break;\n                /* FALLTHROUGH */\n        default:\n                errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        /*\n         * If the key/data pair won't fit on a page, store it on overflow\n         * pages.  Only put the key on the overflow page if the pair are\n         * still too big after moving the data to an overflow page.\n         *\n         * XXX\n         * If the insert fails later on, the overflow pages aren't recovered.\n         */\n\n        dflags = 0;\n        if (key->size + data->size > t->bt_ovflsize) {\n                if (key->size > t->bt_ovflsize) {\nstorekey:               if (__ovfl_put(t, key, &pg) == RET_ERROR)\n                                return (RET_ERROR);\n                        tkey.data = kb;\n                        tkey.size = NOVFLSIZE;\n                        memmove(kb, &pg, sizeof(pgno_t));\n                        size32 = key->size;\n                        memmove(kb + sizeof(pgno_t),\n                            &size32, sizeof(u_int32_t));\n                        dflags |= P_BIGKEY;\n                        key = &tkey;\n                }\n                if (key->size + data->size > t->bt_ovflsize) {\n                        if (__ovfl_put(t, data, &pg) == RET_ERROR)\n                                return (RET_ERROR);\n                        tdata.data = db;\n                        tdata.size = NOVFLSIZE;\n                        memmove(db, &pg, sizeof(pgno_t));\n                        size32 = data->size;\n                        memmove(db + sizeof(pgno_t),\n                            &size32, sizeof(u_int32_t));\n                        dflags |= P_BIGDATA;\n                        data = &tdata;\n                }\n                if (key->size + data->size > t->bt_ovflsize)\n                        goto storekey;\n        }\n\n        /* Replace the cursor. */\n        if (flags == R_CURSOR) {\n                if ((h = mpool_get(t->bt_mp, t->bt_cursor.pg.pgno, 0)) == NULL)\n                        return (RET_ERROR);\n                idx = t->bt_cursor.pg.index;\n                goto delete;\n        }\n\n        /*\n         * Find the key to delete, or, the location at which to insert.\n         * Bt_fast and __bt_search both pin the returned page.\n         */\n\n        if (t->bt_order == NOT || (e = bt_fast(t, key, data, &exact)) == NULL)\n                if ((e = __bt_search(t, key, &exact)) == NULL)\n                        return (RET_ERROR);\n        h = e->page;\n        idx = e->index;\n\n        /*\n         * Add the key/data pair to the tree.  If an identical key is already\n         * in the tree, and R_NOOVERWRITE is set, an error is returned.  If\n         * R_NOOVERWRITE is not set, the key is either added (if duplicates are\n         * permitted) or an error is returned.\n         */\n\n        switch (flags) {\n        case R_NOOVERWRITE:\n                if (!exact)\n                        break;\n                mpool_put(t->bt_mp, h, 0);\n                return (RET_SPECIAL);\n        default:\n                if (!exact || !F_ISSET(t, B_NODUPS))\n                        break;\n                /*\n                 * !!!\n                 * Note, the delete may empty the page, so we need to put a\n                 * new entry into the page immediately.\n                 */\n\ndelete:         if (__bt_dleaf(t, key, h, idx) == RET_ERROR) {\n                        mpool_put(t->bt_mp, h, 0);\n                        return (RET_ERROR);\n                }\n                break;\n        }\n\n        /*\n         * If not enough room, or the user has put a ceiling on the number of\n         * keys permitted in the page, split the page.  The split code will\n         * insert the key and data and unpin the current page.  If inserting\n         * into the offset array, shift the pointers up.\n         */\n\n        nbytes = NBLEAFDBT(key->size, data->size);\n        if (h->upper - h->lower < nbytes + sizeof(indx_t)) {\n                if ((status = __bt_split(t, h, key,\n                    data, dflags, nbytes, idx)) != RET_SUCCESS)\n                        return (status);\n                goto success;\n        }\n\n        if (idx < (nxtindex = NEXTINDEX(h)))\n                memmove(h->linp + idx + 1, h->linp + idx,\n                    (nxtindex - idx) * sizeof(indx_t));\n        h->lower += sizeof(indx_t);\n\n        h->linp[idx] = h->upper -= nbytes;\n        dest = (char *)h + h->upper;\n        WR_BLEAF(dest, key, data, dflags);\n\n        /* If the cursor is on this page, adjust it as necessary. */\n        if (F_ISSET(&t->bt_cursor, CURS_INIT) &&\n            !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) &&\n            t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index >= idx)\n                ++t->bt_cursor.pg.index;\n\n        if (t->bt_order == NOT) {\n                if (h->nextpg == P_INVALID) {\n                        if (idx == NEXTINDEX(h) - 1) {\n                                t->bt_order = FORWARD;\n                                t->bt_last.index = idx;\n                                t->bt_last.pgno = h->pgno;\n                        }\n                } else if (h->prevpg == P_INVALID) {\n                        if (idx == 0) {\n                                t->bt_order = BACK;\n                                t->bt_last.index = 0;\n                                t->bt_last.pgno = h->pgno;\n                        }\n                }\n        }\n\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n\nsuccess:\n        if (flags == R_SETCURSOR)\n                __bt_setcur(t, e->page->pgno, e->index);\n\n        F_SET(t, B_MODIFIED);\n        return (RET_SUCCESS);\n}\n\n#ifdef STATISTICS\nunsigned long bt_cache_hit, bt_cache_miss;\n#endif /* ifdef STATISTICS */\n\n/*\n * BT_FAST -- Do a quick check for sorted data.\n *\n * Parameters:\n *      t:      tree\n *      key:    key to insert\n *\n * Returns:\n *      EPG for new record or NULL if not found.\n */\n\nstatic EPG *\nbt_fast(BTREE *t, const DBT *key, const DBT *data, int *exactp)\n{\n        PAGE *h;\n        u_int32_t nbytes;\n        int cmp;\n\n        if ((h = mpool_get(t->bt_mp, t->bt_last.pgno, 0)) == NULL) {\n                t->bt_order = NOT;\n                return (NULL);\n        }\n        t->bt_cur.page = h;\n        t->bt_cur.index = t->bt_last.index;\n\n        /*\n         * If won't fit in this page or have too many keys in this page,\n         * have to search to get split stack.\n         */\n\n        nbytes = NBLEAFDBT(key->size, data->size);\n        if (h->upper - h->lower < nbytes + sizeof(indx_t))\n                goto miss;\n\n        if (t->bt_order == FORWARD) {\n                if (t->bt_cur.page->nextpg != P_INVALID)\n                        goto miss;\n                if (t->bt_cur.index != NEXTINDEX(h) - 1)\n                        goto miss;\n                if ((cmp = __bt_cmp(t, key, &t->bt_cur)) < 0)\n                        goto miss;\n                t->bt_last.index = cmp ? ++t->bt_cur.index : t->bt_cur.index;\n        } else {\n                if (t->bt_cur.page->prevpg != P_INVALID)\n                        goto miss;\n                if (t->bt_cur.index != 0)\n                        goto miss;\n                if ((cmp = __bt_cmp(t, key, &t->bt_cur)) > 0)\n                        goto miss;\n                t->bt_last.index = 0;\n        }\n        *exactp = cmp == 0;\n#ifdef STATISTICS\n        ++bt_cache_hit;\n#endif /* ifdef STATISTICS */\n        return (&t->bt_cur);\n\nmiss:\n#ifdef STATISTICS\n        ++bt_cache_miss;\n#endif /* ifdef STATISTICS */\n        t->bt_order = NOT;\n        mpool_put(t->bt_mp, h, 0);\n        return (NULL);\n}\n"
  },
  {
    "path": "db/btree/bt_search.c",
    "content": "/*      $OpenBSD: bt_search.c,v 1.10 2005/08/05 13:02:59 espie Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <stdio.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic int __bt_snext(BTREE *, PAGE *, const DBT *, int *);\nstatic int __bt_sprev(BTREE *, PAGE *, const DBT *, int *);\n\n/*\n * __bt_search --\n *      Search a btree for a key.\n *\n * Parameters:\n *      t:      tree to search\n *      key:    key to find\n *      exactp: pointer to exact match flag\n *\n * Returns:\n *      The EPG for matching record, if any, or the EPG for the location\n *      of the key, if it were inserted into the tree, is entered into\n *      the bt_cur field of the tree.  A pointer to the field is returned.\n */\n\nEPG *\n__bt_search(BTREE *t, const DBT *key, int *exactp)\n{\n        PAGE *h;\n        indx_t base, idx, lim;\n        pgno_t pg;\n        int cmp;\n\n        BT_CLR(t);\n        for (pg = P_ROOT;;) {\n                if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                        return (NULL);\n\n                /* Do a binary search on the current page. */\n                t->bt_cur.page = h;\n                for (base = 0, lim = NEXTINDEX(h); lim; lim >>= 1) {\n                        t->bt_cur.index = idx = base + (lim >> 1);\n                        if ((cmp = __bt_cmp(t, key, &t->bt_cur)) == 0) {\n                                if (h->flags & P_BLEAF) {\n                                        *exactp = 1;\n                                        return (&t->bt_cur);\n                                }\n                                goto next;\n                        }\n                        if (cmp > 0) {\n                                base = idx + 1;\n                                --lim;\n                        }\n                }\n\n                /*\n                 * If it's a leaf page, we're almost done.  If no duplicates\n                 * are allowed, or we have an exact match, we're done.  Else,\n                 * it's possible that there were matching keys on this page,\n                 * which later deleted, and we're on a page with no matches\n                 * while there are matches on other pages.  If at the start or\n                 * end of a page, check the adjacent page.\n                 */\n\n                if (h->flags & P_BLEAF) {\n                        if (!F_ISSET(t, B_NODUPS)) {\n                                if (base == 0 &&\n                                    h->prevpg != P_INVALID &&\n                                    __bt_sprev(t, h, key, exactp))\n                                        return (&t->bt_cur);\n                                if (base == NEXTINDEX(h) &&\n                                    h->nextpg != P_INVALID &&\n                                    __bt_snext(t, h, key, exactp))\n                                        return (&t->bt_cur);\n                        }\n                        *exactp = 0;\n                        t->bt_cur.index = base;\n                        return (&t->bt_cur);\n                }\n\n                /*\n                 * No match found.  Base is the smallest index greater than\n                 * key and may be zero or a last + 1 index.  If it's non-zero,\n                 * decrement by one, and record the internal page which should\n                 * be a parent page for the key.  If a split later occurs, the\n                 * inserted page will be to the right of the saved page.\n                 */\n\n                idx = base ? base - 1 : base;\n\nnext:           BT_PUSH(t, h->pgno, idx);\n                pg = GETBINTERNAL(h, idx)->pgno;\n                mpool_put(t->bt_mp, h, 0);\n        }\n}\n\n/*\n * __bt_snext --\n *      Check for an exact match after the key.\n *\n * Parameters:\n *      t:      tree\n *      h:      current page\n *      key:    key\n *      exactp: pointer to exact match flag\n *\n * Returns:\n *      If an exact match found.\n */\n\nstatic int\n__bt_snext(BTREE *t, PAGE *h, const DBT *key, int *exactp)\n{\n        EPG e;\n\n        /*\n         * Get the next page.  The key is either an exact\n         * match, or not as good as the one we already have.\n         */\n\n        if ((e.page = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL)\n                return (0);\n        e.index = 0;\n        if (__bt_cmp(t, key, &e) == 0) {\n                mpool_put(t->bt_mp, h, 0);\n                t->bt_cur = e;\n                *exactp = 1;\n                return (1);\n        }\n        mpool_put(t->bt_mp, e.page, 0);\n        return (0);\n}\n\n/*\n * __bt_sprev --\n *      Check for an exact match before the key.\n *\n * Parameters:\n *      t:      tree\n *      h:      current page\n *      key:    key\n *      exactp: pointer to exact match flag\n *\n * Returns:\n *      If an exact match found.\n */\n\nstatic int\n__bt_sprev(BTREE *t, PAGE *h, const DBT *key, int *exactp)\n{\n        EPG e;\n\n        /*\n         * Get the previous page.  The key is either an exact\n         * match, or not as good as the one we already have.\n         */\n\n        if ((e.page = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL)\n                return (0);\n        e.index = NEXTINDEX(e.page) - 1;\n        if (__bt_cmp(t, key, &e) == 0) {\n                mpool_put(t->bt_mp, h, 0);\n                t->bt_cur = e;\n                *exactp = 1;\n                return (1);\n        }\n        mpool_put(t->bt_mp, e.page, 0);\n        return (0);\n}\n"
  },
  {
    "path": "db/btree/bt_seq.c",
    "content": "/*      $OpenBSD: bt_seq.c,v 1.12 2022/12/27 17:10:06 jmc Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic int __bt_first(BTREE *, const DBT *, EPG *, int *);\nstatic int __bt_seqadv(BTREE *, EPG *, int);\nstatic int __bt_seqset(BTREE *, EPG *, DBT *, int);\n\n/*\n * Sequential scan support.\n *\n * The tree can be scanned sequentially, starting from either end of the\n * tree or from any specific key.  A scan request before any scanning is\n * done is initialized as starting from the least node.\n */\n\n/*\n * __bt_seq --\n *      Btree sequential scan interface.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key for positioning and return value\n *      data:   data return value\n *      flags:  R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV.\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key.\n */\n\nint\n__bt_seq(const DB *dbp, DBT *key, DBT *data, unsigned int flags)\n{\n        BTREE *t;\n        EPG e;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /*\n         * If scan uninitialized as yet, or starting at a specific record, set\n         * the scan to a specific key.  Both __bt_seqset and __bt_seqadv pin\n         * the page the cursor references if they're successful.\n         */\n\n        switch (flags) {\n        case R_NEXT:\n        case R_PREV:\n                if (F_ISSET(&t->bt_cursor, CURS_INIT)) {\n                        status = __bt_seqadv(t, &e, flags);\n                        break;\n                }\n                /* FALLTHROUGH */\n        case R_FIRST:\n        case R_LAST:\n        case R_CURSOR:\n                status = __bt_seqset(t, &e, key, flags);\n                break;\n        default:\n                errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        if (status == RET_SUCCESS) {\n                __bt_setcur(t, e.page->pgno, e.index);\n\n                status =\n                    __bt_ret(t, &e, key, &t->bt_rkey, data, &t->bt_rdata, 0);\n\n                /*\n                 * If the user is doing concurrent access, we copied the\n                 * key/data, toss the page.\n                 */\n\n                if (F_ISSET(t, B_DB_LOCK))\n                        mpool_put(t->bt_mp, e.page, 0);\n                else\n                        t->bt_pinned = e.page;\n        }\n        return (status);\n}\n\n/*\n * __bt_seqset --\n *      Set the sequential scan to a specific key.\n *\n * Parameters:\n *      t:      tree\n *      ep:     storage for returned key\n *      key:    key for initial scan position\n *      flags:  R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV\n *\n * Side effects:\n *      Pins the page the cursor references.\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key.\n */\n\nstatic int\n__bt_seqset(BTREE *t, EPG *ep, DBT *key, int flags)\n{\n        PAGE *h;\n        pgno_t pg;\n        int exact;\n\n        /*\n         * Find the first, last or specific key in the tree and point the\n         * cursor at it.  The cursor may not be moved until a new key has\n         * been found.\n         */\n\n        switch (flags) {\n        case R_CURSOR:                          /* Keyed scan. */\n\n                /*\n                 * Find the first instance of the key or the smallest key\n                 * which is greater than or equal to the specified key.\n                 */\n\n                if (key->data == NULL || key->size == 0) {\n                        errno = EINVAL;\n                        return (RET_ERROR);\n                }\n                return (__bt_first(t, key, ep, &exact));\n        case R_FIRST:                           /* First record. */\n        case R_NEXT:\n                /* Walk down the left-hand side of the tree. */\n                for (pg = P_ROOT;;) {\n                        if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                                return (RET_ERROR);\n\n                        /* Check for an empty tree. */\n                        if (NEXTINDEX(h) == 0) {\n                                mpool_put(t->bt_mp, h, 0);\n                                return (RET_SPECIAL);\n                        }\n\n                        if (h->flags & (P_BLEAF | P_RLEAF))\n                                break;\n                        pg = GETBINTERNAL(h, 0)->pgno;\n                        mpool_put(t->bt_mp, h, 0);\n                }\n                ep->page = h;\n                ep->index = 0;\n                break;\n        case R_LAST:                            /* Last record. */\n        case R_PREV:\n                /* Walk down the right-hand side of the tree. */\n                for (pg = P_ROOT;;) {\n                        if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                                return (RET_ERROR);\n\n                        /* Check for an empty tree. */\n                        if (NEXTINDEX(h) == 0) {\n                                mpool_put(t->bt_mp, h, 0);\n                                return (RET_SPECIAL);\n                        }\n\n                        if (h->flags & (P_BLEAF | P_RLEAF))\n                                break;\n                        pg = GETBINTERNAL(h, NEXTINDEX(h) - 1)->pgno;\n                        mpool_put(t->bt_mp, h, 0);\n                }\n\n                ep->page = h;\n                ep->index = NEXTINDEX(h) - 1;\n                break;\n        }\n        return (RET_SUCCESS);\n}\n\n/*\n * __bt_seqadvance --\n *      Advance the sequential scan.\n *\n * Parameters:\n *      t:      tree\n *      flags:  R_NEXT, R_PREV\n *\n * Side effects:\n *      Pins the page the new key/data record is on.\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key.\n */\n\nstatic int\n__bt_seqadv(BTREE *t, EPG *ep, int flags)\n{\n        CURSOR *c;\n        PAGE *h;\n        indx_t idx;\n        pgno_t pg;\n        int exact;\n\n        /*\n         * There are a couple of states that we can be in.  The cursor has\n         * been initialized by the time we get here, but that's all we know.\n         */\n\n        c = &t->bt_cursor;\n\n        /*\n         * The cursor was deleted where there weren't any duplicate records,\n         * so the key was saved.  Find out where that key would go in the\n         * current tree.  It doesn't matter if the returned key is an exact\n         * match or not -- if it's an exact match, the record was added after\n         * the delete so we can just return it.  If not, as long as there's\n         * a record there, return it.\n         */\n\n        if (F_ISSET(c, CURS_ACQUIRE))\n                return (__bt_first(t, &c->key, ep, &exact));\n\n        /* Get the page referenced by the cursor. */\n        if ((h = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL)\n                return (RET_ERROR);\n\n        /*\n         * Find the next/previous record in the tree and point the cursor at\n         * it.  The cursor may not be moved until a new key has been found.\n         */\n\n        switch (flags) {\n        case R_NEXT:                    /* Next record. */\n\n                /*\n                 * The cursor was deleted in duplicate records, and moved\n                 * forward to a record that has yet to be returned.  Clear\n                 * that flag, and return the record.\n                 */\n\n                if (F_ISSET(c, CURS_AFTER))\n                        goto usecurrent;\n                idx = c->pg.index;\n                if (++idx == NEXTINDEX(h)) {\n                        pg = h->nextpg;\n                        mpool_put(t->bt_mp, h, 0);\n                        if (pg == P_INVALID)\n                                return (RET_SPECIAL);\n                        if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                                return (RET_ERROR);\n                        idx = 0;\n                }\n                break;\n        case R_PREV:                    /* Previous record. */\n\n                /*\n                 * The cursor was deleted in duplicate records, and moved\n                 * backward to a record that has yet to be returned.  Clear\n                 * that flag, and return the record.\n                 */\n\n                if (F_ISSET(c, CURS_BEFORE)) {\nusecurrent:             F_CLR(c, CURS_AFTER | CURS_BEFORE);\n                        ep->page = h;\n                        ep->index = c->pg.index;\n                        return (RET_SUCCESS);\n                }\n                idx = c->pg.index;\n                if (idx == 0) {\n                        pg = h->prevpg;\n                        mpool_put(t->bt_mp, h, 0);\n                        if (pg == P_INVALID)\n                                return (RET_SPECIAL);\n                        if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                                return (RET_ERROR);\n                        idx = NEXTINDEX(h) - 1;\n                } else\n                        --idx;\n                break;\n        }\n\n        ep->page = h;\n        ep->index = idx;\n        return (RET_SUCCESS);\n}\n\n/*\n * __bt_first --\n *      Find the first entry.\n *\n * Parameters:\n *      t:      the tree\n *    key:      the key\n *  erval:      return EPG\n * exactp:      pointer to exact match flag\n *\n * Returns:\n *      The first entry in the tree greater than or equal to key,\n *      or RET_SPECIAL if no such key exists.\n */\n\nstatic int\n__bt_first(BTREE *t, const DBT *key, EPG *erval, int *exactp)\n{\n        PAGE *h;\n        EPG *ep, save;\n        pgno_t pg;\n\n        /*\n         * Find any matching record; __bt_search pins the page.\n         *\n         * If it's an exact match and duplicates are possible, walk backwards\n         * in the tree until we find the first one.  Otherwise, make sure it's\n         * a valid key (__bt_search may return an index just past the end of a\n         * page) and return it.\n         */\n\n        if ((ep = __bt_search(t, key, exactp)) == NULL)\n                return (0);\n        if (*exactp) {\n                if (F_ISSET(t, B_NODUPS)) {\n                        *erval = *ep;\n                        return (RET_SUCCESS);\n                }\n\n                /*\n                 * Walk backwards, as long as the entry matches and there are\n                 * keys left in the tree.  Save a copy of each match in case\n                 * we go too far.\n                 */\n\n                save = *ep;\n                h = ep->page;\n                do {\n                        if (save.page->pgno != ep->page->pgno) {\n                                mpool_put(t->bt_mp, save.page, 0);\n                                save = *ep;\n                        } else\n                                save.index = ep->index;\n\n                        /*\n                         * Don't unpin the page the last (or original) match\n                         * was on, but make sure it's unpinned if an error\n                         * occurs.\n                         */\n\n                        if (ep->index == 0) {\n                                if (h->prevpg == P_INVALID)\n                                        break;\n                                if (h->pgno != save.page->pgno)\n                                        mpool_put(t->bt_mp, h, 0);\n                                if ((h = mpool_get(t->bt_mp,\n                                    h->prevpg, 0)) == NULL) {\n                                        if (h->pgno == save.page->pgno)\n                                                mpool_put(t->bt_mp,\n                                                    save.page, 0);\n                                        return (RET_ERROR);\n                                }\n                                ep->page = h;\n                                ep->index = NEXTINDEX(h);\n                        }\n                        --ep->index;\n                } while (__bt_cmp(t, key, ep) == 0);\n\n                /*\n                 * Reach here with the last page that was looked at pinned,\n                 * which may or may not be the same as the last (or original)\n                 * match page.  If it's not useful, release it.\n                 */\n\n                if (h->pgno != save.page->pgno)\n                        mpool_put(t->bt_mp, h, 0);\n\n                *erval = save;\n                return (RET_SUCCESS);\n        }\n\n        /* If at the end of a page, find the next entry. */\n        if (ep->index == NEXTINDEX(ep->page)) {\n                h = ep->page;\n                pg = h->nextpg;\n                mpool_put(t->bt_mp, h, 0);\n                if (pg == P_INVALID)\n                        return (RET_SPECIAL);\n                if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                        return (RET_ERROR);\n                ep->index = 0;\n                ep->page = h;\n        }\n        *erval = *ep;\n        return (RET_SUCCESS);\n}\n\n/*\n * __bt_setcur --\n *      Set the cursor to an entry in the tree.\n *\n * Parameters:\n *      t:      the tree\n *   pgno:      page number\n *    idx:      page index\n */\n\nvoid\n__bt_setcur(BTREE *t, pgno_t pgno, unsigned int idx)\n{\n        /* Lose any already deleted key. */\n        if (t->bt_cursor.key.data != NULL) {\n                free(t->bt_cursor.key.data);\n                t->bt_cursor.key.size = 0;\n                t->bt_cursor.key.data = NULL;\n        }\n        F_CLR(&t->bt_cursor, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE);\n\n        /* Update the cursor. */\n        t->bt_cursor.pg.pgno = pgno;\n        t->bt_cursor.pg.index = idx;\n        F_SET(&t->bt_cursor, CURS_INIT);\n}\n"
  },
  {
    "path": "db/btree/bt_split.c",
    "content": "/*      $OpenBSD: bt_split.c,v 1.13 2005/08/05 13:03:00 espie Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\nstatic int       bt_broot(BTREE *, PAGE *, PAGE *, PAGE *);\nstatic PAGE     *bt_page(BTREE *, PAGE *, PAGE **, PAGE **, indx_t *, size_t);\nstatic int       bt_preserve(BTREE *, pgno_t);\nstatic PAGE     *bt_psplit(BTREE *, PAGE *, PAGE *, PAGE *, indx_t *, size_t);\nstatic PAGE     *bt_root(BTREE *, PAGE *, PAGE **, PAGE **, indx_t *, size_t);\nstatic int       bt_rroot(BTREE *, PAGE *, PAGE *, PAGE *);\nstatic recno_t   rec_total(PAGE *);\n\n#ifdef STATISTICS\nunsigned long  bt_rootsplit, bt_split, bt_sortsplit, bt_pfxsaved;\n#endif /* ifdef STATISTICS */\n\n/*\n * __BT_SPLIT -- Split the tree.\n *\n * Parameters:\n *      t:      tree\n *      sp:     page to split\n *      key:    key to insert\n *      data:   data to insert\n *      flags:  BIGKEY/BIGDATA flags\n *      ilen:   insert length\n *      skip:   index to leave open\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__bt_split(BTREE *t, PAGE *sp, const DBT *key, const DBT *data, int flags,\n    size_t ilen, u_int32_t argskip)\n{\n        BINTERNAL *bi;\n        BLEAF *bl, *tbl;\n        DBT a, b;\n        EPGNO *parent;\n        PAGE *h, *l, *r, *lchild, *rchild;\n        indx_t nxtindex;\n        u_int16_t skip;\n        u_int32_t n, nbytes, nksize;\n        int parentsplit;\n        char *dest;\n\n        /*\n         * Split the page into two pages, l and r.  The split routines return\n         * a pointer to the page into which the key should be inserted and with\n         * skip set to the offset which should be used.  Additionally, l and r\n         * are pinned.\n         */\n\n        skip = argskip;\n        h = sp->pgno == P_ROOT ?\n            bt_root(t, sp, &l, &r, &skip, ilen) :\n            bt_page(t, sp, &l, &r, &skip, ilen);\n        if (h == NULL)\n                return (RET_ERROR);\n\n        /*\n         * Insert the new key/data pair into the leaf page.  (Key inserts\n         * always cause a leaf page to split first.)\n         */\n\n        h->linp[skip] = h->upper -= ilen;\n        dest = (char *)h + h->upper;\n        if (F_ISSET(t, R_RECNO))\n                WR_RLEAF(dest, data, flags)\n        else\n                WR_BLEAF(dest, key, data, flags)\n\n        /* If the root page was split, make it look right. */\n        if (sp->pgno == P_ROOT &&\n            (F_ISSET(t, R_RECNO) ?\n            bt_rroot(t, sp, l, r) : bt_broot(t, sp, l, r)) == RET_ERROR)\n                goto err2;\n\n        /*\n         * Now we walk the parent page stack -- a LIFO stack of the pages that\n         * were traversed when we searched for the page that split.  Each stack\n         * entry is a page number and a page index offset.  The offset is for\n         * the page traversed on the search.  We've just split a page, so we\n         * have to insert a new key into the parent page.\n         *\n         * If the insert into the parent page causes it to split, may have to\n         * continue splitting all the way up the tree.  We stop if the root\n         * splits or the page inserted into didn't have to split to hold the\n         * new key.  Some algorithms replace the key for the old page as well\n         * as the new page.  We don't, as there's no reason to believe that the\n         * first key on the old page is any better than the key we have, and,\n         * in the case of a key being placed at index 0 causing the split, the\n         * key is unavailable.\n         *\n         * There are a maximum of 5 pages pinned at any time.  We keep the left\n         * and right pages pinned while working on the parent.   The 5 are the\n         * two children, left parent and right parent (when the parent splits)\n         * and the root page or the overflow key page when calling bt_preserve.\n         * This code must make sure that all pins are released other than the\n         * root page or overflow page which is unlocked elsewhere.\n         */\n\n        while ((parent = BT_POP(t)) != NULL) {\n                lchild = l;\n                rchild = r;\n\n                /* Get the parent page. */\n                if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL)\n                        goto err2;\n\n                /*\n                 * The new key goes ONE AFTER the index, because the split\n                 * was to the right.\n                 */\n\n                skip = parent->index + 1;\n\n                /*\n                 * Calculate the space needed on the parent page.\n                 *\n                 * Prefix trees: space hack when inserting into BINTERNAL\n                 * pages.  Retain only what's needed to distinguish between\n                 * the new entry and the LAST entry on the page to its left.\n                 * If the keys compare equal, retain the entire key.  Note,\n                 * we don't touch overflow keys, and the entire key must be\n                 * retained for the next-to-left most key on the leftmost\n                 * page of each level, or the search will fail.  Applicable\n                 * ONLY to internal pages that have leaf pages as children.\n                 * Further reduction of the key between pairs of internal\n                 * pages loses too much information.\n                 */\n\n                switch (rchild->flags & P_TYPE) {\n                case P_BINTERNAL:\n                        bi = GETBINTERNAL(rchild, 0);\n                        nbytes = NBINTERNAL(bi->ksize);\n                        break;\n                case P_BLEAF:\n                        bl = GETBLEAF(rchild, 0);\n                        nbytes = NBINTERNAL(bl->ksize);\n                        if (t->bt_pfx && !(bl->flags & P_BIGKEY) &&\n                            (h->prevpg != P_INVALID || skip > 1)) {\n                                tbl = GETBLEAF(lchild, NEXTINDEX(lchild) - 1);\n                                a.size = tbl->ksize;\n                                a.data = tbl->bytes;\n                                b.size = bl->ksize;\n                                b.data = bl->bytes;\n                                nksize = t->bt_pfx(&a, &b);\n                                n = NBINTERNAL(nksize);\n                                if (n < nbytes) {\n#ifdef STATISTICS\n                                        bt_pfxsaved += nbytes - n;\n#endif /* ifdef STATISTICS */\n                                        nbytes = n;\n                                } else\n                                        nksize = 0;\n                        } else\n                                nksize = 0;\n                        break;\n                case P_RINTERNAL:\n                case P_RLEAF:\n                        nbytes = NRINTERNAL;\n                        break;\n                default:\n                        abort();\n                }\n\n                /* Split the parent page if necessary or shift the indices. */\n                if (h->upper - h->lower < nbytes + sizeof(indx_t)) {\n                        sp = h;\n                        h = h->pgno == P_ROOT ?\n                            bt_root(t, h, &l, &r, &skip, nbytes) :\n                            bt_page(t, h, &l, &r, &skip, nbytes);\n                        if (h == NULL)\n                                goto err1;\n                        parentsplit = 1;\n                } else {\n                        if (skip < (nxtindex = NEXTINDEX(h)))\n                                memmove(h->linp + skip + 1, h->linp + skip,\n                                    (nxtindex - skip) * sizeof(indx_t));\n                        h->lower += sizeof(indx_t);\n                        parentsplit = 0;\n                }\n\n                /* Insert the key into the parent page. */\n                switch (rchild->flags & P_TYPE) {\n                case P_BINTERNAL:\n                        h->linp[skip] = h->upper -= nbytes;\n                        dest = (char *)h + h->linp[skip];\n                        memmove(dest, bi, nbytes);\n                        ((BINTERNAL *)dest)->pgno = rchild->pgno;\n                        break;\n                case P_BLEAF:\n                        h->linp[skip] = h->upper -= nbytes;\n                        dest = (char *)h + h->linp[skip];\n                        WR_BINTERNAL(dest, nksize ? nksize : bl->ksize,\n                            rchild->pgno, bl->flags & P_BIGKEY);\n                        memmove(dest, bl->bytes, nksize ? nksize : bl->ksize);\n                        if (bl->flags & P_BIGKEY &&\n                            bt_preserve(t, *(pgno_t *)bl->bytes) == RET_ERROR)\n                                goto err1;\n                        break;\n                case P_RINTERNAL:\n\n                        /*\n                         * Update the left page count.  If split\n                         * added at index 0, fix the correct page.\n                         */\n\n                        if (skip > 0)\n                                dest = (char *)h + h->linp[skip - 1];\n                        else\n                                dest = (char *)l + l->linp[NEXTINDEX(l) - 1];\n                        ((RINTERNAL *)dest)->nrecs = rec_total(lchild);\n                        ((RINTERNAL *)dest)->pgno = lchild->pgno;\n\n                        /* Update the right page count. */\n                        h->linp[skip] = h->upper -= nbytes;\n                        dest = (char *)h + h->linp[skip];\n                        ((RINTERNAL *)dest)->nrecs = rec_total(rchild);\n                        ((RINTERNAL *)dest)->pgno = rchild->pgno;\n                        break;\n                case P_RLEAF:\n\n                        /*\n                         * Update the left page count.  If split\n                         * added at index 0, fix the correct page.\n                         */\n\n                        if (skip > 0)\n                                dest = (char *)h + h->linp[skip - 1];\n                        else\n                                dest = (char *)l + l->linp[NEXTINDEX(l) - 1];\n                        ((RINTERNAL *)dest)->nrecs = NEXTINDEX(lchild);\n                        ((RINTERNAL *)dest)->pgno = lchild->pgno;\n\n                        /* Update the right page count. */\n                        h->linp[skip] = h->upper -= nbytes;\n                        dest = (char *)h + h->linp[skip];\n                        ((RINTERNAL *)dest)->nrecs = NEXTINDEX(rchild);\n                        ((RINTERNAL *)dest)->pgno = rchild->pgno;\n                        break;\n                default:\n                        abort();\n                }\n\n                /* Unpin the held pages. */\n                if (!parentsplit) {\n                        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                        break;\n                }\n\n                /* If the root page was split, make it look right. */\n                if (sp->pgno == P_ROOT &&\n                    (F_ISSET(t, R_RECNO) ?\n                    bt_rroot(t, sp, l, r) : bt_broot(t, sp, l, r)) == RET_ERROR)\n                        goto err1;\n\n                mpool_put(t->bt_mp, lchild, MPOOL_DIRTY);\n                mpool_put(t->bt_mp, rchild, MPOOL_DIRTY);\n        }\n\n        /* Unpin the held pages. */\n        mpool_put(t->bt_mp, l, MPOOL_DIRTY);\n        mpool_put(t->bt_mp, r, MPOOL_DIRTY);\n\n        /* Clear any pages left on the stack. */\n        return (RET_SUCCESS);\n\n        /*\n         * If something fails in the above loop we were already walking back\n         * up the tree and the tree is now inconsistent.  Nothing much we can\n         * do about it but release any memory we're holding.\n         */\n\nerr1:   mpool_put(t->bt_mp, lchild, MPOOL_DIRTY);\n        mpool_put(t->bt_mp, rchild, MPOOL_DIRTY);\n\nerr2:   mpool_put(t->bt_mp, l, 0);\n        mpool_put(t->bt_mp, r, 0);\n        __dbpanic(t->bt_dbp);\n        return (RET_ERROR);\n}\n\n/*\n * BT_PAGE -- Split a non-root page of a btree.\n *\n * Parameters:\n *      t:      tree\n *      h:      root page\n *      lp:     pointer to left page pointer\n *      rp:     pointer to right page pointer\n *      skip:   pointer to index to leave open\n *      ilen:   insert length\n *\n * Returns:\n *      Pointer to page in which to insert or NULL on error.\n */\n\nstatic PAGE *\nbt_page(BTREE *t, PAGE *h, PAGE **lp, PAGE **rp, indx_t *skip, size_t ilen)\n{\n        PAGE *l, *r, *tp;\n        pgno_t npg;\n\n#ifdef STATISTICS\n        ++bt_split;\n#endif /* ifdef STATISTICS */\n        /* Put the new right page for the split into place. */\n        if ((r = __bt_new(t, &npg)) == NULL)\n                return (NULL);\n        r->pgno = npg;\n        r->lower = BTDATAOFF;\n        r->upper = t->bt_psize;\n        r->nextpg = h->nextpg;\n        r->prevpg = h->pgno;\n        r->flags = h->flags & P_TYPE;\n\n        /*\n         * If we're splitting the last page on a level because we're appending\n         * a key to it (skip is NEXTINDEX()), it's likely that the data is\n         * sorted.  Adding an empty page on the side of the level is less work\n         * and can push the fill factor much higher than normal.  If we're\n         * wrong it's no big deal, we'll just do the split the right way next\n         * time.  It may look like it's equally easy to do a similar hack for\n         * reverse sorted data, that is, split the tree left, but it's not.\n         * Don't even try.\n         */\n\n        if (h->nextpg == P_INVALID && *skip == NEXTINDEX(h)) {\n#ifdef STATISTICS\n                ++bt_sortsplit;\n#endif /* ifdef STATISTICS */\n                h->nextpg = r->pgno;\n                r->lower = BTDATAOFF + sizeof(indx_t);\n                *skip = 0;\n                *lp = h;\n                *rp = r;\n                return (r);\n        }\n\n        /* Put the new left page for the split into place. */\n        if ((l = (PAGE *)malloc(t->bt_psize)) == NULL) {\n                mpool_put(t->bt_mp, r, 0);\n                return (NULL);\n        }\n        memset(l, 0xff, t->bt_psize);\n        l->pgno = h->pgno;\n        l->nextpg = r->pgno;\n        l->prevpg = h->prevpg;\n        l->lower = BTDATAOFF;\n        l->upper = t->bt_psize;\n        l->flags = h->flags & P_TYPE;\n\n        /* Fix up the previous pointer of the page after the split page. */\n        if (h->nextpg != P_INVALID) {\n                if ((tp = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) {\n                        free(l);\n                        /* XXX mpool_free(t->bt_mp, r->pgno); */\n                        return (NULL);\n                }\n                tp->prevpg = r->pgno;\n                mpool_put(t->bt_mp, tp, MPOOL_DIRTY);\n        }\n\n        /*\n         * Split right.  The key/data pairs aren't sorted in the btree page so\n         * it's simpler to copy the data from the split page onto two new pages\n         * instead of copying half the data to the right page and compacting\n         * the left page in place.  Since the left page can't change, we have\n         * to swap the original and the allocated left page after the split.\n         */\n\n        tp = bt_psplit(t, h, l, r, skip, ilen);\n\n        /* Move the new left page onto the old left page. */\n        memmove(h, l, t->bt_psize);\n        if (tp == l)\n                tp = h;\n        free(l);\n\n        *lp = h;\n        *rp = r;\n        return (tp);\n}\n\n/*\n * BT_ROOT -- Split the root page of a btree.\n *\n * Parameters:\n *      t:      tree\n *      h:      root page\n *      lp:     pointer to left page pointer\n *      rp:     pointer to right page pointer\n *      skip:   pointer to index to leave open\n *      ilen:   insert length\n *\n * Returns:\n *      Pointer to page in which to insert or NULL on error.\n */\n\nstatic PAGE *\nbt_root(BTREE *t, PAGE *h, PAGE **lp, PAGE **rp, indx_t *skip, size_t ilen)\n{\n        PAGE *l, *r, *tp;\n        pgno_t lnpg, rnpg;\n\n#ifdef STATISTICS\n        ++bt_split;\n        ++bt_rootsplit;\n#endif /* ifdef STATISTICS */\n        /* Put the new left and right pages for the split into place. */\n        if ((l = __bt_new(t, &lnpg)) == NULL ||\n            (r = __bt_new(t, &rnpg)) == NULL)\n                return (NULL);\n        l->pgno = lnpg;\n        r->pgno = rnpg;\n        l->nextpg = r->pgno;\n        r->prevpg = l->pgno;\n        l->prevpg = r->nextpg = P_INVALID;\n        l->lower = r->lower = BTDATAOFF;\n        l->upper = r->upper = t->bt_psize;\n        l->flags = r->flags = h->flags & P_TYPE;\n\n        /* Split the root page. */\n        tp = bt_psplit(t, h, l, r, skip, ilen);\n\n        *lp = l;\n        *rp = r;\n        return (tp);\n}\n\n/*\n * BT_RROOT -- Fix up the recno root page after it has been split.\n *\n * Parameters:\n *      t:      tree\n *      h:      root page\n *      l:      left page\n *      r:      right page\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nstatic int\nbt_rroot(BTREE *t, PAGE *h, PAGE *l, PAGE *r)\n{\n        char *dest;\n\n        /* Insert the left and right keys, set the header information. */\n        h->linp[0] = h->upper = t->bt_psize - NRINTERNAL;\n        dest = (char *)h + h->upper;\n        WR_RINTERNAL(dest,\n            l->flags & P_RLEAF ? NEXTINDEX(l) : rec_total(l), l->pgno);\n\n        h->linp[1] = h->upper -= NRINTERNAL;\n        dest = (char *)h + h->upper;\n        WR_RINTERNAL(dest,\n            r->flags & P_RLEAF ? NEXTINDEX(r) : rec_total(r), r->pgno);\n\n        h->lower = BTDATAOFF + 2 * sizeof(indx_t);\n\n        /* Unpin the root page, set to recno internal page. */\n        h->flags &= ~P_TYPE;\n        h->flags |= P_RINTERNAL;\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n\n        return (RET_SUCCESS);\n}\n\n/*\n * BT_BROOT -- Fix up the btree root page after it has been split.\n *\n * Parameters:\n *      t:      tree\n *      h:      root page\n *      l:      left page\n *      r:      right page\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nstatic int\nbt_broot(BTREE *t, PAGE *h, PAGE *l, PAGE *r)\n{\n        BINTERNAL *bi;\n        BLEAF *bl;\n        u_int32_t nbytes;\n        char *dest;\n\n        /*\n         * If the root page was a leaf page, change it into an internal page.\n         * We copy the key we split on (but not the key's data, in the case of\n         * a leaf page) to the new root page.\n         *\n         * The btree comparison code guarantees that the left-most key on any\n         * level of the tree is never used, so it doesn't need to be filled in.\n         */\n\n        nbytes = NBINTERNAL(0);\n        h->linp[0] = h->upper = t->bt_psize - nbytes;\n        dest = (char *)h + h->upper;\n        WR_BINTERNAL(dest, 0, l->pgno, 0);\n\n        switch (h->flags & P_TYPE) {\n        case P_BLEAF:\n                bl = GETBLEAF(r, 0);\n                nbytes = NBINTERNAL(bl->ksize);\n                h->linp[1] = h->upper -= nbytes;\n                dest = (char *)h + h->upper;\n                WR_BINTERNAL(dest, bl->ksize, r->pgno, 0);\n                memmove(dest, bl->bytes, bl->ksize);\n\n                /*\n                 * If the key is on an overflow page, mark the overflow chain\n                 * so it isn't deleted when the leaf copy of the key is deleted.\n                 */\n\n                if (bl->flags & P_BIGKEY &&\n                    bt_preserve(t, *(pgno_t *)bl->bytes) == RET_ERROR)\n                        return (RET_ERROR);\n                break;\n        case P_BINTERNAL:\n                bi = GETBINTERNAL(r, 0);\n                nbytes = NBINTERNAL(bi->ksize);\n                h->linp[1] = h->upper -= nbytes;\n                dest = (char *)h + h->upper;\n                memmove(dest, bi, nbytes);\n                ((BINTERNAL *)dest)->pgno = r->pgno;\n                break;\n        default:\n                abort();\n        }\n\n        /* There are two keys on the page. */\n        h->lower = BTDATAOFF + 2 * sizeof(indx_t);\n\n        /* Unpin the root page, set to btree internal page. */\n        h->flags &= ~P_TYPE;\n        h->flags |= P_BINTERNAL;\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n\n        return (RET_SUCCESS);\n}\n\n/*\n * BT_PSPLIT -- Do the real work of splitting the page.\n *\n * Parameters:\n *      t:      tree\n *      h:      page to be split\n *      l:      page to put lower half of data\n *      r:      page to put upper half of data\n *      pskip:  pointer to index to leave open\n *      ilen:   insert length\n *\n * Returns:\n *      Pointer to page in which to insert.\n */\n\nstatic PAGE *\nbt_psplit(BTREE *t, PAGE *h, PAGE *l, PAGE *r, indx_t *pskip, size_t ilen)\n{\n        BINTERNAL *bi;\n        BLEAF *bl;\n        CURSOR *c;\n        RLEAF *rl;\n        PAGE *rval;\n        void *src;\n        indx_t full, half, nxt, off, skip, top, used;\n        u_int32_t nbytes;\n        int bigkeycnt, isbigkey;\n\n        /*\n         * Split the data to the left and right pages.  Leave the skip index\n         * open.  Additionally, make some effort not to split on an overflow\n         * key.  This makes internal page processing faster and can save\n         * space as overflow keys used by internal pages are never deleted.\n         */\n\n        bigkeycnt = 0;\n        skip = *pskip;\n        full = t->bt_psize - BTDATAOFF;\n        half = full / 2;\n        used = 0;\n        for (nxt = off = 0, top = NEXTINDEX(h); nxt < top; ++off) {\n                if (skip == off) {\n                        nbytes = ilen;\n                        isbigkey = 0;           /* XXX: not really known. */\n                } else\n                        switch (h->flags & P_TYPE) {\n                        case P_BINTERNAL:\n                                src = bi = GETBINTERNAL(h, nxt);\n                                nbytes = NBINTERNAL(bi->ksize);\n                                isbigkey = bi->flags & P_BIGKEY;\n                                break;\n                        case P_BLEAF:\n                                src = bl = GETBLEAF(h, nxt);\n                                nbytes = NBLEAF(bl);\n                                isbigkey = bl->flags & P_BIGKEY;\n                                break;\n                        case P_RINTERNAL:\n                                src = GETRINTERNAL(h, nxt);\n                                nbytes = NRINTERNAL;\n                                isbigkey = 0;\n                                break;\n                        case P_RLEAF:\n                                src = rl = GETRLEAF(h, nxt);\n                                nbytes = NRLEAF(rl);\n                                isbigkey = 0;\n                                break;\n                        default:\n                                abort();\n                        }\n\n                /*\n                 * If the key/data pairs are substantial fractions of the max\n                 * possible size for the page, it's possible to get situations\n                 * where we decide to try and copy too much onto the left page.\n                 * Make sure that doesn't happen.\n                 */\n\n                if ((skip <= off && used + nbytes + sizeof(indx_t) >= full) ||\n                    nxt == top - 1) {\n                        --off;\n                        break;\n                }\n\n                /* Copy the key/data pair, if not the skipped index. */\n                if (skip != off) {\n                        ++nxt;\n\n                        l->linp[off] = l->upper -= nbytes;\n                        memmove((char *)l + l->upper, src, nbytes);\n                }\n\n                used += nbytes + sizeof(indx_t);\n                if (used >= half) {\n                        if (!isbigkey || bigkeycnt == 3)\n                                break;\n                        else\n                                ++bigkeycnt;\n                }\n        }\n\n        /*\n         * Off is the last offset that's valid for the left page.\n         * Nxt is the first offset to be placed on the right page.\n         */\n\n        l->lower += (off + 1) * sizeof(indx_t);\n\n        /*\n         * If splitting the page that the cursor was on, the cursor has to be\n         * adjusted to point to the same record as before the split.  If the\n         * cursor is at or past the skipped slot, the cursor is incremented by\n         * one.  If the cursor is on the right page, it is decremented by the\n         * number of records split to the left page.\n         */\n\n        c = &t->bt_cursor;\n        if (F_ISSET(c, CURS_INIT) && c->pg.pgno == h->pgno) {\n                if (c->pg.index >= skip)\n                        ++c->pg.index;\n                if (c->pg.index < nxt)                  /* Left page. */\n                        c->pg.pgno = l->pgno;\n                else {                                  /* Right page. */\n                        c->pg.pgno = r->pgno;\n                        c->pg.index -= nxt;\n                }\n        }\n\n        /*\n         * If the skipped index was on the left page, just return that page.\n         * Otherwise, adjust the skip index to reflect the new position on\n         * the right page.\n         */\n\n        if (skip <= off) {\n                skip = MAX_PAGE_OFFSET;\n                rval = l;\n        } else {\n                rval = r;\n                *pskip -= nxt;\n        }\n\n        for (off = 0; nxt < top; ++off) {\n                if (skip == nxt) {\n                        ++off;\n                        skip = MAX_PAGE_OFFSET;\n                }\n                switch (h->flags & P_TYPE) {\n                case P_BINTERNAL:\n                        src = bi = GETBINTERNAL(h, nxt);\n                        nbytes = NBINTERNAL(bi->ksize);\n                        break;\n                case P_BLEAF:\n                        src = bl = GETBLEAF(h, nxt);\n                        nbytes = NBLEAF(bl);\n                        break;\n                case P_RINTERNAL:\n                        src = GETRINTERNAL(h, nxt);\n                        nbytes = NRINTERNAL;\n                        break;\n                case P_RLEAF:\n                        src = rl = GETRLEAF(h, nxt);\n                        nbytes = NRLEAF(rl);\n                        break;\n                default:\n                        abort();\n                }\n                ++nxt;\n                r->linp[off] = r->upper -= nbytes;\n                memmove((char *)r + r->upper, src, nbytes);\n        }\n        r->lower += off * sizeof(indx_t);\n\n        /* If the key is being appended to the page, adjust the index. */\n        if (skip == top)\n                r->lower += sizeof(indx_t);\n\n        return (rval);\n}\n\n/*\n * BT_PRESERVE -- Mark a chain of pages as used by an internal node.\n *\n * Chains of indirect blocks pointed to by leaf nodes get reclaimed when the\n * record that references them gets deleted.  Chains pointed to by internal\n * pages never get deleted.  This routine marks a chain as pointed to by an\n * internal page.\n *\n * Parameters:\n *      t:      tree\n *      pg:     page number of first page in the chain.\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nstatic int\nbt_preserve(BTREE *t, pgno_t pg)\n{\n        PAGE *h;\n\n        if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                return (RET_ERROR);\n        h->flags |= P_PRESERVE;\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n        return (RET_SUCCESS);\n}\n\n/*\n * REC_TOTAL -- Return the number of recno entries below a page.\n *\n * Parameters:\n *      h:      page\n *\n * Returns:\n *      The number of recno entries below a page.\n *\n * XXX\n * These values could be set by the bt_psplit routine.  The problem is that the\n * entry has to be popped off of the stack etc. or the values have to be passed\n * all the way back to bt_split/bt_rroot and it's not very clean.\n */\n\nstatic recno_t\nrec_total(PAGE *h)\n{\n        recno_t recs;\n        indx_t nxt, top;\n\n        for (recs = 0, nxt = 0, top = NEXTINDEX(h); nxt < top; ++nxt)\n                recs += GETRINTERNAL(h, nxt)->nrecs;\n        return (recs);\n}\n"
  },
  {
    "path": "db/btree/bt_utils.c",
    "content": "/*      $OpenBSD: bt_utils.c,v 1.13 2022/12/27 17:10:10 jmc Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"btree.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\n/*\n * __bt_ret --\n *      Build return key/data pair.\n *\n * Parameters:\n *      t:      tree\n *      e:      key/data pair to be returned\n *      key:    user's key structure (NULL if not to be filled in)\n *      rkey:   memory area to hold key\n *      data:   user's data structure (NULL if not to be filled in)\n *      rdata:  memory area to hold data\n *       copy:  always copy the key/data item\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nint\n__bt_ret(BTREE *t, EPG *e, DBT *key, DBT *rkey, DBT *data, DBT *rdata, int copy)\n{\n        BLEAF *bl;\n        void *p;\n\n        bl = GETBLEAF(e->page, e->index);\n\n        /*\n         * We must copy big keys/data to make them contiguous.  Otherwise,\n         * leave the page pinned and don't copy unless the user specified\n         * concurrent access.\n         */\n\n        if (key == NULL)\n                goto dataonly;\n\n        if (bl->flags & P_BIGKEY) {\n                if (__ovfl_get(t, bl->bytes,\n                    &key->size, &rkey->data, &rkey->size))\n                        return (RET_ERROR);\n                key->data = rkey->data;\n        } else if (copy || F_ISSET(t, B_DB_LOCK)) {\n                if (bl->ksize > rkey->size) {\n                        p = realloc(rkey->data, bl->ksize);\n                        if (p == NULL)\n                                return (RET_ERROR);\n                        rkey->data = p;\n                        rkey->size = bl->ksize;\n                }\n                memmove(rkey->data, bl->bytes, bl->ksize);\n                key->size = bl->ksize;\n                key->data = rkey->data;\n        } else {\n                key->size = bl->ksize;\n                key->data = bl->bytes;\n        }\n\ndataonly:\n        if (data == NULL)\n                return (RET_SUCCESS);\n\n        if (bl->flags & P_BIGDATA) {\n                if (__ovfl_get(t, bl->bytes + bl->ksize,\n                    &data->size, &rdata->data, &rdata->size))\n                        return (RET_ERROR);\n                data->data = rdata->data;\n        } else if (copy || F_ISSET(t, B_DB_LOCK)) {\n                /* Use +1 in case the first record retrieved is 0 length. */\n                if (bl->dsize + 1 > rdata->size) {\n                        p = realloc(rdata->data, bl->dsize + 1);\n                        if (p == NULL)\n                                return (RET_ERROR);\n                        rdata->data = p;\n                        rdata->size = bl->dsize + 1;\n                }\n                memmove(rdata->data, bl->bytes + bl->ksize, bl->dsize);\n                data->size = bl->dsize;\n                data->data = rdata->data;\n        } else {\n                data->size = bl->dsize;\n                data->data = bl->bytes + bl->ksize;\n        }\n\n        return (RET_SUCCESS);\n}\n\n/*\n * __BT_CMP -- Compare a key to a given record.\n *\n * Parameters:\n *      t:      tree\n *      k1:     DBT pointer of first arg to comparison\n *      e:      pointer to EPG for comparison\n *\n * Returns:\n *      < 0 if k1 is < record\n *      = 0 if k1 is = record\n *      > 0 if k1 is > record\n */\n\nint\n__bt_cmp(BTREE *t, const DBT *k1, EPG *e)\n{\n        BINTERNAL *bi;\n        BLEAF *bl;\n        DBT k2;\n        PAGE *h;\n        void *bigkey;\n\n        /*\n         * The left-most key on internal pages, at any level of the tree, is\n         * guaranteed by the following code to be less than any user key.\n         * This saves us from having to update the leftmost key on an internal\n         * page when the user inserts a new key in the tree smaller than\n         * anything we've yet seen.\n         */\n\n        h = e->page;\n        if (e->index == 0 && h->prevpg == P_INVALID && !(h->flags & P_BLEAF))\n                return (1);\n\n        bigkey = NULL;\n        if (h->flags & P_BLEAF) {\n                bl = GETBLEAF(h, e->index);\n                if (bl->flags & P_BIGKEY)\n                        bigkey = bl->bytes;\n                else {\n                        k2.data = bl->bytes;\n                        k2.size = bl->ksize;\n                }\n        } else {\n                bi = GETBINTERNAL(h, e->index);\n                if (bi->flags & P_BIGKEY)\n                        bigkey = bi->bytes;\n                else {\n                        k2.data = bi->bytes;\n                        k2.size = bi->ksize;\n                }\n        }\n\n        if (bigkey) {\n                if (__ovfl_get(t, bigkey,\n                    &k2.size, &t->bt_rdata.data, &t->bt_rdata.size))\n                        return (RET_ERROR);\n                k2.data = t->bt_rdata.data;\n        }\n        return ((*t->bt_cmp)(k1, &k2));\n}\n\n/*\n * __BT_DEFCMP -- Default comparison routine.\n *\n * Parameters:\n *      a:      DBT #1\n *      b:      DBT #2\n *\n * Returns:\n *      < 0 if a is < b\n *      = 0 if a is = b\n *      > 0 if a is > b\n */\n\nint\n__bt_defcmp(const DBT *a, const DBT *b)\n{\n        size_t len;\n        unsigned char *p1, *p2;\n\n        /*\n         * XXX\n         * If a size_t doesn't fit in an int, this routine can lose.\n         * What we need is a integral type which is guaranteed to be\n         * larger than a size_t, and there is no such thing.\n         */\n\n        len = MINIMUM(a->size, b->size);\n        for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2)\n                if (*p1 != *p2)\n                        return ((int)*p1 - (int)*p2);\n        return ((int)a->size - (int)b->size);\n}\n\n/*\n * __BT_DEFPFX -- Default prefix routine.\n *\n * Parameters:\n *      a:      DBT #1\n *      b:      DBT #2\n *\n * Returns:\n *      Number of bytes needed to distinguish b from a.\n */\n\nsize_t\n__bt_defpfx(const DBT *a, const DBT *b)\n{\n        unsigned char *p1, *p2;\n        size_t cnt, len;\n\n        cnt = 1;\n        len = MINIMUM(a->size, b->size);\n        for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2, ++cnt)\n                if (*p1 != *p2)\n                        return (cnt);\n\n        /* a->size must be <= b->size, or they wouldn't be in this order. */\n        return (a->size < b->size ? a->size + 1 : a->size);\n}\n"
  },
  {
    "path": "db/btree/btree.h",
    "content": "/*      $OpenBSD: btree.h,v 1.7 2015/07/16 04:27:33 tedu Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)btree.h     8.11 (Berkeley) 8/17/94\n */\n\n/* Macros to set/clear/test flags. */\n#define F_SET(p, f)     (p)->flags |= (f)\n#define F_CLR(p, f)     (p)->flags &= ~(f)\n#define F_ISSET(p, f)   ((p)->flags & (f))\n\n#include <mpool.h>\n\n#define DEFMINKEYPAGE   (2)             /* Minimum keys per page */\n#define MINCACHE        (5)             /* Minimum cached pages */\n#define MINPSIZE        (512)           /* Minimum page size */\n\n/*\n * Page 0 of a btree file contains a copy of the meta-data.  This page is also\n * used as an out-of-band page, i.e. page pointers that point to nowhere point\n * to page 0.  Page 1 is the root of the btree.\n */\n\n#define P_INVALID        0              /* Invalid tree page number. */\n#define P_META           0              /* Tree metadata page number. */\n#define P_ROOT           1              /* Tree root page number. */\n\n/*\n * There are five page layouts in the btree: btree internal pages (BINTERNAL),\n * btree leaf pages (BLEAF), recno internal pages (RINTERNAL), recno leaf pages\n * (RLEAF) and overflow pages.  All five page types have a page header (PAGE).\n * This implementation requires that values within structures NOT be padded.\n * (ANSI C permits random padding.)  If your compiler pads randomly you'll have\n * to do some work to get this package to run.\n */\n\ntypedef struct _page {\n        pgno_t  pgno;                   /* this page's page number */\n        pgno_t  prevpg;                 /* left sibling */\n        pgno_t  nextpg;                 /* right sibling */\n\n#define P_BINTERNAL     0x01            /* btree internal page */\n#define P_BLEAF         0x02            /* leaf page */\n#define P_OVERFLOW      0x04            /* overflow page */\n#define P_RINTERNAL     0x08            /* recno internal page */\n#define P_RLEAF         0x10            /* leaf page */\n#define P_TYPE          0x1f            /* type mask */\n#define P_PRESERVE      0x20            /* never delete this chain of pages */\n        u_int32_t flags;\n\n        indx_t  lower;                  /* lower bound of free space on page */\n        indx_t  upper;                  /* upper bound of free space on page */\n        indx_t  linp[1];                /* indx_t-aligned VAR. LENGTH DATA */\n} PAGE;\n\n/* First and next index. */\n#define BTDATAOFF                                                       \\\n        (sizeof(pgno_t) + sizeof(pgno_t) + sizeof(pgno_t) +             \\\n            sizeof(u_int32_t) + sizeof(indx_t) + sizeof(indx_t))\n#define NEXTINDEX(p)    (((p)->lower - BTDATAOFF) / sizeof(indx_t))\n\n/*\n * For pages other than overflow pages, there is an array of offsets into the\n * rest of the page immediately following the page header.  Each offset is to\n * an item which is unique to the type of page.  The h_lower offset is just\n * past the last filled-in index.  The h_upper offset is the first item on the\n * page.  Offsets are from the beginning of the page.\n *\n * If an item is too big to store on a single page, a flag is set and the item\n * is a { page, size } pair such that the page is the first page of an overflow\n * chain with size bytes of item.  Overflow pages are simply bytes without any\n * external structure.\n *\n * The page number and size fields in the items are pgno_t-aligned so they can\n * be manipulated without copying.  (This presumes that 32 bit items can be\n * manipulated on this system.)\n */\n\n#define LALIGN(n)       (((n) + sizeof(pgno_t) - 1) & ~(sizeof(pgno_t) - 1))\n#define NOVFLSIZE       (sizeof(pgno_t) + sizeof(u_int32_t))\n\n/*\n * For the btree internal pages, the item is a key.  BINTERNALs are {key, pgno}\n * pairs, such that the key compares less than or equal to all of the records\n * on that page.  For a tree without duplicate keys, an internal page with two\n * consecutive keys, a and b, will have all records greater than or equal to a\n * and less than b stored on the page associated with a.  Duplicate keys are\n * somewhat special and can cause duplicate internal and leaf page records and\n * some minor modifications of the above rule.\n */\n\ntypedef struct _binternal {\n        u_int32_t ksize;                /* key size */\n        pgno_t  pgno;                   /* page number stored on */\n#define P_BIGDATA       0x01            /* overflow data */\n#define P_BIGKEY        0x02            /* overflow key */\n        unsigned char  flags;\n        char    bytes[1];               /* data */\n} BINTERNAL;\n\n/* Get the page's BINTERNAL structure at index indx. */\n#define GETBINTERNAL(pg, indx)                                          \\\n        ((BINTERNAL *)((char *)(pg) + (pg)->linp[indx]))\n\n/* Get the number of bytes in the entry. */\n#define NBINTERNAL(len)                                                 \\\n        LALIGN(sizeof(u_int32_t) + sizeof(pgno_t) + sizeof(unsigned char) + (len))\n\n/* Copy a BINTERNAL entry to the page. */\n#define WR_BINTERNAL(p, size, pgno, flags) {                            \\\n        *(u_int32_t *)p = size;                                         \\\n        p += sizeof(u_int32_t);                                         \\\n        *(pgno_t *)p = pgno;                                            \\\n        p += sizeof(pgno_t);                                            \\\n        *(unsigned char *)p = flags;                                    \\\n        p += sizeof(unsigned char);                                     \\\n}\n\n/*\n * For the recno internal pages, the item is a page number with the number of\n * keys found on that page and below.\n */\n\ntypedef struct _rinternal {\n        recno_t nrecs;                  /* number of records */\n        pgno_t  pgno;                   /* page number stored below */\n} RINTERNAL;\n\n/* Get the page's RINTERNAL structure at index indx. */\n#define GETRINTERNAL(pg, indx)                                          \\\n        ((RINTERNAL *)((char *)(pg) + (pg)->linp[indx]))\n\n/* Get the number of bytes in the entry. */\n#define NRINTERNAL                                                      \\\n        LALIGN(sizeof(recno_t) + sizeof(pgno_t))\n\n/* Copy a RINTERAL entry to the page. */\n#define WR_RINTERNAL(p, nrecs, pgno) {                                  \\\n        *(recno_t *)p = nrecs;                                          \\\n        p += sizeof(recno_t);                                           \\\n        *(pgno_t *)p = pgno;                                            \\\n}\n\n/* For the btree leaf pages, the item is a key and data pair. */\ntypedef struct _bleaf {\n        u_int32_t       ksize;          /* size of key */\n        u_int32_t       dsize;          /* size of data */\n        unsigned char  flags;           /* P_BIGDATA, P_BIGKEY */\n        char    bytes[1];               /* data */\n} BLEAF;\n\n/* Get the page's BLEAF structure at index indx. */\n#define GETBLEAF(pg, indx)                                              \\\n        ((BLEAF *)((char *)(pg) + (pg)->linp[indx]))\n\n/* Get the number of bytes in the entry. */\n#define NBLEAF(p)       NBLEAFDBT((p)->ksize, (p)->dsize)\n\n/* Get the number of bytes in the user's key/data pair. */\n#define NBLEAFDBT(ksize, dsize)                                         \\\n        LALIGN(sizeof(u_int32_t) + sizeof(u_int32_t) + sizeof(unsigned char) + \\\n            (ksize) + (dsize))\n\n/* Copy a BLEAF entry to the page. */\n#define WR_BLEAF(p, key, data, flags) {                                 \\\n        *(u_int32_t *)p = key->size;                                    \\\n        p += sizeof(u_int32_t);                                         \\\n        *(u_int32_t *)p = data->size;                                   \\\n        p += sizeof(u_int32_t);                                         \\\n        *(unsigned char *)p = flags;                                    \\\n        p += sizeof(unsigned char);                                     \\\n        memmove(p, key->data, key->size);                               \\\n        p += key->size;                                                 \\\n        memmove(p, data->data, data->size);                             \\\n}\n\n/* For the recno leaf pages, the item is a data entry. */\ntypedef struct _rleaf {\n        u_int32_t       dsize;          /* size of data */\n        unsigned char  flags;                  /* P_BIGDATA */\n        char    bytes[1];\n} RLEAF;\n\n/* Get the page's RLEAF structure at index indx. */\n#define GETRLEAF(pg, indx)                                              \\\n        ((RLEAF *)((char *)(pg) + (pg)->linp[indx]))\n\n/* Get the number of bytes in the entry. */\n#define NRLEAF(p)       NRLEAFDBT((p)->dsize)\n\n/* Get the number of bytes from the user's data. */\n#define NRLEAFDBT(dsize)                                                \\\n        LALIGN(sizeof(u_int32_t) + sizeof(unsigned char) + (dsize))\n\n/* Copy a RLEAF entry to the page. */\n#define WR_RLEAF(p, data, flags) {                                      \\\n        *(u_int32_t *)p = data->size;                                   \\\n        p += sizeof(u_int32_t);                                         \\\n        *(unsigned char *)p = flags;                                    \\\n        p += sizeof(unsigned char);                                     \\\n        memmove(p, data->data, data->size);                             \\\n}\n\n/*\n * A record in the tree is either a pointer to a page and an index in the page\n * or a page number and an index.  These structures are used as a cursor, stack\n * entry and search returns as well as to pass records to other routines.\n *\n * One comment about searches.  Internal page searches must find the largest\n * record less than key in the tree so that descents work.  Leaf page searches\n * must find the smallest record greater than key so that the returned index\n * is the record's correct position for insertion.\n */\n\ntypedef struct _epgno {\n        pgno_t  pgno;                   /* the page number */\n        indx_t  index;                  /* the index on the page */\n} EPGNO;\n\ntypedef struct _epg {\n        PAGE    *page;                  /* the (pinned) page */\n        indx_t   index;                 /* the index on the page */\n} EPG;\n\n/*\n * About cursors.  The cursor (and the page that contained the key/data pair\n * that it referenced) can be deleted, which makes things a bit tricky.  If\n * there are no duplicates of the cursor key in the tree (i.e. B_NODUPS is set\n * or there simply aren't any duplicates of the key) we copy the key that it\n * referenced when it's deleted, and reacquire a new cursor key if the cursor\n * is used again.  If there are duplicates keys, we move to the next/previous\n * key, and set a flag so that we know what happened.  NOTE: if duplicate (to\n * the cursor) keys are added to the tree during this process, it is undefined\n * if they will be returned or not in a cursor scan.\n *\n * The flags determine the possible states of the cursor:\n *\n * CURS_INIT    The cursor references *something*.\n * CURS_ACQUIRE The cursor was deleted, and a key has been saved so that\n *              we can reacquire the right position in the tree.\n * CURS_AFTER, CURS_BEFORE\n *              The cursor was deleted, and now references a key/data pair\n *              that has not yet been returned, either before or after the\n *              deleted key/data pair.\n * XXX\n * This structure is broken out so that we can eventually offer multiple\n * cursors as part of the DB interface.\n */\n\ntypedef struct _cursor {\n        EPGNO    pg;                    /* B: Saved tree reference. */\n        DBT      key;                   /* B: Saved key, or key.data == NULL. */\n        recno_t  rcursor;               /* R: recno cursor (1-based) */\n\n#define CURS_ACQUIRE    0x01            /*  B: Cursor needs to be reacquired. */\n#define CURS_AFTER      0x02            /*  B: Unreturned cursor after key. */\n#define CURS_BEFORE     0x04            /*  B: Unreturned cursor before key. */\n#define CURS_INIT       0x08            /* RB: Cursor initialized. */\n        u_int8_t flags;\n} CURSOR;\n\n/*\n * The metadata of the tree.  The nrecs field is used only by the RECNO code.\n * This is because the btree doesn't really need it and it requires that every\n * put or delete call modify the metadata.\n */\n\ntypedef struct _btmeta {\n        u_int32_t       magic;          /* magic number */\n        u_int32_t       version;        /* version */\n        u_int32_t       psize;          /* page size */\n        u_int32_t       free;           /* page number of first free page */\n        u_int32_t       nrecs;          /* R: number of records */\n\n#define SAVEMETA        (B_NODUPS | R_RECNO)\n        u_int32_t       flags;          /* bt_flags & SAVEMETA */\n} BTMETA;\n\n/* The in-memory btree/recno data structure. */\ntypedef struct _btree {\n        MPOOL    *bt_mp;                /* memory pool cookie */\n\n        DB       *bt_dbp;               /* pointer to enclosing DB */\n\n        EPG       bt_cur;               /* current (pinned) page */\n        PAGE     *bt_pinned;            /* page pinned across calls */\n\n        CURSOR    bt_cursor;            /* cursor */\n\n#define BT_PUSH(t, p, i) {                                              \\\n        t->bt_sp->pgno = p;                                             \\\n        t->bt_sp->index = i;                                            \\\n        ++t->bt_sp;                                                     \\\n}\n#define BT_POP(t)       (t->bt_sp == t->bt_stack ? NULL : --t->bt_sp)\n#define BT_CLR(t)       (t->bt_sp = t->bt_stack)\n        EPGNO     bt_stack[50];         /* stack of parent pages */\n        EPGNO    *bt_sp;                /* current stack pointer */\n\n        DBT       bt_rkey;              /* returned key */\n        DBT       bt_rdata;             /* returned data */\n\n        int       bt_fd;                /* tree file descriptor */\n\n        pgno_t    bt_free;              /* next free page */\n        u_int32_t bt_psize;             /* page size */\n        indx_t    bt_ovflsize;          /* cut-off for key/data overflow */\n        int       bt_lorder;            /* byte order */\n                                        /* sorted order */\n        enum { NOT, BACK, FORWARD } bt_order;\n        EPGNO     bt_last;              /* last insert */\n\n                                        /* B: key comparison function */\n        int     (*bt_cmp)(const DBT *, const DBT *);\n                                        /* B: prefix comparison function */\n        size_t  (*bt_pfx)(const DBT *, const DBT *);\n                                        /* R: recno input function */\n        int     (*bt_irec)(struct _btree *, recno_t);\n\n        FILE     *bt_rfp;               /* R: record FILE pointer */\n        int       bt_rfd;               /* R: record file descriptor */\n\n        caddr_t   bt_cmap;              /* R: current point in mapped space */\n        caddr_t   bt_smap;              /* R: start of mapped space */\n        caddr_t   bt_emap;              /* R: end of mapped space */\n        size_t    bt_msize;             /* R: size of mapped region. */\n\n        recno_t   bt_nrecs;             /* R: number of records */\n        size_t    bt_reclen;            /* R: fixed record length */\n        unsigned char    bt_bval;       /* R: delimiting byte/pad character */\n\n/*\n * NB:\n * B_NODUPS and R_RECNO are stored on disk, and may not be changed.\n */\n\n#define B_INMEM         0x00001         /* in-memory tree */\n#define B_METADIRTY     0x00002         /* need to write metadata */\n#define B_MODIFIED      0x00004         /* tree modified */\n#define B_NEEDSWAP      0x00008         /* if byte order requires swapping */\n#define B_RDONLY        0x00010         /* read-only tree */\n\n#define B_NODUPS        0x00020         /* no duplicate keys permitted */\n#define R_RECNO         0x00080         /* record oriented tree */\n\n#define R_CLOSEFP       0x00040         /* opened a file pointer */\n#define R_EOF           0x00100         /* end of input file reached. */\n#define R_FIXLEN        0x00200         /* fixed length records */\n#define R_INMEM         0x00800         /* in-memory file */\n#define R_MODIFIED      0x01000         /* modified file */\n#define R_RDONLY        0x02000         /* read-only file */\n\n#define B_DB_LOCK       0x04000         /* DB_LOCK specified. */\n#define B_DB_SHMEM      0x08000         /* DB_SHMEM specified. */\n#define B_DB_TXN        0x10000         /* DB_TXN specified. */\n        u_int32_t flags;\n} BTREE;\n\n#include \"extern.h\"\n"
  },
  {
    "path": "db/btree/extern.h",
    "content": "/*      $OpenBSD: extern.h,v 1.8 2015/08/27 04:37:09 guenther Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)extern.h    8.10 (Berkeley) 7/20/94\n */\n\n__BEGIN_HIDDEN_DECLS\nint      __bt_close(DB *);\nint      __bt_cmp(BTREE *, const DBT *, EPG *);\nint      __bt_defcmp(const DBT *, const DBT *);\nsize_t   __bt_defpfx(const DBT *, const DBT *);\nint      __bt_delete(const DB *, const DBT *, unsigned int);\nint      __bt_dleaf(BTREE *, const DBT *, PAGE *, unsigned int);\nint      __bt_fd(const DB *);\nint      __bt_free(BTREE *, PAGE *);\nint      __bt_get(const DB *, const DBT *, DBT *, unsigned int);\nPAGE    *__bt_new(BTREE *, pgno_t *);\nvoid     __bt_pgin(void *, pgno_t, void *);\nvoid     __bt_pgout(void *, pgno_t, void *);\nint      __bt_put(const DB *dbp, DBT *, const DBT *, unsigned int);\nint      __bt_ret(BTREE *, EPG *, DBT *, DBT *, DBT *, DBT *, int);\nEPG     *__bt_search(BTREE *, const DBT *, int *);\nint      __bt_seq(const DB *, DBT *, DBT *, unsigned int);\nvoid     __bt_setcur(BTREE *, pgno_t, unsigned int);\nint      __bt_split(BTREE *, PAGE *,\n            const DBT *, const DBT *, int, size_t, u_int32_t);\nint      __bt_sync(const DB *, unsigned int);\n\nint      __ovfl_delete(BTREE *, void *);\nint      __ovfl_get(BTREE *, void *, size_t *, void **, size_t *);\nint      __ovfl_put(BTREE *, const DBT *, pgno_t *);\n\n#ifdef DEBUG\nvoid     __bt_dnpage(DB *, pgno_t);\nvoid     __bt_dpage(PAGE *);\nvoid     __bt_dump(DB *);\n#endif /* ifdef DEBUG */\n#ifdef STATISTICS\nvoid     __bt_stat(DB *);\n#endif /* ifdef STATISTICS */\n__END_HIDDEN_DECLS\n"
  },
  {
    "path": "db/db/db.c",
    "content": "/*      $OpenBSD: db.c,v 1.13 2015/09/05 11:28:35 guenther Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/file.h>\n#include <sys/types.h>\n\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_unistd.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n\n#undef open\n\nstatic int __dberr(void);\n\nDB *\ndbopen(const char *fname, int flags, int mode, DBTYPE type,\n    const void *openinfo)\n{\n\n#define DB_FLAGS        (DB_LOCK | DB_SHMEM | DB_TXN)\n#define USE_OPEN_FLAGS                                                  \\\n        (O_CREAT | O_EXCL | O_EXLOCK | O_NOFOLLOW | O_NONBLOCK |        \\\n         O_ACCMODE | O_SHLOCK | O_SYNC | O_TRUNC)\n\n        if (((flags & O_ACCMODE) == O_RDONLY || (flags & O_ACCMODE) == O_RDWR)\n            && (flags & ~(USE_OPEN_FLAGS | DB_FLAGS)) == 0)\n                switch (type) {\n                case DB_BTREE:\n                        return (__bt_open(fname, flags & USE_OPEN_FLAGS,\n                            mode, openinfo, flags & DB_FLAGS));\n                case DB_HASH:\n                        return (__hash_open(fname, flags & USE_OPEN_FLAGS,\n                            mode, openinfo, flags & DB_FLAGS));\n                case DB_RECNO:\n                        return (__rec_open(fname, flags & USE_OPEN_FLAGS,\n                            mode, openinfo, flags & DB_FLAGS));\n                }\n        errno = EINVAL;\n        return (NULL);\n}\nDEF_WEAK(dbopen);\n\nstatic int\n__dberr(void)\n{\n        return (RET_ERROR);\n}\n\n/*\n * __DBPANIC -- Stop.\n *\n * Parameters:\n *      dbp:    pointer to the DB structure.\n */\n\nvoid\n__dbpanic(DB *dbp)\n{\n        /* The only thing that can succeed is a close. */\n        dbp->del  = (int (*)(const struct __db *, const DBT*, unsigned int))__dberr;\n        dbp->fd   = (int (*)(const struct __db *))__dberr;\n        dbp->get  = (int (*)(const struct __db *, const DBT*, DBT *, unsigned int))__dberr;\n        dbp->put  = (int (*)(const struct __db *, DBT *, const DBT *, unsigned int))__dberr;\n        dbp->seq  = (int (*)(const struct __db *, DBT *, DBT *, unsigned int))__dberr;\n        dbp->sync = (int (*)(const struct __db *, unsigned int))__dberr;\n}\n"
  },
  {
    "path": "db/hash/bsd_ndbm.h",
    "content": "/*      $OpenBSD: ndbm.h,v 1.6 2004/05/03 17:27:50 millert Exp $        */\n/*      $NetBSD:  ndbm.h,v 1.6 1995/07/20 23:31:11 jtc Exp $            */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)ndbm.h      8.1 (Berkeley) 6/2/93\n */\n\n#ifndef _NDBM_H_\n# define _NDBM_H_\n\n# include \"../../include/compat.h\"\n\n# include <bsd_db.h>\n# include <compat_bsd_db.h>\n\n# undef open\n\n/* Map dbm interface onto db(3). */\n# define DBM_RDONLY     O_RDONLY\n\n/* Flags to dbm_store(). */\n# define DBM_INSERT      0\n# define DBM_REPLACE     1\n\n/*\n * The db(3) support for ndbm(3) always appends this suffix to the\n * file name to avoid overwriting the user's original database.\n */\n\n# define DBM_SUFFIX     \".db\"\n\ntypedef struct {\n        void *dptr;\n        size_t dsize;\n} datum;\n\ntypedef DB DBM;\n# define dbm_pagfno(a)  DBM_PAGFNO_NOT_AVAILABLE\n\n__BEGIN_DECLS\nint      dbm_clearerr(DBM *);\nvoid     dbm_close(DBM *);\nint      dbm_delete(DBM *, datum);\nint      dbm_error(DBM *);\ndatum    dbm_fetch(DBM *, datum);\ndatum    dbm_firstkey(DBM *);\ndatum    dbm_nextkey(DBM *);\nDBM     *dbm_open(const char *, int, mode_t);\nint      dbm_store(DBM *, datum, datum, int);\nint      dbm_dirfno(DBM *);\nint      dbm_rdonly(DBM *);\n__END_DECLS\n\n#endif /* !_NDBM_H_ */\n"
  },
  {
    "path": "db/hash/extern.h",
    "content": "/*      $OpenBSD: extern.h,v 1.9 2016/05/29 20:47:49 guenther Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)extern.h    8.4 (Berkeley) 6/16/94\n */\n\n__BEGIN_HIDDEN_DECLS\nBUFHEAD *__add_ovflpage(HTAB *, BUFHEAD *);\nint      __addel(HTAB *, BUFHEAD *, const DBT *, const DBT *);\nint      __big_delete(HTAB *, BUFHEAD *);\nint      __big_insert(HTAB *, BUFHEAD *, const DBT *, const DBT *);\nint      __big_keydata(HTAB *, BUFHEAD *, DBT *, DBT *, int);\nint      __big_return(HTAB *, BUFHEAD *, int, DBT *, int);\nint      __big_split(HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *,\n            int, u_int32_t, SPLIT_RETURN *);\nint      __buf_free(HTAB *, int, int);\nvoid     __buf_init(HTAB *, int);\nu_int32_t        __call_hash(HTAB *, char *, int);\nint      __delpair(HTAB *, BUFHEAD *, int);\nint      __expand_table(HTAB *);\nint      __find_bigpair(HTAB *, BUFHEAD *, int, char *, int);\nu_int16_t        __find_last_page(HTAB *, BUFHEAD **);\nvoid     __free_ovflpage(HTAB *, BUFHEAD *);\nBUFHEAD *__get_buf(HTAB *, u_int32_t, BUFHEAD *, int);\nint      __get_page(HTAB *, char *, u_int32_t, int, int, int);\nint      __ibitmap(HTAB *, int, int, int);\nu_int32_t        __log2(u_int32_t);\nint      __put_page(HTAB *, char *, u_int32_t, int, int);\nvoid     __reclaim_buf(HTAB *, BUFHEAD *);\nint      __split_page(HTAB *, u_int32_t, u_int32_t);\n\n#ifdef HASH_STATISTICS\nextern int hash_accesses, hash_collisions, hash_expansions, hash_overflows;\n#endif /* ifdef HASH_STATISTICS */\n__END_HIDDEN_DECLS\n"
  },
  {
    "path": "db/hash/hash.c",
    "content": "/*      $OpenBSD: hash.c,v 1.29 2016/09/21 04:38:56 guenther Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#ifndef EFTYPE\n# define EFTYPE ENOTSUP\n#endif /* ifndef EFTYPE */\n\n#include \"../../include/compat.h\"\n\n#include <sys/stat.h>\n\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n#ifdef DEBUG\n# include <assert.h>\n#endif /* ifdef DEBUG */\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"hash.h\"\n#include \"page.h\"\n#include \"extern.h\"\n\n#undef open\n\n#define MAXIMUM(a, b)   (((a) > (b)) ? (a) : (b))\n\n#if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) )\n# define BIG_ENDIAN     4321\n# define LITTLE_ENDIAN  1234\n#endif /* if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) */\n\n#if ( !defined(BYTE_ORDER) && defined(_AIX) )\n# if ( defined(__powerpc__) || defined(__PPC__) \\\n    || defined(_ARCH_PPC) )\n#  define BYTE_ORDER BIG_ENDIAN /* Assume AIX/PPC is big-endian */\n# endif /* if ( defined(__powerpc__) || defined(__PPC__)\n             || defined(_ARCH_PPC) ) */\n#endif /* if ( !defined(BYTE_ORDER) && defined(_AIX) ) */\n\nstatic int   alloc_segs(HTAB *, int);\nstatic int   flush_meta(HTAB *);\nstatic int   hash_access(HTAB *, ACTION, DBT *, DBT *);\nstatic int   hash_close(DB *);\nstatic int   hash_delete(const DB *, const DBT *, u_int32_t);\nstatic int   hash_fd(const DB *);\nstatic int   hash_get(const DB *, const DBT *, DBT *, u_int32_t);\nstatic int   hash_put(const DB *, DBT *, const DBT *, u_int32_t);\nstatic void *hash_realloc(SEGMENT **, int, int);\nstatic int   hash_seq(const DB *, DBT *, DBT *, u_int32_t);\nstatic int   hash_sync(const DB *, u_int32_t);\nstatic int   hdestroy(HTAB *);\nstatic HTAB *init_hash(HTAB *, const char *, const HASHINFO *);\nstatic int   init_htab(HTAB *, int);\n#if BYTE_ORDER == LITTLE_ENDIAN\nstatic void  swap_header(HTAB *);\nstatic void  swap_header_copy(HASHHDR *, HASHHDR *);\n#endif /* if BYTE_ORDER == LITTLE_ENDIAN */\n\n/* Fast arithmetic, relying on powers of 2, */\n#define MOD(x, y)               ((x) & ((y) - 1))\n\n#define RETURN_ERROR(ERR, LOC)  { save_errno = ERR; goto LOC; }\n\n/* Return values */\n#define SUCCESS  (0)\n#define ERROR   (-1)\n#define ABNORMAL (1)\n\n#ifdef HASH_STATISTICS\nint hash_accesses, hash_collisions, hash_expansions, hash_overflows;\n#endif /* ifdef HASH_STATISTICS */\n\n/************************** INTERFACE ROUTINES ***************************/\n/* OPEN/CLOSE */\n\nDB *\n__hash_open(const char *file, int flags, int mode,\n    const HASHINFO *info,       /* Special directives for create */\n    int dflags)\n{\n        HTAB *hashp;\n        struct stat statbuf;\n        DB *dbp;\n        int bpages, hdrsize, new_table, nsegs, save_errno;\n\n        if ((flags & O_ACCMODE) == O_WRONLY) {\n                errno = EINVAL;\n                return (NULL);\n        }\n\n        if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB))))\n                return (NULL);\n        hashp->fp = -1;\n\n        /*\n         * Even if user wants write only, we need to be able to read\n         * the actual file, so we need to open it read/write. But, the\n         * field in the hashp structure needs to be accurate so that\n         * we can check accesses.\n         */\n\n        hashp->flags = flags;\n\n        if (file) {\n                if ((hashp->fp = open(file, flags | O_CLOEXEC, mode)) == -1)\n                        RETURN_ERROR(errno, error0);\n                new_table = fstat(hashp->fp, &statbuf) == 0 &&\n                    statbuf.st_size == 0 && (flags & O_ACCMODE) != O_RDONLY;\n        } else\n                new_table = 1;\n\n        if (new_table) {\n                if (!(hashp = init_hash(hashp, file, info)))\n                        RETURN_ERROR(errno, error1);\n        } else {\n                /* Table already exists */\n                if (info && info->hash)\n                        hashp->hash = info->hash;\n                else\n                        hashp->hash = __default_hash;\n\n                hdrsize = read(hashp->fp, &hashp->hdr, sizeof(HASHHDR));\n#if BYTE_ORDER == LITTLE_ENDIAN\n                swap_header(hashp);\n#endif /* if BYTE_ORDER == LITTLE_ENDIAN */\n                if (hdrsize == -1)\n                        RETURN_ERROR(errno, error1);\n                if (hdrsize != sizeof(HASHHDR))\n                        RETURN_ERROR(EFTYPE, error1);\n                /* Verify file type, versions and hash function */\n                if (hashp->MAGIC != HASHMAGIC)\n                        RETURN_ERROR(EFTYPE, error1);\n#define OLDHASHVERSION  1\n                if (hashp->VERSION != HASHVERSION &&\n                    hashp->VERSION != OLDHASHVERSION)\n                        RETURN_ERROR(EFTYPE, error1);\n                if (hashp->hash(CHARKEY, sizeof(CHARKEY)) != hashp->H_CHARKEY)\n                        RETURN_ERROR(EFTYPE, error1);\n                /*\n                 * Figure out how many segments we need.  Max_Bucket is the\n                 * maximum bucket number, so the number of buckets is\n                 * max_bucket + 1.\n                 */\n\n                nsegs = (hashp->MAX_BUCKET + 1 + hashp->SGSIZE - 1) /\n                         hashp->SGSIZE;\n                if (alloc_segs(hashp, nsegs))\n                        /*\n                         * If alloc_segs fails, table will have been destroyed\n                         * and errno will have been set.\n                         */\n                        return (NULL);\n                /* Read in bitmaps */\n                bpages = (hashp->SPARES[hashp->OVFL_POINT] +\n                    (hashp->BSIZE << BYTE_SHIFT) - 1) >>\n                    (hashp->BSHIFT + BYTE_SHIFT);\n\n                hashp->nmaps = bpages;\n                (void)memset(&hashp->mapp[0], 0, bpages * sizeof(u_int32_t *));\n        }\n\n        /* Initialize Buffer Manager */\n        if (info && info->cachesize)\n                __buf_init(hashp, info->cachesize);\n        else\n                __buf_init(hashp, DEF_BUFSIZE);\n\n        hashp->new_file = new_table;\n        hashp->save_file = file && (hashp->flags & O_RDWR);\n        hashp->cbucket = -1;\n        if (!(dbp = (DB *)malloc(sizeof(DB)))) {\n                save_errno = errno;\n                hdestroy(hashp);\n                errno = save_errno;\n                return (NULL);\n        }\n        dbp->internal = hashp;\n        dbp->close    = hash_close;\n        dbp->del      = hash_delete;\n        dbp->fd       = hash_fd;\n        dbp->get      = hash_get;\n        dbp->put      = hash_put;\n        dbp->seq      = hash_seq;\n        dbp->sync     = hash_sync;\n        dbp->type     = DB_HASH;\n\n#ifdef DEBUG\n        (void)fprintf(stderr,\n\"%s\\n%s%p\\n%s%d\\n%s%d\\n%s%d\\n%s%d\\n%s%d\\n%s%d\\n%s%d\\n%s%d\\n%s%d\\n%s%x\\n%s%x\\n%s%d\\n%s%d\\n\",\n            \"init_htab:\",\n            \"TABLE POINTER   \", hashp,\n            \"BUCKET SIZE     \", hashp->BSIZE,\n            \"BUCKET SHIFT    \", hashp->BSHIFT,\n            \"DIRECTORY SIZE  \", hashp->DSIZE,\n            \"SEGMENT SIZE    \", hashp->SGSIZE,\n            \"SEGMENT SHIFT   \", hashp->SSHIFT,\n            \"FILL FACTOR     \", hashp->FFACTOR,\n            \"MAX BUCKET      \", hashp->MAX_BUCKET,\n            \"OVFL POINT      \", hashp->OVFL_POINT,\n            \"LAST FREED      \", hashp->LAST_FREED,\n            \"HIGH MASK       \", hashp->HIGH_MASK,\n            \"LOW  MASK       \", hashp->LOW_MASK,\n            \"NSEGS           \", hashp->nsegs,\n            \"NKEYS           \", hashp->NKEYS);\n#endif /* ifdef DEBUG */\n#ifdef HASH_STATISTICS\n        hash_overflows = hash_accesses = hash_collisions = hash_expansions = 0;\n#endif /* ifdef HASH_STATISTICS */\n        return (dbp);\n\nerror1:\n        if (hashp != NULL)\n                (void)close(hashp->fp);\n\nerror0:\n        free(hashp);\n        errno = save_errno;\n        return (NULL);\n}\n\nstatic int\nhash_close(DB *dbp)\n{\n        HTAB *hashp;\n        int retval;\n\n        if (!dbp)\n                return (ERROR);\n\n        hashp = (HTAB *)dbp->internal;\n        retval = hdestroy(hashp);\n        free(dbp);\n        return (retval);\n}\n\nstatic int\nhash_fd(const DB *dbp)\n{\n        HTAB *hashp;\n\n        if (!dbp)\n                return (ERROR);\n\n        hashp = (HTAB *)dbp->internal;\n        if (hashp->fp == -1) {\n                errno = ENOENT;\n                return (-1);\n        }\n        return (hashp->fp);\n}\n\n/************************** LOCAL CREATION ROUTINES **********************/\nstatic HTAB *\ninit_hash(HTAB *hashp, const char *file, const HASHINFO *info)\n{\n        struct stat statbuf;\n        int nelem;\n\n        nelem          = 1;\n        hashp->NKEYS   = 0;\n        hashp->LORDER  = BYTE_ORDER;\n        hashp->BSIZE   = DEF_BUCKET_SIZE;\n        hashp->BSHIFT  = DEF_BUCKET_SHIFT;\n        hashp->SGSIZE  = DEF_SEGSIZE;\n        hashp->SSHIFT  = DEF_SEGSIZE_SHIFT;\n        hashp->DSIZE   = DEF_DIRSIZE;\n        hashp->FFACTOR = DEF_FFACTOR;\n        hashp->hash    = __default_hash;\n        memset(hashp->SPARES, 0, sizeof(hashp->SPARES));\n        memset(hashp->BITMAPS, 0, sizeof (hashp->BITMAPS));\n\n        /* Fix bucket size to be optimal for file system */\n        if (file != NULL) {\n                if (stat(file, &statbuf))\n                        return (NULL);\n                hashp->BSIZE = statbuf.st_blksize;\n                hashp->BSHIFT = __log2(hashp->BSIZE);\n        }\n\n        if (info) {\n                if (info->bsize) {\n                        /* Round pagesize up to power of 2 */\n                        hashp->BSHIFT = __log2(info->bsize);\n                        hashp->BSIZE = 1 << hashp->BSHIFT;\n                        if (hashp->BSIZE > MAX_BSIZE) {\n                                errno = EINVAL;\n                                return (NULL);\n                        }\n                }\n                if (info->ffactor)\n                        hashp->FFACTOR = info->ffactor;\n                if (info->hash)\n                        hashp->hash = info->hash;\n                if (info->nelem)\n                        nelem = info->nelem;\n                if (info->lorder) {\n                        if (info->lorder != BIG_ENDIAN &&\n                            info->lorder != LITTLE_ENDIAN) {\n                                errno = EINVAL;\n                                return (NULL);\n                        }\n                        hashp->LORDER = info->lorder;\n                }\n        }\n        /* init_htab should destroy the table and set errno if it fails */\n        if (init_htab(hashp, nelem))\n                return (NULL);\n        else\n                return (hashp);\n}\n\n/*\n * This calls alloc_segs which may run out of memory.  Alloc_segs will destroy\n * the table and set errno, so we just pass the error information along.\n *\n * Returns 0 on No Error\n */\n\nstatic int\ninit_htab(HTAB *hashp, int nelem)\n{\n        int nbuckets, nsegs, l2;\n\n        /*\n         * Divide number of elements by the fill factor and determine a\n         * desired number of buckets.  Allocate space for the next greater\n         * power of two number of buckets.\n         */\n\n        nelem = (nelem - 1) / hashp->FFACTOR + 1;\n\n        l2 = __log2(MAXIMUM(nelem, 2));\n        nbuckets = 1 << l2;\n\n        hashp->SPARES[l2]     = l2 + 1;\n        hashp->SPARES[l2 + 1] = l2 + 1;\n        hashp->OVFL_POINT     = l2;\n        hashp->LAST_FREED     = 2;\n\n        /* First bitmap page is at: splitpoint l2 page offset 1 */\n        if (__ibitmap(hashp, OADDR_OF(l2, 1), l2 + 1, 0))\n                return (-1);\n\n        hashp->MAX_BUCKET = hashp->LOW_MASK = nbuckets - 1;\n        hashp->HIGH_MASK  = (nbuckets << 1) - 1;\n        hashp->HDRPAGES   = ((MAXIMUM(sizeof(HASHHDR), MINHDRSIZE) - 1) >>\n            hashp->BSHIFT) + 1;\n\n        nsegs = (nbuckets - 1) / hashp->SGSIZE + 1;\n        nsegs = 1 << __log2(nsegs);\n\n        if (nsegs > hashp->DSIZE)\n                hashp->DSIZE = nsegs;\n        return (alloc_segs(hashp, nsegs));\n}\n\n/********************** DESTROY/CLOSE ROUTINES ************************/\n\n/*\n * Flushes any changes to the file if necessary and destroys the hashp\n * structure, freeing all allocated space.\n */\n\nstatic int\nhdestroy(HTAB *hashp)\n{\n        int i, save_errno;\n\n        save_errno = 0;\n\n#ifdef HASH_STATISTICS\n        (void)fprintf(stderr, \"hdestroy: accesses %lu collisions %lu\\n\",\n            (unsigned long)hash_accesses,\n            (unsigned long)hash_collisions);\n        (void)fprintf(stderr, \"hdestroy: expansions %lu\\n\",\n            (unsigned long)hash_expansions);\n        (void)fprintf(stderr, \"hdestroy: overflows %lu\\n\",\n            (unsigned long)hash_overflows);\n        (void)fprintf(stderr, \"keys %lu maxp %lu segmentcount %lu\\n\",\n            (unsigned long)hashp->NKEYS,\n            (unsigned long)hashp->MAX_BUCKET,\n            (unsigned long)hashp->nsegs);\n\n        for (i = 0; i < NCACHED; i++)\n                (void)fprintf(stderr,\n                    \"spares[%d] = %d\\n\", i, hashp->SPARES[i]);\n#endif /* ifdef HASH_STATISTICS */\n\n        /*\n         * Call on buffer manager to free buffers, and if required,\n         * write them to disk.\n         */\n\n        if (__buf_free(hashp, 1, hashp->save_file))\n                save_errno = errno;\n        if (hashp->dir) {\n                free(*hashp->dir);      /* Free initial segments */\n                /* Free extra segments */\n                while (hashp->exsegs--)\n                        free(hashp->dir[--hashp->nsegs]);\n                free(hashp->dir);\n        }\n        if (flush_meta(hashp) && !save_errno)\n                save_errno = errno;\n        /* Free Bigmaps */\n        for (i = 0; i < hashp->nmaps; i++)\n                free(hashp->mapp[i]);\n        free(hashp->tmp_key);\n        free(hashp->tmp_buf);\n\n        if (hashp->fp != -1)\n                (void)close(hashp->fp);\n\n        free(hashp);\n\n        if (save_errno) {\n                errno = save_errno;\n                return (ERROR);\n        }\n        return (SUCCESS);\n}\n\n/*\n * Write modified pages to disk\n *\n * Returns:\n *       0 == OK\n *      -1 ERROR\n */\n\nstatic int\nhash_sync(const DB *dbp, u_int32_t flags)\n{\n        HTAB *hashp;\n\n        if (flags != 0) {\n                errno = EINVAL;\n                return (ERROR);\n        }\n\n        if (!dbp)\n                return (ERROR);\n\n        hashp = (HTAB *)dbp->internal;\n        if (!hashp->save_file)\n                return (0);\n        if (__buf_free(hashp, 0, 1) || flush_meta(hashp))\n                return (ERROR);\n        hashp->new_file = 0;\n        return (0);\n}\n\n/*\n * Returns:\n *       0 == OK\n *      -1 indicates that errno should be set\n */\n\nstatic int\nflush_meta(HTAB *hashp)\n{\n        HASHHDR *whdrp;\n#if BYTE_ORDER == LITTLE_ENDIAN\n        HASHHDR whdr;\n#endif /* if BYTE_ORDER == LITTLE_ENDIAN */\n        int fp, i, wsize;\n\n        if (!hashp->save_file)\n                return (0);\n        hashp->MAGIC     = HASHMAGIC;\n        hashp->VERSION   = HASHVERSION;\n        hashp->H_CHARKEY = hashp->hash(CHARKEY, sizeof(CHARKEY));\n\n        fp = hashp->fp;\n        whdrp = &hashp->hdr;\n        (void)whdrp;\n#if BYTE_ORDER == LITTLE_ENDIAN\n        whdrp = &whdr;\n        swap_header_copy(&hashp->hdr, whdrp);\n#endif /* if BYTE_ORDER == LITTLE_ENDIAN */\n        if ((wsize = pwrite(fp, whdrp, sizeof(HASHHDR), 0)) == -1)\n                return (-1);\n        else\n                if (wsize != sizeof(HASHHDR)) {\n                        errno = EFTYPE;\n                        hashp->err = errno;\n                        return (-1);\n                }\n        for (i = 0; i < NCACHED; i++)\n                if (hashp->mapp[i])\n                        if (__put_page(hashp, (char *)hashp->mapp[i],\n                                hashp->BITMAPS[i], 0, 1))\n                                return (-1);\n        return (0);\n}\n\n/*******************************SEARCH ROUTINES *****************************/\n/*\n * All the access routines return\n *\n * Returns:\n *       0 on SUCCESS\n *       1 to indicate an external ERROR (i.e. key not found, etc)\n *      -1 to indicate an internal ERROR (i.e. out of memory, etc)\n */\n\nstatic int\nhash_get(const DB *dbp, const DBT *key, DBT *data, u_int32_t flag)\n{\n        HTAB *hashp;\n\n        hashp = (HTAB *)dbp->internal;\n        if (flag) {\n                hashp->err = errno = EINVAL;\n                return (ERROR);\n        }\n        return (hash_access(hashp, HASH_GET, (DBT *)key, data));\n}\n\nstatic int\nhash_put(const DB *dbp, DBT *key, const DBT *data, u_int32_t flag)\n{\n        HTAB *hashp;\n\n        hashp = (HTAB *)dbp->internal;\n        if (flag && flag != R_NOOVERWRITE) {\n                hashp->err = errno = EINVAL;\n                return (ERROR);\n        }\n        if ((hashp->flags & O_ACCMODE) == O_RDONLY) {\n                hashp->err = errno = EPERM;\n                return (ERROR);\n        }\n        return (hash_access(hashp, flag == R_NOOVERWRITE ?\n            HASH_PUTNEW : HASH_PUT, (DBT *)key, (DBT *)data));\n}\n\nstatic int\nhash_delete(const DB *dbp, const DBT *key,\n    u_int32_t flag)             /* Ignored */\n{\n        HTAB *hashp;\n\n        hashp = (HTAB *)dbp->internal;\n        if (flag && flag != R_CURSOR) {\n                hashp->err = errno = EINVAL;\n                return (ERROR);\n        }\n        if ((hashp->flags & O_ACCMODE) == O_RDONLY) {\n                hashp->err = errno = EPERM;\n                return (ERROR);\n        }\n        return (hash_access(hashp, HASH_DELETE, (DBT *)key, NULL));\n}\n\n/*\n * Assume that hashp has been set in wrapper routine.\n */\n\nstatic int\nhash_access(HTAB *hashp, ACTION action, DBT *key, DBT *val)\n{\n        BUFHEAD *rbufp;\n        BUFHEAD *bufp, *save_bufp;\n        u_int16_t *bp;\n        int n, ndx, off, size;\n        char *kp;\n        u_int16_t pageno;\n\n#ifdef HASH_STATISTICS\n        hash_accesses++;\n#endif /* ifdef HASH_STATISTICS */\n\n        off = hashp->BSIZE;\n        size = key->size;\n        kp = (char *)key->data;\n        rbufp = __get_buf(hashp, __call_hash(hashp, kp, size), NULL, 0);\n        if (!rbufp)\n                return (ERROR);\n        save_bufp = rbufp;\n\n        /* Pin the bucket chain */\n        rbufp->flags |= BUF_PIN;\n        for (bp = (u_int16_t *)rbufp->page, n = *bp++, ndx = 1; ndx < n;)\n                if (bp[1] >= REAL_KEY) {\n                        /* Real key/data pair */\n                        if (size == off - *bp &&\n                            memcmp(kp, rbufp->page + *bp, size) == 0)\n                                goto found;\n                        off = bp[1];\n#ifdef HASH_STATISTICS\n                        hash_collisions++;\n#endif /* ifdef HASH_STATISTICS */\n                        bp += 2;\n                        ndx += 2;\n                } else if (bp[1] == OVFLPAGE) {\n                        rbufp = __get_buf(hashp, *bp, rbufp, 0);\n                        if (!rbufp) {\n                                save_bufp->flags &= ~BUF_PIN;\n                                return (ERROR);\n                        }\n                        /* FOR LOOP INIT */\n                        bp = (u_int16_t *)rbufp->page;\n                        n = *bp++;\n                        ndx = 1;\n                        off = hashp->BSIZE;\n                } else if (bp[1] < REAL_KEY) {\n                        if ((ndx =\n                            __find_bigpair(hashp, rbufp, ndx, kp, size)) > 0)\n                                goto found;\n                        if (ndx == -2) {\n                                bufp = rbufp;\n                                if (!(pageno =\n                                    __find_last_page(hashp, &bufp))) {\n                                        ndx = 0;\n                                        (void)ndx;\n                                        rbufp = bufp;\n                                        break;  /* FOR */\n                                }\n                                rbufp = __get_buf(hashp, pageno, bufp, 0);\n                                if (!rbufp) {\n                                        save_bufp->flags &= ~BUF_PIN;\n                                        return (ERROR);\n                                }\n                                /* FOR LOOP INIT */\n                                bp = (u_int16_t *)rbufp->page;\n                                n = *bp++;\n                                ndx = 1;\n                                off = hashp->BSIZE;\n                        } else {\n                                save_bufp->flags &= ~BUF_PIN;\n                                return (ERROR);\n                        }\n                }\n\n        /* Not found */\n        switch (action) {\n        case HASH_PUT:\n        case HASH_PUTNEW:\n                if (__addel(hashp, rbufp, key, val)) {\n                        save_bufp->flags &= ~BUF_PIN;\n                        return (ERROR);\n                } else {\n                        save_bufp->flags &= ~BUF_PIN;\n                        return (SUCCESS);\n                }\n        case HASH_GET:\n        case HASH_DELETE:\n        default:\n                save_bufp->flags &= ~BUF_PIN;\n                return (ABNORMAL);\n        }\n\nfound:\n        switch (action) {\n        case HASH_PUTNEW:\n                save_bufp->flags &= ~BUF_PIN;\n                return (ABNORMAL);\n        case HASH_GET:\n                bp = (u_int16_t *)rbufp->page;\n                if (bp[ndx + 1] < REAL_KEY) {\n                        if (__big_return(hashp, rbufp, ndx, val, 0))\n                                return (ERROR);\n                } else {\n                        val->data = (unsigned char *)rbufp->page + (int)bp[ndx + 1];\n                        val->size = bp[ndx] - bp[ndx + 1];\n                }\n                break;\n        case HASH_PUT:\n                if ((__delpair(hashp, rbufp, ndx)) ||\n                    (__addel(hashp, rbufp, key, val))) {\n                        save_bufp->flags &= ~BUF_PIN;\n                        return (ERROR);\n                }\n                break;\n        case HASH_DELETE:\n                if (__delpair(hashp, rbufp, ndx))\n                        return (ERROR);\n                break;\n        default:\n                abort();\n        }\n        save_bufp->flags &= ~BUF_PIN;\n        return (SUCCESS);\n}\n\nstatic int\nhash_seq(const DB *dbp, DBT *key, DBT *data, u_int32_t flag)\n{\n        u_int32_t bucket;\n        BUFHEAD *bufp;\n        HTAB *hashp;\n        u_int16_t *bp, ndx;\n\n        hashp = (HTAB *)dbp->internal;\n        if (flag && flag != R_FIRST && flag != R_NEXT) {\n                hashp->err = errno = EINVAL;\n                return (ERROR);\n        }\n#ifdef HASH_STATISTICS\n        hash_accesses++;\n#endif /* ifdef HASH_STATISTICS */\n        if ((hashp->cbucket < 0) || (flag == R_FIRST)) {\n                hashp->cbucket = 0;\n                hashp->cndx = 1;\n                hashp->cpage = NULL;\n        }\n next_bucket:\n        for (bp = NULL; !bp || !bp[0]; ) {\n                if (!(bufp = hashp->cpage)) {\n                        for (bucket = hashp->cbucket;\n                            bucket <= hashp->MAX_BUCKET;\n                            bucket++, hashp->cndx = 1) {\n                                bufp = __get_buf(hashp, bucket, NULL, 0);\n                                if (!bufp)\n                                        return (ERROR);\n                                hashp->cpage = bufp;\n                                bp = (u_int16_t *)bufp->page;\n                                if (bp[0])\n                                        break;\n                        }\n                        hashp->cbucket = bucket;\n                        if (hashp->cbucket > hashp->MAX_BUCKET) {\n                                hashp->cbucket = -1;\n                                return (ABNORMAL);\n                        }\n                } else {\n                        bp = (u_int16_t *)hashp->cpage->page;\n                        if (flag == R_NEXT) {\n                                hashp->cndx += 2;\n                                if (hashp->cndx > bp[0]) {\n                                        hashp->cpage = NULL;\n                                        hashp->cbucket++;\n                                        hashp->cndx = 1;\n                                        goto next_bucket;\n                                }\n                        }\n                }\n\n#ifdef DEBUG\n                assert(bp);\n                assert(bufp);\n#endif /* ifdef DEBUG */\n                while (bp[hashp->cndx + 1] == OVFLPAGE) {\n                        bufp = hashp->cpage =\n                            __get_buf(hashp, bp[hashp->cndx], bufp, 0);\n                        if (!bufp)\n                                return (ERROR);\n                        bp = (u_int16_t *)(bufp->page);\n                        hashp->cndx = 1;\n                }\n                if (!bp[0]) {\n                        hashp->cpage = NULL;\n                        ++hashp->cbucket;\n                }\n        }\n        ndx = hashp->cndx;\n        if (bp[ndx + 1] < REAL_KEY) {\n                if (__big_keydata(hashp, bufp, key, data, 1))\n                        return (ERROR);\n        } else {\n                if (hashp->cpage == 0)\n                        return (ERROR);\n                key->data = (unsigned char *)hashp->cpage->page + bp[ndx];\n                key->size = (ndx > 1 ? bp[ndx - 1] : hashp->BSIZE) - bp[ndx];\n                data->data = (unsigned char *)hashp->cpage->page + bp[ndx + 1];\n                data->size = bp[ndx] - bp[ndx + 1];\n        }\n        return (SUCCESS);\n}\n\n/********************************* UTILITIES ************************/\n\n/*\n * Returns:\n *       0 ==> OK\n *      -1 ==> Error\n */\n\nint\n__expand_table(HTAB *hashp)\n{\n        u_int32_t old_bucket, new_bucket;\n        int dirsize, new_segnum, spare_ndx;\n\n#ifdef HASH_STATISTICS\n        hash_expansions++;\n#endif /* ifdef HASH_STATISTICS */\n        new_bucket = ++hashp->MAX_BUCKET;\n        old_bucket = (hashp->MAX_BUCKET & hashp->LOW_MASK);\n\n        new_segnum = new_bucket >> hashp->SSHIFT;\n\n        /* Check if we need a new segment */\n        if (new_segnum >= hashp->nsegs) {\n                /* Check if we need to expand directory */\n                if (new_segnum >= hashp->DSIZE) {\n                        /* Reallocate directory */\n                        dirsize = hashp->DSIZE * sizeof(SEGMENT *);\n                        if (!hash_realloc(&hashp->dir, dirsize, dirsize << 1))\n                                return (-1);\n                        hashp->DSIZE = dirsize << 1;\n                }\n                if ((hashp->dir[new_segnum] =\n                    (SEGMENT)calloc(hashp->SGSIZE, sizeof(SEGMENT))) == NULL)\n                        return (-1);\n                hashp->exsegs++;\n                hashp->nsegs++;\n        }\n\n        /*\n         * If the split point is increasing (MAX_BUCKET's log base 2\n         * * increases), we need to copy the current contents of the spare\n         * split bucket to the next bucket.\n         */\n\n        spare_ndx = __log2(hashp->MAX_BUCKET + 1);\n        if (spare_ndx > hashp->OVFL_POINT) {\n                hashp->SPARES[spare_ndx] = hashp->SPARES[hashp->OVFL_POINT];\n                hashp->OVFL_POINT = spare_ndx;\n        }\n\n        if (new_bucket > hashp->HIGH_MASK) {\n                /* Starting a new doubling */\n                hashp->LOW_MASK = hashp->HIGH_MASK;\n                hashp->HIGH_MASK = new_bucket | hashp->LOW_MASK;\n        }\n        /* Relocate records to the new bucket */\n        return (__split_page(hashp, old_bucket, new_bucket));\n}\n\n/*\n * If realloc guarantees that the pointer is not destroyed if the realloc\n * fails, then this routine can go away.\n */\n\nstatic void *\nhash_realloc(SEGMENT **p_ptr, int oldsize, int newsize)\n{\n        void *p;\n\n        if ((p = malloc(newsize))) {\n                memmove(p, *p_ptr, oldsize);\n                memset((char *)p + oldsize, 0, newsize - oldsize);\n                free(*p_ptr);\n                *p_ptr = p;\n        }\n        return (p);\n}\n\nu_int32_t\n__call_hash(HTAB *hashp, char *k, int len)\n{\n        int n, bucket;\n\n        n = hashp->hash(k, len);\n        bucket = n & hashp->HIGH_MASK;\n        if (bucket > hashp->MAX_BUCKET)\n                bucket = bucket & hashp->LOW_MASK;\n        return (bucket);\n}\n\n/*\n * Allocate segment table.  On error, destroy the table and set errno.\n *\n * Returns 0 on success\n */\n\nstatic int\nalloc_segs(HTAB *hashp, int nsegs)\n{\n        int i;\n        SEGMENT store;\n\n        int save_errno;\n\n        if ((hashp->dir =\n            (SEGMENT *)calloc(hashp->DSIZE, sizeof(SEGMENT *))) == NULL) {\n                save_errno = errno;\n                (void)hdestroy(hashp);\n                errno = save_errno;\n                return (-1);\n        }\n        hashp->nsegs = nsegs;\n        if (nsegs == 0)\n                return (0);\n        /* Allocate segments */\n        if ((store = (SEGMENT)calloc(nsegs << hashp->SSHIFT,\n            sizeof(SEGMENT))) == NULL) {\n                save_errno = errno;\n                (void)hdestroy(hashp);\n                errno = save_errno;\n                return (-1);\n        }\n        for (i = 0; i < nsegs; i++)\n                hashp->dir[i] = &store[i << hashp->SSHIFT];\n        return (0);\n}\n\n#if BYTE_ORDER == LITTLE_ENDIAN\n\n/*\n * Hashp->hdr needs to be byteswapped.\n */\n\nstatic void\nswap_header_copy(HASHHDR *srcp, HASHHDR *destp)\n{\n        int i;\n\n        P_32_COPY(srcp->magic,      destp->magic);\n        P_32_COPY(srcp->version,    destp->version);\n        P_32_COPY(srcp->lorder,     destp->lorder);\n        P_32_COPY(srcp->bsize,      destp->bsize);\n        P_32_COPY(srcp->bshift,     destp->bshift);\n        P_32_COPY(srcp->dsize,      destp->dsize);\n        P_32_COPY(srcp->ssize,      destp->ssize);\n        P_32_COPY(srcp->sshift,     destp->sshift);\n        P_32_COPY(srcp->ovfl_point, destp->ovfl_point);\n        P_32_COPY(srcp->last_freed, destp->last_freed);\n        P_32_COPY(srcp->max_bucket, destp->max_bucket);\n        P_32_COPY(srcp->high_mask,  destp->high_mask);\n        P_32_COPY(srcp->low_mask,   destp->low_mask);\n        P_32_COPY(srcp->ffactor,    destp->ffactor);\n        P_32_COPY(srcp->nkeys,      destp->nkeys);\n        P_32_COPY(srcp->hdrpages,   destp->hdrpages);\n        P_32_COPY(srcp->h_charkey,  destp->h_charkey);\n        for (i = 0; i < NCACHED; i++) {\n                P_32_COPY(srcp->spares[i],  destp->spares[i]);\n                P_16_COPY(srcp->bitmaps[i], destp->bitmaps[i]);\n        }\n}\n\nstatic void\nswap_header(HTAB *hashp)\n{\n        HASHHDR *hdrp;\n        int i;\n\n        hdrp = &hashp->hdr;\n\n        M_32_SWAP(hdrp->magic);\n        M_32_SWAP(hdrp->version);\n        M_32_SWAP(hdrp->lorder);\n        M_32_SWAP(hdrp->bsize);\n        M_32_SWAP(hdrp->bshift);\n        M_32_SWAP(hdrp->dsize);\n        M_32_SWAP(hdrp->ssize);\n        M_32_SWAP(hdrp->sshift);\n        M_32_SWAP(hdrp->ovfl_point);\n        M_32_SWAP(hdrp->last_freed);\n        M_32_SWAP(hdrp->max_bucket);\n        M_32_SWAP(hdrp->high_mask);\n        M_32_SWAP(hdrp->low_mask);\n        M_32_SWAP(hdrp->ffactor);\n        M_32_SWAP(hdrp->nkeys);\n        M_32_SWAP(hdrp->hdrpages);\n        M_32_SWAP(hdrp->h_charkey);\n        for (i = 0; i < NCACHED; i++) {\n                M_32_SWAP(hdrp->spares[i]);\n                M_16_SWAP(hdrp->bitmaps[i]);\n        }\n}\n#endif /* if BYTE_ORDER == LITTLE_ENDIAN */\n"
  },
  {
    "path": "db/hash/hash.h",
    "content": "/*      $OpenBSD: hash.h,v 1.9 2004/06/21 23:13:22 marc Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)hash.h      8.3 (Berkeley) 5/31/94\n */\n\n/* Operations */\ntypedef enum {\n        HASH_GET, HASH_PUT, HASH_PUTNEW, HASH_DELETE, HASH_FIRST, HASH_NEXT\n} ACTION;\n\n/* Buffer Management structures */\ntypedef struct _bufhead BUFHEAD;\n\nstruct _bufhead {\n        BUFHEAD         *prev;          /* LRU links */\n        BUFHEAD         *next;          /* LRU links */\n        BUFHEAD         *ovfl;          /* Overflow page buffer header */\n        u_int32_t        addr;          /* Address of this page */\n        char            *page;          /* Actual page data */\n        char            flags;\n#define BUF_MOD         0x0001\n#define BUF_DISK        0x0002\n#define BUF_BUCKET      0x0004\n#define BUF_PIN         0x0008\n};\n\n#define IS_BUCKET(X)    ((X) & BUF_BUCKET)\n\ntypedef BUFHEAD **SEGMENT;\n\n/* Hash Table Information */\ntypedef struct hashhdr {                /* Disk resident portion */\n        int32_t         magic;          /* Magic NO for hash tables */\n        int32_t         version;        /* Version ID */\n        u_int32_t       lorder;         /* Byte Order */\n        int32_t         bsize;          /* Bucket/Page Size */\n        int32_t         bshift;         /* Bucket shift */\n        int32_t         dsize;          /* Directory Size */\n        int32_t         ssize;          /* Segment Size */\n        int32_t         sshift;         /* Segment shift */\n        int32_t         ovfl_point;     /* Where overflow pages are being\n                                         * allocated */\n        int32_t         last_freed;     /* Last overflow page freed */\n        int32_t         max_bucket;     /* ID of Maximum bucket in use */\n        int32_t         high_mask;      /* Mask to modulo into entire table */\n        int32_t         low_mask;       /* Mask to modulo into lower half of\n                                         * table */\n        int32_t         ffactor;        /* Fill factor */\n        int32_t         nkeys;          /* Number of keys in hash table */\n        int32_t         hdrpages;       /* Size of table header */\n        int32_t         h_charkey;      /* value of hash(CHARKEY) */\n#define NCACHED 32                      /* number of bit maps and spare\n                                         * points */\n        int32_t         spares[NCACHED];/* spare pages for overflow */\n        u_int16_t       bitmaps[NCACHED];       /* address of overflow page\n                                                 * bitmaps */\n} HASHHDR;\n\ntypedef struct htab      {              /* Memory resident data structure */\n        HASHHDR         hdr;            /* Header */\n        int             nsegs;          /* Number of allocated segments */\n        int             exsegs;         /* Number of extra allocated\n                                         * segments */\n        u_int32_t                       /* Hash function */\n            (*hash)(const void *, size_t);\n        int             flags;          /* Flag values */\n        int             fp;             /* File pointer */\n        char            *tmp_buf;       /* Temporary Buffer for BIG data */\n        char            *tmp_key;       /* Temporary Buffer for BIG keys */\n        BUFHEAD         *cpage;         /* Current page */\n        int             cbucket;        /* Current bucket */\n        int             cndx;           /* Index of next item on cpage */\n        int             err;            /* Error Number -- for DBM\n                                         * compatibility */\n        int             new_file;       /* Indicates if fd is backing store\n                                         * or no */\n        int             save_file;      /* Indicates whether we need to flush\n                                         * file at\n                                         * exit */\n        u_int32_t       *mapp[NCACHED]; /* Pointers to page maps */\n        int             nmaps;          /* Initial number of bitmaps */\n        int             nbufs;          /* Number of buffers left to\n                                         * allocate */\n        BUFHEAD         bufhead;        /* Header of buffer lru list */\n        SEGMENT         *dir;           /* Hash Bucket directory */\n} HTAB;\n\n/*\n * Constants\n */\n\n#define MAX_BSIZE               65536           /* 2^16 */\n#define MIN_BUFFERS             6\n#define MINHDRSIZE              512\n#define DEF_BUFSIZE             65536           /* 64 K */\n#define DEF_BUCKET_SIZE         4096\n#define DEF_BUCKET_SHIFT        12              /* log2(BUCKET) */\n#define DEF_SEGSIZE             256\n#define DEF_SEGSIZE_SHIFT       8               /* log2(SEGSIZE) */\n#define DEF_DIRSIZE             256\n#define DEF_FFACTOR             65536\n#define MIN_FFACTOR             4\n#define SPLTMAX                 8\n#define CHARKEY                 \"%$sniglet^&\"\n#define NUMKEY                  1038583\n#define BYTE_SHIFT              3\n#define INT_TO_BYTE             2\n#define INT_BYTE_SHIFT          5\n#define ALL_SET                 ((u_int32_t)0xFFFFFFFF)\n#define ALL_CLEAR               0\n\n#define PTROF(X)        ((BUFHEAD *)((ptrdiff_t)(X)&~0x3))\n#define ISMOD(X)        ((u_int32_t)(ptrdiff_t)(X)&0x1)\n#define DOMOD(X)        ((X) = (char *)((ptrdiff_t)(X)|0x1))\n#define ISDISK(X)       ((u_int32_t)(ptrdiff_t)(X)&0x2)\n#define DODISK(X)       ((X) = (char *)((ptrdiff_t)(X)|0x2))\n\n#define BITS_PER_MAP    32\n\n/* Given the address of the beginning of a big map, clear/set the nth bit */\n#define CLRBIT(A, N)    ((A)[(N)/BITS_PER_MAP] &= ~(1U<<((N)%BITS_PER_MAP)))\n#define SETBIT(A, N)    ((A)[(N)/BITS_PER_MAP] |= (1U<<((N)%BITS_PER_MAP)))\n#define ISSET(A, N)     ((A)[(N)/BITS_PER_MAP] & (1U<<((N)%BITS_PER_MAP)))\n\n/* Overflow management */\n/*\n * Overflow page numbers are allocated per split point.  At each doubling of\n * the table, we can allocate extra pages.  So, an overflow page number has\n * the top 5 bits indicate which split point and the lower 11 bits indicate\n * which page at that split point is indicated (pages within split points are\n * numberered starting with 1).\n */\n\n#define SPLITSHIFT      11\n#define SPLITMASK       0x7FF\n#define SPLITNUM(N)     (((u_int32_t)(N)) >> SPLITSHIFT)\n#define OPAGENUM(N)     ((N) & SPLITMASK)\n#define OADDR_OF(S,O)   ((u_int32_t)((u_int32_t)(S) << SPLITSHIFT) + (O))\n\n#define BUCKET_TO_PAGE(B) \\\n        (B) + hashp->HDRPAGES + ((B) ? hashp->SPARES[__log2((B)+1)-1] : 0)\n#define OADDR_TO_PAGE(B)        \\\n        BUCKET_TO_PAGE ( (1 << SPLITNUM((B))) -1 ) + OPAGENUM((B));\n\n/*\n * page.h contains a detailed description of the page format.\n *\n * Normally, keys and data are accessed from offset tables in the top of\n * each page which point to the beginning of the key and data.  There are\n * four flag values which may be stored in these offset tables which indicate\n * the following:\n *\n *\n * OVFLPAGE     Rather than a key data pair, this pair contains\n *              the address of an overflow page.  The format of\n *              the pair is:\n *                  OVERFLOW_PAGE_NUMBER OVFLPAGE\n *\n * PARTIAL_KEY  This must be the first key/data pair on a page\n *              and implies that page contains only a partial key.\n *              That is, the key is too big to fit on a single page\n *              so it starts on this page and continues on the next.\n *              The format of the page is:\n *                  KEY_OFF PARTIAL_KEY OVFL_PAGENO OVFLPAGE\n *\n *                  KEY_OFF -- offset of the beginning of the key\n *                  PARTIAL_KEY -- 1\n *                  OVFL_PAGENO - page number of the next overflow page\n *                  OVFLPAGE -- 0\n *\n * FULL_KEY     This must be the first key/data pair on the page.  It\n *              is used in two cases.\n *\n *              Case 1:\n *                  There is a complete key on the page but no data\n *                  (because it wouldn't fit).  The next page contains\n *                  the data.\n *\n *                  Page format it:\n *                  KEY_OFF FULL_KEY OVFL_PAGENO OVFL_PAGE\n *\n *                  KEY_OFF -- offset of the beginning of the key\n *                  FULL_KEY -- 2\n *                  OVFL_PAGENO - page number of the next overflow page\n *                  OVFLPAGE -- 0\n *\n *              Case 2:\n *                  This page contains no key, but part of a large\n *                  data field, which is continued on the next page.\n *\n *                  Page format it:\n *                  DATA_OFF FULL_KEY OVFL_PAGENO OVFL_PAGE\n *\n *                  KEY_OFF -- offset of the beginning of the data on\n *                              this page\n *                  FULL_KEY -- 2\n *                  OVFL_PAGENO - page number of the next overflow page\n *                  OVFLPAGE -- 0\n *\n * FULL_KEY_DATA\n *              This must be the first key/data pair on the page.\n *              There are two cases:\n *\n *              Case 1:\n *                  This page contains a key and the beginning of the\n *                  data field, but the data field is continued on the\n *                  next page.\n *\n *                  Page format is:\n *                  KEY_OFF FULL_KEY_DATA OVFL_PAGENO DATA_OFF\n *\n *                  KEY_OFF -- offset of the beginning of the key\n *                  FULL_KEY_DATA -- 3\n *                  OVFL_PAGENO - page number of the next overflow page\n *                  DATA_OFF -- offset of the beginning of the data\n *\n *              Case 2:\n *                  This page contains the last page of a big data pair.\n *                  There is no key, only the  tail end of the data\n *                  on this page.\n *\n *                  Page format is:\n *                  DATA_OFF FULL_KEY_DATA <OVFL_PAGENO> <OVFLPAGE>\n *\n *                  DATA_OFF -- offset of the beginning of the data on\n *                              this page\n *                  FULL_KEY_DATA -- 3\n *                  OVFL_PAGENO - page number of the next overflow page\n *                  OVFLPAGE -- 0\n *\n *                  OVFL_PAGENO and OVFLPAGE are optional (they are\n *                  not present if there is no next page).\n */\n\n#define OVFLPAGE        0\n#define PARTIAL_KEY     1\n#define FULL_KEY        2\n#define FULL_KEY_DATA   3\n#define REAL_KEY        4\n\n/* Short hands for accessing structure */\n#define BSIZE           hdr.bsize\n#define BSHIFT          hdr.bshift\n#define DSIZE           hdr.dsize\n#define SGSIZE          hdr.ssize\n#define SSHIFT          hdr.sshift\n#define LORDER          hdr.lorder\n#define OVFL_POINT      hdr.ovfl_point\n#define LAST_FREED      hdr.last_freed\n#define MAX_BUCKET      hdr.max_bucket\n#define FFACTOR         hdr.ffactor\n#define HIGH_MASK       hdr.high_mask\n#define LOW_MASK        hdr.low_mask\n#define NKEYS           hdr.nkeys\n#define HDRPAGES        hdr.hdrpages\n#define SPARES          hdr.spares\n#define BITMAPS         hdr.bitmaps\n#define VERSION         hdr.version\n#define MAGIC           hdr.magic\n#define NEXT_FREE       hdr.next_free\n#define H_CHARKEY       hdr.h_charkey\n"
  },
  {
    "path": "db/hash/hash_bigkey.c",
    "content": "/*      $OpenBSD: hash_bigkey.c,v 1.19 2015/12/28 22:08:18 mmcc Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n/*\n * PACKAGE: hash\n * DESCRIPTION:\n *      Big key/data handling for the hashing package.\n *\n * ROUTINES:\n * External\n *      __big_keydata\n *      __big_split\n *      __big_insert\n *      __big_return\n *      __big_delete\n *      __find_last_page\n * Internal\n *      collect_key\n *      collect_data\n */\n\n#include \"../../include/compat.h\"\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#ifdef DEBUG\n# include <assert.h>\n#endif /* ifdef DEBUG */\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"hash.h\"\n#include \"page.h\"\n#include \"extern.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\nstatic int collect_key(HTAB *, BUFHEAD *, int, DBT *, int);\nstatic int collect_data(HTAB *, BUFHEAD *, int, int);\n\n/*\n * Big_insert\n *\n * You need to do an insert and the key/data pair is too big\n *\n * Returns:\n * 0 ==> OK\n *-1 ==> ERROR\n */\n\nint\n__big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val)\n{\n        u_int16_t *p;\n        int key_size, n, val_size;\n        u_int16_t space, move_bytes, off;\n        char *cp, *key_data, *val_data;\n\n        cp = bufp->page;                /* Character pointer of p. */\n        p = (u_int16_t *)cp;\n\n        key_data = (char *)key->data;\n        key_size = key->size;\n        val_data = (char *)val->data;\n        val_size = val->size;\n\n        /* First move the Key */\n        for (space = FREESPACE(p) - BIGOVERHEAD; key_size;\n            space = FREESPACE(p) - BIGOVERHEAD) {\n                move_bytes = MINIMUM(space, key_size);\n                off = OFFSET(p) - move_bytes;\n                memmove(cp + off, key_data, move_bytes);\n                key_size -= move_bytes;\n                key_data += move_bytes;\n                n = p[0];\n                p[++n] = off;\n                p[0] = ++n;\n                FREESPACE(p) = off - PAGE_META(n);\n                OFFSET(p) = off;\n                p[n] = PARTIAL_KEY;\n                bufp = __add_ovflpage(hashp, bufp);\n                if (!bufp)\n                        return (-1);\n                n = p[0];\n                if (!key_size) {\n                        space = FREESPACE(p);\n                        if (space) {\n                                move_bytes = MINIMUM(space, val_size);\n\n                                /*\n                                 * If the data would fit exactly in the\n                                 * remaining space, we must overflow it to the\n                                 * next page; otherwise the invariant that the\n                                 * data must end on a page with FREESPACE\n                                 * non-zero would fail.\n                                 */\n\n                                if (space == val_size && val_size == val->size)\n                                        goto toolarge;\n                                off = OFFSET(p) - move_bytes;\n                                memmove(cp + off, val_data, move_bytes);\n                                val_data += move_bytes;\n                                val_size -= move_bytes;\n                                p[n] = off;\n                                p[n - 2] = FULL_KEY_DATA;\n                                FREESPACE(p) = FREESPACE(p) - move_bytes;\n                                OFFSET(p) = off;\n                        } else {\n                        toolarge:\n                                p[n - 2] = FULL_KEY;\n                        }\n                }\n                p = (u_int16_t *)bufp->page;\n                cp = bufp->page;\n                bufp->flags |= BUF_MOD;\n        }\n\n        /* Now move the data */\n        for (space = FREESPACE(p) - BIGOVERHEAD; val_size;\n            space = FREESPACE(p) - BIGOVERHEAD) {\n                move_bytes = MINIMUM(space, val_size);\n\n                /*\n                 * Here's the hack to make sure that if the data ends on the\n                 * same page as the key ends, FREESPACE is at least one.\n                 */\n\n                if (space == val_size && val_size == val->size)\n                        move_bytes--;\n                off = OFFSET(p) - move_bytes;\n                memmove(cp + off, val_data, move_bytes);\n                val_size -= move_bytes;\n                val_data += move_bytes;\n                n = p[0];\n                p[++n] = off;\n                p[0] = ++n;\n                FREESPACE(p) = off - PAGE_META(n);\n                OFFSET(p) = off;\n                if (val_size) {\n                        p[n] = FULL_KEY;\n                        bufp = __add_ovflpage(hashp, bufp);\n                        if (!bufp)\n                                return (-1);\n                        cp = bufp->page;\n                        p = (u_int16_t *)cp;\n                } else\n                        p[n] = FULL_KEY_DATA;\n                bufp->flags |= BUF_MOD;\n        }\n        return (0);\n}\n\n/*\n * Called when bufp's page  contains a partial key (index should be 1)\n *\n * All pages in the big key/data pair except bufp are freed.  We cannot\n * free bufp because the page pointing to it is lost and we can't get rid\n * of its pointer.\n *\n * Returns:\n * 0 => OK\n *-1 => ERROR\n */\n\nint\n__big_delete(HTAB *hashp, BUFHEAD *bufp)\n{\n        BUFHEAD *last_bfp, *rbufp;\n        u_int16_t *bp, pageno;\n        int key_done, n;\n\n        rbufp    = bufp;\n        last_bfp = NULL;\n        bp       = (u_int16_t *)bufp->page;\n        pageno   = 0;\n        (void)pageno;\n        key_done = 0;\n\n        while (!key_done || (bp[2] != FULL_KEY_DATA)) {\n                if (bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA)\n                        key_done = 1;\n\n                /*\n                 * If there is freespace left on a FULL_KEY_DATA page, then\n                 * the data is short and fits entirely on this page, and this\n                 * is the last page.\n                 */\n\n                if (bp[2] == FULL_KEY_DATA && FREESPACE(bp))\n                        break;\n                pageno = bp[bp[0] - 1];\n                rbufp->flags |= BUF_MOD;\n                rbufp = __get_buf(hashp, pageno, rbufp, 0);\n                if (last_bfp)\n                        __free_ovflpage(hashp, last_bfp);\n                last_bfp = rbufp;\n                if (!rbufp)\n                        return (-1);            /* Error. */\n                bp = (u_int16_t *)rbufp->page;\n        }\n\n        /*\n         * If we get here then rbufp points to the last page of the big\n         * key/data pair.  Bufp points to the first one -- it should now be\n         * empty pointing to the next page after this pair.  Can't free it\n         * because we don't have the page pointing to it.\n         */\n\n        /* This is information from the last page of the pair. */\n        n = bp[0];\n        pageno = bp[n - 1];\n\n        /* Now, bp is the first page of the pair. */\n        bp = (u_int16_t *)bufp->page;\n        if (n > 2) {\n                /* There is an overflow page. */\n                bp[1] = pageno;\n                bp[2] = OVFLPAGE;\n                bufp->ovfl = rbufp->ovfl;\n        } else\n                /* This is the last page. */\n                bufp->ovfl = NULL;\n        n -= 2;\n        bp[0] = n;\n        FREESPACE(bp) = hashp->BSIZE - PAGE_META(n);\n        OFFSET(bp) = hashp->BSIZE;\n\n        bufp->flags |= BUF_MOD;\n        if (rbufp)\n                __free_ovflpage(hashp, rbufp);\n        if (last_bfp && last_bfp != rbufp)\n                __free_ovflpage(hashp, last_bfp);\n\n        hashp->NKEYS--;\n        return (0);\n}\n\n/*\n * Returns:\n *  0 = key not found\n * -1 = get next overflow page\n * -2 means key not found and this is big key/data\n * -3 error\n */\n\nint\n__find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size)\n{\n        u_int16_t *bp;\n        char *p;\n        int ksize;\n        u_int16_t bytes;\n        char *kkey;\n\n        bp = (u_int16_t *)bufp->page;\n        p = bufp->page;\n        ksize = size;\n        kkey = key;\n\n        for (bytes = hashp->BSIZE - bp[ndx];\n            bytes <= size && bp[ndx + 1] == PARTIAL_KEY;\n            bytes = hashp->BSIZE - bp[ndx]) {\n                if (memcmp(p + bp[ndx], kkey, bytes))\n                        return (-2);\n                kkey += bytes;\n                ksize -= bytes;\n                bufp = __get_buf(hashp, bp[ndx + 2], bufp, 0);\n                if (!bufp)\n                        return (-3);\n                p = bufp->page;\n                bp = (u_int16_t *)p;\n                ndx = 1;\n        }\n\n        if (bytes != ksize || memcmp(p + bp[ndx], kkey, bytes)) {\n#ifdef HASH_STATISTICS\n                ++hash_collisions;\n#endif /* ifdef HASH_STATISTICS */\n                return (-2);\n        } else\n                return (ndx);\n}\n\n/*\n * Given the buffer pointer of the first overflow page of a big pair,\n * find the end of the big pair\n *\n * This will set bpp to the buffer header of the last page of the big pair.\n * It will return the pageno of the overflow page following the last page\n * of the pair; 0 if there isn't any (i.e. big pair is the last key in the\n * bucket)\n */\n\nu_int16_t\n__find_last_page(HTAB *hashp, BUFHEAD **bpp)\n{\n        BUFHEAD *bufp;\n        u_int16_t *bp, pageno;\n        int n;\n\n        bufp = *bpp;\n        bp = (u_int16_t *)bufp->page;\n        for (;;) {\n                n = bp[0];\n\n                /*\n                 * This is the last page if: the tag is FULL_KEY_DATA and\n                 * either only 2 entries OVFLPAGE marker is explicit there\n                 * is freespace on the page.\n                 */\n\n                if (bp[2] == FULL_KEY_DATA &&\n                    ((n == 2) || (bp[n] == OVFLPAGE) || (FREESPACE(bp))))\n                        break;\n\n                pageno = bp[n - 1];\n                bufp = __get_buf(hashp, pageno, bufp, 0);\n                if (!bufp)\n                        return (0);     /* Need to indicate an error! */\n                bp = (u_int16_t *)bufp->page;\n        }\n\n        *bpp = bufp;\n        if (bp[0] > 2)\n                return (bp[3]);\n        else\n                return (0);\n}\n\n/*\n * Return the data for the key/data pair that begins on this page at this\n * index (index should always be 1).\n */\n\nint\n__big_return(HTAB *hashp, BUFHEAD *bufp, int ndx, DBT *val, int set_current)\n{\n        BUFHEAD *save_p;\n        u_int16_t *bp, len, off, save_addr;\n        char *tp;\n\n        bp = (u_int16_t *)bufp->page;\n        while (bp[ndx + 1] == PARTIAL_KEY) {\n                bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                if (!bufp)\n                        return (-1);\n                bp = (u_int16_t *)bufp->page;\n                ndx = 1;\n        }\n\n        if (bp[ndx + 1] == FULL_KEY) {\n                bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                if (!bufp)\n                        return (-1);\n                bp = (u_int16_t *)bufp->page;\n                save_p = bufp;\n                save_addr = save_p->addr;\n                off = bp[1];\n                len = 0;\n        } else\n                if (!FREESPACE(bp)) {\n\n                        /*\n                         * This is a hack.  We can't distinguish between\n                         * FULL_KEY_DATA that contains complete data or\n                         * incomplete data, so we require that if the data\n                         * is complete, there is at least 1 byte of free\n                         * space left.\n                         */\n\n                        off       = bp[bp[0]];\n                        len       = bp[1] - off;\n                        save_p    = bufp;\n                        save_addr = bufp->addr;\n                        bufp      = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                        if (!bufp)\n                                return (-1);\n                        bp = (u_int16_t *)bufp->page;\n                        (void)bp;\n                } else {\n                        /* The data is all on one page. */\n                        tp        = (char *)bp;\n                        off       = bp[bp[0]];\n                        val->data = (unsigned char *)tp + off;\n                        val->size = bp[1] - off;\n                        if (set_current) {\n                                if (bp[0] == 2) {       /* No more buckets in\n                                                         * chain */\n                                        hashp->cpage = NULL;\n                                        hashp->cbucket++;\n                                        hashp->cndx  = 1;\n                                } else {\n                                        hashp->cpage = __get_buf(hashp,\n                                            bp[bp[0] - 1], bufp, 0);\n                                        if (!hashp->cpage)\n                                                return (-1);\n                                        hashp->cndx = 1;\n                                        if (!((u_int16_t *)\n                                            hashp->cpage->page)[0]) {\n                                                hashp->cbucket++;\n                                                hashp->cpage = NULL;\n                                        }\n                                }\n                        }\n                        return (0);\n                }\n\n        val->size = (size_t)collect_data(hashp, bufp, (int)len, set_current);\n        if (val->size == (size_t)-1)\n                return (-1);\n        if (save_p->addr != save_addr) {\n                /* We are pretty short on buffers. */\n                errno = EINVAL;                 /* OUT OF BUFFERS */\n                return (-1);\n        }\n        memmove(hashp->tmp_buf, (save_p->page) + off, len);\n        val->data = (unsigned char *)hashp->tmp_buf;\n        return (0);\n}\n\n/*\n * Count how big the total datasize is by recursing through the pages.  Then\n * allocate a buffer and copy the data as you recurse up.\n */\n\nstatic int\ncollect_data(HTAB *hashp, BUFHEAD *bufp, int len, int set)\n{\n        u_int16_t *bp;\n        char *p;\n        BUFHEAD *xbp;\n        u_int16_t save_addr;\n        int mylen, totlen;\n\n        p         = bufp->page;\n        bp        = (u_int16_t *)p;\n        mylen     = hashp->BSIZE - bp[1];\n        save_addr = bufp->addr;\n\n        if (bp[2] == FULL_KEY_DATA) {           /* End of Data */\n                totlen = len + mylen;\n                free(hashp->tmp_buf);\n                if ((hashp->tmp_buf = (char *)malloc(totlen)) == NULL)\n                        return (-1);\n                if (set) {\n                        hashp->cndx = 1;\n                        if (bp[0] == 2) {       /* No more buckets in chain */\n                                hashp->cpage = NULL;\n                                hashp->cbucket++;\n                        } else {\n                                hashp->cpage =\n                                    __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                                if (!hashp->cpage)\n                                        return (-1);\n                                else if (!((u_int16_t *)hashp->cpage->page)[0]) {\n                                        hashp->cbucket++;\n                                        hashp->cpage = NULL;\n                                }\n                        }\n                }\n        } else {\n                xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                if (!xbp || ((totlen =\n                    collect_data(hashp, xbp, len + mylen, set)) < 1))\n                        return (-1);\n        }\n        if (bufp->addr != save_addr) {\n                errno = EINVAL;                 /* Out of buffers. */\n                return (-1);\n        }\n        memmove(&hashp->tmp_buf[len], (bufp->page) + bp[1], mylen);\n        return (totlen);\n}\n\n/*\n * Fill in the key and data for this big pair.\n */\n\nint\n__big_keydata(HTAB *hashp, BUFHEAD *bufp, DBT *key, DBT *val, int set)\n{\n        key->size = (size_t)collect_key(hashp, bufp, 0, val, set);\n        if (key->size == (size_t)-1)\n                return (-1);\n        key->data = (unsigned char *)hashp->tmp_key;\n        return (0);\n}\n\n/*\n * Count how big the total key size is by recursing through the pages.  Then\n * collect the data, allocate a buffer and copy the key as you recurse up.\n */\n\nstatic int\ncollect_key(HTAB *hashp, BUFHEAD *bufp, int len, DBT *val, int set)\n{\n        BUFHEAD *xbp;\n        char *p;\n        int mylen, totlen;\n        u_int16_t *bp, save_addr;\n\n        p     = bufp->page;\n        bp    = (u_int16_t *)p;\n        mylen = hashp->BSIZE - bp[1];\n\n        save_addr = bufp->addr;\n        totlen = len + mylen;\n        if (bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA) {    /* End of Key. */\n                free(hashp->tmp_key);\n                if ((hashp->tmp_key = (char *)malloc(totlen)) == NULL)\n                        return (-1);\n                if (__big_return(hashp, bufp, 1, val, set))\n                        return (-1);\n        } else {\n                xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                if (!xbp || ((totlen =\n                    collect_key(hashp, xbp, totlen, val, set)) < 1))\n                        return (-1);\n        }\n        if (bufp->addr != save_addr) {\n                errno = EINVAL;         /* MIS -- OUT OF BUFFERS */\n                return (-1);\n        }\n        memmove(&hashp->tmp_key[len], (bufp->page) + bp[1], mylen);\n        return (totlen);\n}\n\n/*\n * Returns:\n *  0 => OK\n * -1 => error\n */\n\nint\n__big_split(HTAB *hashp,\n    BUFHEAD *op,        /* Pointer to where to put keys that go in old bucket */\n    BUFHEAD *np,        /* Pointer to new bucket page */\n    BUFHEAD *big_keyp,  /* Pointer to first page containing the big key/data */\n    int addr,           /* Address of big_keyp */\n    u_int32_t obucket,  /* Old Bucket */\n    SPLIT_RETURN *ret)\n{\n        BUFHEAD *bp, *tmpp;\n        DBT key, val;\n        u_int32_t change;\n        u_int16_t free_space, n, off, *tp;\n\n        bp = big_keyp;\n\n        /* Now figure out where the big key/data goes */\n        if (__big_keydata(hashp, big_keyp, &key, &val, 0))\n                return (-1);\n        change = (__call_hash(hashp, key.data, key.size) != obucket);\n\n        if ((ret->next_addr = __find_last_page(hashp, &big_keyp))) {\n                if (!(ret->nextp =\n                    __get_buf(hashp, ret->next_addr, big_keyp, 0)))\n                        return (-1);\n        } else\n                ret->nextp = NULL;\n\n        /* Now make one of np/op point to the big key/data pair */\n#ifdef DEBUG\n        assert(np->ovfl == NULL);\n#endif /* ifdef DEBUG */\n        if (change)\n                tmpp = np;\n        else\n                tmpp = op;\n\n        tmpp->flags |= BUF_MOD;\n#ifdef DEBUG1\n        (void)fprintf(stderr,\n            \"BIG_SPLIT: %d->ovfl was %d is now %d\\n\", tmpp->addr,\n            (tmpp->ovfl ? tmpp->ovfl->addr : 0), (bp ? bp->addr : 0));\n#endif /* ifdef DEBUG1 */\n        tmpp->ovfl = bp;        /* one of op/np point to big_keyp */\n        tp = (u_int16_t *)tmpp->page;\n#ifdef DEBUG\n        assert(FREESPACE(tp) >= OVFLSIZE);\n#endif /* ifdef DEBUG */\n        n             = tp[0];\n        off           = OFFSET(tp);\n        free_space    = FREESPACE(tp);\n        tp[++n]       = (u_int16_t)addr;\n        tp[++n]       = OVFLPAGE;\n        tp[0]         = n;\n        OFFSET(tp)    = off;\n        FREESPACE(tp) = free_space - OVFLSIZE;\n\n        /*\n         * Finally, set the new and old return values. BIG_KEYP contains a\n         * pointer to the last page of the big key_data pair. Make sure that\n         * big_keyp has no following page (2 elements) or create an empty\n         * following page.\n         */\n\n        ret->newp = np;\n        ret->oldp = op;\n\n        tp = (u_int16_t *)big_keyp->page;\n        big_keyp->flags |= BUF_MOD;\n        if (tp[0] > 2) {\n\n                /*\n                 * There may be either one or two offsets on this page.  If\n                 * there is one, then the overflow page is linked on normally\n                 * and tp[4] is OVFLPAGE.  If there are two, tp[4] contains\n                 * the second offset and needs to get stuffed in after the\n                 * next overflow page is added.\n                 */\n\n                n              = tp[4];\n                free_space     = FREESPACE(tp);\n                off            = OFFSET(tp);\n                tp[0]         -= 2;\n                FREESPACE(tp)  = free_space + OVFLSIZE;\n                OFFSET(tp)     = off;\n                tmpp           = __add_ovflpage(hashp, big_keyp);\n                if (!tmpp)\n                        return (-1);\n                tp[4] = n;\n        } else\n                tmpp = big_keyp;\n\n        if (change)\n                ret->newp = tmpp;\n        else\n                ret->oldp = tmpp;\n        return (0);\n}\n"
  },
  {
    "path": "db/hash/hash_buf.c",
    "content": "/*      $OpenBSD: hash_buf.c,v 1.19 2015/01/16 16:48:51 deraadt Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n/*\n * PACKAGE: hash\n *\n * DESCRIPTION:\n *      Contains buffer management\n *\n * ROUTINES:\n * External\n *      __buf_init\n *      __get_buf\n *      __buf_free\n *      __reclaim_buf\n * Internal\n *      newbuf\n */\n\n#include \"../../include/compat.h\"\n\n#include <errno.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#ifdef DEBUG\n# include <assert.h>\n#endif /* ifdef DEBUG */\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"hash.h\"\n#include \"page.h\"\n#include \"extern.h\"\n\n#define MAXIMUM(a, b)   (((a) > (b)) ? (a) : (b))\n\nstatic BUFHEAD *newbuf(HTAB *, u_int32_t, BUFHEAD *);\n\n/* Unlink B from its place in the lru */\n#define BUF_REMOVE(B) {              \\\n        (B)->prev->next = (B)->next; \\\n        (B)->next->prev = (B)->prev; \\\n}\n\n/* Insert B after P */\n#define BUF_INSERT(B, P) {     \\\n        (B)->next = (P)->next; \\\n        (B)->prev = (P);       \\\n        (P)->next = (B);       \\\n        (B)->next->prev = (B); \\\n}\n\n#define MRU     hashp->bufhead.next\n#define LRU     hashp->bufhead.prev\n\n#define MRU_INSERT(B)   BUF_INSERT((B), &hashp->bufhead)\n#define LRU_INSERT(B)   BUF_INSERT((B), LRU)\n\n/*\n * We are looking for a buffer with address \"addr\".  If prev_bp is NULL, then\n * address is a bucket index.  If prev_bp is not NULL, then it points to the\n * page previous to an overflow page that we are trying to find.\n *\n * CAVEAT:  The buffer header accessed via prev_bp's ovfl field may no longer\n * be valid.  Therefore, you must always verify that its address matches the\n * address you are seeking.\n */\n\nBUFHEAD *\n__get_buf(HTAB *hashp, u_int32_t addr,\n    BUFHEAD *prev_bp,   /* If prev_bp set, indicates a new overflow page. */\n    int newpage)\n{\n        BUFHEAD *bp;\n        u_int32_t is_disk_mask;\n        int is_disk, segment_ndx;\n        SEGMENT segp;\n\n        is_disk      = 0;\n        is_disk_mask = 0;\n        if (prev_bp) {\n                bp = prev_bp->ovfl;\n                if (!bp || (bp->addr != addr))\n                        bp = NULL;\n                if (!newpage)\n                        is_disk = BUF_DISK;\n        } else {\n                /* Grab buffer out of directory */\n                segment_ndx = addr & (hashp->SGSIZE - 1);\n\n                /* valid segment ensured by __call_hash() */\n                segp = hashp->dir[addr >> hashp->SSHIFT];\n#ifdef DEBUG\n                assert(segp != NULL);\n#endif /* ifdef DEBUG */\n                bp           = PTROF(segp[segment_ndx]);\n                is_disk_mask = ISDISK(segp[segment_ndx]);\n                is_disk      = is_disk_mask || !hashp->new_file;\n        }\n\n        if (!bp) {\n                bp = newbuf(hashp, addr, prev_bp);\n                if (!bp ||\n                    __get_page(hashp, bp->page, addr, !prev_bp, is_disk, 0))\n                        return (NULL);\n                if (!prev_bp)\n                        segp[segment_ndx] =\n                            (BUFHEAD *)((ptrdiff_t)bp | is_disk_mask);\n        } else {\n                BUF_REMOVE(bp);\n                MRU_INSERT(bp);\n        }\n        return (bp);\n}\n\n/*\n * We need a buffer for this page. Either allocate one, or evict a resident\n * one (if we have as many buffers as we're allowed) and put this one in.\n *\n * If newbuf finds an error (returning NULL), it also sets errno.\n */\n\nstatic BUFHEAD *\nnewbuf(HTAB *hashp, u_int32_t addr, BUFHEAD *prev_bp)\n{\n        BUFHEAD *bp;            /* The buffer we're going to use */\n        BUFHEAD *xbp;           /* Temp pointer */\n        BUFHEAD *next_xbp;\n        SEGMENT segp;\n        int segment_ndx;\n        u_int16_t oaddr, *shortp;\n\n        oaddr = 0;\n        bp    = LRU;\n\n        /* It is bad to overwrite the page under the cursor. */\n        if (bp == hashp->cpage) {\n                BUF_REMOVE(bp);\n                MRU_INSERT(bp);\n                bp = LRU;\n        }\n\n        /* If prev_bp is part of bp overflow, create a new buffer. */\n        if (hashp->nbufs == 0 && prev_bp && bp->ovfl) {\n                BUFHEAD *ovfl;\n\n                for (ovfl = bp->ovfl; ovfl ; ovfl = ovfl->ovfl) {\n                        if (ovfl == prev_bp) {\n                                hashp->nbufs++;\n                                break;\n                        }\n                }\n        }\n\n        /*\n         * If LRU buffer is pinned, the buffer pool is too small. We need to\n         * allocate more buffers.\n         */\n\n        if (hashp->nbufs || (bp->flags & BUF_PIN) || bp == hashp->cpage) {\n                /* Allocate a new one */\n                if ((bp = (BUFHEAD *)malloc(sizeof(BUFHEAD))) == NULL)\n                        return (NULL);\n                memset(bp, 0xff, sizeof(BUFHEAD));\n                if ((bp->page = (char *)malloc(hashp->BSIZE)) == NULL) {\n                        free(bp);\n                        return (NULL);\n                }\n                memset(bp->page, 0xff, hashp->BSIZE);\n                if (hashp->nbufs)\n                        hashp->nbufs--;\n        } else {\n                /* Kick someone out */\n                BUF_REMOVE(bp);\n\n                /*\n                 * If this is an overflow page with addr 0, it's already been\n                 * flushed back in an overflow chain and initialized.\n                 */\n\n                if ((bp->addr != 0) || (bp->flags & BUF_BUCKET)) {\n\n                        /*\n                         * Set oaddr before __put_page so that you get it\n                         * before bytes are swapped.\n                         */\n\n                        shortp = (u_int16_t *)bp->page;\n                        if (shortp[0])\n                                oaddr = shortp[shortp[0] - 1];\n                        if ((bp->flags & BUF_MOD) && __put_page(hashp, bp->page,\n                            bp->addr, (int)IS_BUCKET(bp->flags), 0))\n                                return (NULL);\n\n                        /*\n                         * Update the pointer to this page (i.e. invalidate it).\n                         *\n                         * If this is a new file (i.e. we created it at open\n                         * time), make sure that we mark pages which have been\n                         * written to disk so we retrieve them from disk later,\n                         * rather than allocating new pages.\n                         */\n\n                        if (IS_BUCKET(bp->flags)) {\n                                segment_ndx = bp->addr & (hashp->SGSIZE - 1);\n                                segp = hashp->dir[bp->addr >> hashp->SSHIFT];\n#ifdef DEBUG\n                                assert(segp != NULL);\n#endif /* ifdef DEBUG */\n\n                                if (hashp->new_file &&\n                                    ((bp->flags & BUF_MOD) ||\n                                    ISDISK(segp[segment_ndx])))\n                                        segp[segment_ndx] = (BUFHEAD *)BUF_DISK;\n                                else\n                                        segp[segment_ndx] = NULL;\n                        }\n\n                        /*\n                         * Since overflow pages can only be access by means of\n                         * their bucket, free overflow pages associated with\n                         * this bucket.\n                         */\n\n                        for (xbp = bp; xbp->ovfl;) {\n                                next_xbp = xbp->ovfl;\n                                xbp->ovfl = 0;\n                                xbp = next_xbp;\n\n                                /* Check that ovfl pointer is up date. */\n                                if (IS_BUCKET(xbp->flags) ||\n                                    (oaddr != xbp->addr))\n                                        break;\n\n                                shortp = (u_int16_t *)xbp->page;\n                                if (shortp[0])\n                                        /* set before __put_page */\n                                        oaddr = shortp[shortp[0] - 1];\n                                if ((xbp->flags & BUF_MOD) && __put_page(hashp,\n                                    xbp->page, xbp->addr, 0, 0))\n                                        return (NULL);\n                                xbp->addr = 0;\n                                xbp->flags = 0;\n                                BUF_REMOVE(xbp);\n                                LRU_INSERT(xbp);\n                        }\n                }\n        }\n\n        /* Now assign this buffer */\n        bp->addr = addr;\n#ifdef DEBUG1\n        (void)fprintf(stderr, \"NEWBUF1: %d->ovfl was %d is now %d\\n\",\n            bp->addr, (bp->ovfl ? bp->ovfl->addr : 0), 0);\n#endif /* ifdef DEBUG1 */\n        bp->ovfl = NULL;\n        if (prev_bp) {\n\n                /*\n                 * If prev_bp is set, this is an overflow page, hook it in to\n                 * the buffer overflow links.\n                 */\n\n#ifdef DEBUG1\n                (void)fprintf(stderr, \"NEWBUF2: %d->ovfl was %d is now %d\\n\",\n                    prev_bp->addr, (prev_bp->ovfl ? prev_bp->ovfl->addr : 0),\n                    (bp ? bp->addr : 0));\n#endif /* ifdef DEBUG1 */\n                prev_bp->ovfl = bp;\n                bp->flags = 0;\n        } else\n                bp->flags = BUF_BUCKET;\n        MRU_INSERT(bp);\n        return (bp);\n}\n\nvoid\n__buf_init(HTAB *hashp, int nbytes)\n{\n        BUFHEAD *bfp;\n        int npages;\n\n        bfp    = &(hashp->bufhead);\n        npages = (nbytes + hashp->BSIZE - 1) >> hashp->BSHIFT;\n        npages = MAXIMUM(npages, MIN_BUFFERS);\n\n        hashp->nbufs = npages;\n        bfp->next    = bfp;\n        bfp->prev    = bfp;\n\n        /*\n         * This space is calloc'd so these are already null.\n         *\n         * bfp->ovfl  = NULL;\n         * bfp->flags = 0;\n         * bfp->page  = NULL;\n         * bfp->addr  = 0;\n         */\n}\n\nint\n__buf_free(HTAB *hashp, int do_free, int to_disk)\n{\n        BUFHEAD *bp;\n\n        /* Need to make sure that buffer manager has been initialized */\n        if (!LRU)\n                return (0);\n        for (bp = LRU; bp != &hashp->bufhead;) {\n                /* Check that the buffer is valid */\n                if (bp->addr || IS_BUCKET(bp->flags)) {\n                        if (to_disk && (bp->flags & BUF_MOD) &&\n                            __put_page(hashp, bp->page,\n                            bp->addr, IS_BUCKET(bp->flags), 0))\n                                return (-1);\n                }\n                /* Check if we are freeing stuff */\n                if (do_free) {\n                        if (bp->page) {\n                                (void)memset(bp->page, 0, hashp->BSIZE);\n                                free(bp->page);\n                        }\n                        BUF_REMOVE(bp);\n                        free(bp);\n                        bp = LRU;\n                } else\n                        bp = bp->prev;\n        }\n        return (0);\n}\n\nvoid\n__reclaim_buf(HTAB *hashp, BUFHEAD *bp)\n{\n        bp->ovfl  = 0;\n        bp->addr  = 0;\n        bp->flags = 0;\n        BUF_REMOVE(bp);\n        LRU_INSERT(bp);\n}\n"
  },
  {
    "path": "db/hash/hash_func.c",
    "content": "/*      $OpenBSD: hash_func.c,v 1.11 2016/05/29 20:47:49 guenther Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n\n/* Chris Torek's hash function. */\nu_int32_t\n__default_hash(const void *key, size_t len)\n{\n        u_int32_t h, loop;\n        u_int8_t *k;\n\n#define HASH4a   h = (h << 5) - h + *k++;\n#define HASH4b   h = (h << 5) + h + *k++;\n#define HASH4 HASH4b\n\n        h = 0;\n        k = (u_int8_t *)key;\n        if (len > 0) {\n                loop = (len + 8 - 1) >> 3;\n\n                switch (len & (8 - 1)) {\n                case 0:\n                        do {    /* All fall throughs */\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 7:\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 6:\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 5:\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 4:\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 3:\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 2:\n                                HASH4;\n                                /* FALLTHROUGH */\n                case 1:\n                                HASH4;\n                                /* FALLTHROUGH */\n                        } while (--loop);\n                }\n\n        }\n        return (h);\n}\n"
  },
  {
    "path": "db/hash/hash_log2.c",
    "content": "/*      $OpenBSD: hash_log2.c,v 1.8 2005/08/05 13:03:00 espie Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"hash.h\"\n#include \"page.h\"\n#include \"extern.h\"\n\nu_int32_t\n__log2(u_int32_t num)\n{\n        u_int32_t i, limit;\n\n        limit = 1;\n        for (i = 0; limit < num; limit = limit << 1, i++);\n        return (i);\n}\n"
  },
  {
    "path": "db/hash/hash_page.c",
    "content": "/*      $OpenBSD: hash_page.c,v 1.23 2016/12/18 17:07:58 krw Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n/*\n * PACKAGE:  hashing\n *\n * DESCRIPTION:\n *      Page manipulation for hashing package.\n *\n * ROUTINES:\n *\n * External\n *      __get_page\n *      __add_ovflpage\n * Internal\n *      overflow_page\n *      open_temp\n */\n\n#ifndef EFTYPE\n# define EFTYPE ENOTSUP\n#endif /* ifndef EFTYPE */\n\n#include \"../../include/compat.h\"\n\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n#ifdef DEBUG\n# include <assert.h>\n#endif /* ifdef DEBUG */\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"hash.h\"\n#include \"page.h\"\n#include \"extern.h\"\n\n#if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) )\n# define BIG_ENDIAN     4321\n# define LITTLE_ENDIAN  1234\n#endif /* if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) */\n\n#if ( !defined(BYTE_ORDER) && defined(_AIX) )\n# if ( defined(__powerpc__) || defined(__PPC__) \\\n    || defined(_ARCH_PPC) )\n#  define BYTE_ORDER BIG_ENDIAN /* Assume AIX/PPC is big-endian */\n# endif /* if ( defined(__powerpc__) || defined(__PPC__)\n             || defined(_ARCH_PPC) ) */\n#endif /* if ( !defined(BYTE_ORDER) && defined(_AIX) ) */\n\n#undef open\n\nstatic u_int32_t *fetch_bitmap(HTAB *, int);\nstatic u_int32_t  first_free(u_int32_t);\nstatic int        open_temp(HTAB *);\nstatic u_int16_t  overflow_page(HTAB *);\nstatic void       putpair(char *, const DBT *, const DBT *);\nstatic void       squeeze_key(u_int16_t *, const DBT *, const DBT *);\nstatic int        ugly_split(HTAB *, u_int32_t, BUFHEAD *, BUFHEAD *, int, int);\n\n#define PAGE_INIT(P) {                                                \\\n        ((u_int16_t *)(P))[0] = 0;                                    \\\n        ((u_int16_t *)(P))[1] = hashp->BSIZE - 3 * sizeof(u_int16_t); \\\n        ((u_int16_t *)(P))[2] = hashp->BSIZE;                         \\\n}\n\n/*\n * This is called AFTER we have verified that there is room on the page for\n * the pair (PAIRFITS has returned true) so we go right ahead and start moving\n * stuff on.\n */\n\nstatic void\nputpair(char *p, const DBT *key, const DBT *val)\n{\n        u_int16_t *bp, n, off;\n\n        bp = (u_int16_t *)p;\n\n        /* Enter the key first. */\n        n = bp[0];\n\n        off     = OFFSET(bp) - key->size;\n        memmove(p + off, key->data, key->size);\n        bp[++n] = off;\n\n        /* Now the data. */\n        off     -= val->size;\n        memmove(p + off, val->data, val->size);\n        bp[++n]  = off;\n\n        /* Adjust page info. */\n        bp[0]     = n;\n        bp[n + 1] = off - ((n + 3) * sizeof(u_int16_t));\n        bp[n + 2] = off;\n}\n\n/*\n * Returns:\n *       0 OK\n *      -1 error\n */\n\nint\n__delpair(HTAB *hashp, BUFHEAD *bufp, int ndx)\n{\n        u_int16_t *bp, newoff, pairlen;\n        int n;\n\n        bp = (u_int16_t *)bufp->page;\n        n = bp[0];\n\n        if (bp[ndx + 1] < REAL_KEY)\n                return (__big_delete(hashp, bufp));\n        if (ndx != 1)\n                newoff = bp[ndx - 1];\n        else\n                newoff = hashp->BSIZE;\n        pairlen = newoff - bp[ndx + 1];\n\n        if (ndx != (n - 1)) {\n                /* Hard Case -- need to shuffle keys */\n                int i;\n                char *src = bufp->page + (int)OFFSET(bp);\n                char *dst = src + (int)pairlen;\n                memmove(dst, src, bp[ndx + 1] - OFFSET(bp));\n\n                /* Now adjust the pointers */\n                for (i = ndx + 2; i <= n; i += 2) {\n                        if (bp[i + 1] == OVFLPAGE) {\n                                bp[i - 2] = bp[i];\n                                bp[i - 1] = bp[i + 1];\n                        } else {\n                                bp[i - 2] = bp[i] + pairlen;\n                                bp[i - 1] = bp[i + 1] + pairlen;\n                        }\n                }\n                if (ndx == hashp->cndx) {\n\n                        /*\n                         * We just removed pair we were \"pointing\" to.\n                         * By moving back the cndx we ensure subsequent\n                         * hash_seq() calls won't skip over any entries.\n                         */\n\n                        hashp->cndx -= 2;\n                }\n        }\n        /* Finally adjust the page data */\n        bp[n] = OFFSET(bp) + pairlen;\n        bp[n - 1] = bp[n + 1] + pairlen + 2 * sizeof(u_int16_t);\n        bp[0] = n - 2;\n        hashp->NKEYS--;\n\n        bufp->flags |= BUF_MOD;\n        return (0);\n}\n\n/*\n * Returns:\n *       0 ==> OK\n *      -1 ==> Error\n */\n\nint\n__split_page(HTAB *hashp, u_int32_t obucket, u_int32_t nbucket)\n{\n        BUFHEAD *new_bufp, *old_bufp;\n        u_int16_t *ino;\n        char *np;\n        DBT key, val;\n        int n, ndx, retval;\n        u_int16_t copyto, diff, off, moved;\n        char *op;\n\n        copyto   = (u_int16_t)hashp->BSIZE;\n        off      = (u_int16_t)hashp->BSIZE;\n        old_bufp = __get_buf(hashp, obucket, NULL, 0);\n        if (old_bufp == NULL)\n                return (-1);\n        new_bufp = __get_buf(hashp, nbucket, NULL, 0);\n        if (new_bufp == NULL)\n                return (-1);\n\n        old_bufp->flags |= (BUF_MOD | BUF_PIN);\n        new_bufp->flags |= (BUF_MOD | BUF_PIN);\n\n        ino = (u_int16_t *)(op = old_bufp->page);\n        np = new_bufp->page;\n\n        moved = 0;\n\n        for (n = 1, ndx = 1; n < ino[0]; n += 2) {\n                if (ino[n + 1] < REAL_KEY) {\n                        retval = ugly_split(hashp, obucket, old_bufp, new_bufp,\n                            (int)copyto, (int)moved);\n                        old_bufp->flags &= ~BUF_PIN;\n                        new_bufp->flags &= ~BUF_PIN;\n                        return (retval);\n\n                }\n                key.data = (unsigned char *)op + ino[n];\n                key.size = off - ino[n];\n\n                if (__call_hash(hashp, key.data, key.size) == obucket) {\n                        /* Don't switch page */\n                        diff = copyto - off;\n                        if (diff) {\n                                copyto = ino[n + 1] + diff;\n                                memmove(op + copyto, op + ino[n + 1],\n                                    off - ino[n + 1]);\n                                ino[ndx] = copyto + ino[n] - ino[n + 1];\n                                ino[ndx + 1] = copyto;\n                        } else\n                                copyto = ino[n + 1];\n                        ndx += 2;\n                } else {\n                        /* Switch page */\n                        val.data = (unsigned char *)op + ino[n + 1];\n                        val.size = ino[n] - ino[n + 1];\n                        putpair(np, &key, &val);\n                        moved += 2;\n                }\n\n                off = ino[n + 1];\n        }\n\n        /* Now clean up the page */\n        ino[0]         -= moved;\n        FREESPACE(ino)  = copyto - sizeof(u_int16_t) * (ino[0] + 3);\n        OFFSET(ino)     = copyto;\n\n#ifdef DEBUG3\n        (void)fprintf(stderr, \"split %d/%d\\n\",\n            ((u_int16_t *)np)[0] / 2,\n            ((u_int16_t *)op)[0] / 2);\n#endif /* ifdef DEBUG3 */\n        /* unpin both pages */\n        old_bufp->flags &= ~BUF_PIN;\n        new_bufp->flags &= ~BUF_PIN;\n        return (0);\n}\n\n/*\n * Called when we encounter an overflow or big key/data page during split\n * handling.  This is special cased since we have to begin checking whether\n * the key/data pairs fit on their respective pages and because we may need\n * overflow pages for both the old and new pages.\n *\n * The first page might be a page with regular key/data pairs in which case\n * we have a regular overflow condition and just need to go on to the next\n * page or it might be a big key/data pair in which case we need to fix the\n * big key/data pair.\n *\n * Returns:\n *       0 ==> success\n *      -1 ==> failure\n */\n\nstatic int\nugly_split(HTAB *hashp,\n    u_int32_t obucket,  /* Same as __split_page. */\n    BUFHEAD *old_bufp,\n    BUFHEAD *new_bufp,\n    int copyto,         /* First byte on page which contains key/data values. */\n    int moved)          /* Number of pairs moved to new page. */\n{\n        BUFHEAD *bufp;  /* Buffer header for ino */\n        u_int16_t *ino; /* Page keys come off of */\n        u_int16_t *np;  /* New page */\n        u_int16_t *op;  /* Page keys go on to if they aren't moving */\n\n        BUFHEAD *last_bfp;      /* Last buf header OVFL needing to be freed */\n        DBT key, val;\n        SPLIT_RETURN ret;\n        u_int16_t n, off, ov_addr, scopyto;\n        char *cino;             /* Character value of ino */\n\n        bufp     = old_bufp;\n        ino      = (u_int16_t *)old_bufp->page;\n        np       = (u_int16_t *)new_bufp->page;\n        op       = (u_int16_t *)old_bufp->page;\n        last_bfp = NULL;\n        scopyto  = (u_int16_t)copyto;    /* ANSI */\n\n        n = ino[0] - 1;\n        while (n < ino[0]) {\n                if (ino[2] < REAL_KEY && ino[2] != OVFLPAGE) {\n                        if (__big_split(hashp, old_bufp,\n                            new_bufp, bufp, bufp->addr, obucket, &ret))\n                                return (-1);\n                        old_bufp = ret.oldp;\n                        if (!old_bufp)\n                                return (-1);\n                        op = (u_int16_t *)old_bufp->page;\n                        new_bufp = ret.newp;\n                        if (!new_bufp)\n                                return (-1);\n                        np = (u_int16_t *)new_bufp->page;\n                        bufp = ret.nextp;\n                        if (!bufp)\n                                return (0);\n                        cino = (char *)bufp->page;\n                        ino = (u_int16_t *)cino;\n                        last_bfp = ret.nextp;\n                } else if (ino[n + 1] == OVFLPAGE) {\n                        ov_addr = ino[n];\n\n                        /*\n                         * Fix up the old page -- the extra 2 are the fields\n                         * which contained the overflow information.\n                         */\n\n                        ino[0] -= (moved + 2);\n                        FREESPACE(ino) =\n                            scopyto - sizeof(u_int16_t) * (ino[0] + 3);\n                        OFFSET(ino) = scopyto;\n\n                        bufp = __get_buf(hashp, ov_addr, bufp, 0);\n                        if (!bufp)\n                                return (-1);\n\n                        ino = (u_int16_t *)bufp->page;\n                        n = 1;\n                        (void)n;\n                        scopyto = hashp->BSIZE;\n                        moved = 0;\n\n                        if (last_bfp)\n                                __free_ovflpage(hashp, last_bfp);\n                        last_bfp = bufp;\n                }\n                /* Move regular sized pairs of there are any */\n                off = hashp->BSIZE;\n                for (n = 1; (n < ino[0]) && (ino[n + 1] >= REAL_KEY); n += 2) {\n                        cino = (char *)ino;\n                        key.data = (unsigned char *)cino + ino[n];\n                        key.size = off - ino[n];\n                        val.data = (unsigned char *)cino + ino[n + 1];\n                        val.size = ino[n] - ino[n + 1];\n                        off = ino[n + 1];\n\n                        if (__call_hash(hashp, key.data, key.size) == obucket) {\n                                /* Keep on old page */\n                                if (PAIRFITS(op, (&key), (&val)))\n                                        putpair((char *)op, &key, &val);\n                                else {\n                                        old_bufp =\n                                            __add_ovflpage(hashp, old_bufp);\n                                        if (!old_bufp)\n                                                return (-1);\n                                        op = (u_int16_t *)old_bufp->page;\n                                        putpair((char *)op, &key, &val);\n                                }\n                                old_bufp->flags |= BUF_MOD;\n                        } else {\n                                /* Move to new page */\n                                if (PAIRFITS(np, (&key), (&val)))\n                                        putpair((char *)np, &key, &val);\n                                else {\n                                        new_bufp =\n                                            __add_ovflpage(hashp, new_bufp);\n                                        if (!new_bufp)\n                                                return (-1);\n                                        np = (u_int16_t *)new_bufp->page;\n                                        putpair((char *)np, &key, &val);\n                                }\n                                new_bufp->flags |= BUF_MOD;\n                        }\n                }\n        }\n        if (last_bfp)\n                __free_ovflpage(hashp, last_bfp);\n        return (0);\n}\n\n/*\n * Add the given pair to the page\n *\n * Returns:\n *      0 ==> OK\n *      1 ==> failure\n */\n\nint\n__addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val)\n{\n        u_int16_t *bp, *sop;\n        int do_expand;\n\n        bp        = (u_int16_t *)bufp->page;\n        do_expand = 0;\n        while (bp[0] && (bp[2] < REAL_KEY || bp[bp[0]] < REAL_KEY))\n                /* Exception case */\n                if (bp[2] == FULL_KEY_DATA && bp[0] == 2)\n                        /* This is the last page of a big key/data pair\n                           and we need to add another page */\n                        break;\n                else if (bp[2] < REAL_KEY && bp[bp[0]] != OVFLPAGE) {\n                        bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                        if (!bufp)\n                                return (-1);\n                        bp = (u_int16_t *)bufp->page;\n                } else if (bp[bp[0]] != OVFLPAGE) {\n                        /* Short key/data pairs, no more pages */\n                        break;\n                } else {\n                        /* Try to squeeze key on this page */\n                        if (bp[2] >= REAL_KEY &&\n                            FREESPACE(bp) >= PAIRSIZE(key, val)) {\n                                squeeze_key(bp, key, val);\n                                goto stats;\n                        } else {\n                                bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0);\n                                if (!bufp)\n                                        return (-1);\n                                bp = (u_int16_t *)bufp->page;\n                        }\n                }\n\n        if (PAIRFITS(bp, key, val))\n                putpair(bufp->page, key, val);\n        else {\n                do_expand = 1;\n                bufp = __add_ovflpage(hashp, bufp);\n                if (!bufp)\n                        return (-1);\n                sop = (u_int16_t *)bufp->page;\n\n                if (PAIRFITS(sop, key, val))\n                        putpair((char *)sop, key, val);\n                else\n                        if (__big_insert(hashp, bufp, key, val))\n                                return (-1);\n        }\nstats:\n        bufp->flags |= BUF_MOD;\n\n        /*\n         * If the average number of keys per bucket exceeds the fill factor,\n         * expand the table.\n         */\n\n        hashp->NKEYS++;\n        if (do_expand ||\n            (hashp->NKEYS / (hashp->MAX_BUCKET + 1) > hashp->FFACTOR))\n                return (__expand_table(hashp));\n        return (0);\n}\n\n/*\n *\n * Returns:\n *      pointer on success\n *      NULL on error\n */\n\nBUFHEAD *\n__add_ovflpage(HTAB *hashp, BUFHEAD *bufp)\n{\n        u_int16_t *sp, ndx, ovfl_num;\n#ifdef DEBUG1\n        int tmp1, tmp2;\n#endif /* ifdef DEBUG1 */\n        sp = (u_int16_t *)bufp->page;\n\n        /* Check if we are dynamically determining the fill factor */\n        if (hashp->FFACTOR == DEF_FFACTOR) {\n                hashp->FFACTOR = sp[0] >> 1;\n                if (hashp->FFACTOR < MIN_FFACTOR)\n                        hashp->FFACTOR = MIN_FFACTOR;\n        }\n        bufp->flags |= BUF_MOD;\n        ovfl_num = overflow_page(hashp);\n#ifdef DEBUG1\n        tmp1 = bufp->addr;\n        tmp2 = bufp->ovfl ? bufp->ovfl->addr : 0;\n#endif /* ifdef DEBUG1 */\n        if (!ovfl_num || !(bufp->ovfl = __get_buf(hashp, ovfl_num, bufp, 1)))\n                return (NULL);\n        bufp->ovfl->flags |= BUF_MOD;\n#ifdef DEBUG1\n        (void)fprintf(stderr, \"ADDOVFLPAGE: %d->ovfl was %d is now %d\\n\",\n            tmp1, tmp2, bufp->ovfl->addr);\n#endif /* ifdef DEBUG1 */\n        ndx = sp[0];\n\n        /*\n         * Since a pair is allocated on a page only if there's room to add\n         * an overflow page, we know that the OVFL information will fit on\n         * the page.\n         */\n\n        sp[ndx + 4] = OFFSET(sp);\n        sp[ndx + 3] = FREESPACE(sp) - OVFLSIZE;\n        sp[ndx + 1] = ovfl_num;\n        sp[ndx + 2] = OVFLPAGE;\n        sp[0]       = ndx + 2;\n#ifdef HASH_STATISTICS\n        hash_overflows++;\n#endif /* ifdef HASH_STATISTICS */\n        return (bufp->ovfl);\n}\n\n/*\n * Returns:\n *       0 indicates SUCCESS\n *      -1 indicates FAILURE\n */\n\nint\n__get_page(HTAB *hashp, char *p, u_int32_t bucket, int is_bucket, int is_disk,\n    int is_bitmap)\n{\n        int fd, page, size, rsize;\n        u_int16_t *bp;\n\n        fd   = hashp->fp;\n        size = hashp->BSIZE;\n\n        if ((fd == -1) || !is_disk) {\n                PAGE_INIT(p);\n                return (0);\n        }\n        if (is_bucket)\n                page = BUCKET_TO_PAGE(bucket);\n        else\n                page = OADDR_TO_PAGE(bucket);\n        if ((rsize = pread(fd, p, size, (off_t)page << hashp->BSHIFT)) == -1)\n                return (-1);\n        bp = (u_int16_t *)p;\n        if (!rsize)\n                bp[0] = 0;      /* We hit the EOF, so initialize a new page */\n        else\n                if (rsize != size) {\n                        errno = EFTYPE;\n                        return (-1);\n                }\n        if (!is_bitmap && !bp[0]) {\n                PAGE_INIT(p);\n        } else\n                if (hashp->LORDER != BYTE_ORDER) {\n                        int i, max;\n\n                        if (is_bitmap) {\n                                max = hashp->BSIZE >> 2; /* divide by 4 */\n                                for (i = 0; i < max; i++)\n                                        M_32_SWAP(((int *)p)[i]);\n                        } else {\n                                M_16_SWAP(bp[0]);\n                                max = bp[0] + 2;\n                                for (i = 1; i <= max; i++)\n                                        M_16_SWAP(bp[i]);\n                        }\n                }\n        return (0);\n}\n\n/*\n * Write page p to disk\n *\n * Returns:\n *       0 ==> OK\n *      -1 ==>failure\n */\n\nint\n__put_page(HTAB *hashp, char *p, u_int32_t bucket, int is_bucket, int is_bitmap)\n{\n        int fd, page, size, wsize;\n\n        size = hashp->BSIZE;\n        if ((hashp->fp == -1) && open_temp(hashp))\n                return (-1);\n        fd = hashp->fp;\n\n        if (hashp->LORDER != BYTE_ORDER) {\n                int i, max;\n\n                if (is_bitmap) {\n                        max = hashp->BSIZE >> 2;        /* divide by 4 */\n                        for (i = 0; i < max; i++)\n                                M_32_SWAP(((int *)p)[i]);\n                } else {\n                        max = ((u_int16_t *)p)[0] + 2;\n                        for (i = 0; i <= max; i++)\n                                M_16_SWAP(((u_int16_t *)p)[i]);\n                }\n        }\n        if (is_bucket)\n                page = BUCKET_TO_PAGE(bucket);\n        else\n                page = OADDR_TO_PAGE(bucket);\n        if ((wsize = pwrite(fd, p, size, (off_t)page << hashp->BSHIFT)) == -1)\n                /* Errno is set */\n                return (-1);\n        if (wsize != size) {\n                errno = EFTYPE;\n                return (-1);\n        }\n        return (0);\n}\n\n#define BYTE_MASK       ((1 << INT_BYTE_SHIFT) -1)\n\n/*\n * Initialize a new bitmap page.  Bitmap pages are left in memory\n * once they are read in.\n */\n\nint\n__ibitmap(HTAB *hashp, int pnum, int nbits, int ndx)\n{\n        u_int32_t *ip;\n        int clearbytes, clearints;\n\n        if ((ip = (u_int32_t *)malloc(hashp->BSIZE)) == NULL)\n                return (1);\n        hashp->nmaps++;\n        clearints  = ((nbits - 1) >> INT_BYTE_SHIFT) + 1;\n        clearbytes = clearints << INT_TO_BYTE;\n        (void)memset((char *)ip, 0, clearbytes);\n        (void)memset(((char *)ip) + clearbytes, 0xFF,\n            hashp->BSIZE - clearbytes);\n        ip[clearints - 1] = ALL_SET << (nbits & BYTE_MASK);\n        SETBIT(ip, 0);\n        hashp->BITMAPS[ndx] = (u_int16_t)pnum;\n        hashp->mapp[ndx] = ip;\n        return (0);\n}\n\nstatic u_int32_t\nfirst_free(u_int32_t map)\n{\n        u_int32_t i, mask;\n\n        mask = 0x1;\n        for (i = 0; i < BITS_PER_MAP; i++) {\n                if (!(mask & map))\n                        return (i);\n                mask = mask << 1;\n        }\n        return (i);\n}\n\nstatic u_int16_t\noverflow_page(HTAB *hashp)\n{\n        u_int32_t *freep;\n        int max_free, offset, splitnum;\n        u_int16_t addr;\n        int bit, first_page, free_bit, free_page, i, in_use_bits, j;\n#ifdef DEBUG2\n        int tmp1, tmp2;\n#endif /* ifdef DEBUG2 */\n        splitnum = hashp->OVFL_POINT;\n        max_free = hashp->SPARES[splitnum];\n\n        free_page = (max_free - 1) >> (hashp->BSHIFT + BYTE_SHIFT);\n        free_bit  = (max_free - 1) & ((hashp->BSIZE << BYTE_SHIFT) - 1);\n\n        /* Look through all the free maps to find the first free block */\n        first_page = hashp->LAST_FREED >>(hashp->BSHIFT + BYTE_SHIFT);\n        for ( i = first_page; i <= free_page; i++ ) {\n                if (!(freep = (u_int32_t *)hashp->mapp[i]) &&\n                    !(freep = fetch_bitmap(hashp, i)))\n                        return (0);\n                if (i == free_page)\n                        in_use_bits = free_bit;\n                else\n                        in_use_bits = (hashp->BSIZE << BYTE_SHIFT) - 1;\n\n                if (i == first_page) {\n                        bit = hashp->LAST_FREED &\n                            ((hashp->BSIZE << BYTE_SHIFT) - 1);\n                        j = bit / BITS_PER_MAP;\n                        bit = bit & ~(BITS_PER_MAP - 1);\n                } else {\n                        bit = 0;\n                        j = 0;\n                }\n                for (; bit <= in_use_bits; j++, bit += BITS_PER_MAP)\n                        if (freep[j] != ALL_SET)\n                                goto found;\n        }\n\n        /* No Free Page Found */\n        hashp->LAST_FREED = hashp->SPARES[splitnum];\n        hashp->SPARES[splitnum]++;\n        offset = hashp->SPARES[splitnum] -\n            (splitnum ? hashp->SPARES[splitnum - 1] : 0);\n\n#define OVMSG   \"HASH: Out of overflow pages.  Increase page size\\n\"\n        if (offset > SPLITMASK) {\n                if (++splitnum >= NCACHED) {\n                        (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1);\n                        errno = EFBIG;\n                        return (0);\n                }\n                hashp->OVFL_POINT       = splitnum;\n                hashp->SPARES[splitnum] = hashp->SPARES[splitnum-1];\n                hashp->SPARES[splitnum-1]--;\n                offset                  = 1;\n        }\n\n        /* Check if we need to allocate a new bitmap page */\n        if (free_bit == (hashp->BSIZE << BYTE_SHIFT) - 1) {\n                free_page++;\n                if (free_page >= NCACHED) {\n                        (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1);\n                        errno = EFBIG;\n                        return (0);\n                }\n\n                /*\n                 * This is tricky.  The 1 indicates that you want the new page\n                 * allocated with 1 clear bit.  Actually, you are going to\n                 * allocate 2 pages from this map.  The first is going to be\n                 * the map page, the second is the overflow page we were\n                 * looking for.  The init_bitmap routine automatically, sets\n                 * the first bit of itself to indicate that the bitmap itself\n                 * is in use.  We would explicitly set the second bit, but\n                 * don't have to if we tell init_bitmap not to leave it clear\n                 * in the first place.\n                 */\n\n                if (__ibitmap(hashp,\n                    (int)OADDR_OF(splitnum, offset), 1, free_page))\n                        return (0);\n                hashp->SPARES[splitnum]++;\n#ifdef DEBUG2\n                free_bit = 2;\n#endif /* ifdef DEBUG2 */\n                offset++;\n                if (offset > SPLITMASK) {\n                        if (++splitnum >= NCACHED) {\n                                (void)!write(STDERR_FILENO, OVMSG,\n                                    sizeof(OVMSG) - 1);\n                                errno = EFBIG;\n                                return (0);\n                        }\n                        hashp->OVFL_POINT       = splitnum;\n                        hashp->SPARES[splitnum] = hashp->SPARES[splitnum-1];\n                        hashp->SPARES[splitnum-1]--;\n                        offset                  = 0;\n                }\n        } else {\n\n                /*\n                 * Free_bit addresses the last used bit.  Bump it to address\n                 * the first available bit.\n                 */\n\n                free_bit++;\n                SETBIT(freep, free_bit);\n        }\n\n        /* Calculate address of the new overflow page */\n        addr = OADDR_OF(splitnum, offset);\n#ifdef DEBUG2\n        (void)fprintf(stderr, \"OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\\n\",\n            addr, free_bit, free_page);\n#endif /* ifdef DEBUG2 */\n        return (addr);\n\nfound:\n        bit = bit + first_free(freep[j]);\n        SETBIT(freep, bit);\n#ifdef DEBUG2\n        tmp1 = bit;\n        tmp2 = i;\n#endif /* ifdef DEBUG2 */\n\n        /*\n         * Bits are addressed starting with 0, but overflow pages are addressed\n         * beginning at 1. Bit is a bit addressnumber, so we need to increment\n         * it to convert it to a page number.\n         */\n\n        bit = 1 + bit + (i * (hashp->BSIZE << BYTE_SHIFT));\n        if (bit >= hashp->LAST_FREED)\n                hashp->LAST_FREED = bit - 1;\n\n        /* Calculate the split number for this page */\n        for (i = 0; (i < splitnum) && (bit > hashp->SPARES[i]); i++);\n        offset = (i ? bit - hashp->SPARES[i - 1] : bit);\n        if (offset >= SPLITMASK) {\n                (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1);\n                errno = EFBIG;\n                return (0);     /* Out of overflow pages */\n        }\n        addr = OADDR_OF(i, offset);\n#ifdef DEBUG2\n        (void)fprintf(stderr, \"OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\\n\",\n            addr, tmp1, tmp2);\n#endif /* ifdef DEBUG2 */\n\n        /* Allocate and return the overflow page */\n        return (addr);\n}\n\n/*\n * Mark this overflow page as free.\n */\n\nvoid\n__free_ovflpage(HTAB *hashp, BUFHEAD *obufp)\n{\n        u_int16_t addr;\n        u_int32_t *freep;\n        int bit_address, free_page, free_bit;\n        u_int16_t ndx;\n\n        addr = obufp->addr;\n#ifdef DEBUG1\n        (void)fprintf(stderr, \"Freeing %d\\n\", addr);\n#endif /* ifdef DEBUG1 */\n        ndx = (((u_int16_t)addr) >> SPLITSHIFT);\n        bit_address =\n            (ndx ? hashp->SPARES[ndx - 1] : 0) + (addr & SPLITMASK) - 1;\n         if (bit_address < hashp->LAST_FREED)\n                hashp->LAST_FREED = bit_address;\n        free_page = (bit_address >> (hashp->BSHIFT + BYTE_SHIFT));\n        free_bit = bit_address & ((hashp->BSIZE << BYTE_SHIFT) - 1);\n\n        if (!(freep = hashp->mapp[free_page]))\n                freep = fetch_bitmap(hashp, free_page);\n#ifdef DEBUG\n\n        /*\n         * This had better never happen.  It means we tried to read a bitmap\n         * that has already had overflow pages allocated off it, and we\n         * failed to read it from the file.\n         */\n\n        if (!freep)\n                assert(0);\n#endif /* ifdef DEBUG */\n        CLRBIT(freep, free_bit);\n#ifdef DEBUG2\n        (void)fprintf(stderr, \"FREE_OVFLPAGE: ADDR: %d BIT: %d PAGE %d\\n\",\n            obufp->addr, free_bit, free_page);\n#endif /* ifdef DEBUG2 */\n        __reclaim_buf(hashp, obufp);\n}\n\n/*\n * Returns:\n *       0 success\n *      -1 failure\n */\n\nstatic int\nopen_temp(HTAB *hashp)\n{\n        sigset_t set, oset;\n        int len;\n        char *envtmp = NULL;\n        char path[PATH_MAX];\n\n        if (issetugid() == 0)\n                envtmp = getenv(\"TMPDIR\");\n        len = snprintf(path,\n            sizeof(path), \"%s/_hash.XXXXXX\", envtmp ? envtmp : \"/tmp\");\n        if (len < 0 || len >= sizeof(path)) {\n                errno = ENAMETOOLONG;\n                return (-1);\n        }\n\n        /* Block signals; make sure file goes away at process exit. */\n        (void)sigfillset(&set);\n        (void)sigprocmask(SIG_BLOCK, &set, &oset);\n#if defined(_AIX) || defined(__solaris__) || defined(__managarm__)\n        if ((hashp->fp = mkstemp(path)) != -1) {\n#else\n        if ((hashp->fp = mkostemp(path, O_CLOEXEC)) != -1) {\n#endif /* if defined(_AIX) || defined(__solaris__) */\n                (void)unlink(path);\n        }\n        (void)sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL);\n        return (hashp->fp != -1 ? 0 : -1);\n}\n\n/*\n * We have to know that the key will fit, but the last entry on the page is\n * an overflow pair, so we need to shift things.\n */\n\nstatic void\nsqueeze_key(u_int16_t *sp, const DBT *key, const DBT *val)\n{\n        char *p;\n        u_int16_t free_space, n, off, pageno;\n\n        p          = (char *)sp;\n        n          = sp[0];\n        free_space = FREESPACE(sp);\n        off        = OFFSET(sp);\n\n        pageno         = sp[n - 1];\n        off           -= key->size;\n        sp[n - 1]      = off;\n        memmove(p + off, key->data, key->size);\n        off           -= val->size;\n        sp[n]          = off;\n        memmove(p + off, val->data, val->size);\n        sp[0]          = n + 2;\n        sp[n + 1]      = pageno;\n        sp[n + 2]      = OVFLPAGE;\n        FREESPACE(sp)  = free_space - PAIRSIZE(key, val);\n        OFFSET(sp)     = off;\n}\n\nstatic u_int32_t *\nfetch_bitmap(HTAB *hashp, int ndx)\n{\n        if (ndx >= hashp->nmaps)\n                return (NULL);\n        if ((hashp->mapp[ndx] = (u_int32_t *)malloc(hashp->BSIZE)) == NULL)\n                return (NULL);\n        if (__get_page(hashp,\n            (char *)hashp->mapp[ndx], hashp->BITMAPS[ndx], 0, 1, 1)) {\n                free(hashp->mapp[ndx]);\n                return (NULL);\n        }\n        return (hashp->mapp[ndx]);\n}\n\n#ifdef DEBUG4\nint\nprint_chain(int addr)\n{\n        BUFHEAD *bufp;\n        short *bp, oaddr;\n\n        (void)fprintf(stderr, \"%d \", addr);\n        bufp = __get_buf(hashp, addr, NULL, 0);\n        bp = (short *)bufp->page;\n        while (bp[0] && ((bp[bp[0]] == OVFLPAGE) ||\n                ((bp[0] > 2) && bp[2] < REAL_KEY))) {\n                oaddr = bp[bp[0] - 1];\n                (void)fprintf(stderr, \"%d \", (int)oaddr);\n                bufp = __get_buf(hashp, (int)oaddr, bufp, 0);\n                bp = (short *)bufp->page;\n        }\n        (void)fprintf(stderr, \"\\n\");\n}\n#endif /* ifdef DEBUG4 */\n"
  },
  {
    "path": "db/hash/ndbm.c",
    "content": "/*      $OpenBSD: ndbm.c,v 1.28 2023/02/17 17:59:36 miod Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"bsd_ndbm.h\"\n#include \"hash.h\"\n\n#undef open\n\n/*\n *\n * This package provides dbm and ndbm compatible interfaces to DB.\n * First are the DBM routines, which call the NDBM routines, and\n * the NDBM routines, which call the DB routines.\n */\n\nstatic DBM *_dbm_open(const char *, const char *, int, mode_t);\n\n/*\n * Returns:\n *      *DBM on success\n *       NULL on failure\n */\n\nstatic DBM *\n_dbm_open(const char *file, const char *suff, int flags, mode_t mode)\n{\n        HASHINFO info;\n        char path[PATH_MAX];\n        int len;\n\n        len = snprintf(path, sizeof path, \"%s%s\", file, suff);\n        if (len < 0 || len >= sizeof path) {\n                errno = ENAMETOOLONG;\n                return (NULL);\n        }\n        /* O_WRONLY not supported by db(3) but traditional ndbm allowed it. */\n        if ((flags & O_ACCMODE) == O_WRONLY) {\n                flags &= ~O_WRONLY;\n                flags |= O_RDWR;\n        }\n        info.bsize     = 4096;\n        info.ffactor   = 40;\n        info.nelem     = 1;\n        info.cachesize = 0;\n        info.hash      = NULL;\n        info.lorder    = 0;\n        return ((DBM *)__hash_open(path, flags, mode, &info, 0));\n}\n\n/*\n * Returns:\n *      *DBM on success\n *       NULL on failure\n */\n\nDBM *\ndbm_open(const char *file, int flags, mode_t mode)\n{\n\n        return(_dbm_open(file, DBM_SUFFIX, flags, mode));\n}\n\n/*\n * Returns:\n *      Nothing.\n */\n\nvoid\ndbm_close(DBM *db)\n{\n\n        (void)(db->close)(db);\n}\nDEF_WEAK(dbm_close);\n\n/*\n * Returns:\n *      DATUM on success\n *      NULL on failure\n */\n\ndatum\ndbm_fetch(DBM *db, datum key)\n{\n        datum retdata;\n        int status;\n        DBT dbtkey, dbtretdata;\n\n        dbtkey.data = key.dptr;\n        dbtkey.size = key.dsize;\n        status      = (db->get)(db, &dbtkey, &dbtretdata, 0);\n        if (status) {\n                dbtretdata.data = NULL;\n                dbtretdata.size = 0;\n        }\n        retdata.dptr  = dbtretdata.data;\n        retdata.dsize = dbtretdata.size;\n        return (retdata);\n}\nDEF_WEAK(dbm_fetch);\n\n/*\n * Returns:\n *      DATUM on success\n *      NULL on failure\n */\n\ndatum\ndbm_firstkey(DBM *db)\n{\n        int status;\n        datum retkey;\n        DBT dbtretkey, dbtretdata;\n\n        status = (db->seq)(db, &dbtretkey, &dbtretdata, R_FIRST);\n        if (status)\n                dbtretkey.data = NULL;\n        retkey.dptr  = dbtretkey.data;\n        retkey.dsize = dbtretkey.size;\n        return (retkey);\n}\nDEF_WEAK(dbm_firstkey);\n\n/*\n * Returns:\n *      DATUM on success\n *      NULL on failure\n */\n\ndatum\ndbm_nextkey(DBM *db)\n{\n        int status;\n        datum retkey;\n        DBT dbtretkey, dbtretdata;\n\n        status = (db->seq)(db, &dbtretkey, &dbtretdata, R_NEXT);\n        if (status)\n                dbtretkey.data = NULL;\n        retkey.dptr  = dbtretkey.data;\n        retkey.dsize = dbtretkey.size;\n        return (retkey);\n}\nDEF_WEAK(dbm_nextkey);\n\n/*\n * Returns:\n *       0 on success\n *      <0 on failure\n */\n\nint\ndbm_delete(DBM *db, datum key)\n{\n        int status;\n        DBT dbtkey;\n\n        dbtkey.data = key.dptr;\n        dbtkey.size = key.dsize;\n        status      = (db->del)(db, &dbtkey, 0);\n        if (status)\n                return (-1);\n        else\n                return (0);\n}\nDEF_WEAK(dbm_delete);\n\n/*\n * Returns:\n *       0 on success\n *      <0 on failure\n *       1 if DBM_INSERT and entry exists\n */\n\nint\ndbm_store(DBM *db, datum key, datum data, int flags)\n{\n        DBT dbtkey, dbtdata;\n\n        dbtkey.data  = key.dptr;\n        dbtkey.size  = key.dsize;\n        dbtdata.data = data.dptr;\n        dbtdata.size = data.dsize;\n        return ((db->put)(db, &dbtkey, &dbtdata,\n            (flags == DBM_INSERT) ? R_NOOVERWRITE : 0));\n}\nDEF_WEAK(dbm_store);\n\nint\ndbm_error(DBM *db)\n{\n        HTAB *hp;\n\n        hp = (HTAB *)db->internal;\n        return (hp->err);\n}\n\nint\ndbm_clearerr(DBM *db)\n{\n        HTAB *hp;\n\n        hp      = (HTAB *)db->internal;\n        hp->err = 0;\n        return (0);\n}\n\nint\ndbm_dirfno(DBM *db)\n{\n\n        return(((HTAB *)db->internal)->fp);\n}\n\nint\ndbm_rdonly(DBM *dbp)\n{\n        HTAB *hashp = (HTAB *)dbp->internal;\n\n        /* Could use DBM_RDONLY instead if we wanted... */\n        return ((hashp->flags & O_ACCMODE) == O_RDONLY);\n}\nDEF_WEAK(dbm_rdonly);\n"
  },
  {
    "path": "db/hash/page.h",
    "content": "/*      $OpenBSD: page.h,v 1.6 2003/06/02 20:18:34 millert Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Margo Seltzer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)page.h      8.2 (Berkeley) 5/31/94\n */\n\n/*\n * Definitions for hashing page file format.\n */\n\n/*\n * routines dealing with a data page\n *\n * page format:\n *      +------------------------------+\n * p    | n | keyoff | datoff | keyoff |\n *      +------------+--------+--------+\n *      | datoff | free  |  ptr  | --> |\n *      +--------+---------------------+\n *      |        F R E E A R E A       |\n *      +--------------+---------------+\n *      |  <---- - - - | data          |\n *      +--------+-----+----+----------+\n *      |  key   | data     | key      |\n *      +--------+----------+----------+\n *\n * Pointer to the free space is always:  p[p[0] + 2]\n * Amount of free space on the page is:  p[p[0] + 1]\n */\n\n/*\n * How many bytes required for this pair?\n *      2 shorts in the table at the top of the page + room for the\n *      key and room for the data\n *\n * We prohibit entering a pair on a page unless there is also room to append\n * an overflow page. The reason for this it that you can get in a situation\n * where a single key/data pair fits on a page, but you can't append an\n * overflow page and later you'd have to split the key/data and handle like\n * a big pair.\n * You might as well do this up front.\n */\n\n#define PAIRSIZE(K,D)   (2*sizeof(u_int16_t) + (K)->size + (D)->size)\n#define BIGOVERHEAD     (4*sizeof(u_int16_t))\n#define KEYSIZE(K)      (4*sizeof(u_int16_t) + (K)->size);\n#define OVFLSIZE        (2*sizeof(u_int16_t))\n#define FREESPACE(P)    ((P)[(P)[0]+1])\n#define OFFSET(P)       ((P)[(P)[0]+2])\n#define PAIRFITS(P,K,D) \\\n        (((P)[2] >= REAL_KEY) && \\\n            (PAIRSIZE((K),(D)) + OVFLSIZE) <= FREESPACE((P)))\n#define PAGE_META(N)    (((N)+3) * sizeof(u_int16_t))\n\ntypedef struct {\n        BUFHEAD *newp;\n        BUFHEAD *oldp;\n        BUFHEAD *nextp;\n        u_int16_t next_addr;\n}       SPLIT_RETURN;\n"
  },
  {
    "path": "db/mpool/mpool.c",
    "content": "/*      $OpenBSD: mpool.c,v 1.21 2015/11/01 03:45:28 guenther Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n\n#define __MPOOLINTERFACE_PRIVATE\n#include <mpool.h>\n\n#undef open\n\nstatic BKT *mpool_bkt(MPOOL *);\nstatic BKT *mpool_look(MPOOL *, pgno_t);\nstatic int  mpool_write(MPOOL *, BKT *);\n\n/*\n * mpool_open --\n *      Initialize a memory pool.\n */\n\nMPOOL *\nmpool_open(void *key, int fd, pgno_t pagesize, pgno_t maxcache)\n{\n        struct stat sb;\n        MPOOL *mp;\n        int entry;\n\n        /*\n         * Get information about the file.\n         *\n         * XXX\n         * We don't currently handle pipes, although we should.\n         */\n\n        if (fstat(fd, &sb))\n                return (NULL);\n        if (!S_ISREG(sb.st_mode)) {\n                errno = ESPIPE;\n                return (NULL);\n        }\n\n        /* Allocate and initialize the MPOOL cookie. */\n        if ((mp = (MPOOL *)calloc(1, sizeof(MPOOL))) == NULL)\n                return (NULL);\n        TAILQ_INIT(&mp->lqh);\n        for (entry = 0; entry < HASHSIZE; ++entry)\n                TAILQ_INIT(&mp->hqh[entry]);\n        mp->maxcache = maxcache;\n        mp->npages   = sb.st_size / pagesize;\n        mp->pagesize = pagesize;\n        mp->fd       = fd;\n        return (mp);\n}\n\n/*\n * mpool_filter --\n *      Initialize input/output filters.\n */\n\nvoid\nmpool_filter(MPOOL *mp, void (*pgin) (void *, pgno_t, void *),\n    void (*pgout) (void *, pgno_t, void *), void *pgcookie)\n{\n        mp->pgin     = pgin;\n        mp->pgout    = pgout;\n        mp->pgcookie = pgcookie;\n}\n\n/*\n * mpool_new --\n *      Get a new page of memory.\n */\n\nvoid *\nmpool_new(MPOOL *mp, pgno_t *pgnoaddr, unsigned int flags)\n{\n        struct _hqh *head;\n        BKT *bp;\n\n        if (mp->npages == MAX_PAGE_NUMBER) {\n                (void)fprintf(stderr, \"mpool_new: page allocation overflow.\\n\");\n                abort();\n        }\n#ifdef STATISTICS\n        ++mp->pagenew;\n#endif /* ifdef STATISTICS */\n\n        /*\n         * Get a BKT from the cache.  Assign a new page number, attach\n         * it to the head of the hash chain, the tail of the lru chain,\n         * and return.\n         */\n\n        if ((bp = mpool_bkt(mp)) == NULL)\n                return (NULL);\n        if (flags == MPOOL_PAGE_REQUEST) {\n                mp->npages++;\n                bp->pgno = *pgnoaddr;\n        } else\n                bp->pgno = *pgnoaddr = mp->npages++;\n\n        bp->flags = MPOOL_PINNED | MPOOL_INUSE;\n\n        head = &mp->hqh[HASHKEY(bp->pgno)];\n        TAILQ_INSERT_HEAD(head, bp, hq);\n        TAILQ_INSERT_TAIL(&mp->lqh, bp, q);\n        return (bp->page);\n}\n\nint\nmpool_delete(MPOOL *mp, void *page)\n{\n        struct _hqh *head;\n        BKT *bp;\n\n        bp = (BKT *)((char *)page - sizeof(BKT));\n\n#ifdef DEBUG\n        if (!(bp->flags & MPOOL_PINNED)) {\n                (void)fprintf(stderr,\n                    \"mpool_delete: page %d not pinned\\n\", bp->pgno);\n                abort();\n        }\n#endif /* ifdef DEBUG */\n\n        /* Remove from the hash and lru queues. */\n        head = &mp->hqh[HASHKEY(bp->pgno)];\n        TAILQ_REMOVE(head, bp, hq);\n        TAILQ_REMOVE(&mp->lqh, bp, q);\n\n        free(bp);\n        mp->curcache--;\n        return (RET_SUCCESS);\n}\n\n/*\n * mpool_get\n *      Get a page.\n */\n\nvoid *\nmpool_get(MPOOL *mp, pgno_t pgno,\n    unsigned int flags)                /* XXX not used? */\n{\n        struct _hqh *head;\n        BKT *bp;\n        off_t off;\n        int nr;\n\n#ifdef STATISTICS\n        ++mp->pageget;\n#endif /* ifdef STATISTICS */\n\n        /* Check for a page that is cached. */\n        if ((bp = mpool_look(mp, pgno)) != NULL) {\n#ifdef DEBUG\n                if (!(flags & MPOOL_IGNOREPIN) && bp->flags & MPOOL_PINNED) {\n                        (void)fprintf(stderr,\n                            \"mpool_get: page %d already pinned\\n\", bp->pgno);\n                        abort();\n                }\n#endif /* ifdef DEBUG */\n\n                /*\n                 * Move the page to the head of the hash chain and the tail\n                 * of the lru chain.\n                 */\n\n                head = &mp->hqh[HASHKEY(bp->pgno)];\n                TAILQ_REMOVE(head, bp, hq);\n                TAILQ_INSERT_HEAD(head, bp, hq);\n                TAILQ_REMOVE(&mp->lqh, bp, q);\n                TAILQ_INSERT_TAIL(&mp->lqh, bp, q);\n\n                /* Return a pinned page. */\n                bp->flags |= MPOOL_PINNED;\n                return (bp->page);\n        }\n\n        /* Get a page from the cache. */\n        if ((bp = mpool_bkt(mp)) == NULL)\n                return (NULL);\n\n        /* Read in the contents. */\n        off = mp->pagesize * pgno;\n        if ((nr = pread(mp->fd, bp->page, mp->pagesize, off)) != mp->pagesize) {\n                switch (nr) {\n                case -1:\n                        /* errno is set for us by pread(). */\n                        free(bp);\n                        mp->curcache--;\n                        return (NULL);\n                case 0:\n                        /*\n                         * A zero-length read means you need to create a\n                         * new page.\n                         */\n                        memset(bp->page, 0, mp->pagesize);\n                        break;\n                default:\n                        /* A partial read is definitely bad. */\n                        free(bp);\n                        mp->curcache--;\n                        errno = EINVAL;\n                        return (NULL);\n                }\n        }\n#ifdef STATISTICS\n        ++mp->pageread;\n#endif /* ifdef STATISTICS */\n\n        /* Set the page number, pin the page. */\n        bp->pgno = pgno;\n        if (!(flags & MPOOL_IGNOREPIN))\n                bp->flags = MPOOL_PINNED;\n        bp->flags |= MPOOL_INUSE;\n\n        /*\n         * Add the page to the head of the hash chain and the tail\n         * of the lru chain.\n         */\n\n        head = &mp->hqh[HASHKEY(bp->pgno)];\n        TAILQ_INSERT_HEAD(head, bp, hq);\n        TAILQ_INSERT_TAIL(&mp->lqh, bp, q);\n\n        /* Run through the user's filter. */\n        if (mp->pgin != NULL)\n                (mp->pgin)(mp->pgcookie, bp->pgno, bp->page);\n\n        return (bp->page);\n}\n\n/*\n * mpool_put\n *      Return a page.\n */\n\nint\nmpool_put(MPOOL *mp, void *page, unsigned int flags)\n{\n        BKT *bp;\n\n#ifdef STATISTICS\n        ++mp->pageput;\n#endif /* ifdef STATISTICS */\n        bp = (BKT *)((char *)page - sizeof(BKT));\n#ifdef DEBUG\n        if (!(bp->flags & MPOOL_PINNED)) {\n                (void)fprintf(stderr,\n                    \"mpool_put: page %d not pinned\\n\", bp->pgno);\n                abort();\n        }\n#endif /* ifdef DEBUG */\n        bp->flags &= ~MPOOL_PINNED;\n        if (flags & MPOOL_DIRTY)\n                bp->flags |= flags & MPOOL_DIRTY;\n        return (RET_SUCCESS);\n}\n\n/*\n * mpool_close\n *      Close the buffer pool.\n */\n\nint\nmpool_close(MPOOL *mp)\n{\n        BKT *bp;\n\n        /* Free up any space allocated to the lru pages. */\n        while ((bp = TAILQ_FIRST(&mp->lqh))) {\n                TAILQ_REMOVE(&mp->lqh, bp, q);\n                free(bp);\n        }\n\n        /* Free the MPOOL cookie. */\n        free(mp);\n        return (RET_SUCCESS);\n}\n\n/*\n * mpool_sync\n *      Sync the pool to disk.\n */\n\nint\nmpool_sync(MPOOL *mp)\n{\n        BKT *bp;\n\n        /* Walk the lru chain, flushing any dirty pages to disk. */\n        TAILQ_FOREACH(bp, &mp->lqh, q)\n                if (bp->flags & MPOOL_DIRTY &&\n                    mpool_write(mp, bp) == RET_ERROR)\n                        return (RET_ERROR);\n\n        /* Sync the file descriptor. */\n        return (fsync(mp->fd) ? RET_ERROR : RET_SUCCESS);\n}\n\n/*\n * mpool_bkt\n *      Get a page from the cache (or create one).\n */\n\nstatic BKT *\nmpool_bkt(MPOOL *mp)\n{\n        struct _hqh *head;\n        BKT *bp;\n\n        /* If under the max cached, always create a new page. */\n        if (mp->curcache < mp->maxcache)\n                goto new;\n\n        /*\n         * If the cache is max'd out, walk the lru list for a buffer we\n         * can flush.  If we find one, write it (if necessary) and take it\n         * off any lists.  If we don't find anything we grow the cache anyway.\n         * The cache never shrinks.\n         */\n\n        TAILQ_FOREACH(bp, &mp->lqh, q)\n                if (!(bp->flags & MPOOL_PINNED)) {\n                        /* Flush if dirty. */\n                        if (bp->flags & MPOOL_DIRTY &&\n                            mpool_write(mp, bp) == RET_ERROR)\n                                return (NULL);\n#ifdef STATISTICS\n                        ++mp->pageflush;\n#endif /* ifdef STATISTICS */\n                        /* Remove from the hash and lru queues. */\n                        head = &mp->hqh[HASHKEY(bp->pgno)];\n                        TAILQ_REMOVE(head, bp, hq);\n                        TAILQ_REMOVE(&mp->lqh, bp, q);\n#ifdef DEBUG\n                        { void *spage;\n                                spage = bp->page;\n                                memset(bp, 0xff, sizeof(BKT) + mp->pagesize);\n                                bp->page = spage;\n                        }\n#endif /* ifdef DEBUG */\n                        bp->flags = 0;\n                        return (bp);\n                }\n\nnew:    if ((bp = (BKT *)malloc(sizeof(BKT) + mp->pagesize)) == NULL)\n                return (NULL);\n#ifdef STATISTICS\n        ++mp->pagealloc;\n#endif /* ifdef STATISTICS */\n        memset(bp, 0xff, sizeof(BKT) + mp->pagesize);\n        bp->page  = (char *)bp + sizeof(BKT);\n        bp->flags = 0;\n        ++mp->curcache;\n        return (bp);\n}\n\n/*\n * mpool_write\n *      Write a page to disk.\n */\n\nstatic int\nmpool_write(MPOOL *mp, BKT *bp)\n{\n        off_t off;\n\n#ifdef STATISTICS\n        ++mp->pagewrite;\n#endif /* ifdef STATISTICS */\n\n        /* Run through the user's filter. */\n        if (mp->pgout)\n                (mp->pgout)(mp->pgcookie, bp->pgno, bp->page);\n\n        off = mp->pagesize * bp->pgno;\n        if (pwrite(mp->fd, bp->page, mp->pagesize, off) != mp->pagesize)\n                return (RET_ERROR);\n\n        /*\n         * Re-run through the input filter since this page may soon be\n         * accessed via the cache, and whatever the user's output filter\n         * did may screw things up if we don't let the input filter\n         * restore the in-core copy.\n         */\n\n        if (mp->pgin)\n                (mp->pgin)(mp->pgcookie, bp->pgno, bp->page);\n\n        bp->flags &= ~MPOOL_DIRTY;\n        return (RET_SUCCESS);\n}\n\n/*\n * mpool_look\n *      Lookup a page in the cache.\n */\n\nstatic BKT *\nmpool_look(MPOOL *mp, pgno_t pgno)\n{\n        struct _hqh *head;\n        BKT *bp;\n\n        head = &mp->hqh[HASHKEY(pgno)];\n        TAILQ_FOREACH(bp, head, hq)\n                if ((bp->pgno == pgno) &&\n                        ((bp->flags & MPOOL_INUSE) == MPOOL_INUSE)) {\n#ifdef STATISTICS\n                        ++mp->cachehit;\n#endif /* ifdef STATISTICS */\n                        return (bp);\n                }\n#ifdef STATISTICS\n        ++mp->cachemiss;\n#endif /* ifdef STATISTICS */\n        return (NULL);\n}\n\n#ifdef STATISTICS\n\n/*\n * mpool_stat\n *      Print out cache statistics.\n */\n\nvoid\nmpool_stat(MPOOL *mp)\n{\n        BKT *bp;\n        int cnt;\n        char *sep;\n\n        (void)fprintf(stderr, \"%lu pages in the file\\n\",\n            (unsigned long)mp->npages);\n        (void)fprintf(stderr,\n            \"page size %lu, caching %lu pages of %lu page max cache\\n\",\n            (unsigned long)mp->pagesize,\n            (unsigned long)mp->curcache,\n            (unsigned long)mp->maxcache);\n        (void)fprintf(stderr, \"%lu page puts, %lu page gets, %lu page new\\n\",\n            mp->pageput, mp->pageget, mp->pagenew);\n        (void)fprintf(stderr, \"%lu page allocs, %lu page flushes\\n\",\n            mp->pagealloc, mp->pageflush);\n        if (mp->cachehit + mp->cachemiss)\n                (void)fprintf(stderr,\n                    \"%.0f%% cache hit rate (%lu hits, %lu misses)\\n\",\n                    ((double)mp->cachehit / (mp->cachehit + mp->cachemiss))\n                    * 100, mp->cachehit, mp->cachemiss);\n        (void)fprintf(stderr, \"%lu page reads, %lu page writes\\n\",\n            mp->pageread, mp->pagewrite);\n\n        sep = \"\";\n        cnt = 0;\n        TAILQ_FOREACH(bp, &mp->lqh, q) {\n                (void)fprintf(stderr, \"%s%d\", sep, bp->pgno);\n                if (bp->flags & MPOOL_DIRTY)\n                        (void)fprintf(stderr, \"d\");\n                if (bp->flags & MPOOL_PINNED)\n                        (void)fprintf(stderr, \"P\");\n                if (++cnt == 10) {\n                        sep = \"\\n\";\n                        cnt = 0;\n                } else\n                        sep = \", \";\n\n        }\n        (void)fprintf(stderr, \"\\n\");\n}\n#endif /* ifdef STATISTICS */\n"
  },
  {
    "path": "db/recno/extern.h",
    "content": "/*      $OpenBSD: extern.h,v 1.7 2015/08/27 04:37:09 guenther Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)extern.h    8.3 (Berkeley) 6/4/94\n */\n\n#include \"../btree/extern.h\"\n\n__BEGIN_HIDDEN_DECLS\nint      __rec_close(DB *);\nint      __rec_delete(const DB *, const DBT *, unsigned int);\nint      __rec_dleaf(BTREE *, PAGE *, u_int32_t);\nint      __rec_fd(const DB *);\nint      __rec_fmap(BTREE *, recno_t);\nint      __rec_fout(BTREE *);\nint      __rec_fpipe(BTREE *, recno_t);\nint      __rec_get(const DB *, const DBT *, DBT *, unsigned int);\nint      __rec_iput(BTREE *, recno_t, const DBT *, unsigned int);\nint      __rec_put(const DB *dbp, DBT *, const DBT *, unsigned int);\nint      __rec_ret(BTREE *, EPG *, recno_t, DBT *, DBT *);\nEPG     *__rec_search(BTREE *, recno_t, enum SRCHOP);\nint      __rec_seq(const DB *, DBT *, DBT *, unsigned int);\nint      __rec_sync(const DB *, unsigned int);\nint      __rec_vmap(BTREE *, recno_t);\nint      __rec_vout(BTREE *);\nint      __rec_vpipe(BTREE *, recno_t);\n__END_HIDDEN_DECLS\n"
  },
  {
    "path": "db/recno/rec_close.c",
    "content": "/*      $OpenBSD: rec_close.c,v 1.13 2016/09/21 04:38:56 guenther Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/uio.h>\n#include <sys/mman.h>\n\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_unistd.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n/*\n * __REC_CLOSE -- Close a recno tree.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__rec_close(DB *dbp)\n{\n        BTREE *t;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        if (__rec_sync(dbp, 0) == RET_ERROR)\n                return (RET_ERROR);\n\n        /* Committed to closing. */\n        status = RET_SUCCESS;\n\n        if (!F_ISSET(t, R_INMEM)) {\n                if (F_ISSET(t, R_CLOSEFP)) {\n                        if (fclose(t->bt_rfp))\n                                status = RET_ERROR;\n                } else {\n                        if (close(t->bt_rfd))\n                                status = RET_ERROR;\n                }\n        }\n\n        if (__bt_close(dbp) == RET_ERROR)\n                status = RET_ERROR;\n\n        return (status);\n}\n\n/*\n * __REC_SYNC -- sync the recno tree to disk.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nint\n__rec_sync(const DB *dbp, unsigned int flags)\n{\n        struct iovec iov[2];\n        BTREE *t;\n        DBT data, key;\n        off_t off;\n        recno_t scursor, trec;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        if (flags == R_RECNOSYNC)\n                return (__bt_sync(dbp, 0));\n\n        if (F_ISSET(t, R_RDONLY | R_INMEM) || !F_ISSET(t, R_MODIFIED))\n                return (RET_SUCCESS);\n\n        /* Read any remaining records into the tree. */\n        if (!F_ISSET(t, R_EOF) && t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR)\n                return (RET_ERROR);\n\n        /* Rewind the file descriptor. */\n        if (lseek(t->bt_rfd, 0, SEEK_SET) != 0)\n                return (RET_ERROR);\n\n        /* Save the cursor. */\n        scursor = t->bt_cursor.rcursor;\n\n        key.size = sizeof(recno_t);\n        key.data = &trec;\n\n        if (F_ISSET(t, R_FIXLEN)) {\n\n                /*\n                 * We assume that fixed length records are all fixed length.\n                 * Any that aren't are either EINVAL'd or corrected by the\n                 * record put code.\n                 */\n\n                status = (dbp->seq)(dbp, &key, &data, R_FIRST);\n                while (status == RET_SUCCESS) {\n                        if (write(t->bt_rfd, data.data, data.size) != data.size)\n                                return (RET_ERROR);\n                        status = (dbp->seq)(dbp, &key, &data, R_NEXT);\n                }\n        } else {\n                iov[1].iov_base = &t->bt_bval;\n                iov[1].iov_len = 1;\n\n                status = (dbp->seq)(dbp, &key, &data, R_FIRST);\n                while (status == RET_SUCCESS) {\n                        iov[0].iov_base = data.data;\n                        iov[0].iov_len = data.size;\n                        if (writev(t->bt_rfd, iov, 2) != data.size + 1)\n                                return (RET_ERROR);\n                        status = (dbp->seq)(dbp, &key, &data, R_NEXT);\n                }\n        }\n\n        /* Restore the cursor. */\n        t->bt_cursor.rcursor = scursor;\n\n        if (status == RET_ERROR)\n                return (RET_ERROR);\n        if ((off = lseek(t->bt_rfd, 0, SEEK_CUR)) == -1)\n                return (RET_ERROR);\n        if (ftruncate(t->bt_rfd, off))\n                return (RET_ERROR);\n        F_CLR(t, R_MODIFIED);\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/recno/rec_delete.c",
    "content": "/*      $OpenBSD: rec_delete.c,v 1.10 2005/08/05 13:03:00 espie Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\nstatic int rec_rdelete(BTREE *, recno_t);\n\n/*\n * __REC_DELETE -- Delete the item(s) referenced by a key.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key to delete\n *      flags:  R_CURSOR if deleting what the cursor references\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found.\n */\n\nint\n__rec_delete(const DB *dbp, const DBT *key, unsigned int flags)\n{\n        BTREE *t;\n        recno_t nrec;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        switch(flags) {\n        case 0:\n                if ((nrec = *(recno_t *)key->data) == 0)\n                        goto einval;\n                if (nrec > t->bt_nrecs)\n                        return (RET_SPECIAL);\n                --nrec;\n                status = rec_rdelete(t, nrec);\n                break;\n        case R_CURSOR:\n                if (!F_ISSET(&t->bt_cursor, CURS_INIT))\n                        goto einval;\n                if (t->bt_nrecs == 0)\n                        return (RET_SPECIAL);\n                status = rec_rdelete(t, t->bt_cursor.rcursor - 1);\n                if (status == RET_SUCCESS)\n                        --t->bt_cursor.rcursor;\n                break;\n        default:\neinval:         errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        if (status == RET_SUCCESS)\n                F_SET(t, B_MODIFIED | R_MODIFIED);\n        return (status);\n}\n\n/*\n * REC_RDELETE -- Delete the data matching the specified key.\n *\n * Parameters:\n *      tree:   tree\n *      nrec:   record to delete\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found.\n */\n\nstatic int\nrec_rdelete(BTREE *t, recno_t nrec)\n{\n        EPG *e;\n        PAGE *h;\n        int status;\n\n        /* Find the record; __rec_search pins the page. */\n        if ((e = __rec_search(t, nrec, SDELETE)) == NULL)\n                return (RET_ERROR);\n\n        /* Delete the record. */\n        h = e->page;\n        status = __rec_dleaf(t, h, e->index);\n        if (status != RET_SUCCESS) {\n                mpool_put(t->bt_mp, h, 0);\n                return (status);\n        }\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n        return (RET_SUCCESS);\n}\n\n/*\n * __REC_DLEAF -- Delete a single record from a recno leaf page.\n *\n * Parameters:\n *      t:      tree\n *      idx:    index on current page to delete\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nint\n__rec_dleaf(BTREE *t, PAGE *h, u_int32_t idx)\n{\n        RLEAF *rl;\n        indx_t *ip, cnt, offset;\n        u_int32_t nbytes;\n        char *from;\n        void *to;\n\n        /*\n         * Delete a record from a recno leaf page.  Internal records are never\n         * deleted from internal pages, regardless of the records that caused\n         * them to be added being deleted.  Pages made empty by deletion are\n         * not reclaimed.  They are, however, made available for reuse.\n         *\n         * Pack the remaining entries at the end of the page, shift the indices\n         * down, overwriting the deleted record and its index.  If the record\n         * uses overflow pages, make them available for reuse.\n         */\n\n        to = rl = GETRLEAF(h, idx);\n        if (rl->flags & P_BIGDATA && __ovfl_delete(t, rl->bytes) == RET_ERROR)\n                return (RET_ERROR);\n        nbytes = NRLEAF(rl);\n\n        /*\n         * Compress the key/data pairs.  Compress and adjust the [BR]LEAF\n         * offsets.  Reset the headers.\n         */\n\n        from = (char *)h + h->upper;\n        memmove(from + nbytes, from, (char *)to - from);\n        h->upper += nbytes;\n\n        offset = h->linp[idx];\n        for (cnt = &h->linp[idx] - (ip = &h->linp[0]); cnt--; ++ip)\n                if (ip[0] < offset)\n                        ip[0] += nbytes;\n        for (cnt = &h->linp[NEXTINDEX(h)] - ip; --cnt; ++ip)\n                ip[0] = ip[1] < offset ? ip[1] + nbytes : ip[1];\n        h->lower -= sizeof(indx_t);\n        --t->bt_nrecs;\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/recno/rec_get.c",
    "content": "/*      $OpenBSD: rec_get.c,v 1.11 2007/08/08 07:16:50 ray Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n/*\n * __REC_GET -- Get a record from the btree.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key to find\n *      data:   data to return\n *      flag:   currently unused\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found.\n */\n\nint\n__rec_get(const DB *dbp, const DBT *key, DBT *data, unsigned int flags)\n{\n        BTREE *t;\n        EPG *e;\n        recno_t nrec;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* Get currently doesn't take any flags, and keys of 0 are illegal. */\n        if (flags || (nrec = *(recno_t *)key->data) == 0) {\n                errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        /*\n         * If we haven't seen this record yet, try to find it in the\n         * original file.\n         */\n\n        if (nrec > t->bt_nrecs) {\n                if (F_ISSET(t, R_EOF | R_INMEM))\n                        return (RET_SPECIAL);\n                if ((status = t->bt_irec(t, nrec)) != RET_SUCCESS)\n                        return (status);\n        }\n\n        --nrec;\n        if ((e = __rec_search(t, nrec, SEARCH)) == NULL)\n                return (RET_ERROR);\n\n        status = __rec_ret(t, e, 0, NULL, data);\n        if (F_ISSET(t, B_DB_LOCK))\n                mpool_put(t->bt_mp, e->page, 0);\n        else\n                t->bt_pinned = e->page;\n        return (status);\n}\n\n/*\n * __REC_FPIPE -- Get fixed length records from a pipe.\n *\n * Parameters:\n *      t:      tree\n *      cnt:    records to read\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__rec_fpipe(BTREE *t, recno_t top)\n{\n        DBT data;\n        recno_t nrec;\n        size_t len;\n        int ch;\n        unsigned char *p;\n        void *tp;\n\n        if (t->bt_rdata.size < t->bt_reclen) {\n                tp = realloc(t->bt_rdata.data, t->bt_reclen);\n                if (tp == NULL)\n                        return (RET_ERROR);\n                t->bt_rdata.data = tp;\n                t->bt_rdata.size = t->bt_reclen;\n        }\n        data.data = t->bt_rdata.data;\n        data.size = t->bt_reclen;\n\n        for (nrec = t->bt_nrecs; nrec < top;) {\n                len = t->bt_reclen;\n                for (p = t->bt_rdata.data;; *p++ = ch)\n                        if ((ch = getc(t->bt_rfp)) == EOF || !--len) {\n                                if (ch != EOF)\n                                        *p = ch;\n                                if (len != 0)\n                                        memset(p, t->bt_bval, len);\n                                if (__rec_iput(t,\n                                    nrec, &data, 0) != RET_SUCCESS)\n                                        return (RET_ERROR);\n                                ++nrec;\n                                break;\n                        }\n                if (ch == EOF)\n                        break;\n        }\n        if (nrec < top) {\n                F_SET(t, R_EOF);\n                return (RET_SPECIAL);\n        }\n        return (RET_SUCCESS);\n}\n\n/*\n * __REC_VPIPE -- Get variable length records from a pipe.\n *\n * Parameters:\n *      t:      tree\n *      cnt:    records to read\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__rec_vpipe(BTREE *t, recno_t top)\n{\n        DBT data;\n        recno_t nrec;\n        size_t len;\n        size_t sz;\n        int bval, ch;\n        unsigned char *p;\n        void *tp;\n\n        bval = t->bt_bval;\n        for (nrec = t->bt_nrecs; nrec < top; ++nrec) {\n                for (p = t->bt_rdata.data,\n                    sz = t->bt_rdata.size;; *p++ = ch, --sz) {\n                        if ((ch = getc(t->bt_rfp)) == EOF || ch == bval) {\n                                data.data = t->bt_rdata.data;\n                                data.size = p - (unsigned char *)t->bt_rdata.data;\n                                if (ch == EOF && data.size == 0)\n                                        break;\n                                if (__rec_iput(t, nrec, &data, 0)\n                                    != RET_SUCCESS)\n                                        return (RET_ERROR);\n                                break;\n                        }\n                        if (sz == 0) {\n                                len = p - (unsigned char *)t->bt_rdata.data;\n                                t->bt_rdata.size += (sz = 256);\n                                tp = realloc(t->bt_rdata.data, t->bt_rdata.size);\n                                if (tp == NULL)\n                                        return (RET_ERROR);\n                                t->bt_rdata.data = tp;\n                                p = (unsigned char *)t->bt_rdata.data + len;\n                        }\n                }\n                if (ch == EOF)\n                        break;\n        }\n        if (nrec < top) {\n                F_SET(t, R_EOF);\n                return (RET_SPECIAL);\n        }\n        return (RET_SUCCESS);\n}\n\n/*\n * __REC_FMAP -- Get fixed length records from a file.\n *\n * Parameters:\n *      t:      tree\n *      cnt:    records to read\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__rec_fmap(BTREE *t, recno_t top)\n{\n        DBT data;\n        recno_t nrec;\n        unsigned char *sp, *ep, *p;\n        size_t len;\n        void *tp;\n\n        if (t->bt_rdata.size < t->bt_reclen) {\n                tp = realloc(t->bt_rdata.data, t->bt_reclen);\n                if (tp == NULL)\n                        return (RET_ERROR);\n                t->bt_rdata.data = tp;\n                t->bt_rdata.size = t->bt_reclen;\n        }\n        data.data = t->bt_rdata.data;\n        data.size = t->bt_reclen;\n\n        sp = (unsigned char *)t->bt_cmap;\n        ep = (unsigned char *)t->bt_emap;\n        for (nrec = t->bt_nrecs; nrec < top; ++nrec) {\n                if (sp >= ep) {\n                        F_SET(t, R_EOF);\n                        return (RET_SPECIAL);\n                }\n                len = t->bt_reclen;\n                for (p = t->bt_rdata.data;\n                    sp < ep && len > 0; *p++ = *sp++, --len);\n                if (len != 0)\n                        memset(p, t->bt_bval, len);\n                if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS)\n                        return (RET_ERROR);\n        }\n        t->bt_cmap = (caddr_t)sp;\n        return (RET_SUCCESS);\n}\n\n/*\n * __REC_VMAP -- Get variable length records from a file.\n *\n * Parameters:\n *      t:      tree\n *      cnt:    records to read\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__rec_vmap(BTREE *t, recno_t top)\n{\n        DBT data;\n        unsigned char *sp, *ep;\n        recno_t nrec;\n        int bval;\n\n        sp = (unsigned char *)t->bt_cmap;\n        ep = (unsigned char *)t->bt_emap;\n        bval = t->bt_bval;\n\n        for (nrec = t->bt_nrecs; nrec < top; ++nrec) {\n                if (sp >= ep) {\n                        F_SET(t, R_EOF);\n                        return (RET_SPECIAL);\n                }\n                for (data.data = sp; sp < ep && *sp != bval; ++sp);\n                data.size = sp - (unsigned char *)data.data;\n                if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS)\n                        return (RET_ERROR);\n                ++sp;\n        }\n        t->bt_cmap = (caddr_t)sp;\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/recno/rec_open.c",
    "content": "/*      $OpenBSD: rec_open.c,v 1.14 2020/12/01 16:19:38 millert Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Mike Olson.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_unistd.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n#undef open\n\nDB *\n__rec_open(const char *fname, int flags, int mode, const RECNOINFO *openinfo,\n    int dflags)\n{\n        BTREE *t;\n        BTREEINFO btopeninfo;\n        DB *dbp;\n        PAGE *h;\n        struct stat sb;\n        int rfd, sverrno;\n\n        /* Open the user's file -- if this fails, we're done. */\n        if (fname != NULL && (rfd = open(fname, flags, mode)) < 0)\n                return (NULL);\n\n        /* Create a btree in memory (backed by disk). */\n        dbp = NULL;\n        if (openinfo) {\n                if (openinfo->flags & ~(R_FIXEDLEN | R_NOKEY | R_SNAPSHOT))\n                        goto einval;\n                btopeninfo.flags      = 0;\n                btopeninfo.cachesize  = openinfo->cachesize;\n                btopeninfo.maxkeypage = 0;\n                btopeninfo.minkeypage = 0;\n                btopeninfo.psize      = openinfo->psize;\n                btopeninfo.compare    = NULL;\n                btopeninfo.prefix     = NULL;\n                btopeninfo.lorder     = openinfo->lorder;\n                dbp = __bt_open(openinfo->bfname,\n                    O_RDWR, S_IRUSR | S_IWUSR, &btopeninfo, dflags);\n        } else\n                dbp = __bt_open(NULL, O_RDWR, S_IRUSR | S_IWUSR, NULL, dflags);\n        if (dbp == NULL)\n                goto err;\n\n        /*\n         * Some fields in the tree structure are recno specific.  Fill them\n         * in and make the btree structure look like a recno structure.  We\n         * don't change the bt_ovflsize value, it's close enough and slightly\n         * bigger.\n         */\n\n        t = dbp->internal;\n        if (openinfo) {\n                if (openinfo->flags & R_FIXEDLEN) {\n                        F_SET(t, R_FIXLEN);\n                        t->bt_reclen = openinfo->reclen;\n                        if (t->bt_reclen == 0)\n                                goto einval;\n                }\n                t->bt_bval = openinfo->bval;\n        } else\n                t->bt_bval = '\\n';\n\n        F_SET(t, R_RECNO);\n        if (fname == NULL)\n                F_SET(t, R_EOF | R_INMEM);\n        else\n                t->bt_rfd = rfd;\n\n        if (fname != NULL) {\n\n                /*\n                 * In 4.4BSD, stat(2) returns true for ISSOCK on pipes.\n                 * Unfortunately, that's not portable, so we use lseek\n                 * and check the errno values.\n                 */\n\n                errno = 0;\n                if (lseek(rfd, 0, SEEK_CUR) == -1 && errno == ESPIPE) {\n                        switch (flags & O_ACCMODE) {\n                        case O_RDONLY:\n                                F_SET(t, R_RDONLY);\n                                break;\n                        default:\n                                goto einval;\n                        }\nslow:                   if ((t->bt_rfp = fdopen(rfd, \"r\")) == NULL)\n                                goto err;\n                        F_SET(t, R_CLOSEFP);\n                        t->bt_irec =\n                            F_ISSET(t, R_FIXLEN) ? __rec_fpipe : __rec_vpipe;\n                } else {\n                        switch (flags & O_ACCMODE) {\n                        case O_RDONLY:\n                                F_SET(t, R_RDONLY);\n                                break;\n                        case O_RDWR:\n                                break;\n                        default:\n                                goto einval;\n                        }\n\n                        if (fstat(rfd, &sb))\n                                goto err;\n                        if (sb.st_size == 0)\n                                F_SET(t, R_EOF);\n                        else {\n                                goto slow;\n                        }\n                }\n        }\n\n        /* Use the recno routines. */\n        dbp->close = __rec_close;\n        dbp->del   = __rec_delete;\n        dbp->fd    = __rec_fd;\n        dbp->get   = __rec_get;\n        dbp->put   = __rec_put;\n        dbp->seq   = __rec_seq;\n        dbp->sync  = __rec_sync;\n        dbp->type  = DB_RECNO;\n\n        /* If the root page was created, reset the flags. */\n        if ((h = mpool_get(t->bt_mp, P_ROOT, 0)) == NULL)\n                goto err;\n        if ((h->flags & P_TYPE) == P_BLEAF) {\n                F_CLR(h, P_TYPE);\n                F_SET(h, P_RLEAF);\n                mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n        } else\n                mpool_put(t->bt_mp, h, 0);\n\n        if (openinfo && openinfo->flags & R_SNAPSHOT &&\n            !F_ISSET(t, R_EOF | R_INMEM) &&\n            t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR)\n                goto err;\n        return (dbp);\n\neinval: errno = EINVAL;\nerr:    sverrno = errno;\n        if (dbp != NULL)\n                (void)__bt_close(dbp);\n        if (fname != NULL)\n                (void)close(rfd);\n        errno = sverrno;\n        return (NULL);\n}\n\nint\n__rec_fd(const DB *dbp)\n{\n        BTREE *t;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /* In-memory database can't have a file descriptor. */\n        if (F_ISSET(t, R_INMEM)) {\n                errno = ENOENT;\n                return (-1);\n        }\n        return (t->bt_rfd);\n}\n"
  },
  {
    "path": "db/recno/rec_put.c",
    "content": "/*      $OpenBSD: rec_put.c,v 1.11 2007/08/08 07:16:50 ray Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n/*\n * __REC_PUT -- Add a recno item to the tree.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key\n *      data:   data\n *      flag:   R_CURSOR, R_IAFTER, R_IBEFORE, R_NOOVERWRITE\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key is\n *      already in the tree and R_NOOVERWRITE specified.\n */\n\nint\n__rec_put(const DB *dbp, DBT *key, const DBT *data, unsigned int flags)\n{\n        BTREE *t;\n        DBT fdata, tdata;\n        recno_t nrec;\n        int status;\n        void *tp;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        /*\n         * If using fixed-length records, and the record is long, return\n         * EINVAL.  If it's short, pad it out.  Use the record data return\n         * memory, it's only short-term.\n         */\n\n        if (F_ISSET(t, R_FIXLEN) && data->size != t->bt_reclen) {\n                if (data->size > t->bt_reclen)\n                        goto einval;\n\n                if (t->bt_rdata.size < t->bt_reclen) {\n                        tp = realloc(t->bt_rdata.data, t->bt_reclen);\n                        if (tp == NULL)\n                                return (RET_ERROR);\n                        t->bt_rdata.data = tp;\n                        t->bt_rdata.size = t->bt_reclen;\n                }\n                memmove(t->bt_rdata.data, data->data, data->size);\n                memset((char *)t->bt_rdata.data + data->size,\n                    t->bt_bval, t->bt_reclen - data->size);\n                fdata.data = t->bt_rdata.data;\n                fdata.size = t->bt_reclen;\n        } else {\n                fdata.data = data->data;\n                fdata.size = data->size;\n        }\n\n        switch (flags) {\n        case R_CURSOR:\n                if (!F_ISSET(&t->bt_cursor, CURS_INIT))\n                        goto einval;\n                nrec = t->bt_cursor.rcursor;\n                break;\n        case R_SETCURSOR:\n                if ((nrec = *(recno_t *)key->data) == 0)\n                        goto einval;\n                break;\n        case R_IAFTER:\n                if ((nrec = *(recno_t *)key->data) == 0) {\n                        nrec = 1;\n                        flags = R_IBEFORE;\n                }\n                break;\n        case 0:\n        case R_IBEFORE:\n                if ((nrec = *(recno_t *)key->data) == 0)\n                        goto einval;\n                break;\n        case R_NOOVERWRITE:\n                if ((nrec = *(recno_t *)key->data) == 0)\n                        goto einval;\n                if (nrec <= t->bt_nrecs)\n                        return (RET_SPECIAL);\n                break;\n        default:\neinval:         errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        /*\n         * Make sure that records up to and including the put record are\n         * already in the database.  If skipping records, create empty ones.\n         */\n\n        if (nrec > t->bt_nrecs) {\n                if (!F_ISSET(t, R_EOF | R_INMEM) &&\n                    t->bt_irec(t, nrec) == RET_ERROR)\n                        return (RET_ERROR);\n                if (nrec > t->bt_nrecs + 1) {\n                        if (F_ISSET(t, R_FIXLEN)) {\n                                if ((tdata.data =\n                                    (void *)malloc(t->bt_reclen)) == NULL)\n                                        return (RET_ERROR);\n                                tdata.size = t->bt_reclen;\n                                memset(tdata.data, t->bt_bval, tdata.size);\n                        } else {\n                                tdata.data = NULL;\n                                tdata.size = 0;\n                        }\n                        while (nrec > t->bt_nrecs + 1)\n                                if (__rec_iput(t,\n                                    t->bt_nrecs, &tdata, 0) != RET_SUCCESS)\n                                        return (RET_ERROR);\n                        if (F_ISSET(t, R_FIXLEN))\n                                free(tdata.data);\n                }\n        }\n\n        if ((status = __rec_iput(t, nrec - 1, &fdata, flags)) != RET_SUCCESS)\n                return (status);\n\n        if (flags == R_SETCURSOR)\n                t->bt_cursor.rcursor = nrec;\n\n        F_SET(t, R_MODIFIED);\n        return (__rec_ret(t, NULL, nrec, key, NULL));\n}\n\n/*\n * __REC_IPUT -- Add a recno item to the tree.\n *\n * Parameters:\n *      t:      tree\n *      nrec:   record number\n *      data:   data\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS\n */\n\nint\n__rec_iput(BTREE *t, recno_t nrec, const DBT *data, unsigned int flags)\n{\n        DBT tdata;\n        EPG *e;\n        PAGE *h;\n        indx_t idx, nxtindex;\n        pgno_t pg;\n        u_int32_t nbytes;\n        int dflags, status;\n        char *dest, db[NOVFLSIZE];\n\n        /*\n         * If the data won't fit on a page, store it on indirect pages.\n         *\n         * XXX\n         * If the insert fails later on, these pages aren't recovered.\n         */\n\n        if (data->size > t->bt_ovflsize) {\n                if (__ovfl_put(t, data, &pg) == RET_ERROR)\n                        return (RET_ERROR);\n                tdata.data = db;\n                tdata.size = NOVFLSIZE;\n                *(pgno_t *)db = pg;\n                *(u_int32_t *)(db + sizeof(pgno_t)) = data->size;\n                dflags = P_BIGDATA;\n                data = &tdata;\n        } else\n                dflags = 0;\n\n        /* __rec_search pins the returned page. */\n        if ((e = __rec_search(t, nrec,\n            nrec > t->bt_nrecs || flags == R_IAFTER || flags == R_IBEFORE ?\n            SINSERT : SEARCH)) == NULL)\n                return (RET_ERROR);\n\n        h = e->page;\n        idx = e->index;\n\n        /*\n         * Add the specified key/data pair to the tree.  The R_IAFTER and\n         * R_IBEFORE flags insert the key after/before the specified key.\n         *\n         * Pages are split as required.\n         */\n\n        switch (flags) {\n        case R_IAFTER:\n                ++idx;\n                break;\n        case R_IBEFORE:\n                break;\n        default:\n                if (nrec < t->bt_nrecs &&\n                    __rec_dleaf(t, h, idx) == RET_ERROR) {\n                        mpool_put(t->bt_mp, h, 0);\n                        return (RET_ERROR);\n                }\n                break;\n        }\n\n        /*\n         * If not enough room, split the page.  The split code will insert\n         * the key and data and unpin the current page.  If inserting into\n         * the offset array, shift the pointers up.\n         */\n\n        nbytes = NRLEAFDBT(data->size);\n        if (h->upper - h->lower < nbytes + sizeof(indx_t)) {\n                status = __bt_split(t, h, NULL, data, dflags, nbytes, idx);\n                if (status == RET_SUCCESS)\n                        ++t->bt_nrecs;\n                return (status);\n        }\n\n        if (idx < (nxtindex = NEXTINDEX(h)))\n                memmove(h->linp + idx + 1, h->linp + idx,\n                    (nxtindex - idx) * sizeof(indx_t));\n        h->lower += sizeof(indx_t);\n\n        h->linp[idx] = h->upper -= nbytes;\n        dest = (char *)h + h->upper;\n        if (data == NULL)\n                return (RET_ERROR);\n        WR_RLEAF(dest, data, dflags);\n\n        ++t->bt_nrecs;\n        F_SET(t, B_MODIFIED);\n        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/recno/rec_search.c",
    "content": "/*      $OpenBSD: rec_search.c,v 1.11 2005/08/05 13:03:00 espie Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *    w\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <stdio.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n/*\n * __REC_SEARCH -- Search a btree for a key.\n *\n * Parameters:\n *      t:      tree to search\n *      recno:  key to find\n *      op:     search operation\n *\n * Returns:\n *      EPG for matching record, if any, or the EPG for the location of the\n *      key, if it were inserted into the tree.\n *\n * Returns:\n *      The EPG for matching record, if any, or the EPG for the location\n *      of the key, if it were inserted into the tree, is entered into\n *      the bt_cur field of the tree.  A pointer to the field is returned.\n */\n\nEPG *\n__rec_search(BTREE *t, recno_t recno, enum SRCHOP op)\n{\n        indx_t idx;\n        PAGE *h;\n        EPGNO *parent;\n        RINTERNAL *r;\n        pgno_t pg;\n        indx_t top;\n        recno_t total;\n        int sverrno;\n\n        BT_CLR(t);\n        for (pg = P_ROOT, total = 0;;) {\n                if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL)\n                        goto err;\n                if (h->flags & P_RLEAF) {\n                        t->bt_cur.page = h;\n                        t->bt_cur.index = recno - total;\n                        return (&t->bt_cur);\n                }\n                for (idx = 0, top = NEXTINDEX(h);;) {\n                        r = GETRINTERNAL(h, idx);\n                        if (++idx == top || total + r->nrecs > recno)\n                                break;\n                        total += r->nrecs;\n                }\n\n                BT_PUSH(t, pg, idx - 1);\n\n                pg = r->pgno;\n                switch (op) {\n                case SDELETE:\n                        --GETRINTERNAL(h, (idx - 1))->nrecs;\n                        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                        break;\n                case SINSERT:\n                        ++GETRINTERNAL(h, (idx - 1))->nrecs;\n                        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                        break;\n                case SEARCH:\n                        mpool_put(t->bt_mp, h, 0);\n                        break;\n                }\n\n        }\n        /* Try and recover the tree. */\nerr:    sverrno = errno;\n        if (op != SEARCH)\n                while  ((parent = BT_POP(t)) != NULL) {\n                        if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL)\n                                break;\n                        if (op == SINSERT)\n                                --GETRINTERNAL(h, parent->index)->nrecs;\n                        else\n                                ++GETRINTERNAL(h, parent->index)->nrecs;\n                        mpool_put(t->bt_mp, h, MPOOL_DIRTY);\n                }\n        errno = sverrno;\n        return (NULL);\n}\n"
  },
  {
    "path": "db/recno/rec_seq.c",
    "content": "/*      $OpenBSD: rec_seq.c,v 1.8 2005/08/05 13:03:00 espie Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n/*\n * __REC_SEQ -- Recno sequential scan interface.\n *\n * Parameters:\n *      dbp:    pointer to access method\n *      key:    key for positioning and return value\n *      data:   data return value\n *      flags:  R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV.\n *\n * Returns:\n *      RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key.\n */\n\nint\n__rec_seq(const DB *dbp, DBT *key, DBT *data, unsigned int flags)\n{\n        BTREE *t;\n        EPG *e;\n        recno_t nrec;\n        int status;\n\n        t = dbp->internal;\n\n        /* Toss any page pinned across calls. */\n        if (t->bt_pinned != NULL) {\n                mpool_put(t->bt_mp, t->bt_pinned, 0);\n                t->bt_pinned = NULL;\n        }\n\n        switch(flags) {\n        case R_CURSOR:\n                if ((nrec = *(recno_t *)key->data) == 0)\n                        goto einval;\n                break;\n        case R_NEXT:\n                if (F_ISSET(&t->bt_cursor, CURS_INIT)) {\n                        nrec = t->bt_cursor.rcursor + 1;\n                        break;\n                }\n                /* FALLTHROUGH */\n        case R_FIRST:\n                nrec = 1;\n                break;\n        case R_PREV:\n                if (F_ISSET(&t->bt_cursor, CURS_INIT)) {\n                        if ((nrec = t->bt_cursor.rcursor - 1) == 0)\n                                return (RET_SPECIAL);\n                        break;\n                }\n                /* FALLTHROUGH */\n        case R_LAST:\n                if (!F_ISSET(t, R_EOF | R_INMEM) &&\n                    t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR)\n                        return (RET_ERROR);\n                nrec = t->bt_nrecs;\n                break;\n        default:\neinval:         errno = EINVAL;\n                return (RET_ERROR);\n        }\n\n        if (t->bt_nrecs == 0 || nrec > t->bt_nrecs) {\n                if (!F_ISSET(t, R_EOF | R_INMEM) &&\n                    (status = t->bt_irec(t, nrec)) != RET_SUCCESS)\n                        return (status);\n                if (t->bt_nrecs == 0 || nrec > t->bt_nrecs)\n                        return (RET_SPECIAL);\n        }\n\n        if ((e = __rec_search(t, nrec - 1, SEARCH)) == NULL)\n                return (RET_ERROR);\n\n        F_SET(&t->bt_cursor, CURS_INIT);\n        t->bt_cursor.rcursor = nrec;\n\n        status = __rec_ret(t, e, nrec, key, data);\n        if (F_ISSET(t, B_DB_LOCK))\n                mpool_put(t->bt_mp, e->page, 0);\n        else\n                t->bt_pinned = e->page;\n        return (status);\n}\n"
  },
  {
    "path": "db/recno/rec_utils.c",
    "content": "/*      $OpenBSD: rec_utils.c,v 1.10 2022/12/17 17:10:06 jmc Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../../include/compat.h\"\n\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include <bsd_db.h>\n#include <compat_bsd_db.h>\n#include \"recno.h\"\n\n/*\n * __rec_ret --\n *      Build return data.\n *\n * Parameters:\n *      t:      tree\n *      e:      key/data pair to be returned\n *   nrec:      record number\n *    key:      user's key structure\n *   data:      user's data structure\n *\n * Returns:\n *      RET_SUCCESS, RET_ERROR.\n */\n\nint\n__rec_ret(BTREE *t, EPG *e, recno_t nrec, DBT *key, DBT *data)\n{\n        RLEAF *rl;\n        void *p;\n\n        if (key == NULL)\n                goto dataonly;\n\n        /* We have to copy the key, it's not on the page. */\n        if (sizeof(recno_t) > t->bt_rkey.size) {\n                p = realloc(t->bt_rkey.data, sizeof(recno_t));\n                if (p == NULL)\n                        return (RET_ERROR);\n                t->bt_rkey.data = p;\n                t->bt_rkey.size = sizeof(recno_t);\n        }\n        memmove(t->bt_rkey.data, &nrec, sizeof(recno_t));\n        key->size = sizeof(recno_t);\n        key->data = t->bt_rkey.data;\n\ndataonly:\n        if (data == NULL)\n                return (RET_SUCCESS);\n\n        /*\n         * We must copy big keys/data to make them contiguous.  Otherwise,\n         * leave the page pinned and don't copy unless the user specified\n         * concurrent access.\n         */\n\n        rl = GETRLEAF(e->page, e->index);\n        if (rl->flags & P_BIGDATA) {\n                if (__ovfl_get(t, rl->bytes,\n                    &data->size, &t->bt_rdata.data, &t->bt_rdata.size))\n                        return (RET_ERROR);\n                data->data = t->bt_rdata.data;\n        } else if (F_ISSET(t, B_DB_LOCK)) {\n                /* Use +1 in case the first record retrieved is 0 length. */\n                if (rl->dsize + 1 > t->bt_rdata.size) {\n                        p = realloc(t->bt_rdata.data, rl->dsize + 1);\n                        if (p == NULL)\n                                return (RET_ERROR);\n                        t->bt_rdata.data = p;\n                        t->bt_rdata.size = rl->dsize + 1;\n                }\n                memmove(t->bt_rdata.data, rl->bytes, rl->dsize);\n                data->size = rl->dsize;\n                data->data = t->bt_rdata.data;\n        } else {\n                data->size = rl->dsize;\n                data->data = rl->bytes;\n        }\n        return (RET_SUCCESS);\n}\n"
  },
  {
    "path": "db/recno/recno.h",
    "content": "/*      $OpenBSD: recno.h,v 1.5 2003/06/02 20:18:34 millert Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)recno.h     8.1 (Berkeley) 6/4/93\n */\n\nenum SRCHOP { SDELETE, SINSERT, SEARCH};        /* Rec_search operation. */\n\n#include \"../btree/btree.h\"\n#include \"extern.h\"\n"
  },
  {
    "path": "docs/USD.doc/edit/edit.vindex",
    "content": ".\\\"        $OpenBSD: edit.vindex,v 1.3 2003/06/03 02:56:21 millert Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)edit.vindex        8.1 (Berkeley) 6/8/93\n.\\\"\n.bd I\n.ND\n.TL\nIndex\n.sp 3\n.2C\n.nf\naddressing, \\fIsee\\fR line numbers\nappend mode, 4\nbackslash (\\\\), 18\nbuffer, 2\ncommand mode, 4\ncontext search, 8, 10, 13, 18\ncontrol characters (``^'' notation), 8\ncontrol-d, 6\ncurrent filename, 19, 20\ncurrent line (.), 9, 15\ndiagnostic messages, 4\ndisk, 2\ndocumentation, 21\nedit (to begin editing session), 3, 7\nediting commands:\n.in +2\nappend (a), 4, 7\nchange (c), 16\ncopy (co), 13\ndelete (d), 13-14\nedit (e), 12\nfile (f), 19\nglobal (g), 18-19\nmove (m), 12-13\nnumber (nu), 9\npreserve (pre), 20-21\nprint (p), 8\nquit (q), 5, 11\nquit! (q!), 11\nread (r), 20\nrecover (rec), 20\nsubstitute (s), 9-10, 17, 18\nundo (u), 14, 17\nwrite (w), 5-6, 11, 19-20\nz, 11\n.sp 10i\n! (shell escape), 19\n$= , 15\n+, 15\n\\-, 15\n//, 8, 18\n??, 18\n\\&\\fB.\\fR, 9, 15\n\\&\\fB.\\fR=, 9, 15\n.in -2\nerasing\n.ti +2\ncharacters (#), 8\n.ti +2\nlines (@), 8\nex (text editor), 21\n\\fIEx Reference Manual\\fR, 21\nfile, 1\nfile recovery, 20\nfilename, 2\nInterrupt (message), 7\nline numbers, \\fIsee also\\fR current line\n.ti +2\ndollar sign ($), 8, 12-13, 15\n.ti +2\ndot (.), 9, 15\n.ti +2\nrelative (+ and \\-), 15, 16\nlogging out, 6\nlogin procedure, 2\n``magic'' characters, 21\nnon-printing characters, 8\n``not found'' (message), 3\nprogram, 1\nrecovery \\fIsee\\fR file recovery\nshell, 18\nshell escape (!), 19\nspecial characters (^, $, \\e), 18\ntext input mode, 4\nUNIX, 1\n"
  },
  {
    "path": "docs/USD.doc/edit/edittut.ms",
    "content": ".\\\"        $OpenBSD: edittut.ms,v 1.8 2022/12/26 19:16:03 jmc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)edittut.ms        8.3 (Berkeley) 8/18/96\n.\\\"\n.ie n \\{\\\n.po 5n\n.ll 70n\n.\\}\n.el \\{\\\n.ll 6.5i\n.nr LL 6.5i\n.\\}\n.EH 'USD:11-%''Ex:  A Tutorial'\n.OH 'Ex:  A Tutorial''USD:11-%'\n.LP\n.ds u \\s-2UNIX\\s0\n.ND\n.sp 4\n.ce\n\\f3\\s+2Ex:  A Tutorial\\s0\\f1\n.sp\n.ce 3\n.I\nRicki Blau\n.sp\nJames Joyce\n.R\n.sp\n.ce 3\nComputing Services\nUniversity of California\nBerkeley, California 94720\n.sp 3\n.ce\n.I\nABSTRACT\n.R\n.sp\n.LP\nThis narrative introduction to the use of the text editor\n.I ex\nassumes no prior familiarity with computers or with text editing.\nIts aim is to lead the beginning \\s-2UNIX*\\s+2 user through the\n.FS\n*UNIX is a trademark of The Open Group.\n.FE\nfundamental steps of writing and revising a file of text.\n.\\\" Edit,\n.\\\" a version of the text editor\n.\\\" .I ex,\n.\\\" was designed to provide an informative environment\n.\\\" for new and casual users.\n.PP\nWe welcome comments and suggestions about this tutorial\nand the \\s-2UNIX\\s+2 documentation in general.\n.sp 1v\nSeptember 1981\n.bp\n.ll 6.5i\n.nr LL 6.5i\n.nr LT 6.5i\n.ds u \\s-2UNIX\\s0\n.ce\n\\s+2\\f3Contents\\f1\\s0\n.LP\n.nf\nIntroduction\\ \\ \\ 3\n.sp\nSession 1\\ \\ 4\n.in +.5i\nMaking contact with \\s-2UNIX\\s+2\\ \\ \\ 4\nLogging in\\ \\ 4\nAsking for \\fIex\\fR\\ \\ \\ 4\nThe ``Command not found'' message\\ \\ \\ 5\nA summary\\ \\ 5\nEntering text\\ \\ \\ 5\nMessages from \\fIex\\fR\\ \\ \\ 5\nText input mode\\ \\ \\ 6\nMaking corrections\\ \\ \\ 6\nWriting text to disk\\ \\ \\ 7\nSigning off\\ \\ 7\n.in -.5i\n.sp\nSession 2\\ \\ \\ 8\n.in +.5i\nAdding more text to the file\\ \\ \\ 8\nInterrupt\\ \\ \\ 8\nMaking corrections\\ \\ \\ 8\nListing what's in the buffer (p)\\ \\ \\ 9\nFinding things in the buffer\\ \\ \\ 9\nThe current line\\ \\ \\ 10\nNumbering lines (nu)\\ \\ \\ 10\nSubstitute command (s)\\ \\ \\ 10\nAnother way to list what's in the buffer (z)\\ \\ \\ 11\nSaving the modified text\\ \\ \\ 12\n.in -.5i\n.sp\nSession 3\\ \\ \\ 13\n.in +.5i\nBringing text into the buffer (e)\\ \\ \\ 13\nMoving text in the buffer (m)\\ \\ \\ 13\nCopying lines (copy)\\ \\ \\ 14\nDeleting lines (d)\\ \\ \\ 14\nA word or two of caution\\ \\ \\ 15\nUndo (u) to the rescue\\ \\ \\ 15\nMore about the dot (.) and buffer end ($)\\ \\ \\ 16\nMoving around in the buffer (+ and \\-)\\ \\ \\ 16\nChanging lines (c)\\ \\ \\ 17\n.in -.5i\n.sp\nSession 4\\ \\ \\ 18\n.in +.5i\nMaking commands global (g)\\ \\ \\ 18\nMore about searching and substituting\\ \\ \\ 19\nSpecial characters\\ \\ \\ 19\nIssuing \\s-2UNIX\\s+2 commands from the editor\\ \\ \\ 20\nFilenames and file manipulation\\ \\ \\ 20\nThe file (f) command\\ \\ \\ 20\nReading additional files (r)\\ \\ \\ 21\nWriting parts of the buffer\\ \\ \\ 21\nRecovering files\\ \\ \\ 21\nOther recovery techniques\\ \\ \\ 21\nOptions, set, and editor startup files\\ \\ \\ 22\nFurther reading and other information\\ \\ \\ 22\n.in -.5i\n.sp\nIndex\\ \\ \\ 23\n.bp\n.SH\n.ce\n\\s+2Introduction\\s0\n.PP\nText editing using a terminal connected to a computer\nallows you to create, modify, and print text\neasily.\nA\n.I\ntext editor\n.R\nis a program\nthat assists you\nas you create and modify text.\nThe text editor you will learn here is named\n.I ex .\nCreating text using\n.I ex\nis as easy as typing it\non an electric typewriter.\nModifying text involves telling the text editor\nwhat you want to add, change, or delete.\nYou can review your text\nby typing a command\nto print the file contents\nas they are currently.\nAnother program (which we do not discuss in this\ndocument), a text formatter,\nrearranges your text\nfor you into ``finished form.''\n.PP\nThese lessons assume no prior familiarity with computers\nor with text editing.\nThey consist of a series of text editing sessions\nwhich lead you through the fundamental steps\nof creating and revising text.\nAfter scanning each lesson and before beginning the next,\nyou should try the examples at a terminal to get a feeling\nfor the actual process of text editing.\nIf you set aside some time for experimentation,\nyou will soon become familiar with using the\ncomputer to write and modify text.\nIn addition to the actual use of the text editor,\nother features of \\s-2UNIX\\s0 will be very important to your work.\nYou can begin to\nlearn about these other features by\nreading one of the other tutorials\nthat provide a general introduction to the system.\nYou will be ready to proceed with this lesson as soon as\nyou are familiar with (1) your terminal and its special keys,\n(2) how to log in,\n(3) and the ways of correcting typing errors.\nLet's first define some terms:\n.sp .5\n.IP program 12\nA set of instructions, given to the computer,\ndescribing the sequence of steps the computer performs\nin order to accomplish a specific task.\nThe task must be specific,\nsuch as balancing your checkbook\nor editing your text.\nA general task,\nsuch as working for world peace,\nis something we can all do,\nbut not something we can currently write programs to do.\n.IP UNIX\n\\s-2UNIX\\s0 is a special type of program,\ncalled an operating system, that supervises the machinery\nand all other programs comprising the total\ncomputer system.\n.IP ex\n.I ex\nis the name of the \\s-2UNIX\\s0 text editor you will be learning to use,\nand is a program that aids you in writing or revising text.\n.\\\" Edit was designed for beginning users,\n.\\\" and is a simplified version of an editor named\n.\\\" .I ex.\n.IP file\nEach \\s-2UNIX\\s0 account is allotted\nspace for the permanent storage of information,\nsuch as programs, data or text.\nA file is a logical unit of data,\nfor example, an essay, a program,\nor a chapter from a book,\nwhich is stored on a computer system.\nOnce you create a file,\nit is kept until you instruct the system to remove it.\nYou may create a file during one \\s-2UNIX\\s0 session,\nend the session,\nand return to use it at a later time.\nFiles contain anything you choose to write and store in them.\nThe sizes of files vary to suit your needs;\none file might hold only a single number,\nyet another might contain\na very long document or program.\nThe only way to save\ninformation from one session to the next is to store it in a file,\nwhich you will learn in Session 1.\n.IP filename\nFilenames are used to distinguish one file from another,\nserving the same purpose as the labels of manila\nfolders in a file cabinet.\nIn order to write or access information in a file,\nyou use the name of that file in a \\s-2UNIX\\s0 command,\nand the system will automatically locate the file.\n.IP disk\nFiles are stored on an input/output device called a disk,\nwhich looks something like a stack of phonograph records.\nEach surface is coated with a material similar to that\non magnetic recording tape,\nand information is recorded on it.\n.IP buffer\nA temporary work space, made available to the user\nfor the duration of a session of text editing\nand used for creating and modifying\nthe text file.\nWe can think of the buffer as a blackboard that is\nerased after each class, where each session with the editor\nis a class.\n.bp\n.SH\n.ce 1\n\\s+2Session 1\\s0\n.sp 1\n.SH\nMaking contact with \\s-1UNIX\\s0\n.PP\nTo use the editor you must first make contact with the computer\nby logging in to \\s-2UNIX\\s0.\nWe'll quickly review the standard \\s-2UNIX\\s0 login procedure\nfor the two ways you can make contact:\non a terminal that is directly linked to the computer,\nor over a telephone line where the computer answers your call.\n.SH\nDirectly-linked terminals\n.PP\nTurn on your terminal and press the \\s-1RETURN\\s0 key.\nYou are now ready to log in.\n.SH\nDial-up terminals\n.PP\nIf your terminal connects with the computer over a telephone line,\nturn on the terminal, dial the system access number,\nand, when you hear a high-pitched tone, place the\ntelephone handset in the acoustic coupler, if you are using one.\nYou are now ready to log in.\n.SH\nLogging in\n.PP\nThe message inviting you to log in is:\n.DS I 1i\nlogin:\n.DE\n.LP\nType your login name, which identifies you to \\s-2UNIX\\s0,\non the same line as the login message,\nand press \\s-2RETURN\\s+2.\nIf the terminal you are using\nhas both upper and lower case,\n.B\nbe sure you enter your login name in lower case;\n.R\notherwise \\s-2UNIX\\s0 assumes your terminal\nhas only upper case and will not recognize lower case\nletters you may type.\n\\s-2UNIX\\s0 types ``login:'' and you reply\nwith your login name, for example ``susan'':\n.DS I 1i\nlogin: \\fBsusan\\fR \\fI(and press the \\s-2RETURN\\s0 key)\\fR\n.DE\n(In the examples, input you would type appears in\n.B \"bold face\"\nto distinguish it from the responses from \\s-2UNIX\\s0.)\n.PP\n\\s-2UNIX\\s0 will next respond with a request for a password\nas an additional precaution to prevent\nunauthorized people from using your account.\nThe password will not appear when you type it,\nto prevent others from seeing it.\nThe message is:\n.DS I 1i\nPassword:    \\fI(type your password and press \\s-2RETURN\\s+2)\\fR\n.DE\nIf any of the information you gave during the login\nsequence was mistyped or incorrect,\n\\s-2UNIX\\s0 will respond with\n.DS I 1i\nLogin incorrect\nlogin:\n.DE\nin which case you should start the login process anew.\nAssuming that you have successfully\nlogged in, \\s-2UNIX\\s0\nwill print the message of the day and eventually will present\nyou with a `%' at the beginning of a fresh line.\nThe `%' is the \\s-2UNIX\\s0 prompt symbol\nwhich tells you that \\s-2UNIX\\s0 is ready to accept a command.\n.LP\nNote: users of ksh(1) will instead be prompted with a `$'.\n.bd I 3\n.SH\nAsking for \\fIex\\fP\n.fl\n.bd I\n.PP\nYou are ready to tell \\s-2UNIX\\s0 that you\nwant to work with\n.I ex ,\nthe text editor.\nNow is a convenient time to choose\na name for the file of text you are about to create.\nTo begin your editing session,\ntype\n.B ex\nfollowed by a space and then the filename\nyou have selected; for example, ``text''.\nAfter that,\npress the \\s-2RETURN\\s0 key and wait for \\fIex\\fP's response:\n.DS I 1i\n% \\fBex text\\fP    \\fI(followed by a \\s-2RETURN\\s+2)\\fR\ntext: new file: line 1\n:\n.DE\nIf you typed the command correctly,\nyou will now be in communication with\n.I ex .\n.I Ex\nhas set aside a buffer for use as\na temporary working space during your current editing session.\nSince ``text'' is a new file we are about to create\nthe editor was unable to find that file, which it\nconfirms by saying:\n.DS I 1i\ntext: new file: line 1\n.DE\nOn the next line appears \\fIex\\fP's prompt `:',\nannouncing that you are in \\f2command mode\\f1 and\n.I ex\nexpects a command from you.\nYou may now begin to create the new file.\n.SH\nThe ``Command not found'' message\n.PP\nIf you misspelled ex by typing, say, ``ec'',\nthis might appear:\n.DS I 1i\n% \\fBec\\fP\nec: Command not found.\n%\n.DE\nYour mistake in calling ex ``ec'' was\ntreated by \\s-2UNIX\\s0 as a request\nfor a program named ``ec''.\nSince there is no program\nnamed ``ec'',\n\\s-2UNIX\\s0 reported that the program was ``not found''\n(but be careful, there\n.I is\na program named ``ed'').\nA new % indicates that \\s-2UNIX\\s0 is ready for another command,\nand you may then enter the correct command.\n.SH\nA summary\n.PP\nYour exchange with \\s-2UNIX\\s0 as you logged in and made contact with\n.I ex\nshould look something like this:\n.DS I 1i\nlogin: \\fBsusan\\fP\nPassword:\n\\&... A Message of General Interest ...\n% \\fBex text\\fP\ntext: new file: line 1\n:\n.DE\n.SH\nEntering text\n.PP\nYou may now begin entering text into the buffer.\nThis is done by \\fIappending\\fP (or adding) text to whatever\nis currently in the buffer.\nSince there is nothing in the buffer at the moment,\nyou are appending text to nothing;\nin effect,\nsince you are adding text to nothing\nyou are creating text.\nMost edit commands have two equivalent forms:\na word that suggests what the command does,\nand a shorter abbreviation of that word.\nMany beginners find the full command names\neasier to remember at first,\nbut once you are familiar with editing you may\nprefer to type the shorter abbreviations.\nThe command to input text is ``append''.\n(It may be abbreviated `a'.)\nType\n.B append\nand press the \\s-2RETURN\\s0 key.\n.DS I 1i\n% \\fBex text\n\\fR:\\|\\fBappend\n.R\n.DE\n.SH\n.bd I 3\nMessages from\n.I ex\n.fl\n.bd I\n.PP\nIf you make a mistake in entering a command and\ntype something that\n.I ex\ndoes not recognize,\nit will respond with a message\nintended to help you diagnose your error.\nFor example, if you misspell the command to input text by typing,\nperhaps, ``add'' instead of ``append'' or `a',\nyou will receive this message:\n.DS I 1i\n:\\|\\fBadd\\fR\nThe add command is unknown\n:\n.DE\nWhen you receive a diagnostic message,\ncheck what you typed in order to determine what\npart of your command confused\n.I ex .\nThe message above means that\n.I ex\nwas unable to recognize your mistyped command\nand, therefore, did not execute it.\nInstead, a new `:'\nappeared to let you know that\n.I ex\nis again ready to execute a command.\n.SH\nText input mode\n.PP\nBy giving the command ``append'' (or using the abbreviation `a'),\nyou entered\n.I\ntext input mode,\n.R\nalso known as\n.I\nappend mode.\n.R\nWhen you enter text input mode,\n.I ex\nstops sending you a prompt.\nYou will not receive any prompts\nor error messages\nwhile in text input mode.\nYou can enter\npretty much anything you want on the lines.\nThe lines are transmitted one by one to the buffer\nand held there during the editing session.\nYou may append as much text as you want, and\n.I\nwhen you wish to stop entering text lines you should\ntype a period as the only character on the line\nand press the \\s-2RETURN\\s0 key.\n.R\nWhen you type the period and press \\s-2RETURN\\s0,\nyou signal that you want to stop appending text,\nand\n.I ex\nresponds by allowing\nyou to exit text input mode and reenter command mode.\n.I Ex\nwill again\nprompt you for a command by printing `:'.\n.PP\nLeaving append mode does not destroy the text in\nthe buffer.\nYou have to leave append\nmode to do any of the other kinds of editing,\nsuch as changing, adding, or printing text.\nIf you type a period as the first character and\ntype any other character on the same line,\n.I ex\nwill believe you want to remain in append mode\nand will not let you out.\nAs this can be very frustrating,\nbe sure to type\n.B only\nthe period and the \\s-2RETURN\\s0 key.\n.PP\nThis is a good place to learn an important\nlesson about computers and text:  a blank space is\na character as far as a computer is concerned.\nIf you so much as type a period followed by a blank\n(that is, type a period and then the space bar on the keyboard),\nyou will remain in append mode with the last line of text\nbeing:\n.DS I 1i\n.B\n.ps +2\n\\&.\n.ps -2\n.R\n.DE\nLet's say that you enter the lines\n(try to type\n.B exactly\nwhat you see, including ``thiss''):\n.DS I 1i\n.B\nThis is some sample text.\nAnd thiss is some more text.\nText editing is strange, but nice.\n\\&.\n.R\n.DE\nThe last line is the period followed by a \\s-2RETURN\\s0\nthat gets you out of append mode.\n.SH\nMaking corrections\n.PP\nIf you have read a general introduction to \\s-2UNIX\\s0,\nyou will recall that it is possible to erase individual\nletters that you have typed.\nThis is done by typing the designated erase character\nas many times as there are characters\nyou want to erase.\n.PP\nThe usual erase character varies from place to place and\nuser to user.  Often it\nis the backspace,\nso you can correct typing errors\nin the line you are typing\nby typing the backspace key.  (Sometimes it is the DEL key.)\nIf you type the erase character\nyou will notice\nthat the terminal backspaces in the line you are on.\nYou can backspace over your error,\nand then type what you wanted.\n.PP\nIf you make a bad start\nin a line\nand would like to begin again,\nyou will have to backspace to the beginning of the line,\nor you can use `^U' to erase everything on the line:\n.DS I 1i\n.B\nText edtiing is strange, but^U\nText editing is strange, but nice.\n.R\n.fl\n.\\\" .bd S\n.DE\nWhen you type `^U', you erase\nthe entire line typed so far\nand are given a fresh line to type on.\nYou may immediately begin to retype the line.\nAdditionally, `^W' may be used to delete the last word typed.\nThese methods, unfortunately, do not work after you type the\nline and press \\s-2RETURN\\s+2.\nTo make corrections in lines that have been completed,\nit is necessary to use the editing commands\ncovered in the next sessions.\n.SH\nWriting text to disk\n.PP\nYou are now ready to edit the text.  One common operation\nis to write the text to disk as a file for safekeeping\nafter the session is over.\nThis is the only way to save information from one session to the next,\nsince the editor's buffer is temporary and will last only until the\nend of the editing session.\nLearning how to write a file to disk is second in\nimportance only to entering the text.\nTo write the contents of the buffer to a disk\nfile, use the command ``write''\n(or its abbreviation `w'):\n.DS I 1i\n:\\|\\fBwrite\n.R\n.DE\n.I Ex\nwill copy the contents of the buffer to a disk file.\nIf the file does not yet exist,\na new file will be created automatically\nand the presence of a ``[new file]'' will be noted.\nThe newly-created file will be given the name specified when\nyou entered the editor, in this case ``text''.\nTo confirm that the disk file has been successfully written,\n.I ex\nwill repeat the filename and give\nthe number of lines and the total\nnumber of characters in the file.\nThe buffer remains unchanged by the ``write'' command.\nAll of the lines that were written to disk will still be\nin the buffer,\nshould you want to modify or add to them.\n.PP\n.I Ex\nmust have a name for the file to be written.\nIf you forgot to indicate the name of the file\nwhen you began to edit,\na temporary filename will be used.\nHowever, if you end your editing session without writing your changes\nto a non-temporary file, they will be lost.\nIn this case, you can specify the filename in a new write command:\n.DS I 1i\n:\\|\\fBwrite text\n.R\n.DE\nAfter the ``write'' (or `w'), type a space and then the name of the file.\n.SH\nSigning off\n.PP\nWe have done enough for this first lesson on using the\n\\s-2UNIX\\s0 text editor, and are ready to quit the session with edit.\nTo do this we type ``quit'' (or `q') and press \\s-2RETURN\\s+2:\n.DS I 1i\n:\\|\\fBwrite\n.R\ntext: new file: 3 lines, 90 characters\n:\\|\\fBquit\\fR\n%\n.DE\nThe % is from \\s-2UNIX\\s0 to tell you that your session with\n.I ex\nis over and you may command \\s-2UNIX\\s0 further.\nSince we want\nto end the entire session at the terminal, we also need to\nexit from \\s-2UNIX\\s0.\nIn response to the \\s-2UNIX\\s0 prompt of ``\\|%\\|''\ntype the command\n.DS I 1i\n%\\|\\fBlogout\\fR\n.DE\nThis will end your session with \\s-2UNIX\\s0, and will ready the\nterminal for the next user.\nIt is always important to type \\fBlogout\\fR at the end of a session\nto make absolutely sure no one\ncould accidentally stumble into your abandoned\nsession and thus gain access to your files,\ntempting even the most honest of souls.\n.LP\nNote: ksh(1) users may have to type\n.B exit\nto end their session.\n.sp 1\n.PP\nThis is the end of the first session on \\s-2UNIX\\s0 text editing.\n.bp\n.TL\nSession 2\n.sp\n.PP\nLog in with \\s-2UNIX\\s0 as in the first session:\n.DS I 1i\nlogin: \\fBsusan\\fP  \\fI(carriage return)\\fR\nPassword:       \\fI(give password and carriage return)\\fR\n.if t .sp .2v\n.if n .sp 1\n\\&... A Message of General Interest ...\n%\n.DE\nWhen you indicate you want to edit,\nyou can specify the name of the file you worked on last time.\nThis will start\n.I ex\nworking, and it will fetch the contents of the\nfile into the buffer, so that you can resume editing the same file.\nWhen\n.I ex\nhas copied the file into the buffer, it\nwill repeat its name and report the current line number\n(generally the last line of the file).\nThus,\n.DS I 1i\n.B\n% ex text\n.R\ntext: unmodified: line 3\n:\n.DE\nmeans you asked\n.I ex\nto fetch the file named ``text'' for editing,\ncausing it to copy the text into the buffer.\n.I Ex\nawaits your further instructions,\nand indicates this by its prompt character, the colon (:).\nIn this session, we will append more text to our file,\nprint the contents of the buffer, and learn to change the text of a line.\n.SH\nAdding more text to the file\n.PP\nIf you want to add more to the end of your\ntext you may do so by using the\n.B append\ncommand to enter text input mode.\nWhen ``append'' is the first command\nof your editing session,\nthe lines you enter\nare placed at the end of the buffer.\nHere we'll use the abbreviation for the append command, `a':\n.DS I 1i\n:\\|\\fBa\nThis is text added in Session 2.\nIt doesn't mean much here, but\nit does illustrate the editor.\n\\|\\fB\\s+2\\&.\\s-2\n.R\n.DE\nYou may recall that once you enter append mode\nusing the `a' (or ``append'') command,\nyou need to type a line containing only a period (.)\nto exit append mode.\n.SH\nInterrupt\n.PP\nShould you press the `^C' key\nwhile working with\n.I ex ,\nit will send this message to you:\n.DS I 1i\nInterrupted\n:\n.DE\nAny command that\n.I ex\nmight be executing is terminated by `^C', causing\n.I ex\nto prompt you for a new command.\nIf you are appending text at the time,\nyou will exit from append mode\nand be expected to give another command.\nThe line of text you were typing\nwhen the append command was interrupted\nwill not be entered into the buffer.\n.SH\nMaking corrections\n.PP\nIf while typing the line you hit an incorrect key,\nrecall that\nyou may delete the incorrect character\nor cancel the entire line of input by erasing in the usual way.\nRefer either\nto the last few pages of Session 1\nif you need to review\nthe procedures for making a correction.\nThe most important idea to remember is that\nerasing a character or cancelling a line must be done\nbefore you press the \\s-2RETURN\\s+2 key.\n.SH\nListing what's in the buffer (p)\n.PP\nHaving appended text to what you wrote in Session 1,\nyou might want to see all the lines in the buffer.\nTo print the contents of the buffer, type the command:\n.DS I 1i\n:\\|\\fB1,$p\n.R\n.DE\nThe `1'\n.\\\" .FS\n.\\\" *The numeral ``one'' is the top left-most key,\n.\\\" and should not be confused with the letter ``el''.\n.\\\" .FE\nstands for line 1 of the buffer;\nthe `$' is a special symbol designating the last line\nof the buffer;\nand `p' is the print command.\nThus this command prints from line 1\nto the end of the buffer.\nThe command ``1,$p'' gives you:\n.DS I 1i\nThis is some sample text.\nAnd thiss is some more text.\nText editing is strange, but nice.\nThis is text added in Session 2.\nIt doesn't mean much here, but\nit does illustrate the editor.\n.DE\n.PP\nAdditionally, the percentage symbol (`%') may be used as a shorthand for `1,$'.\nThus the commands `%p' and `1,$p' are identical.\n.PP\nOccasionally, you may accidentally\ntype a character that can't be printed,\nwhich can be done by striking a key\nwhile the \\s-2CTRL\\s0 key is pressed.\nIn printing lines,\n.I ex\nuses a special notation to\nshow the existence of non-printing characters.\nSuppose you had introduced the non-printing character ``control-A''\ninto the word ``illustrate''\nby accidentally pressing the \\s-2CTRL\\s0 key while\ntyping `a'.\nThis can happen on many terminals\nbecause the \\s-2CTRL\\s+2 key and the `A' key\nare beside each other.\nIf your finger presses between the two keys,\ncontrol-A results.\nWhen asked to print the contents of the buffer,\nedit would display\n.DS I 1i\nit does illustr^Ate the editor.\n.DE\nTo represent the control-A,\n.I ex\nshows `^A'.\nThe sequence `^' followed by a capital\nletter stands for the one character\nentered by holding down the \\s-2CTRL\\s0 key and typing the letter\nwhich appears after the `^'.\nWe'll soon discuss the commands that can be used\nto correct this typing error.\n.PP\nIn looking over the text we see that\n``this'' is typed as ``thiss'' in the second line,\na deliberate error so we can learn to make corrections.\nLet's correct the spelling.\n.SH\nFinding things in the buffer\n.PP\nIn order to change something in the buffer we first need to\nfind it.\nWe can find ``thiss'' in the text we have\nentered by looking at a listing\nof the lines.\nPhysically speaking, we search the lines\nof text looking for ``thiss'' and stop searching when\nwe have found it.\nThe way to tell\n.I ex\nto search for something\nis to type it inside slash marks:\n.DS I 1i\n:\\|\\fB/thiss/\n.R\n.DE\nBy typing\n.B /thiss/\nand pressing \\s-1RETURN\\s0,\nyou instruct\n.I ex\nto search for ``thiss''.\nIf you ask\n.I ex\nto look for a pattern of characters\nwhich it cannot find in the buffer,\nit will respond ``Pattern not found''.\nWhen\n.I ex\nfinds\nthe characters ``thiss'', it will print the line of text\nfor your inspection:\n.DS I 1i\nAnd thiss is some more text.\n.DE\n.I Ex\nis now positioned in the buffer at the\nline it just printed,\nready to make a change in the line.\n.bp\n.SH\nThe current line\n.PP\n.I Ex\nkeeps track of the line in the buffer where it is located\nat all times during an editing session.\nIn general, the line that has been most recently\nprinted, entered, or changed\nis the current location in the buffer.\nThe editor is prepared to make changes\nat the current location in the buffer,\nunless you direct it to another location.\n.PP\nIn particular,\nwhen you bring a file into the buffer,\nyou will be located at the last line in the file,\nwhere the editor left off copying the lines\nfrom the file to the buffer.\nIf your first editing command is ``append'',\nthe lines you enter are added\nto the end of the file,\nafter the current line \\(em\nthe last line in the file.\n.PP\nYou can refer to your current location in the buffer by the\nsymbol\nperiod (.) usually known by the name ``dot''.\nIf you type `.' and carriage\nreturn you will be instructing\n.I ex\nto print the current line:\n.DS I 1i\n:\\|\\fB\\s+2\\&.\\s-2\n.R\nAnd thiss is some more text.\n.DE\n.PP\nIf you want to know the number of the current line,\nyou can type\n.B \\&.=\nand press \\s-2RETURN\\s+2,\nand\n.I ex\nwill respond with the line number:\n.DS I 1i\n:\\|\\fB\\s+2.\\s-2=\n.R\n2\n.DE\nIf you type the number of any line and press \\s-2RETURN\\s+2,\n.I ex\nwill position you at that line and\nprint its contents:\n.DS I 1i\n:\\|\\fB2\n.R\nAnd thiss is some more text.\n.DE\nYou should experiment with these commands\nto gain experience in using them to make changes.\n.SH\nNumbering lines (nu)\n.PP\nThe\n.B\nnumber (nu)\n.R\ncommand is similar to print,\ngiving both the number and the text of each printed line.\nTo see the number and the text of the current line type\n.DS I 1i\n:\\|\\fBnu\n.R\n\\0\\0\\0\\0\\02\\0\\0And thiss is some more text.\n.DE\nNote that the shortest abbreviation for the number command is\n``nu'' (and not `n', which is used for a different command).\nYou may specify a range of lines\nto be listed by the number command in the same way that lines\nare specified for print.\nFor example, \\f31,$nu\\f1 lists all lines in the buffer with their\ncorresponding line numbers.\n.SH\nSubstitute command (s)\n.PP\nNow that you have found the misspelled word,\nyou can change it from ``thiss'' to ``this''.\nAs far as\n.I ex\nis concerned,\nchanging things is a matter of\nsubstituting one thing for another.\nAs\n.I a\nstood for\n.I append ,\nso\n.I s\nstands for\n.I substitute .\nWe will use the abbreviation `s' to reduce the chance\nof mistyping the substitute command.\nThis command will instruct\n.I ex\nto make the change:\n.DS I 1i\n\\f32s/thiss/this/\\f1\n.DE\nWe first indicate the line to be changed, line 2,\nand then\ntype an `s' to indicate we want\n.I ex\nto make a substitution.\nInside the first set of slashes\nare the characters that we want to change,\nfollowed by the characters to replace them,\nand then a closing slash mark.\nTo summarize:\n.DS I 1i\n2s/ \\fIwhat is to be changed\\fR / \\fIwhat to change it to \\fR/\n.DE\nIf\n.I ex\nfinds an exact match of the characters to be\nchanged it will make the change\n.B only\nin the first occurrence of the characters.\nIf it does not find the characters\nto be changed, it will respond:\n.DS I 1i\nNo match found\n.DE\nindicating that your instructions could not be carried out.\nWhen\n.I ex\ndoes find the characters that you want to change,\nit will make the substitution and automatically print\nthe changed line, so that you can check that the correct substitution\nwas made.\nIn the example,\n.DS I 1i\n:\\|\\fB2s/thiss/this/\n.R\nAnd this is some more text.\n.DE\nline 2 (and line 2 only) will be searched for the characters\n``thiss'', and when the first exact match is found, ``thiss''\nwill be changed to ``this''.\nStrictly speaking, it was not necessary above to\nspecify  the number of the line to be changed.\nIn\n.DS I 1i\n:\\|\\fBs/thiss/this/\n.R\n.DE\n.I ex\nwill assume that we mean to change\nthe line where we are currently located (`.').\nIn this case,\nthe command without a line number would have produced the same result\nbecause we were already located\nat the line we wished to change.\n.PP\nFor another illustration of the substitute command,\nlet us choose the line:\n.DS I 1i\nText editing is strange, but nice.\n.DE\nYou can make this line a bit more positive\nby taking out the characters ``strange, but\\ '' so the line\nreads:\n.DS I 1i\nText editing is nice.\n.DE\nA command that will first position\n.I ex\nat the desired line\nand then make the substitution is:\n.DS I 1i\n:\\|\\fB/strange/s/strange, but //\n.R\n.DE\n.LP\nWhat we have done here is combine our search with\nour substitution.\nSuch combinations are perfectly legal,\nand speed up editing quite a bit\nonce you get used to them.\nThat is, you do not necessarily have to use\nline numbers to identify a line to\n.I ex .\nInstead, you may identify the line you want to change\nby asking\n.I ex\nto search for a specified pattern of letters\nthat occurs in that line.\nThe parts of the above command are:\n.TS\nl l.\n\\fB/strange/\\fP\ttells \\fIex\\fP to find the characters ``strange'' in the text\n\\fBs\\fP\ttells \\fIex\\fP to make a substitution\n\\fB/strange, but //\\fP\tsubstitutes nothing at all for the characters ``strange, but ''\n.TE\n.PP\nYou should note the space after ``but'' in ``/strange, but /''.\nIf you do not indicate that the space is to be taken out,\nyour line will read:\n.DS I 1i\n.if t Text editing is   nice.\n.if n Text editing is  nice.\n.DE\nwhich looks a little funny\nbecause of the extra space between ``is'' and ``nice''.\nAgain, we realize from this that a blank space\nis a real character to a computer, and in editing text\nwe need to be aware of spaces\nwithin a line just as we would be aware of an `a' or\na `4'.\n.SH\nAnother way to list what's in the buffer (z)\n.PP\nAlthough the print command is useful for looking at specific lines\nin the buffer,\nother commands may be more convenient for\nviewing large sections of text.\nYou can ask to see a screen full of text at a time\nby using the command\n.B z.\nIf you type\n.DS I 1i\n:\\|\\fB1z\n.R\n.DE\n.I ex\nwill start with line 1 and continue printing lines,\nstopping either when the screen of\nyour terminal is full\nor when the last line in the buffer has been printed.\nIf you want to read the next segment of text, type the command\n.DS I 1i\n:\\|\\fBz\n.DE\nIf no starting line number is given for the z command,\nprinting will start at the ``current'' line, in this case the\nlast line printed.\nViewing lines in the buffer one screen full at a time\nis known as \\fIpaging\\fR.\nPaging can also be used to print\na section of text on a hard-copy terminal.\n.SH\nSaving the modified text\n.PP\nThis seems to be a good place to pause in our work,\nand so we should end the second session.\nIf you (in haste) type `q' to quit the session\nyour dialogue with\n.I ex\nwill be:\n.DS\n:\\|\\fBq\n.R\nFile may be modified since last complete write; write or use ! to override\n:\n.DE\nThis is \\fIex\\fP's warning that you have not written\nthe modified contents of the buffer to disk.\nYou run the risk of losing the work you did\nduring the editing session since you typed the latest write\ncommand.\nBecause in this lesson we have not written\nto disk at all, everything we have done\nwould have been lost\nif\n.I ex\nhad obeyed the \\fBq\\fR command.\nIf you did not want to save the work done during\nthis editing session, you would have to type ``q!''\nor (``quit!'')\nto confirm that you indeed wanted to end the session\nimmediately,\nleaving the file as it was\nafter the most recent ``write'' command.\nHowever,\nsince you want to save what\nyou have edited, you need to type:\n.DS I 1i\n:\\|\\fBw\n.R\ntext: 6 lines, 171 characters\n.DE\nand then follow with the commands to quit and logout:\n.DS I 1i\n:\\|\\fBq\n% \\fBlogout\\fR\n.DE\nand hang up the phone or turn off the terminal when\n\\s-2UNIX\\s0 asks for a name.\nTerminals connected to the port selector\nwill stop after the logout command,\nand pressing keys on the keyboard will do nothing.\n.sp 1\n.PP\nThis is the end of the second session on \\s-2UNIX\\s0 text editing.\n.bp\n.TL\nSession 3\n.SH\nBringing text into the buffer (e)\n.PP\nLog in to \\s-2UNIX\\s0 and make contact with\n.I ex .\nYou should try to log in without\nlooking at the notes, but if you must\nthen by all means do.\n.PP\nDid you remember to give the name of the file\nyou wanted to edit?\nThat is, did you type\n.DS I 1i\n% \\fBex text\\fR\n.DE\nor simply\n.DS I 1i\n% \\fBex\\fR\n.DE\nBoth ways get you in contact with\n.I ex ,\nbut the first way\nwill bring a copy of the file named ``text'' into\nthe buffer.\nIf you did forget to tell\n.I ex\nthe name of your file,\nyou can get it into the buffer by\ntyping:\n.DS I 1i\n:\\|\\fBe text\n.R\ntext: unmodified: line 6\n.DE\nThe command\n.B edit ,\nwhich may be abbreviated \\fBe\\fR,\ntells\n.I ex\nthat you want\nto erase anything that might already be in\nthe buffer and bring a copy of the file ``text'' into the buffer\nfor editing.\nYou may also use the edit (e) command to change files in\nthe middle of an editing session,\nor to give\n.I ex\nthe name of a new file that you want to create.\nBecause the\n.B edit\ncommand clears the buffer,\nyou will receive a warning if you try to edit a new file without\nhaving saved a copy of the old file.\nThis gives you a chance to write the contents of the buffer to disk\nbefore editing the next file.\n.SH\nMoving text in the buffer (m)\n.PP\n.I Ex\nallows you to move lines of text\nfrom one location in the buffer to another\nby means of the\n.B move\n(\\fBm\\fR) command.\nThe first two examples are for illustration only,\nthough after you have read this Session\nyou are welcome to return to them for practice.\nThe command\n.DS I 1i\n:\\|\\fB2,4m$\n.R\n.DE\ndirects\n.I ex\nto move lines 2, 3, and 4\nto the end of the buffer ($).\nThe format for the move command is that you specify\nthe first line to be moved, the last line to be moved,\nthe move command `m', and the line after which\nthe moved text is to be placed.\nSo,\n.DS I 1i\n:\\|\\fB1,3m6\n.R\n.DE\nwould instruct\n.I ex\nto move lines 1 through 3 (inclusive)\nto a location after line 6 in the buffer.\nTo move only one line, say, line 4,\nto a location in the buffer after line 5,\nthe command would be ``4m5''.\n.PP\nLet's move some text using the command:\n.DS I 1i\n:\\|\\fB5,$m1\n.R\nit does illustrate the editor.\n.DE\nAfter executing the\n.B move\ncommand,\n.I ex\nprints the last moved line for your inspection.\nIf you want to see more than just the last line,\nyou can then\nuse the\n.B print\n(\\fBp\\fP),\n.B z ,\nor\n.B number\n(\\fBnu\\fP) commands to view more text.\nThe buffer should now contain:\n.DS I 1i\nThis is some sample text.\nIt doesn't mean much here, but\nit does illustrate the editor.\nAnd this is some more text.\nText editing is nice.\nThis is text added in Session 2.\n.DE\nYou can restore the original order by typing:\n.DS I 1i\n:\\|\\fB4,$m1\n.R\n.DE\nor, combining context searching and the move command:\n.DS I 1i\n:\\|\\fB/And this is some/,/This is text/m/This is some sample/\n.R\n.DE\n(Do not type both examples here!)\nThe problem with combining context searching\nwith the move command\nis that your chance of making a typing error\nin such a long command is greater than\nif you type line numbers.\n.SH\nCopying lines (copy)\n.PP\nThe\n.B copy\ncommand\nis used to make a second copy of specified lines,\nleaving the original lines where they were.\nCopy\nhas the same format as the move command, for example:\n.DS I 1i\n:\\|\\fB2,5copy $\n.R\n.DE\nmakes a copy of lines 2 through 5,\nplacing the added lines after the buffer's end ($).\nExperiment with the copy command\nso that you can become familiar with how it works.\nNote that the shortest abbreviation for copy is\n\\f3co\\f1 (and\nnot the letter `c', which has another meaning).\n.SH\nDeleting lines (d)\n.PP\nSuppose you want to delete\nthe line\n.DS I 1i\nThis is text added in Session 2.\n.DE\nfrom the buffer.\nIf you know the number of the line to be deleted,\nyou can type\nthat number followed by\n\\fBdelete\\fR or \\fBd\\fR.\nThis example deletes line 4,\nwhich is ``This is text added in Session 2.''\nif you typed the commands\nsuggested so far.\n.DS I 1i\n:\\|\\fB4d\n.R\nIt doesn't mean much here, but\n.DE\nHere `4' is the number of the line to be deleted,\nand ``delete'' or `d' is the command to delete the line.\nAfter executing the delete command,\n.I ex\nprints the line that has become the current line (`.').\n.PP\nIf you do not happen to know the line number\nyou can search for the line and then delete it using this\nsequence of commands:\n.DS I 1i\n:\\|\\fB/added in Session 2./\n.R\nThis is text added in Session 2.\n:\\|\\fBd\n.R\nIt doesn't mean much here, but\n.DE\nThe ``/added in Session 2./''\nasks\n.I ex\nto locate and print\nthe line containing the indicated text,\nstarting its search at the current line\nand moving line by line\nuntil it finds the text.\nOnce you are sure that you have correctly specified the line\nyou want to delete,\nyou can enter the\n.B delete\n(\\fBd\\fP) command.\nIn this case it is not necessary to\nspecify a line number before the `d'.\nIf no line number is given,\n.I ex\ndeletes the current line (`.'),\nthat is, the line found by our search.\nAfter the deletion, your buffer should contain:\n.DS I 1i\nThis is some sample text.\nAnd this is some more text.\nText editing is nice.\nIt doesn't mean much here, but\nit does illustrate the editor.\nAnd this is some more text.\nText editing is nice.\nThis is text added in Session 2.\nIt doesn't mean much here, but\n.DE\nTo delete both lines 2 and 3:\n.DS I 1i\nAnd this is some more text.\nText editing is nice.\n.DE\nyou type\n.DS I 1i\n:\\|\\f32,3d\\f1\n.DE\nwhich specifies the range of lines from 2 to 3,\nand the operation on those lines \\(em `d' for delete.\n.PP\nThe previous example assumes that you know the line numbers for\nthe lines to be deleted.\nIf you do not you might combine the search command\nwith the delete command:\n.DS I 1i\n:\\|\\fB/And this is some/,/Text editing is nice./d\n.R\n.DE\n.SH\nA word or two of caution\n.PP\nIn using the search function to locate lines to\nbe deleted you should be\n.B\nabsolutely sure\n.R\nthe characters you give as the basis for the search\nwill take\n.I ex\nto the line you want deleted.\n.I Ex\nwill search for the first\noccurrence of the characters starting from where\nyou last edited \\-\nthat is, from the line you see printed if you type dot (.).\n.PP\nA search based on too few\ncharacters may result in the wrong lines being deleted,\nwhich\n.I ex\nwill do as easily as if you had meant it.\nFor this reason, it is usually safer\nto specify the search and then delete in two separate steps,\nat least until you become familiar enough with using the editor\nthat you understand how best to specify searches.\nFor a beginner it is not a bad idea to double-check\neach command before pressing \\s-2RETURN\\s+2 to send the command on its way.\n.SH\nUndo (u) to the rescue\n.PP\nThe\n.B undo\n(\\fBu\\fP)\ncommand has the ability to\nreverse the effects of the last command that changed the buffer.\nTo undo the previous command, type\n`u' or ``undo''.\nUndo can rescue\nthe contents of the buffer from many an unfortunate mistake.\nHowever, its powers are not unlimited,\nso it is still wise to be reasonably\ncareful about the commands you give.\n.PP\nIt is possible to undo only commands which\nhave the power to change the buffer \\(em for example,\n.B delete ,\n.B append ,\n.B move ,\n.B copy ,\n.B substitute ,\nand even\n.B undo\nitself.\nThe commands\n.B write\n(\\fBw\\fP) and\n.B edit\n(\\fBe\\fP), which interact with disk files,\ncannot be undone, nor can commands that do not change\nthe buffer, such as print.\nMost importantly,\nthe\n.B only\ncommand that can be reversed by undo\nis the\nlast ``undo-able'' command you typed.\n.PP\nTo illustrate,\nlet's issue an\n.B undo\ncommand.\nRecall that the last buffer-changing command we gave deleted\nthe lines formerly numbered 2 and 3.\nTyping\n.B undo\nat this moment will reverse the effects\nof the deletion, causing those two lines to be\nreplaced in the buffer.\n.DS I 1i\n:\\|\\fBu\n.R\nAnd this is some more text.\n.DE\nHere again,\n.I ex\nprints the text of the line which is now ``dot'' (the current line).\n.SH\nMore about the dot (.) and buffer end ($)\n.PP\nThe function assumed by the symbol dot depends on its context.\nIt can be used:\n.IP\n1.  to exit from append mode; we type dot (and only a dot) on\na line and press \\s-2RETURN\\s+2;\n.IP\n2.  to refer to the line we are at in the buffer.\n.LP\nDot can also be combined with the equal sign to get\nthe number of the line currently being edited:\n.DS I 1i\n:\\|\\fB\\&.=\n.R\n.DE\nIf we type `\\fB.\\fR=' we are asking for the number of the line,\nand if we type `\\fB.\\fR' we are asking for the text of the line.\n.PP\nIn this editing session and the last, we used the dollar\nsign to indicate the end of the buffer\nin commands such as\n.B print ,\n.B copy ,\nand\n.B move .\nThe dollar sign as a command asks edit to print the last\nline in the buffer.\nIf the dollar sign is combined with the equal sign (\\f3$=\\f1)\nedit will print the line number corresponding to the\nlast line in the buffer.\n.PP\n`\\fB.\\fR' and `$', then, represent line numbers.\nWhenever appropriate, these symbols can be used in\nplace of line numbers in commands.\nFor example\n.DS I 1i\n:\\|\\fB\\s+2.\\s-2,$d\n.R\n.DE\ninstructs edit to delete all lines from the current line (\\fB.\\fR)\nto the end of the buffer.\n.SH\nMoving around in the buffer  (+ and \\-)\n.PP\nWhen you are editing\nyou often want\nto go back and re-read a previous line.\nYou could specify a context search for a line you want to\nread if you remember some of its text,\nbut if you simply want to see what was written a few, say 3, lines\nago, you can type\n.DS I 1i\n.B \\-3p\n.DE\nThis tells\n.I ex\nto move back to a position 3 lines\nbefore the current line (.)\nand print that line.\nYou can move forward in the buffer similarly:\n.DS I 1i\n.B +2p\n.DE\ninstructs\n.I ex\nto print the line that is 2\nahead of your current position.\n.PP\nYou may use `+' and `\\-' in any command where edit\naccepts line numbers.\nLine numbers specified with `+' or `\\-'\ncan be combined to print a range of lines.\nThe command\n.DS I 1i\n:\\|\\fB\\-1,+2copy$\n.R\n.DE\nmakes a copy of 4 lines:  the current line, the line before it,\nand the two after it.\nThe copied lines will be placed after the last line\nin the buffer ($),\nand the original lines referred to by `\\-1' and `+2'\nremain where they are.\n.PP\nTry typing only `\\-'; you will move back one line just as\nif you had typed `\\-1p'.\nTyping the command `+' works similarly.\nYou might also try typing a few plus or minus signs in a row\n(such as ``+++'') to see \\fIex\\fP's response.\nTyping \\s-2RETURN\\s+2 alone on a line is the equivalent\nof typing ``+1p''; it will move you one line ahead in the buffer\nand print that line.\n.PP\nIf you are at the last line of the buffer and try\nto move further ahead, perhaps by typing a `+' or\na carriage return alone on the line,\n.I ex\nwill remind you that you are at the end of the buffer:\n.sp\n.nf\n.ti 1i\nIllegal address: only 6 lines in the file\n.fi\n.LP\nSimilarly, if you try to move to a position before the first line,\n.I ex\nwill print a message similar to:\n.sp\n.nf\n.ti 1i\nThe print command doesn't permit an address of 0\n.fi\n.LP\nThe number associated with a buffer line is the line's ``address'',\nin that it can be used to locate the line.\n.SH\nChanging lines (c)\n.PP\nYou can also delete certain lines and\ninsert new text in their place.\nThis can be accomplished easily with the\n.B change\n(\\fBc\\fP)\ncommand.\nThe change command instructs\n.I ex\nto delete specified lines\nand then switch to text input mode to\naccept the text that will replace them.\nLet's say you want to change the first two lines in the buffer:\n.DS I 1i\nThis is some sample text.\nAnd this is some more text.\n.DE\nto read\n.DS I 1i\nThis text was created with the \\s-2UNIX\\s0 text editor.\n.DE\nTo do so, you type:\n.DS I 1i\n:\\|\\fB1,2c\nThis text was created with the \\s-2UNIX\\s0 text editor.\n\\s+2\\&.\\s-2\n.R\n:\n.DE\nIn the command\n.B 1,2c\nwe specify that we want to change\nthe range of lines beginning with 1 and ending with 2\nby giving line numbers as with the\n.B print\ncommand.\nThese lines will be deleted.\nAny text typed on the following lines will be inserted into\nthe position where lines were deleted by the change command.\n.B\nYou will remain in text input mode until you exit in the usual way,\nby typing a period alone on a line.\n.R\nNote that the number of lines added to the buffer need not be\nthe same as the number of lines deleted.\n.sp 1\n.PP\nThis is the end of the third session on text editing with \\s-2UNIX\\s0.\n.bp\n.SH\n.ce 1\n\\s+2Session 4\\s0\n.sp\n.PP\nThis lesson covers several topics, starting with\ncommands that apply throughout the buffer,\ncharacters with special meanings,\nand how to issue \\s-2UNIX\\s0 commands while in the editor.\nThe next topics deal with files:\nmore on reading and writing,\nand methods of recovering files lost in a crash.\nThe final section suggests sources of further information.\n.SH\nMaking commands global (g)\n.PP\nOne disadvantage to the commands we have used for\nsearching or substituting is that if you\nhave a number of instances of a word to change\nit appears that you have to type the command\nrepeatedly, once for\neach time the change needs to be made.\n.I Ex\nhowever, provides a way to make commands\napply to the entire contents of the buffer \\-\nthe\n.B global\n(\\fBg\\fP)\ncommand.\n.PP\nTo print all lines\ncontaining a certain sequence of characters\n(say, ``text'')\nthe command is:\n.DS I 1i\n:\\|\\fBg/text/p\n.R\n.DE\nThe `g' instructs\n.I ex\nto\nmake a global search for all lines\nin the buffer containing the characters  ``text''.\nThe `p' prints the lines found.\n.PP\nTo issue a global command, start by typing a `g' and then a search\npattern identifying\nthe lines to be affected.\nThen, on the same line, type the command to be\nexecuted for the identified lines.\nGlobal substitutions are frequently useful.\nFor example,\nto change all instances of the word ``text'' to the word ``material''\nthe command would be a combination of the global search and the\nsubstitute command:\n.DS I 1i\n:\\|\\fBg/text/s/text/material/g\n.R\n.DE\nNote the `g' at the end of the global command,\nwhich instructs edit to change\neach and every instance of ``text'' to ``material''.\nIf you do not type the `g' at the end of the command\nonly the\n.I first\ninstance of ``text'' \\fIin each line\\fR will be changed\n(the normal result of the substitute command).\nThe `g' at the end of the command is independent of the `g'\nat the beginning.\nYou may give a command such as:\n.DS I 1i\n:\\|\\fB5s/text/material/g\n.R\n.DE\nto change every instance of ``text'' in line 5 alone.\nFurther, neither command will change ``text'' to ``material''\nif ``Text'' begins with a capital rather than a lower-case `t'.\n.PP\n.I Ex\ndoes not automatically print the lines modified by a\nglobal command.\nIf you want the lines to be printed, type a `p'\nat the end of the global command:\n.DS I 1i\n:\\|\\fBg/text/s/text/material/gp\n.R\n.DE\nYou should be careful\nabout using the global command in combination with any other \\-\nin essence, be sure of what you are telling edit to do\nto the entire buffer.\nFor example,\n.DS I 1i\n:\\|\\fBg/ /d\n.DE\nwill delete every line containing a blank anywhere in it.\nThis could adversely affect\nyour document, since most lines have spaces between words\nand thus would be deleted.\nFortunately, the\n.B undo\ncommand can reverse the effects of a\n.B global\ncommand.\nYou should experiment with the\n.B global\ncommand on a small file of text to see what it can do for you.\n.SH\nMore about searching and substituting\n.PP\nIn using slashes to identify a character string\nthat we want to search for or change,\nwe have always specified the exact characters.\nThere is a less tedious way to\nrepeat the same string of characters.\nTo change ``text'' to ``texts'' we may type either\n.DS I 1i\n:\\|\\fB/text/s/text/texts/\n.R\n.DE\nas we have done in the past,\nor a somewhat abbreviated command:\n.DS I 1i\n:\\|\\fB/text/s//texts/\n.R\n.DE\nIn this example, the characters to be changed\nare not specified \\-\nthere are no characters, not even a space,\nbetween the two slash marks\nthat indicate what is to be changed.\nThis lack of characters between the slashes\nis taken by the editor to mean\n``use the characters we last searched for as the characters to be changed.''\n.PP\nSimilarly, the last context search may be repeated\nby typing a pair of slashes with nothing between them:\n.DS I 1i\n:\\|\\fB/does/\n.R\nIt doesn't mean much here, but\n:\\|\\fB//\n.R\nit does illustrate the editor.\n.DE\n(You should note that the search command found the characters ``does''\nin the word ``doesn't'' in the first search request.)\nBecause no characters are specified for the second search,\nthe editor scans the buffer for the next occurrence of the\ncharacters ``does''.\n.PP\n.I Ex\nnormally searches forward through the buffer,\nwrapping around from the end of the buffer to the beginning,\nuntil the specified character string is found.\nIf you want to search in the reverse direction,\nuse question marks (`?') instead of slashes\nto surround the characters you are searching for.\n.PP\nIt is also possible\nto repeat the last substitution\nwithout having to retype the entire command.\nAn ampersand (`&') used as a command\nrepeats the most recent substitute command,\nusing the same search and replacement patterns.\nAfter altering the current line by typing\n.DS I 1i\n:\\|\\fBs/text/texts/\n.R\n.DE\nyou type\n.DS I 1i\n:\\|\\fB/text/&\n.R\n.DE\nor simply\n.DS I 1i\n:\\|\\fB//&\n.R\n.DE\nto make the same change on the next line in the buffer\ncontaining the characters ``text''.\n.SH\nSpecial characters\n.PP\nSome characters have special meanings when\nused in specifying searches.\n`$' is taken by the editor to mean ``end of the line''\nand is used to identify strings\nthat occur at the end of a line.\n.DS I 1i\n:\\|\\fBg/text.$/s//material./p\n.R\n.DE\ntells the editor to search for all lines ending in ``text.''\n(and nothing else, not even a blank space),\nto change each final ``text.'' to ``material.'',\nand print the changed lines.\n.PP\nThe symbol `^' indicates the beginning of a line.\nThus,\n.DS I 1i\n:\\|\\fBs/^/1. /\n.R\n.DE\ninstructs the editor to insert ``1.'' and a space at the beginning\nof the current line.\n.PP\nThe characters `$' and `^' have special meanings only in the context\nof searching.\nAt other times, they are ordinary characters.\nIf you ever need to search for a character that has a special meaning,\nyou must indicate that the\ncharacter is to lose temporarily\nits special significance by typing another special character,\nthe backslash (\\\\), before it.\n.DS I 1i\n:\\|\\fBs/\\\\\\\\\\&$/dollar/\n.R\n.DE\nlooks for the character `$' in the current\nline and replaces it by the word ``dollar''.\nWere it not for the backslash, the `$' would have represented\n``the end of the line'' in your search\nrather than the character `$'.\nThe backslash retains its special significance\nunless it is preceded by another backslash.\n.LP\nFor a complete list of special characters, see the\n\"Ex Reference Manual\", /usr/share/doc/usd/13.ex/.\n.SH\nIssuing \\s-2UNIX\\s0 commands from the editor\n.PP\nAfter creating several files with the editor,\nyou may want to delete files\nno longer useful to you or ask for a list of your files.\nRemoving and listing files are not functions of the editor,\nand so they require the use of \\s-2UNIX\\s0 system commands\n(also referred to as ``shell'' commands, as\n``shell'' is the name of the program that processes \\s-2UNIX\\s0 commands).\nYou do not need to quit the editor to execute a \\s-2UNIX\\s0 command\nas long as you indicate that it\nis to be sent to the shell for execution.\nTo use the \\s-2UNIX\\s0 command\n.B rm\nto remove the file named ``junk'' type:\n.DS I 1i\n:\\|\\fB!rm junk\n.R\n!\n:\n.DE\nThe exclamation mark (`!')\nindicates that the rest of the line is to be processed as a shell command.\nIf the buffer contents have not been written since the last change,\na warning will be printed before the command is executed:\n.DS I 1i\nFile may be modified since last write.\n.DE\nThe editor prints a `!' when the command is completed.\nOther tutorials describe useful features of the system,\nof which an editor is only one part.\n.SH\nFilenames and file manipulation\n.PP\nThroughout each editing session,\n.I ex\nkeeps track of the name of the file being edited as the\n.I \"current filename\" .\n.I Ex\nremembers as the current filename the name given\nwhen you entered the editor.\nThe current filename changes whenever the\n.B edit\n(\\fBe\\fP) command\nis used to specify a new file.\nOnce\n.I ex\nhas recorded a current filename,\nit inserts that name into any command where a filename has been omitted.\nIf a write command does not specify a file,\n.I ex\nas we have seen, supplies the current filename.\nIf you are editing a file named ``draft3'' having 283 lines in it,\nyou can have the editor write onto a different file\nby including its name in the write command:\n.DS I 1i\n:\\fB\\|w chapter3\n.R\nchapter3: new file: 283 lines, 8698 characters\n.DE\nThe current filename remembered by the editor\n.I\nwill not be changed as a result of the write command.\n.R\nThus, if the next write command\ndoes not specify a name,\nedit will write onto the current file (``draft3'')\nand not onto the file ``chapter3''.\n.SH\nThe file (f) command\n.PP\nTo ask for the current filename, type\n.B file\n(or\n.B f ).\nIn response, the editor provides current information about the buffer,\nincluding the filename, your current position, the number of\nlines in the buffer,\nand the percent of the distance through the file\nyour current location is.\n.DS I 1i\n:\\|\\fBf\n.R\ntext: modified: line 3 of 4 [75%]\n.DE\n.\\\"The expression ``[Edited]'' indicates that the buffer contains\n.\\\"either the editor's copy of the existing file ``text''\n.\\\"or a file which you are just now creating.\nIf the contents of the buffer have changed\nsince the last time the file was written,\nthe editor will tell you that the file has been ``modified:''.\nAfter you save the changes by writing onto a disk file,\nthe buffer will no longer be considered modified:\n.DS I 1i\n:\\|\\fBw\n.R\ntext: 4 lines, 88 characters\n:\\|\\fBf\n.R\ntext: unmodified: line 3 of 4 [75%]\n.DE\n.SH\nReading additional files (r)\n.PP\nThe\n.B read\n(\\fBr\\fP) command allows you to add the contents of a file\nto the buffer\nat a specified location,\nessentially copying new lines\nbetween two existing lines.\nTo use it, specify the line after which the new text will be placed,\nthe\n.B read\n(\\fBr\\fP) command,\nand then the name of the file.\nIf you have a file named ``example'', the command\n.DS I 1i\n:\\|\\fB$r example\n.R\nexample: 18 lines, 473 characters\n.DE\nreads the file ``example''\nand adds it to the buffer after the last line.\nThe current filename is not changed by the read command.\n.SH\nWriting parts of the buffer\n.PP\nThe\n.B write\n(\\fBw\\fP)\n.R\ncommand can write all or part of the buffer\nto a file you specify.\nWe are already familiar with\nwriting the entire contents of the\nbuffer to a disk file.\nTo write only part of the buffer onto a file,\nindicate the beginning and ending lines before the write command,\nfor example\n.DS I 1i\n:\\|\\fB45,$w ending\n.R\n.DE\nHere all lines from 45 through the end of the buffer\nare written onto the file named\n.I ending .\nThe lines remain in the buffer\nas part of the document you are editing,\nand you may continue to edit the entire buffer.\nYour original file is unaffected\nby your command to write part of the buffer\nto another file.\nEdit still remembers whether you have saved changes to the buffer\nin your original file or not.\n.SH\nRecovering files\n.PP\nAlthough it does not happen very often,\nthere are times \\s-2UNIX\\s+2 stops working\nbecause of some malfunction.\nThis situation is known as a \\fIcrash\\fR.\nUnder most circumstances,\n.I ex 's\ncrash recovery feature\nis able to save work to within a few lines of changes\nbefore a crash (or a remote connection timeout).\nIf you lose the contents of an editing buffer in a system crash,\nyou will normally receive mail when you log in that gives\nthe name of the recovered file.\nTo recover the file,\nenter the editor and type the command\n.B recover\n(\\fBrec\\fR),\nfollowed by the name of the lost file.\nFor example,\nto recover the buffer for an editing session\ninvolving the file ``chap6'', the command is:\n.DS I 1i\n.R\n:\\|\\fBrecover chap6\n.R\n.DE\nRecover is sometimes unable to save the entire buffer successfully,\nso always check the contents of the saved buffer carefully\nbefore writing it back onto the original file.\nFor best results,\nwrite the buffer to a new file temporarily\nso you can examine it without risk to the original file.\nUnfortunately,\nyou cannot use the recover command\nto retrieve a file you removed\nusing the shell command \\f3rm\\f1.\n.SH\nOther recovery techniques\n.PP\nIf something goes wrong when you are using the editor,\nit may be possible to save your work by using the command\n.B preserve\n(\\fBpre\\fR),\nwhich saves the buffer as if the system had crashed.\nIf you are writing a file and you get the message\n``Quota exceeded'', you have tried to use more disk storage\nthan is allotted to your account.\n.I\nProceed with caution\n.R\nbecause it is likely that only a part\nof the editor's buffer is now present in the file you tried to write.\nIn this case you should use the shell escape from the editor (!)\nto remove some files you don't need and try to write\nthe file again.\nIf this is not possible and you cannot find someone to help you,\nenter the command\n.DS I 1i\n:\\|\\fBpreserve\n.R\n.DE\nand wait for the reply,\n.DS I 1i\nFile preserved\n.DE\nIf you do not receive this reply,\nseek help immediately.\nDo not simply leave the editor.\nIf you do, the buffer will be lost,\nand you may not be able to save your file.\nIf the reply is ``File preserved''\nyou can leave the editor\n(or log out)\nto remedy the situation.\nAfter a\n.B preserve ,\nyou can use the\n.B recover\ncommand\nonce the problem has been corrected,\nor the \\fB\\-r\\fR option of the\n.I ex\ncommand\nif you leave the editor and want to return.\n.PP\nIf you make an undesirable change to the buffer\nand type a write command before discovering your mistake,\nthe modified version will replace any previous version of the file.\nShould you ever lose a good version of a document in this way,\ndo not panic and leave the editor.\nAs long as you stay in the editor,\nthe contents of the buffer remain accessible.\nDepending on the nature of the problem,\nit may be possible\nto restore the buffer to a more complete\nstate with the\n.B undo\ncommand.\nAfter fixing the damaged buffer, you can again write the file\nto disk.\n.SH\nOptions, set, and editor startup files\n.PP\nThe editor has a set of options, some of which have been mentioned above.\nThe most useful options are given in the following table.\n.PP\nThe options are of three kinds:  numeric options, string options, and\ntoggle options.  You can set numeric and string options by a statement\nof the form\n.DS\n\\fBset\\fR \\fIopt\\fR\\fB=\\fR\\fIval\\fR\n.DE\nand toggle options can be set or unset by statements of one of the forms\n.DS\n\\fBset\\fR \\fIopt\\fR\n\\fBset\\fR \\fBno\\fR\\fIopt\\fR\n.DE\n.KF\n.TS\nlb lb lb lb\nl l l a.\nName\tDefault\tDescription\n_\nautoindent\tnoai\tSupply indentation automatically\nautowrite\tnoaw\tAutomatic write before \\fB:n\\fR, \\fB:ta\\fR, \\fB^^\\fR, \\fB!\\fR\nignorecase\tnoic\tIgnore case in searching\n.\\\" lisp\tnolisp\t\\fB( { ) }\\fR commands deal with S-expressions\nlist\tnolist\tTabs print as ^I; end of lines marked $\nmagic\tmagic\t. [ and * are special in scans\nnumber\tnonu\tLines prefixed with line numbers\nreport\treport=5\tNumber of line which editor reports after changes.\nshiftwidth\tsw=8\tShift distance for <, > and \\fB^D\\fP and \\fB^T\\fR\nterm\t$TERM\tThe kind of terminal you are using.\n.TE\n.KE\nThese statements can be placed in your EXINIT in your environment,\nor given while you are running\n.I ex\nby typing them at the \\fB:\\fR prompt and following them with a \\s-2CR\\s0.\n.PP\nYou can get a list of all options which you have changed by the\ncommand \\fB:set\\fR\\s-2CR\\s0, or the value of a single option by the\ncommand \\fB:set\\fR \\fIopt\\fR\\fB?\\fR\\s-2CR\\s0.\nA list of all possible options and their values is generated by\n\\fB:set all\\fP\\s-2CR\\s0.\nSet can be abbreviated \\fBse\\fP.\nMultiple options can be placed on one line, e.g.\n\\fB:se ai aw nu\\fP\\s-2CR\\s0.\n.PP\nOptions set by the \\fBset\\fP command only last\nwhile you stay in the editor.\nIt is common to want to have certain options set whenever you\nuse the editor.\nThis can be accomplished by creating a list of commands\nwhich are to be run every time you start up \\fIex\\fP.\nFor example:\n.DS\n\\fBset\\fP ai ic report=1\n.DE\nsets the options \\fIautoindent\\fP, \\fIignorecase\\fP, and \\fIreport\\fP\n(to the value of 1).\nThis string should be placed in the variable EXINIT in your environment.\nIf you use the shell \\fIcsh\\fP,\nput this line in the file\n.I .login\nin your home directory:\n.DS I\nsetenv EXINIT 'set ai ic report=1'\n.DE\nIf you use the standard shell \\fIsh\\fP,\nput these lines in the file\n.I .profile\nin your home directory:\n.DS\nexport EXINIT='set ai ic report=1'\n.DE\nOf course, the particulars of the line would depend on which options\nyou wanted to set.\n.SH\nFurther reading and other information\n.PP\nThese lessons are intended to introduce you to the editor\nand its more commonly-used commands.\nWe have not covered all of the editor's commands,\nbut a selection of commands\nthat should be sufficient to accomplish most of your editing tasks.\nYou can find out more about the editor in the\n.I\nEx Reference Manual.\n.R\nOne way to become familiar with the manual is to begin by reading\nthe description of commands that you already know.\n.bd I 3\n.SH\n.ce 1\n\\s+2Index\\s0\n.LP\n.sp 2\n.2C\n.nf\naddressing, \\fIsee\\fR line numbers\nampersand, 20\nappend mode, 6-7\nappend (a) command, 6, 7, 9\n``At end of file'' (message), 18\nbackslash (\\\\), 21\nbuffer, 3\ncaret (^), 10, 20\nchange (c) command, 18\ncommand mode, 5-6\n``Command not found'' (message), 6\ncontext search, 10-12, 19-21\ncontrol characters (`^' notation), 10\ncontrol-H, 7\ncopy (co) command, 15\ncorrections, 7, 16\ncurrent filename, 21\ncurrent line (\\|.\\|), 11, 17\ndelete (d) command, 15-16\ndial-up, 5\ndisk, 3\ndocumentation, 3, 23\ndollar ($), 10, 11, 17, 20-21\ndot (\\f3\\|.\\|\\f1) 11, 17\nedit (text editor), 3, 5, 23\nedit (e) command, 5, 9, 14\nediting commands:\n.in +.25i\nappend (a), 6, 7, 9\nchange (c), 18\ncopy (co), 15\ndelete (d), 15-16\nedit (text editor), 3, 5, 23\nedit (e), 5, 9, 14\nfile (f), 21-22\nglobal (g), 19\nmove (m), 14-15\nnumber (nu), 11\npreserve (pre), 22-23\nprint (p), 10\nquit (q), 8, 13\nread (r), 22\nrecover (rec), 22, 23\nsubstitute (s), 11-12, 19, 20\nundo (u), 16-17, 23\nwrite (w), 8, 13, 21, 22\nz, 12-13\n! (shell escape), 21\n$=, 17\n+, 17\n\\-, 17\n//, 12, 20\n??, 20\n\\&., 11, 17\n\\&.=, 11, 17\n.in -.25i\nentering text, 3, 6-7\nerasing\n.in +.25i\ncharacters (^H), 7\nlines (@), 7\n.in -.25i\nerror corrections, 7, 16\nex (text editor), 23\n\\fIEx Reference Manual\\fR, 23\nexclamation (!), 21\nfile, 3\nfile (f) command, 21-22\nfile recovery, 22-23\nfilename, 3, 21\nglobal (g) command, 19\ninput mode, 6-7\nInterrupt (message), 9\nline numbers, \\fIsee also\\fR current line\n.in +.25i\ndollar sign ($), 10, 11, 17\ndot (\\|.\\|), 11, 17\nrelative (+ and \\-), 17\n.in -.25i\nlist, 10\nlogging in, 4-6\nlogging out, 8\n``Login incorrect'' (message), 5\nminus (\\-), 17\nmove (m) command, 14-15\n``Negative address\\(emfirst buffer line is 1'' (message), 18\n``No current filename'' (message), 8\n``No such file or directory'' (message), 5, 6\n``No write since last change'' (message), 21\nnon-printing characters, 10\n``Nonzero address required'' (message), 18\n``Not an editor command'' (message), 6\n``Not that many lines in buffer'' (message), 18\nnumber (nu) command, 11\noptions, 22\npassword, 5\nperiod (\\|.\\|), 11, 17\nplus (+), 17\npreserve (pre) command, 22-23\nprint (p) command, 10\nprogram, 3\nprompts\n.in .25i\n% (\\s-2UNIX\\s0), 5\n: (edit), 5, 6, 7\n\\0 (append), 7\n.in -.25i\nquestion (?), 20\nquit (q) command, 8, 13\nread (r) command, 22\nrecover (rec) command, 22, 23\nrecovery, \\fIsee\\fR\\| file recovery\nreferences, 3, 23\nremove (rm) command, 21, 22\nreverse command effects (undo), 16-17, 23\nsearching, 10-12, 19-21\nshell, 21\nshell escape (!), 21\nslash (/), 11-12, 20\nspecial characters (^, $, \\\\), 10, 11, 17, 20-21\nsubstitute (s) command, 11-12, 19, 20\nterminals, 4-5\ntext input mode, 7\nundo (u) command, 16-17, 23\n\\s-1UNIX\\s0, 3\nwrite (w) command, 8, 13, 21, 22\nz command, 12-13\n"
  },
  {
    "path": "docs/USD.doc/exref/ex.rm",
    "content": ".\\\"        $OpenBSD: ex.rm,v 1.9 2022/12/26 19:16:03 jmc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)ex.rm        8.5 (Berkeley) 8/18/96\n.\\\"\n.if n \\{\\\n.po 5n\n.ll 70n\n.\\}\n.nr LL 6.5i\n.nr FL 6.5i\n.EH 'USD:12-%''Ex Reference Manual'\n.OH 'Ex Reference Manual''USD:12-%'\n.nr )P 0\n.de ZP\n.nr pd \\\\n()P\n.nr )P 0\n.if \\\\n(.$=0 .IP\n.if \\\\n(.$=1 .IP \"\\\\$1\"\n.if \\\\n(.$>=2 .IP \"\\\\$1\" \"\\\\$2\"\n.nr )P \\\\n(pd\n.rm pd\n..\n.de LC\n.br\n.sp .1i\n.ne 4\n.LP\n.ta 4.0i\n..\n.\\\" .bd S B 3\n.\\\".RP\n.TL\nEx Reference Manual\n.br\nVersion 3.7\n.AU\nWilliam Joy\n.AU\nMark Horton\n.AI\nComputer Science Division\nDepartment of Electrical Engineering and Computer Science\nUniversity of California, Berkeley\nBerkeley, Ca.  94720\n.AB\n.I Ex\nis a line oriented text editor, which supports both command and display\noriented editing.\nThis reference manual describes the command oriented part of\n.I ex ;\nthe display editing features of\n.I ex\nare described in\n.I \"An Introduction to Display Editing with Vi\" .\nOther documents about the editor include the introduction\n.I \"Edit: A tutorial\",\nthe\n.I \"Ex/edit Command Summary\",\nand a\n.I \"Vi Quick Reference\"\ncard.\n.AE\n.NH 1\nStarting ex\n.PP\n.FS\nThe financial support of an \\s-2IBM\\s0 Graduate Fellowship and the National\nScience Foundation under grants MCS74-07644-A03 and MCS78-07291 is gratefully\nacknowledged.\n.FE\nEach instance of the editor has a set of options,\nwhich can be set to tailor it to your liking.\n.\\\" The command\n.\\\" .I edit\n.\\\" invokes a version of\n.\\\" .I ex\n.\\\" designed for more casual or beginning\n.\\\" users by changing the default settings of some of these options.\n.\\\" To simplify the description which follows we\n.\\\" assume the default settings of the options.\n.PP\nWhen invoked,\n.I ex\ndetermines the terminal type from the \\s-2TERM\\s0 variable in the environment.\n.\\\" If there is a \\s-2TERMCAP\\s0 variable in the environment, and the type\n.\\\" of the terminal described there matches the \\s-2TERM\\s0 variable,\n.\\\" then that description\n.\\\" is used.  Also if the \\s-2TERMCAP\\s0 variable contains a pathname (beginning\n.\\\" with a \\fB/\\fR) then the editor will seek the description of the terminal\n.\\\" in that file (rather than the default /etc/termcap).\nIf there is a variable \\s-2NEXINIT\\s0 in the environment, then the editor\nwill execute the commands in that variable,\notherwise if there is a variable \\s-2EXINIT\\s0 in the environment,\nthen the editor will execute the commands in that variable.\nIf there is a file\n.I \\&.nexrc\nin your \\s-2HOME\\s0 directory\n.I ex\nreads commands from that file, simulating a\n.I source\ncommand.\nOtherwise, if there is a file\n.I \\&.exrc\nin your \\s-2HOME\\s0 directory,\n.I ex\nwill read that.\nAdditionally,\n.I ex\nwill read startup commands from the current working directory,\nif they are placed in the files\n.I \\&.nexrc\nor\n.I \\&.exrc .\nOption setting commands placed in\n\\s-2NEXINIT\\s0, \\s-2EXINIT\\s0,\n.I \\&.nexrc ,\nor\n.I \\&.exrc\nwill be executed before each editor session.\n.PP\nA command to enter\n.I ex\nhas the following prototype\n(brackets `[' `]' surround optional parameters here):\n.DS\n\\fBex\\fP [\\fB\\-\\fP] [\\fB\\-FRrSsv\\fP] [\\fB\\-c\\fP \\fIcmd\\fP] [\\fB\\-t\\fP \\fItag\\fP] [\\fB\\-w\\fP \\fIsize\\fP] [\\fB+\\fP\\fIcommand\\fP] [\\fIfile ...\\fP]\n.DE\nThe most common case edits a single file with no options, i.e.:\n.DS I\nex name\n.DE\nThe\n.B \\-\ncommand line option\noption suppresses all interactive-user feedback\nand is useful in processing editor scripts in command files.\nThe\n.B \\-s\noption is the POSIX equivalent to\n.B \\- ;\nboth forms are identical.\nThe\n.B \\-v\noption is equivalent to using\n.I vi\nrather than\n.I ex .\nThe\n.B \\-t\noption is equivalent to an initial\n.I tag\ncommand, editing the file containing the\n.I tag\nand positioning the editor at its definition.\nThe\n.B \\-r\noption is used in recovering after an editor or system crash,\nretrieving the last saved version of the named file or,\nif no file is specified,\ntyping a list of saved files.\n.\\\" The\n.\\\" .B \\-l\n.\\\" option sets up for editing \\s-2LISP\\s0, setting the\n.\\\" .I showmatch\n.\\\" and\n.\\\" .I lisp\n.\\\" options.\nThe\n.B \\-w\noption sets the default window size to\n.I n ,\nand is useful on dialups to start in small windows.\n.\\\" The\n.\\\" .B \\-x\n.\\\" option causes\n.\\\" .I ex\n.\\\" to prompt for a\n.\\\" .I key ,\n.\\\" which is used to encrypt and decrypt the contents of the file,\n.\\\" which should already be encrypted using the same key,\n.\\\" see\n.\\\" .I crypt (1).\n.PP\nThe\n.B \\-R\noption sets the\n.I readonly\noption at the start.\nThe\n.B \\-S\noption\ncause\n.I ex\nto be run with the\n.I secure\noption, disallowing all access to external programs.\nThe\n.B \\-F\noption prevents\n.I ex\nfrom copying the entire file at startup\n(the default is to make a copy of the file at startup).\n.PP\n.I Name\narguments indicate files to be edited.\nAn argument of the form\n\\fB+\\fIcommand\\fR\nindicates that the editor should begin by executing the specified command.\nIf\n.I command\nis omitted, then it defaults to ``$'', positioning the editor at the last\nline of the first file initially.  Other useful commands here are scanning\npatterns of the form ``/pat'' or line numbers, e.g. ``+100'' starting\nat line 100.\nThe form\n\\fB\\-c \\fIcmd\\fR\nis the POSIX equivalent to\n\\fB+\\fIcommand\\fR;\nboth forms are identical.\n.NH 1\nFile manipulation\n.NH 2\nCurrent file\n.PP\n.I Ex\nis normally editing the contents of a single file,\nwhose name is recorded in the\n.I current\nfile name.\n.I Ex\nperforms all editing actions in a buffer\n(actually a temporary file)\ninto which the text of the file is initially read.\nChanges made to the buffer have no effect on the file being\nedited unless and until the buffer contents are written out to the\nfile with a\n.I write\ncommand.\nAfter the buffer contents are written,\nthe previous contents of the written file are no longer accessible.\nWhen a file is edited,\nits name becomes the current file name,\nand its contents are read into the buffer.\n.PP\nThe current file is almost always considered to be\n.I edited .\nThis means that the contents of the buffer are logically\nconnected with the current file name,\nso that writing the current buffer contents onto that file,\neven if it exists,\nis a reasonable action.\nIf the current file is not\n.I edited\nthen\n.I ex\nwill not normally write on it if it already exists.*\n.FS\n* The\n.I file\ncommand will say ``[Not edited]'' if the current file is not considered\nedited.\n.FE\n.NH 2\nAlternate file\n.PP\nEach time a new value is given to the current file name,\nthe previous current file name is saved as the\n.I alternate\nfile name.\nSimilarly if a file is mentioned but does not become the current file,\nit is saved as the alternate file name.\n.NH 2\nFilename expansion\n.PP\nFilenames within the editor may be specified using the normal\nshell expansion conventions.\nIn addition,\nthe character `%' in filenames is replaced by the\n.I current\nfile name and the character\n`#' by the\n.I alternate\nfile name.\nThis makes it easy to deal alternately with\ntwo files and eliminates the need for retyping the\nname supplied on an\n.I edit\ncommand after a\n.I \"No write since last change\"\ndiagnostic is received.\n.NH 2\nMultiple files and named buffers\n.PP\nIf more than one file is given on the command line,\nthen the first file is edited as described above.\nThe remaining arguments are placed with the first file in the\n.I \"argument list\" .\nThe current argument list may be displayed with the\n.I args\ncommand.\nThe next file in the argument list may be edited with the\n.I next\ncommand.\nThe argument list may also be respecified by specifying\na list of names to the\n.I next\ncommand.\nThese names are expanded,\nthe resulting list of names becomes the new argument list,\nand\n.I ex\nedits the first file on the list.\n.PP\nFor saving blocks of text while editing, and especially when editing\nmore than one file,\n.I ex\nhas a group of named buffers.\nThese are similar to the normal buffer, except that only a limited number\nof operations are available on them.\nThe buffers have names\n.I a\nthrough\n.I z .\nIt is also possible to refer to\n.I A\nthrough\n.I Z ;\nthe upper case buffers are the same as the lower but commands\nappend to named buffers rather than replacing\nif upper case names are used.\n.NH 2\nRead only\n.PP\nIt is possible to use\n.I ex\nin\n.I \"read only\"\nmode to look at files that you have no intention of modifying.\nThis mode protects you from accidentally overwriting the file.\nRead only mode is on when the\n.I readonly\noption is set.\nIt can be turned on with the\n.B \\-R\ncommand line option,\nby the\n.I view\ncommand line invocation,\nor by setting the\n.I readonly\noption.\nIt can be cleared by setting\n.I noreadonly .\nIt is possible to write, even while in read only mode, by indicating\nthat you really know what you are doing.\nYou can write to a different file, or can use the ! form of write,\neven while in read only mode.\n.NH 1\nExceptional Conditions\n.NH 2\nErrors and interrupts\n.PP\nWhen errors occur\n.I ex\n(optionally) rings the terminal bell and, in any case, prints an error\ndiagnostic.  If the primary input is from a file, editor processing\nwill terminate.  If an interrupt signal is received,\n.I ex\nprints ``Interrupt'' and returns to its command level.  If the primary\ninput is a file, then\n.I ex\nwill exit when this occurs.\n.NH 2\nRecovering from hangups and crashes\n.PP\nIf a hangup signal is received and the buffer has been modified since\nit was last written out, or if the system crashes, either the editor\n(in the first case) or the system (after it reboots in the second) will\nattempt to preserve the buffer.  The next time you log in you should be\nable to recover the work you were doing, losing at most a few lines of\nchanges from the last point before the hangup or editor crash.  To\nrecover a file you can use the\n.B \\-r\noption.  If you were editing the file\n.I resume ,\nthen you should change\nto the directory where you were when the crash occurred, giving the command\n.DS I\nex -r resume\n.DE\nAfter checking that the retrieved file is indeed ok, you can\n.I write\nit over the previous contents of that file.\n.PP\nYou will normally get mail from the system telling you when a file has\nbeen saved after a crash.  The command\n.DS I\nex -r\n.DE\nwill print a list of the files which have been saved for you.\n(In the case of a hangup,\nthe file will not appear in the list,\nalthough it can be recovered.)\n.NH 1\nEditing modes\n.PP\n.I Ex\nhas five distinct modes.  The primary mode is\n.I command\nmode.  Commands are entered in command mode when a `:' prompt is\npresent, and are executed each time a complete line is sent.  In\n.I \"text input\"\nmode,\n.I ex\ngathers input lines and places them in the file.  The\n.I append ,\n.I insert ,\nand\n.I change\ncommands use text input mode.\nNo prompt is printed when you are in text input mode.\nThis mode is left by typing a `.' alone at the beginning of a line, and\n.I command\nmode resumes.\n.PP\nThe last two modes are\n.\\\" .I open\n.\\\" and\n.I visual\nmode, entered by the command of the same name, and, within\nvisual mode\n.I \"text insertion\"\nmode.\n.\\\" .I Open\n.\\\" and\n.I Visual\nmode allows local editing operations to be performed on the text in the\nfile.\n.\\\" The\n.\\\" .I open\n.\\\" command displays one line at a time on any terminal while\n.I Visual\nmode works on \\s-2CRT\\s0 terminals with random positioning cursors, using the\nscreen as a window for file editing changes.\nThese modes are described (only) in\n.I \"An Introduction to Display Editing with Vi\" .\n.NH\nCommand structure\n.PP\nMost command names are English words,\nand initial prefixes of the words are acceptable abbreviations.\nThe ambiguity of abbreviations is resolved in favor of the more commonly\nused commands.*\n.FS\n* As an example, the command\n.I substitute\ncan be abbreviated `s'\nwhile the shortest available abbreviation for the\n.I set\ncommand is `se'.\n.FE\n.NH 2\nCommand parameters\n.PP\nMost commands accept prefix addresses specifying the lines in the file\nupon which they are to have effect.\nThe forms of these addresses will be discussed below.\nA number of commands also may take a trailing\n.I count\nargument, specifying the number of lines to be involved in the command.\nCounts are rounded down if necessary.\nThus the command ``10p'' will print the tenth line in the buffer while\n``delete 5'' will delete five lines from the buffer,\nstarting with the current line.\n.PP\nSome commands take other information or parameters,\nthis information always being given after the command name.\nExamples would be option names in a\n.I set\ncommand i.e. ``set number'',\na file name in an\n.I edit\ncommand,\na regular expression in a\n.I substitute\ncommand,\nor a target address for a\n.I copy\ncommand, i.e. ``1,5 copy 25''.\n.NH 2\nCommand variants\n.PP\nA number of commands have two distinct variants.\nThe variant form of the command is invoked by placing an\n`!' immediately after the command name.\nSome of the default variants may be controlled by options;\nin this case, the `!' serves to toggle the default.\n.NH 2\nFlags after commands\n.PP\nThe characters `#', `p' and `l' may be placed after many commands.**\n.FS\n**\nA `p' or `l' must be preceded by a blank or tab\nexcept in the single special case `dp'.\n.FE\nIn this case, the command abbreviated by these characters\nis executed after the command completes.\nSince\n.I ex\nnormally prints the new current line after each change, `p' is rarely necessary.\nAny number of `+' or `\\-' characters may also be given with these flags.\nIf they appear, the specified offset is applied to the current line\nvalue before the printing command is executed.\n.NH 2\nComments\n.PP\nIt is possible to give editor commands which are ignored.\nThis is useful when making complex editor scripts\nfor which comments are desired.\nThe comment character is the double quote: \".\nAny command line beginning with \" is ignored.\nComments beginning with \" may also be placed at the ends\nof commands, except in cases where they could be confused as part\nof text (shell escapes and the substitute and map commands).\n.NH 2\nMultiple commands per line\n.PP\nMore than one command may be placed on a line by separating each pair\nof commands by a `|' character.\nHowever the\n.I global\ncommands,\ncomments,\nand the shell escape `!'\nmust be the last command on a line, as they are not terminated by a `|'.\n.NH 2\nReporting large changes\n.PP\nMost commands which change the contents of the editor buffer give\nfeedback if the scope of the change exceeds a threshold given by the\n.I report\noption.\nThis feedback helps to detect undesirably large changes so that they may\nbe quickly and easily reversed with an\n.I undo .\nAfter commands with more global effect such as\n.I global\nor\n.I visual ,\nyou will be informed if the net change in the number of lines\nin the buffer during this command exceeds this threshold.\n.NH 1\nCommand addressing\n.NH 2\nAddressing primitives\n.IP \\fB.\\fR 20\nThe current line.\nMost commands leave the current line as the last line which they affect.\nThe default address for most commands is the current line,\nthus `\\fB.\\fR' is rarely used alone as an address.\n.IP \\fIn\\fR 20\nThe \\fIn\\fRth line in the editor's buffer, lines being numbered\nsequentially from 1.\n.IP \\fB$\\fR 20\nThe last line in the buffer.\n.IP \\fB%\\fR 20\nAn abbreviation for ``1,$''; the entire buffer.\n.IP \\fI+n\\fR,\\ \\fI\\-n\\fR 20\nAn offset relative to the current buffer line.*\n.FS\n*\nThe forms `.+3', `+3', and `+++' are all equivalent;\nif the current line is line 100i, they all address line 103.\n.FE\n.IP \\fB/\\fIpat\\fR\\fB/\\fR,\\ \\fB?\\fIpat\\fR\\fB?\\fR 20\nScan forward and backward respectively for a line containing \\fIpat\\fR, a\nregular expression (as defined below).  The scans normally wrap around the end\nof the buffer.\nIf all that is desired is to print the next line containing \\fIpat\\fR, then\nthe trailing \\fB/\\fR or \\fB?\\fR may be omitted.\nIf \\fIpat\\fP is omitted or explicitly empty, then the last\nregular expression specified is located.*\n.FS\n* The forms \\fB\\e/\\fP and \\fB\\e?\\fP scan\nusing the last regular expression used in a scan; after a substitute\n\\fB//\\fP and \\fB??\\fP would scan using the substitute's regular expression.\n.FE\n.IP \\fB\\(aa\\(aa\\fP\\ \\fB\\(aa\\fP\\fIx\\fP 20\nBefore each non-relative motion of the current line `\\fB.\\fP',\nthe previous current line is marked with a tag, subsequently referred to as\n\\(aa\\(aa.\nThis makes it easy to refer or return to this previous context.\nMarks may also be established by the\n.I mark\ncommand, using single lower case letters\n.I x\nand the marked lines referred to as\n\\(aa\\fIx\\fR.\n.NH 2\nCombining addressing primitives\n.PP\nAddresses to commands consist of a series of addressing primitives,\nseparated by `,' or `;'.\nSuch address lists are evaluated left-to-right.\nWhen addresses are separated by `;' the current line `\\fB.\\fR'\nis set to the value of the previous addressing expression\nbefore the next address is interpreted.\nIf more addresses are given than the command requires,\nthen all but the last one or two are ignored.\nIf the command takes two addresses, the first addressed line must\nprecede the second in the buffer.**\n.FS\n** NULL address specifications are permitted in a list of addresses,\nthe default in this case is the current line `.';\nthus `,100' is equivalent to `\\fB.\\fR,100'.\nIt is an error to give a prefix address to a command which expects none.\n.FE\n.NH 1\nCommand descriptions\n.PP\nThe following form is a prototype for all\n.I ex\ncommands:\n.DS\n\\fIaddress\\fR \\fBcommand\\fR \\fI! parameters count flags\\fR\n.DE\nAll parts are optional; the degenerate case is the empty command which prints\nthe next line in the file.  For sanity with use from within\n.I visual\nmode,\n.I ex\nignores a ``:'' preceding any command.\n.PP\nIn the following command descriptions, the\ndefault addresses are shown in parentheses,\nwhich are\n.I not ,\nhowever,\npart of the command.\n.LC\n\\fBabbreviate\\fR \\fIword rhs\\fP\tabbr: \\fBab\\fP\n.ZP\nAdd the named abbreviation to the current list.\nWhen in input mode in visual, if\n.I word\nis typed as a complete word, it will be changed to\n.I rhs .\n.LC\n( \\fB.\\fR ) \\fBappend\\fR\tabbr: \\fBa\\fR\n.br\n\\fItext\\fR\n.br\n\\&\\fB.\\fR\n.ZP\nReads the input text and places it after the specified line.\nAfter the command, `\\fB.\\fR'\naddresses the last line input or the\nspecified line if no lines were input.\nIf address `0' is given,\ntext is placed at the beginning of the buffer.\n.LC\n\\fBa!\\fR\n.br\n\\fItext\\fR\n.br\n\\&\\fB.\\fR\n.ZP\nThe variant flag to\n.I append\ntoggles the setting for the\n.I autoindent\noption during the input of\n.I text .\n.LC\n\\fBargs\\fR\n.ZP\nThe members of the argument list are printed, with the current argument\ndelimited by `[' and `]'.\n.LC\n\\fBcd\\fR \\fIdirectory\\fR\n.ZP\nThe\n.I cd\ncommand is a synonym for\n.I chdir .\n.LC\n( \\fB.\\fP , \\fB.\\fP ) \\fBchange\\fP \\fIcount\\fP\tabbr: \\fBc\\fP\n.br\n\\fItext\\fP\n.br\n\\&\\fB.\\fP\n.ZP\nReplaces the specified lines with the input \\fItext\\fP.\nThe current line becomes the last line input;\nif no lines were input, it is left as for a\n\\fIdelete\\fP.\n.LC\n\\fBc!\\fP\n.br\n\\fItext\\fP\n.br\n\\&\\fB.\\fP\n.ZP\nThe variant toggles\n.I autoindent\nduring the\n.I change.\n.LC\n\\fBchdir\\fR \\fIdirectory\\fR\tabbrev: \\fBchd\\fP\n.ZP\nThe specified \\fIdirectory\\fR becomes the current directory.\nIf no directory is specified, the current value of the\n.I home\noption is used as the target directory.\nAfter a\n.I chdir\nthe current file is not considered to have been\nedited so that write restrictions on pre-existing files apply.\n.LC\n( \\fB.\\fP , \\fB.\\fP )\\|\\fBcopy\\fP \\fIaddr\\fP \\fIflags\\fP\tabbr: \\fBco\\fP\n.ZP\nA\n.I copy\nof the specified lines is placed after\n.I addr ,\nwhich may be `0'.\nThe current line\n`\\fB.\\fR'\naddresses the last line of the copy.\nThe command\n.I t\nis a synonym for\n.I copy .\n.LC\n( \\fB.\\fR , \\fB.\\fR )\\|\\fBdelete\\fR \\fIbuffer\\fR \\fIcount\\fR \\fIflags\\fR\tabbr: \\fBd\\fR\n.ZP\nRemoves the specified lines from the buffer.\nThe line after the last line deleted becomes the current line;\nif the lines deleted were originally at the end,\nthe new last line becomes the current line.\nIf a named\n.I buffer\nis specified by giving a letter,\nthen the specified lines are saved in that buffer,\nor appended to it if an upper case letter is used.\n.LC\n\\fBedit\\fR \\fIfile\\fR\tabbr: \\fBe\\fR\n.br\n\\fBex\\fR \\fIfile\\fR\n.ZP\nUsed to begin an editing session on a new file.\nThe editor\nfirst checks to see if the buffer has been modified since the last\n.I write\ncommand was issued.\nIf it has been,\na warning is issued and the\ncommand is aborted.\nThe\ncommand otherwise deletes the entire contents of the editor buffer,\nmakes the named file the current file and prints the new filename.\nAfter ensuring that this file is sensible*\n.FS\n* I.e., that it is not a special file such as a directory,\na block or character special file other than\n.I /dev/tty ,\nor a terminal.\n.FE\nthe editor reads the file into its buffer.\n.IP\nIf the read of the file completes without error,\nthe number of lines and characters read is typed.\nIf the last line of the input file is missing the trailing\nnewline character, it will be supplied and a complaint will be issued.\nThis command leaves the current line `\\fB.\\fR' at the last line read.**\n.FS\n** If executed from within\n.\\\" .I open\n.\\\" or\n.I visual,\nthe current line is initially the first line of the file.\n.FE\n.LC\n\\fBe!\\fR \\fIfile\\fR\n.ZP\nThe variant form suppresses the complaint about modifications having\nbeen made and not written from the editor buffer, thus\ndiscarding all changes which have been made before editing the new file.\n.LC\n\\fBe\\fR \\fB+\\fIn\\fR \\fIfile\\fR\n.ZP\nCauses the editor to begin at line\n.I n\nrather than at the last line;\n\\fIn\\fR may also be an editor command containing no spaces, e.g.: ``+/pat''.\n.LC\n\\fBfile\\fR\tabbr: \\fBf\\fR\n.ZP\nPrints the current file name,\nwhether it has been `[Modified]' since the last\n.I write\ncommand,\nwhether it is\n.I \"read only\" ,\nthe current line,\nthe number of lines in the buffer,\nand the percentage of the way through the buffer of the current line.*\n.FS\n* In the rare case that the current file is `[Not edited]' this is\nnoted also; in this case you have to use the form \\fBw!\\fR to write to\nthe file, since the editor is not sure that a \\fBwrite\\fR will not\ndestroy a file unrelated to the current contents of the buffer.\n.FE\n.LC\n\\fBfile\\fR \\fIfile\\fR\n.ZP\nThe current file name is changed to\n.I file ,\nwhich is considered\n`[Not edited]'.\n.LC\n( 1 , $ ) \\fBglobal\\fR /\\fIpat\\|\\fR/ \\fIcmds\\fR\tabbr: \\fBg\\fR\n.ZP\nFirst marks each line among those specified which matches\nthe given regular expression.\nThen the given command list is executed with `\\fB.\\fR' initially\nset to each marked line.\n.IP\nThe command list consists of the remaining commands on the current\ninput line and may continue to multiple lines by ending all but the\nlast such line with a `\\e'.\nIf\n.I cmds\n(and possibly the trailing \\fB/\\fR delimiter) is omitted, each line matching\n.I pat\nis printed.\n.I Append ,\n.I insert ,\nand\n.I change\ncommands and associated input are permitted;\nthe `\\fB.\\fR' terminating input may be omitted if it would be on the\nlast line of the command list.\n.\\\" .I Open\n.\\\" and\n.I Visual\ncommands are permitted in the command list and take input from the terminal.\n.IP\nThe\n.I global\ncommand itself may not appear in\n.I cmds .\nThe\n.I undo\ncommand is also not permitted there,\nas\n.I undo\ninstead can be used to reverse the entire\n.I global\ncommand.\nThe options\n.I autoprint\nand\n.I autoindent\nare inhibited during a\n.I global ,\n(and possibly the trailing \\fB/\\fR delimiter) and the value of the\n.I report\noption is temporarily infinite,\nin deference to a \\fIreport\\fR for the entire global.\nFinally, the context mark ('') is set to the value of\n`.' before the global command begins and is not changed during a global\ncommand,\nexcept perhaps by a\n.\\\" .I open\n.\\\" or\n.I visual\nwithin the\n.I global .\n.LC\n\\fBg!\\fR \\fB/\\fIpat\\fB/\\fR \\fIcmds\\fR\tabbr: \\fBv\\fR\n.IP\nThe variant form of \\fIglobal\\fR runs \\fIcmds\\fR at each line not matching\n\\fIpat\\fR.\n.LC\n( \\fB.\\fR )\\|\\fBinsert\\fR\tabbr: \\fBi\\fR\n.br\n\\fItext\\fR\n.br\n\\&\\fB.\\fR\n.ZP\nPlaces the given text before the specified line.\nThe current line is left at the last line input;\nif there were none input it is left at the line before the addressed line.\nThis command differs from\n.I append\nonly in the placement of text.\n.KS\n.LC\n\\fBi!\\fR\n.br\n\\fItext\\fR\n.br\n\\&\\fB.\\fR\n.ZP\nThe variant toggles\n.I autoindent\nduring the\n.I insert.\n.KE\n.LC\n( \\fB.\\fR , \\fB.\\fR+1 ) \\fBjoin\\fR \\fIcount\\fR \\fIflags\\fR\tabbr: \\fBj\\fR\n.ZP\nPlaces the text from a specified range of lines\ntogether on one line.\nWhitespace is adjusted at each junction to provide at least\none blank character, two if there was a `\\fB.\\fR' at the end of the line,\nor none if the first following character is a `)'.\nIf there is already whitespace at the end of the line,\nthen the whitespace at the start of the next line will be discarded.\n.LC\n\\fBj!\\fR\n.ZP\nThe variant causes a simpler\n.I join\nwith no whitespace processing; the characters in the lines are simply\nconcatenated.\n.LC\n( \\fB.\\fR ) \\fBk\\fR \\fIx\\fR\n.ZP\nThe\n.I k\ncommand is a synonym for\n.I mark .\nIt does not require a blank or tab before the following letter.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fBlist\\fR \\fIcount\\fR \\fIflags\\fR\n.ZP\nPrints the specified lines in a more unambiguous way:\ntabs are printed as `^I'\nand the end of each line is marked with a trailing `$'.\nThe current line is left at the last line printed.\n.LC\n\\fBmap\\fR \\fIlhs\\fR \\fIrhs\\fR\n.ZP\nThe\n.I map\ncommand is used to define macros for use in\n.I visual\nmode.\n.I Lhs\nshould be a single character, or the sequence ``#n'', for n a digit,\nreferring to function key \\fIn\\fR.  When this character or function key\nis typed in\n.I visual\nmode, it will be as though the corresponding \\fIrhs\\fR had been typed.\nOn terminals without function keys, you can type ``#n''.\nSee section 6.8 of the ``Introduction to Display Editing with Vi''\nfor more details.\n.LC\n( \\fB.\\fR ) \\fBmark\\fR \\fIx\\fR\n.ZP\nGives the specified line mark\n.I x ,\na single lower case letter.\nThe\n.I x\nmust be preceded by a blank or a tab.\nThe addressing form 'x then addresses this line.\nThe current line is not affected by this command.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fBmove\\fR \\fIaddr\\fR\tabbr: \\fBm\\fR\n.ZP\nThe\n.I move\ncommand repositions the specified lines to be after\n.I addr .\nThe first of the moved lines becomes the current line.\n.LC\n\\fBnext\\fR\tabbr: \\fBn\\fR\n.ZP\nThe next file from the command line argument list is edited.\n.LC\n\\fBn!\\fR\n.ZP\nThe variant suppresses warnings about the modifications to the buffer not\nhaving been written out, discarding (irretrievably) any changes which may\nhave been made.\n.LC\n\\fBn\\fR \\fIfilelist\\fR\n.br\n\\fBn\\fR \\fB+\\fIcommand\\fR \\fIfilelist\\fR\n.ZP\nThe specified\n.I filelist\nis expanded and the resulting list replaces the\ncurrent argument list;\nthe first file in the new list is then edited.\nIf\n.I command\nis given (it must contain no spaces), then it is executed after editing the first such file.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fBnumber\\fR \\fIcount\\fR \\fIflags\\fR\tabbr: \\fB#\\fR or \\fBnu\\fR\n.ZP\nPrints each specified line preceded by its buffer line\nnumber.\nThe current line is left at the last line printed.\n.\\\" .KS\n.\\\" .LC\n.\\\" ( \\fB.\\fR ) \\fBopen\\fR \\fIflags\\fR\tabbr: \\fBo\\fR\n.\\\" .br\n.\\\" ( \\fB.\\fR ) \\fBopen\\fR /\\fIpat\\|\\fR/ \\fIflags\\fR\n.\\\" .ZP\n.\\\" Enters intraline editing \\fIopen\\fR mode at each addressed line.\n.\\\" If\n.\\\" .I pat\n.\\\" is given,\n.\\\" then the cursor will be placed initially at the beginning of the\n.\\\" string matched by the pattern.\n.\\\" To exit this mode use Q.\n.\\\" See\n.\\\" .I \"An Introduction to Display Editing with Vi\"\n.\\\" for more details.\n.\\\" .KE\n.LC\n\\fBpreserve\\fR\tabbrev: \\fBpre\\fR\n.ZP\nThe current editor buffer is saved as though the system had just crashed.\nThis command is for use only in emergencies when a\n.I write\ncommand has resulted in an error and you don't know how to save your work.\nAfter a\n.I preserve\nyou should seek help.\n.LC\n( \\fB.\\fR , \\fB.\\fR )\\|\\fBprint\\fR \\fIcount\\fR\tabbr: \\fBp\\fR or \\fBP\\fR\n.ZP\nPrints the specified lines\nwith non-printing characters printed as control characters `^\\fIx\\fR\\|';\ndelete (octal 177) is represented as `^?'.\nThe current line is left at the last line printed.\n.LC\n( \\fB.\\fR )\\|\\fBput\\fR \\fIbuffer\\fR\tabbr: \\fBpu\\fR\n.ZP\nPuts back\npreviously\n.I deleted\nor\n.I yanked\nlines.\nNormally used with\n.I delete\nto effect movement of lines,\nor with\n.I yank\nto effect duplication of lines.\nIf no\n.I buffer\nis specified, then the last\n.I deleted\nor\n.I yanked\ntext is restored.*\n.FS\n* But no modifying commands may intervene between the\n.I delete\nor\n.I yank\nand the\n.I put ,\nnor may lines be moved between files without using a named buffer.\n.FE\nBy using a named buffer, text may be restored that was saved there at any\nprevious time.\n.LC\n\\fBquit\\fR\tabbr: \\fBq\\fR\n.ZP\nCauses\n.I ex\nto terminate.\nNo automatic write of the editor buffer to a file is performed.\nHowever,\n.I ex\nissues a warning message if the file has changed\nsince the last\n.I write\ncommand was issued, and does not\n.I quit .**\n.FS\n** \\fIEx\\fR\nwill also issue a diagnostic if there are more files in the argument\nlist.\n.FE\nNormally, you will wish to save your changes, and you\nshould give a \\fIwrite\\fR command;\nif you wish to discard them, use the \\fBq!\\fR command variant.\n.LC\n\\fBq!\\fR\n.ZP\nQuits from the editor, discarding changes to the buffer without complaint.\n.LC\n( \\fB.\\fR ) \\fBread\\fR \\fIfile\\fR\tabbr: \\fBr\\fR\n.ZP\nPlaces a copy of the text of the given file in the\nediting buffer after the specified line.\nIf no\n.I file\nis given, the current file name is used.\nThe current file name is not changed unless there is none, in which\ncase\n.I file\nbecomes the current name.\nThe sensibility restrictions for the\n.I edit\ncommand apply here also.\nIf the file buffer is empty and there is no current name then\n.I ex\ntreats this as an\n.I edit\ncommand.\n.IP\nAddress `0' is legal for this command and causes the file to be read at\nthe beginning of the buffer.\nStatistics are given as for the\n.I edit\ncommand when the\n.I read\nsuccessfully terminates.\nAfter a\n.I read\nthe current line is the last line read.*\n.FS\n* Within\n.\\\" .I open\n.\\\" and\n.I visual\nmode,\nthe current line is set to the first line read rather than the last.\n.FE\n.LC\n( \\fB.\\fR ) \\fBread\\fR  \\fB!\\fR\\fIcommand\\fR\n.ZP\nReads the output of the command\n.I command\ninto the buffer after the specified line.\nThis is not a variant form of the command, rather a read\nspecifying a\n.I command\nrather than a\n.I filename ;\na blank or tab before the \\fB!\\fR is mandatory.\n.LC\n\\fBrecover \\fIfile\\fR\n.ZP\nRecovers\n.I file\nfrom the system save area.\nUsed, for example, after a remote connection has timed out**\n.FS\n** The system saves a copy of the file you were editing only if you\nhave made changes to the file.\n.FE\nor a system crash** or\n.I preserve\ncommand.\nExcept when you use\n.I preserve\nyou will be notified by mail when a file is saved.\n.LC\n\\fBrewind\\fR\tabbr: \\fBrew\\fR\n.ZP\nThe argument list is rewound, and the first file in the list is edited.\n.LC\n\\fBrew!\\fR\n.ZP\nRewinds the argument list discarding any changes made to the current buffer.\n.LC\n\\fBset\\fR \\fIparameter\\fR\n.ZP\nWith no arguments, prints those options whose values have been\nchanged from their defaults;\nwith parameter\n.I all\nit prints all of the option values.\n.IP\nGiving an option name followed by a `?'\ncauses the current value of that option to be printed.\nThe `?' is unnecessary unless the option is Boolean valued.\nBoolean options are given values either by the form\n`set \\fIoption\\fR' to turn them on or\n`set no\\fIoption\\fR' to turn them off;\nstring and numeric options are assigned via the form\n`set \\fIoption\\fR=value'.\n.IP\nMore than one parameter may be given to\n.I set ;\nthey are interpreted left-to-right.\n.LC\n\\fBshell\\fR\tabbr: \\fBsh\\fR\n.IP\nA new shell is created.\nWhen it terminates, editing resumes.\n.LC\n\\fBsource\\fR \\fIfile\\fR\tabbr: \\fBso\\fR\n.IP\nReads and executes commands from the specified file.\n.I Source\ncommands may be nested.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fBsubstitute\\fR /\\fIpat\\fR\\|/\\fIrepl\\fR\\|/ \\fIoptions\\fR \\fIcount\\fR \\fIflags\\fR\\ \\&abbr: \\fBs\\fR\n.IP\nOn each specified line, the first instance of pattern\n.I pat\nis replaced by replacement pattern\n.I repl .\nIf the\n.I global\nindicator option character `g'\nappears, then all instances are substituted;\nif the\n.I confirm\nindication character `c' appears,\nthen before each substitution the line to be substituted\nis typed with the string to be substituted marked\nwith `^' characters.\nBy typing a `y' one can cause the substitution to be performed,\nany other input causes no change to take place.\nAfter a\n.I substitute\nthe current line is the last line substituted.\n.IP\nLines may be split by substituting\nnew-line characters into them.\nThe newline in\n.I repl\nmust be escaped by preceding it with a `\\e'.\nOther metacharacters available in\n.I pat\nand\n.I repl\nare described below.\n.LC\n.B stop\n.ZP\nSuspends the editor, returning control to the top level shell.\nIf\n.I autowrite\nis set and there are unsaved changes,\na write is done first unless the form\n.B stop !\nis used.\nThis command is only available where supported by the tty driver\nand operating system.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fBsubstitute\\fR \\fIoptions\\fR \\fIcount\\fR \\fIflags\\fR\tabbr: \\fBs\\fR\n.ZP\nIf\n.I pat\nand\n.I repl\nare omitted, then the last substitution is repeated.\nThis is a synonym for the\n.B &\ncommand.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fBt\\fR \\fIaddr\\fR \\fIflags\\fR\n.ZP\nThe\n.I t\ncommand is a synonym for\n.I copy .\n.LC\n\\fBtag\\fR \\fItagstring\\fR\tabbrev: ta\n.ZP\nThe focus of editing switches to the location of\n.I tagstring ,\nswitching to a different line in the current file where it is defined,\nor if necessary to another file.*\n.FS\n* If you have modified the current file before giving a\n.I tag\ncommand, you must write it out; giving another\n.I tag\ncommand, specifying no\n.I tag\nwill reuse the previous tag.\n.FE\n.IP\nThe tags file is normally created by a program such as\n.I ctags ,\nand consists of a number of lines with three fields separated by blanks\nor tabs.  The first field gives the name of the tag,\nthe second the name of the file where the tag resides, and the third\ngives an addressing form which can be used by the editor to find the tag;\nthis field is usually a contextual scan using `/\\fIpat\\fR/' to be immune\nto minor changes in the file.  Such scans are always performed as if\n.I nomagic\nwas set.\n.IP\nThe tag names in the tags file must be sorted alphabetically.\n.LC\n\\fBunabbreviate\\fR \\fIword\\fP\tabbr: \\fBuna\\fP\n.ZP\nDelete\n.I word\nfrom the list of abbreviations.\n.LC\n\\fBundo\\fR\tabbr: \\fBu\\fR\n.ZP\nReverses the changes made in the buffer by the last\nbuffer editing command.\nNote that\n.I global\ncommands are considered a single command for the purpose of\n.I undo\n(as is\n.\\\" .I open\n.\\\" and\n.I visual .)\nAlso, the commands\n.I write\nand\n.I edit\nwhich interact with the\nfile system cannot be undone.\n.I Undo\nis its own inverse.\n.IP\n.I Undo\nalways marks the previous value of the current line `\\fB.\\fR'\nas ''.\nAfter an\n.I undo\nthe current line is the first line restored\nor the line before the first line deleted if no lines were restored.\nFor commands with more global effect\nsuch as\n.I global\nand\n.I visual\nthe current line regains it's pre-command value after an\n.I undo .\n.LC\n\\fBunmap\\fR \\fIlhs\\fR\n.ZP\nThe macro expansion associated by\n.I map\nfor\n.I lhs\nis removed.\n.LC\n( 1 , $ ) \\fBv\\fR /\\fIpat\\fR\\|/ \\fIcmds\\fR\n.ZP\nA synonym for the\n.I global\ncommand variant \\fBg!\\fR, running the specified \\fIcmds\\fR on each\nline which does not match \\fIpat\\fR.\n.LC\n\\fBversion\\fR\tabbr: \\fBve\\fR\n.ZP\nPrints the current version number of the editor\nas well as the date the editor was last changed.\n.LC\n( \\fB.\\fR ) \\fBvisual\\fR \\fItype\\fR \\fIcount\\fR \\fIflags\\fR\tabbr: \\fBvi\\fR\n.ZP\nEnters visual mode at the specified line.\n.I Type\nis optional and may be `\\-' , `^' or `\\fB.\\fR'\nas in the\n.I z\ncommand to specify the placement of the specified line on the screen.\nBy default, if\n.I type\nis omitted, the specified line is placed as the first on the screen.\nA\n.I count\nspecifies an initial window size; the default is the value of the option\n.I window .\nSee the document\n.I \"An Introduction to Display Editing with Vi\"\nfor more details.\nTo exit this mode, type\n.I Q .\n.LC\n\\fBvisual\\fP file\n.br\n\\fBvisual\\fP +\\fIn\\fP file\n.ZP\nFrom visual mode,\nthis command is the same as edit.\n.LC\n( 1 , $ ) \\fBwrite\\fR \\fIfile\\fR\tabbr: \\fBw\\fR\n.ZP\nWrites changes made back to \\fIfile\\fR, printing the number of lines and\ncharacters written.\nNormally \\fIfile\\fR is omitted and the text goes back where it came from.\nIf a \\fIfile\\fR is specified, then text will be written to that file.*\n.FS\n* The editor writes to a file only if it is\nthe current file and is\n.I edited ,\nif the file does not exist,\nor if the file is actually a teletype,\n.I /dev/tty ,\n.I /dev/null .\nOtherwise, you must give the variant form \\fBw!\\fR to force the write.\n.FE\nIf the file does not exist, it is created.\nThe current file name is changed only if there is no current file\nname; the current line is never changed.\n.IP\nIf an error occurs while writing the current and\n.I edited\nfile, the editor\nconsiders that there has been ``No write since last change''\neven if the buffer had not previously been modified.\n.LC\n( 1 , $ ) \\fBwrite>>\\fR \\fIfile\\fR\tabbr: \\fBw>>\\fR\n.ZP\nWrites the buffer contents at the end of\nan existing file.\n.IP\n.LC\n\\fBw!\\fR \\fIname\\fR\n.ZP\nOverrides the checking of the normal \\fIwrite\\fR command,\nand will write to any file which the system permits.\n.LC\n( 1 , $ ) \\fBw\\fR  \\fB!\\fR\\fIcommand\\fR\n.ZP\nWrites the specified lines into\n.I command.\nNote the difference between \\fBw!\\fR which overrides checks and\n\\fBw\\ \\ !\\fR which writes to a command.\n.LC\n\\fBwq\\fR \\fIname\\fR\n.ZP\nLike a \\fIwrite\\fR and then a \\fIquit\\fR command.\n.LC\n\\fBwq!\\fR \\fIname\\fR\n.ZP\nThe variant overrides checking on the sensibility of the\n.I write\ncommand, as \\fBw!\\fR does.\n.LC\n\\fBxit\\fP \\fIname\\fR\tabbr: \\fBx\\fR\n.ZP\nIf any changes have been made and not written, writes the buffer out.\nThen, in any case, quits.\n.LC\n( \\fB.\\fR , \\fB.\\fR )\\|\\fByank\\fR \\fIbuffer\\fR \\fIcount\\fR\tabbr: \\fBya\\fR\n.ZP\nPlaces the specified lines in the named\n.I buffer,\nfor later retrieval via\n.I put.\nIf no buffer name is specified, the lines go to a more volatile place;\nsee the \\fIput\\fR command description.\n.LC\n( \\fB.+1\\fR ) \\fBz\\fR \\fIcount\\fR\n.ZP\nPrint the next \\fIcount\\fR lines, default \\fIwindow\\fR.\n.LC\n( \\fB.\\fR ) \\fBz\\fR \\fItype\\fR \\fIcount\\fR\n.ZP\nPrints a window of text with the specified line at the top.\nIf \\fItype\\fR is `\\-', the line is placed at the bottom; a `\\fB.\\fR' causes\nthe line to be placed in the center.*\nA count gives the number of lines to be displayed rather than\ndouble the number specified by the \\fIscroll\\fR option.\nOn a \\s-2CRT\\s0 the screen is cleared before display begins unless a\ncount which is less than the screen size is given.\nThe current line is left at the last line printed.\n.FS\n* Forms `z=' and `z^' also exist; `z=' places the current line in the\ncenter, surrounds it with lines of `\\-' characters and leaves the current\nline at this line.  The form `z^' prints the window before `z\\-'\nwould.  The characters `+', `^' and `\\-' may be repeated for cumulative\neffect.\nOn some v2 editors, no\n.I type\nmay be given.\n.FE\n.LC\n\\fB!\\fR \\fIcommand\\fR\\fR\n.ZP\nThe remainder of the line after the `!' character is sent to a shell\nto be executed.\nWithin the text of\n.I command\nthe characters\n`%' and `#' are expanded as in filenames and the character\n`!' is replaced with the text of the previous command.\nThus, in particular,\n`!!' repeats the last such shell escape.\nIf any such expansion is performed, the expanded line will be echoed.\nThe current line is unchanged by this command.\n.IP\nIf there has been ``[No\\ write]'' of the buffer contents since the last\nchange to the editing buffer, then a diagnostic will be printed\nbefore the command is executed as a warning.\nA single `!' is printed when the command completes.\n.LC\n( \\fIaddr\\fR , \\fIaddr\\fR ) \\fB!\\fR \\fIcommand\\fR\\fR\n.ZP\nTakes the specified address range and supplies it as\nstandard input to\n.I command ;\nthe resulting output then replaces the input lines.\n.LC\n( $ ) \\fB=\\fR\n.ZP\nPrints the line number of the\naddressed line.\nThe current line is unchanged.\n.KS\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fB>\\fR \\fIcount\\fR \\fIflags\\fR\n.br\n( \\fB.\\fR , \\fB.\\fR ) \\fB<\\fR \\fIcount\\fR \\fIflags\\fR\n.IP\nPerform intelligent shifting on the specified lines;\n\\fB<\\fR shifts left and \\fB>\\fR shifts right.\nThe quantity of shift is determined by the\n.I shiftwidth\noption and the repetition of the specification character.\nOnly whitespace (blanks and tabs) is shifted;\nno non-white characters are discarded in a left-shift.\nThe current line becomes the last line which changed due to the\nshifting.\n.KE\n.LC\n\\fB^D\\fR\n.ZP\nAn end-of-file from a terminal input scrolls through the file.\nThe\n.I scroll\noption specifies the size of the scroll, normally a half screen of text.\n.LC\n( \\fB.\\fR+1 , \\fB.\\fR+1 )\n.br\n( \\fB.\\fR+1 , \\fB.\\fR+1 )\n.ZP\nAn address alone causes the addressed lines to be printed.\nA blank line prints the next line in the file.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fB&\\fR \\fIoptions\\fR \\fIcount\\fR \\fIflags\\fR\n.ZP\nRepeats the previous\n.I substitute\ncommand.\n.LC\n( \\fB.\\fR , \\fB.\\fR ) \\fB\\s+2~\\s0\\fR \\fIoptions\\fR \\fIcount\\fR \\fIflags\\fR\n.ZP\nReplaces the previous regular expression with the previous\nreplacement pattern from a substitution.\n.NH 1\nRegular expressions and substitute replacement patterns\n.NH 2\nRegular expressions\n.PP\nA regular expression specifies a set of strings of characters.\nA member of this set of strings is said to be\n.I matched\nby the regular expression.\n.I Ex\nremembers two previous regular expressions:\nthe previous regular expression used in a\n.I substitute\ncommand\nand the previous regular expression used elsewhere\n(referred to as the previous \\fIscanning\\fR regular expression.)\nThe previous regular expression\ncan always be referred to by a NULL \\fIre\\fR, e.g. `//' or `??'.\n.NH 2\nMagic and nomagic\n.PP\nThe regular expressions allowed by\n.I ex\nare constructed in one of two ways depending on the setting of\nthe\n.I magic\noption.\nThe\n.I ex\nand\n.I vi\ndefault setting of\n.I magic\ngives quick access to a powerful set of regular expression\nmetacharacters.\nThe disadvantage of\n.I magic\nis that the user must remember that these metacharacters are\n.I magic\nand precede them with the character `\\e'\nto use them as ``ordinary'' characters.\nWith\n.I nomagic ,\n.\\\" the default for\n.\\\" .I edit ,\nregular expressions are much simpler,\nthere being only two metacharacters.\nThe power of the other metacharacters is still available by preceding\nthe (now) ordinary character with a `\\e'.\nNote that `\\e' is thus always a metacharacter.\n.PP\nThe remainder of the discussion of regular expressions assumes\nthat\nthat the setting of this option is\n.I magic .*\n.FS\n* To discern what is true with\n.I nomagic\nit suffices to remember that the only\nspecial characters in this case will be `^' at the beginning\nof a regular expression,\n`$' at the end of a regular expression,\nand `\\e'.\nWith\n.I nomagic\nthe characters `\\s+2~\\s0' and `&' also lose their special meanings\nrelated to the replacement pattern of a substitute.\n.FE\n.NH 2\nBasic regular expression summary\n.PP\nThe following basic constructs are used to construct\n.I magic\nmode regular expressions.\n.IP \\fIchar\\fR 15\nAn ordinary character matches itself.\nThe characters `^' at the beginning of a line,\n`$' at the end of line,\n`*' as any character other than the first,\n`.', `\\e', `[', and `\\s+2~\\s0' are not ordinary characters and\nmust be escaped (preceded) by `\\e' to be treated as such.\n.IP \\fB^\\fR\nAt the beginning of a pattern\nforces the match to succeed only at the beginning of a line.\n.IP \\fB$\\fR\nAt the end of a regular expression forces the match to\nsucceed only at the end of the line.\n.IP \\&\\fB.\\fR\nMatches any single character except\nthe new-line character.\n.IP \\fB\\e<\\fR\nForces the match\nto occur only at the beginning of a ``variable'' or ``word'';\nthat is, either at the beginning of a line, or just before\na letter, digit, or underline and after a character not one of\nthese.\n.IP \\fB\\e>\\fR\nSimilar to `\\e<', but matching the end of a ``variable''\nor ``word'', i.e. either the end of the line or before character\nwhich is neither a letter, nor a digit, nor the underline character.\n.IP \\fB[\\fIstring\\fB]\\fR\nMatches any (single) character in the class defined by\n.I string .\nMost characters in\n.I string\ndefine themselves.\nA pair of characters separated by `\\-' in\n.I string\ndefines the set of characters collating between the specified lower and upper\nbounds, thus `[a\\-z]' as a regular expression matches\nany (single) lower-case letter.\nIf the first character of\n.I string\nis an `^' then the construct\nmatches those characters which it otherwise would not;\nthus `[^a\\-z]' matches anything but a lower-case letter (and of course a\nnewline).\nTo place any of the characters\n`^', `[', or `\\-' in\n.I string\nyou must escape them with a preceding `\\e'.\n.\\\"\n.\\\" document extended regexps (set extended)\n.\\\"\n.NH 2\nCombining regular expression primitives\n.PP\nThe concatenation of two regular expressions matches the leftmost and\nthen longest string\nwhich can be divided with the first piece matching the first regular\nexpression and the second piece matching the second.\nAny of the (single character matching) regular expressions mentioned\nabove may be followed by the character `*' to form a regular expression\nwhich matches any number of adjacent occurrences (including 0) of characters\nmatched by the regular expression it follows.\n.PP\nThe character `\\s+2~\\s0' may be used in a regular expression,\nand matches the text which defined the replacement part\nof the last\n.I substitute\ncommand.\nA regular expression may be enclosed between the sequences\n`\\e(' and `\\e)' with side effects in the\n.I substitute\nreplacement patterns.\n.NH 2\nSubstitute replacement patterns\n.PP\nThe basic metacharacters for the replacement pattern are\n`&' and `~'; these are\ngiven as `\\e&' and `\\e~' when\n.I nomagic\nis set.\nEach instance of `&' is replaced by the characters\nwhich the regular expression matched.\nThe metacharacter `~' stands, in the replacement pattern,\nfor the defining text of the previous replacement pattern.\n.PP\nOther metasequences possible in the replacement pattern\nare always introduced by the escaping character `\\e'.\nThe sequence `\\e\\fIn\\fR' is replaced by the text matched\nby the \\fIn\\fR-th regular subexpression enclosed between\n`\\e(' and `\\e)'.*\n.FS\n* When nested, parenthesized subexpressions are present,\n\\fIn\\fR is determined by counting occurrences of `\\e(' starting from the left.\n.FE\nThe sequences `\\eu' and `\\el' cause the immediately following character in\nthe replacement to be converted to upper- or lower-case respectively\nif this character is a letter.\nThe sequences `\\eU' and `\\eL' turn such conversion on, either until\n`\\eE' or `\\ee' is encountered, or until the end of the replacement pattern.\n.de LC\n.br\n.sp .1i\n.ne 4\n.LP\n.ta 3i\n..\n.NH 1\nOption descriptions\n.PP\n.LC\n\\fBautoindent\\fR, \\fBai\\fR\tdefault: noai\n.ZP\nCan be used to ease the preparation of structured program text.\nAt the beginning of each\n.I append ,\n.I change ,\nor\n.I insert\ncommand\nor when a new line is opened\nor created by an\n.I append ,\n.I change ,\n.I insert ,\nor\n.I substitute\noperation within\n.\\\" .I open\n.\\\" or\n.I visual\nmode,\n.I ex\nlooks at the line being appended after,\nthe first line changed\nor the line inserted before and calculates the amount of whitespace\nat the start of the line.\nIt then aligns the cursor at the level of indentation so determined.\n.IP\nIf the user then types lines of text in,\nthey will continue to be justified at the displayed indenting level.\nIf more whitespace is typed at the beginning of a line,\nthe following line will start aligned with the first non-white character\nof the previous line.\nTo back the cursor up to the preceding tab stop one can hit\n\\fB^D\\fR.\nThe tab stops going backwards are defined at multiples of the\n.I shiftwidth\noption.\nYou\n.I cannot\nbackspace over the indent,\nexcept by sending an end-of-file with a \\fB^D\\fR.\n.IP\nSpecially processed in this mode is a line with no characters added\nto it, which turns into a completely blank line (the whitespace\nprovided for the\n.I autoindent\nis discarded.)\nAlso specially processed in this mode are lines beginning with\na `^' and immediately followed by a \\fB^D\\fR.\nThis causes the input to be repositioned at the beginning of the line,\nbut retaining the previous indent for the next line.\nSimilarly, a `0' followed by a \\fB^D\\fR\nrepositions at the beginning but without\nretaining the previous indent.\n.IP\n.I Autoindent\ndoesn't happen in\n.I global\ncommands or when the input is not a terminal.\n.LC\n\\fBautoprint\\fR, \\fBap\\fR\tdefault: ap\n.ZP\nCauses the current line to be printed after each\n.I delete ,\n.I copy ,\n.I join ,\n.I move ,\n.I substitute ,\n.I t ,\n.I undo ,\nor\n.I shift\ncommand.\nThis has the same effect as supplying a trailing `p'\nto each such command.\n.I Autoprint\nis suppressed in globals,\nand only applies to the last of many commands on a line.\n.LC\n\\fBautowrite\\fR, \\fBaw\\fR\tdefault: noaw\n.ZP\nCauses the contents of the buffer to be written to the current file\nif you have modified it and give a\n.I next ,\n.I rewind ,\n.I stop ,\n.I tag ,\nor\n.I !\ncommand, or a \\fB^^\\fR (switch files) or \\fB^]\\fR (tag goto) command\nin\n.I visual .\nNote, that the\n.\\\" .I edit\n.\\\" and\n.I ex\ncommand does\n.B not\nautowrite.\nIn each case, there is an equivalent way of switching when autowrite\nis set to avoid the\n.I autowrite\n(\\fIedit\\fR\nfor\n.I next ,\n.I rewind!\nfor\n.I rewind ,\n.I stop!\nfor\n.I stop ,\n.I tag!\nfor\n.I tag ,\n.I shell\nfor\n.I ! ,\nand\n\\fB:e\\ #\\fR and a \\fB:ta!\\fR command from within\n.I visual ).\n.LC\n\\fBbeautify\\fR, \\fBbf\\fR\tdefault: nobeautify\n.ZP\nCauses all control characters except tab, newline and form-feed\nto be discarded from the input.\nA complaint is registered the first time a\nbackspace character is discarded.\n.I Beautify\ndoes not apply to command input.\n.LC\n\\fBdirectory\\fR, \\fBdir\\fR\tdefault: dir=/tmp\n.ZP\nSpecifies the directory in which\n.I ex\nplaces its buffer file.\nIf this directory in not\nwritable, then the editor will exit abruptly when it fails to\ncreate its buffer there.\n.LC\n\\fBedcompatible\\fR\tdefault: noedcompatible\n.ZP\nCauses the presence or absence of\n.B g\nand\n.B c\nsuffixes on substitute commands to be remembered, and to be toggled\nby repeating the suffices.  The suffix\n.B r\nmakes the substitution be as in the\n.I ~\ncommand, instead of like\n.I & .\n.LC\n\\fBerrorbells\\fR, \\fBeb\\fR\tdefault: noeb\n.ZP\nError messages are preceded by a bell.*\n.FS\n* Bell ringing in\n.\\\" .I open\n.\\\" and\n.I visual\nmode on errors is not suppressed by setting\n.I noeb .\n.FE\nIf possible the editor always places the error message in a standout mode of the\nterminal (such as inverse video) instead of ringing the bell.\n.LC\n\\fBhardtabs\\fR, \\fBht\\fR\tdefault: ht=0\n.ZP\nGives the boundaries on which terminal hardware tabs are set (or\non which the system expands tabs).\n.LC\n\\fBignorecase\\fR, \\fBic\\fR\tdefault: noic\n.ZP\nAll upper case characters in the text are mapped to lower case in regular\nexpression matching.\nIn addition, all upper case characters in regular expressions are mapped\nto lower case except in character class specifications.\n.\\\" .LC\n.\\\" \\fBlisp\\fR\tdefault: nolisp\n.\\\" .ZP\n.\\\" \\fIAutoindent\\fR indents appropriately for\n.\\\" .I lisp\n.\\\" code, and the \\fB( ) { } [[\\fR and \\fB]]\\fR commands in\n.\\\" .I open\n.\\\" and\n.\\\" .I visual\n.\\\" are modified to have meaning for \\fIlisp\\fR.\n.LC\n\\fBlist\\fR\tdefault: nolist\n.ZP\nAll printed lines will be displayed (more) unambiguously,\nshowing tabs and end-of-lines as in the\n.I list\ncommand.\n.LC\n\\fBmagic\\fR\tdefault: magic for \\fIex\\fR and \\fIvi\\fR\n.\\\" .FS\n.\\\" \\(dg \\fINomagic\\fR for \\fIedit\\fR.\n.\\\" .FE\n.ZP\nIf\n.I nomagic\nis set, the number of regular expression metacharacters is greatly reduced,\nwith only `^' and `$' having special effects.\nIn addition the metacharacters\n`~'\nand\n`&'\nof the replacement pattern are treated as normal characters.\nAll the normal metacharacters may be made\n.I magic\nwhen\n.I nomagic\nis set by preceding them with a `\\e'.\n.LC\n\\fBmesg\\fR\tdefault: mesg\n.ZP\nCauses write permission to be turned off to the terminal\nwhile you are in visual mode, if\n.I nomesg\nis set.\n.LC\n\\fBmodeline\\fR\tdefault: nomodeline\n.ZP\nIf\n.I modeline\nis set, then the first 5 lines and the last five lines of the file\nwill be checked for ex command lines and the commands issued.\nTo be recognized as a command line, the line must have the string\n.B ex:\nor\n.B vi:\npreceded by a tab or a space.  This string may be anywhere in the\nline and anything after the\n.B :\nis interpreted as editor commands.  This option defaults to off because\nof unexpected behavior when editing files such as\n.I /etc/passwd .\n.LC\n\\fBnumber, nu\\fR\tdefault: nonumber\n.ZP\nCauses all output lines to be printed with their\nline numbers.\nIn addition each input line will be prompted for by supplying the line number\nit will have.\n.LC\n\\fBopen\\fR\tdefault: open\n.ZP\nIf \\fInoopen\\fR, the commands\n.I open\nand\n.I visual\nare not permitted.\n.\\\" This is set for\n.\\\" .I edit\n.\\\" to prevent confusion resulting from accidental entry to\n.\\\" open or visual mode.\n.LC\n\\fBoptimize, opt\\fR\tdefault: optimize\n.ZP\nThroughput of text is expedited by setting the terminal\nto not do automatic carriage returns\nwhen printing more than one (logical) line of output,\ngreatly speeding output on terminals without addressable\ncursors when text with leading whitespace is printed.\n.LC\n\\fBparagraphs,\\ para\\fR\tdefault: para=IPLPPPQPP\\ \\&LIpplpipbp\n.ZP\nSpecifies the paragraphs for the \\fB{\\fR and \\fB}\\fR operations in\n.\\\" .I open\n.\\\" and\n.I visual\nmode.\nThe pairs of characters in the option's value are the names\nof the macros which start paragraphs.\n.LC\n\\fBprompt\\fR\tdefault: prompt\n.ZP\nCommand mode input is prompted for with a `:'.\n.LC\n\\fBredraw\\fR\tdefault: noredraw\n.ZP\nThe editor simulates (using great amounts of output), an intelligent\nterminal on a dumb terminal (e.g. during insertions in\n.I visual\nthe characters to the right of the cursor position are refreshed\nas each input character is typed).\nUseful only at very high speed.\n.LC\n\\fBremap\\fP\tdefault: remap\n.ZP\nIf on, macros are repeatedly tried until they are unchanged.\nFor example, if\n.B o\nis mapped to\n.B O ,\nand\n.B O\nis mapped to\n.B I ,\nthen if\n.I remap\nis set,\n.B o\nwill map to\n.B I ,\nbut if\n.I noremap\nis set, it will map to\n.B O .\n.LC\n\\fBreport\\fR\tdefault: report=5\n.\\\" .FS\n.\\\" \\(dg 2 for \\fIedit\\fR.\n.\\\" .FE\n.ZP\nSpecifies a threshold for feedback from commands.\nAny command which modifies more than the specified number of lines\nwill provide feedback as to the scope of its changes.\nFor commands such as\n.I global ,\n.I open ,\n.I undo ,\nand\n.I visual\nwhich have potentially more far reaching scope,\nthe net change in the number of lines in the buffer is\npresented at the end of the command, subject to this same threshold.\nThus notification is suppressed during a\n.I global\ncommand on the individual commands performed.\n.LC\n\\fBscroll\\fR\tdefault: scroll=\\(12 window\n.ZP\nDetermines the number of logical lines scrolled when an end-of-file\nis received from a terminal input in command mode,\nand the number of lines printed by a command mode\n.I z\ncommand (double the value of\n.I scroll ).\n.LC\n\\fBsections\\fR\tdefault: sections=NHSHH\\ \\&HU\n.ZP\nSpecifies the section macros for the \\fB[[\\fR and \\fB]]\\fR operations\nin\n.\\\" .I open\n.\\\" and\n.I visual\nmode.\nThe pairs of characters in the options's value are the names\nof the macros which start paragraphs.\n.LC\n\\fBshell\\fR, \\fBsh\\fR\tdefault: sh=/bin/sh\n.ZP\nGives the path name of the shell forked for\nthe shell escape command `!', and by the\n.I shell\ncommand.\nThe default is taken from SHELL in the environment, if present.\n.LC\n\\fBshiftwidth\\fR, \\fBsw\\fR\tdefault: sw=8\n.ZP\nGives the width a software tab stop,\nused in reverse tabbing with \\fB^D\\fR when using\n.I autoindent\nto append text,\nand by the shift commands.\n.LC\n\\fBshowmatch, sm\\fR\tdefault: nosm\n.ZP\nIn\n.I open\nand\n.I visual\nmode, when a \\fB)\\fR or \\fB}\\fR is typed, move the cursor to the matching\n\\fB(\\fR or \\fB{\\fR for one second if this matching character is on the\nscreen.\n.\\\" Extremely useful with\n.\\\" .I lisp.\n.\\\" .LC\n.\\\" \\fBslowopen, slow\\fR\tterminal dependent\n.\\\" .ZP\n.\\\" Affects the display algorithm used in\n.\\\" .I visual\n.\\\" mode, holding off display updating during input of new text to improve\n.\\\" throughput when the terminal in use is both slow and unintelligent.\n.\\\" See\n.\\\" .I \"An Introduction to Display Editing with Vi\"\n.\\\" for more details.\n.LC\n\\fBtabstop,\\ ts\\fR\tdefault: ts=8\n.ZP\nThe editor expands tabs in the input file to be on\n.I tabstop\nboundaries for the purposes of display.\n.LC\n\\fBtaglength,\\ tl\\fR\tdefault: tl=0\n.ZP\nTags are not significant beyond this many characters.\nA value of zero (the default) means that all characters are significant.\n.LC\n\\fBtags\\fR\tdefault: tags=tags\n.ZP\nA path of files to be used as tag files for the\n.I tag\ncommand.\nA requested tag is searched for in the specified files, sequentially.\nBy default, files called\n.B tags\nare searched for in the current directory.\n.\\\" and in /usr/lib\n.\\\" (a master file for the entire system).\n.LC\n\\fBterm\\fR\tfrom environment TERM\n.ZP\nThe terminal type of the output device.\n.LC\n\\fBterse\\fR\tdefault: noterse\n.ZP\nShorter error diagnostics are produced for the experienced user.\n.LC\n\\fBwarn\\fR\tdefault: warn\n.ZP\nWarn if there has been `[No write since last change]' before a `!'\ncommand escape.\n.LC\n\\fBwindow\\fR\tfrom environment LINES\n.ZP\nThe number of lines in a text window in the\n.I visual\ncommand.\nThe default is 8 at slow speeds (600 baud or less),\n16 at medium speed (1200 baud),\nand the full screen (minus one line) at higher speeds.\n.LC\n\\fBw300,\\ w1200\\, w9600\\fR\n.ZP\nThese are not true options but set\n.B window\nonly if the speed is slow (300), medium (1200), or high (9600),\nrespectively.\nThey are suitable for an EXINIT\nand make it easy to change the 8/16/full screen rule.\n.LC\n\\fBwrapscan\\fR, \\fBws\\fR\tdefault: ws\n.ZP\nSearches using the regular expressions in addressing\nwill wrap around past the end of the file.\n.LC\n\\fBwrapmargin\\fR, \\fBwm\\fR\tdefault: wm=0\n.ZP\nDefines a margin for automatic wrapover of text during input in\n.\\\" .I open\n.\\\" and\n.I visual\nmode.  See\n.I \"An Introduction to Text Editing with Vi\"\nfor details.\n.LC\n\\fBwriteany\\fR, \\fBwa\\fR\tdefault: nowa\n.IP\nInhibit the checks normally made before\n.I write\ncommands, allowing a write to any file which the system protection\nmechanism will allow.\n.NH 1\nAcknowledgements\n.PP\nChuck Haley contributed greatly to the early development of\n.I ex .\nBruce Englar encouraged the redesign which led to\n.I ex\nversion 1.\nBill Joy wrote versions 1 and 2.0 through 2.7,\nand created the framework that users see in the present editor.\nMark Horton added macros and other features and made the\neditor work on a large number of terminals and Unix systems.\n"
  },
  {
    "path": "docs/USD.doc/exref/ex.summary",
    "content": ".\\\"        $OpenBSD: ex.summary,v 1.6 2004/01/30 23:14:26 jmc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)ex.summary        8.3 (Berkeley) 8/18/96\n.\\\"\n.ds p \\v'-0.2'.\\v'+0.2'\n.ds U \\s-2UNIX\\s+2\n.ds c \\v'-0.2':\\v'+0.2'\n.nr LL 6.5i\n.lt 6.5i\n.ll 6.5i\n.ds CH\n.ds LF Computing Services, U.C. Berkeley\n.ds RF April 3, 1979\n.de SP\n.sp 1v\n..\n.nr PI 3n\n.nr PD 0\n.ND\n.ps 12\n.ft B\n.ce 1\n.\\\" Ex/Edit Command Summary (Version 2.0)\nEx Command Summary (Version 2.0)\n.sp 1\n.ft R\n.nr VS 11\n.nr PS 9\n.\\\" .2C\n.PP\n.I Ex\n.\\\" and\n.\\\" .I edit\nis a text editor, used for creating\nand modifying files of text on the \\*U\ncomputer system.\n.\\\" .I Edit\n.\\\" is a variant of\n.\\\" .I ex\n.\\\" with features designed to\n.\\\" make it less complicated\n.\\\" to learn and use.\n.\\\" In terms of command syntax and effect\n.\\\" the editors are essentially identical,\n.\\\" and this command summary applies to both.\n.PP\nThe summary is meant as a quick reference\nfor users already acquainted\nwith\n.\\\" .I edit\n.\\\" or\n.I ex .\nA fuller explanation of the editor is available\nin the document\n.\\\" .I\n.\\\" Edit: A Tutorial\n.\\\" .R\n.\\\" (a self-teaching introduction) and the\n.I\nEx Reference Manual\n.R\n(the comprehensive reference source for\n.\\\" both \\fIedit\\fP and\n.I ex ).\n.\\\" Both of these writeups are available in the\n.\\\" Computing Services Library.\n.PP\nIn the examples included with the\nsummary, commands and text entered by\nthe user are printed in \\fBboldface\\fR to\ndistinguish them from responses printed\nby the computer.\n.sp 1v\n.LP\n.B\nThe Editor Buffer\n.PP\nIn order to perform its tasks\nthe editor sets aside a temporary\nwork space,\ncalled a \\fIbuffer\\fR,\nseparate from the user's permanent\nfile.\nBefore starting to work on an existing\nfile the editor makes a copy of it in the\nbuffer, leaving the original untouched.\nAll editing changes are made to the\nbuffer copy, which must then\nbe written back to the permanent\nfile in order to update the\nold version.\nThe buffer disappears\nat the end of the editing session.\n.sp 1v\n.LP\n.B\nEditing: Command and Text Input Modes\n.PP\n.R\nDuring an editing session there are\ntwo usual modes of operation:\n\\fIcommand\\fP mode and \\fItext input\\fP\nmode.\n(This disregards, for the moment,\n.\\\" .I open\n.\\\" and\n.I visual\nmode, discussed below.)\nIn command mode, the editor issues a\ncolon prompt (:)\nto show that it is ready to\naccept and execute a command.\nIn text input mode, on the other hand, there is\nno prompt and the editor merely accepts text to\nbe added to the buffer.\nText input mode is initiated by the commands\n\\fIappend\\fP, \\fIinsert\\fP, and \\fIchange\\fP,\nand is terminated by typing a period as the\nfirst and only character on a line.\n.sp 1v\n.LP\n.B\nLine Numbers and Command Syntax\n.PP\n.R\nThe editor keeps track of lines of text\nin the buffer by numbering them consecutively\nstarting with 1 and renumbering\nas lines are added or deleted.\nAt any given time the editor is positioned\nat one of these lines; this position is\ncalled the \\fIcurrent line\\fP.\nGenerally, commands that change the\ncontents of the buffer print the\nnew current line at the end of their\nexecution.\n.PP\nMost commands can be preceded by one or two\nline-number addresses which indicate the lines\nto be affected.\nIf one number is given the command operates on\nthat line only; if two, on an inclusive range\nof lines.\nCommands that can take line-number prefixes also\nassume default prefixes if none are given.\nThe default assumed by each command is designed\nto make it convenient to use in many instances\nwithout any line-number prefix.\nFor the most part, a command used without a\nprefix operates on the current line,\nthough exceptions to this rule should be noted.\nThe \\fIprint\\fP command\nby itself, for instance, causes\none line, the current line, to be\nprinted at the terminal.\n.PP\nThe summary shows the number of line addresses\nthat can be\nprefixed to each command as well as\nthe defaults assumed if they are omitted.\nFor example,\n.I (.,.)\nmeans that up to 2 line-numbers may be given,\nand that if none is given the\ncommand operates on the current line.\n(In the address prefix notation, ``.'' stands\nfor the current line and ``$'' stands for\nthe last line of the buffer.)\nIf no such notation appears, no\nline-number prefix may be used.\n.PP\nSome commands take trailing\ninformation;\nonly\nthe more important instances of this\nare mentioned in the summary.\n.sp 1v\n.LP\n.B\n.\\\" Open and Visual Modes\nVisual Mode\n.PP\n.R\nBesides command and text input modes,\n.I ex\n.\\\" and\n.\\\" .I edit\nprovides on some CRT terminals another mode of editing:\n.\\\" .I open\n.\\\" and\n.I visual .\nIn this mode the cursor can\nbe moved to individual words\nor characters in a line.\nThe commands then given are very different\nfrom the standard editor commands; most do not appear on the screen when\ntyped.\n.I\nAn Introduction to Display Editing with Vi\n.R\nprovides a full discussion.\n.sp 1v\n.LP\n.B\nSpecial Characters\n.PP\n.R\n.fi\nSome characters take on special meanings\nwhen used in context searches\nand in patterns given to the \\fIsubstitute\\fP command.\n.\\\" For \\fIedit\\fR, these are ``^'' and ``$'',\n.\\\" meaning the beginning and end of a line,\n.\\\" respectively.\n.I Ex\nhas the following special characters:\n.sp 1v\n.B\n.ce 1\n^     $     \\&.     &     *     [     ]     ~\n.R\n.sp 1v\nTo use one of the special characters as its\nsimple graphic representation\nrather than with its special meaning,\nprecede it by a backslash (\\\\).\nThe backslash always has a special meaning.\n.sp 1v\n.1C\n.TS\ncp10 cp10 cp10 cp10\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\nName\tAbbr\tDescription\tExamples\n.sp 1.75\n(.)\\fBappend\ta\tT{\nBegins text input mode,\nadding lines to the buffer after\nthe line specified. Appending continues\nuntil ``.'' is typed alone at the\nbeginning of a new line, followed by\na carriage return. \\fI0a\\fR places\nlines at the beginning of the buffer.\nT}\tT{\n.nf\n\\fR:\\fBa\nThree lines of text\nare added to the buffer\nafter the current line.\n\\*p\n.R\n\\*c\n.fi\nT}\n.SP\n\\fR(.,.)\\fBchange\tc\tT{\nDeletes indicated line(s) and\ninitiates text input mode to\nreplace them with new text which follows.\nNew text is terminated the same way\nas with \\fIappend\\fR.\nT}\tT{\n.nf\n:\\fB5,6c\nLines 5 and 6 are\ndeleted and replaced by\nthese three lines.\n\\*p\n.R\n\\*c\n.fi\nT}\n.SP\n\\fR(.,.)\\fBcopy \\fIaddr\tco\tT{\nPlaces a copy of the specified lines\nafter the line indicated by \\fIaddr\\fR.\nThe example places a copy of lines 8 through\n12, inclusive, after line 25.\nT}\tT{\n.nf\n\\fR:\\fB8,12co 25\\fP\nLast line copied\nis printed\n\\*c\n.fi\nT}\n.SP\n\\fR(.,.)\\fBdelete\td\tT{\nRemoves lines from the buffer\nand prints the current line after the deletion.\nT}\tT{\n.nf\n\\fR:\\fB13,15d\\fP\nNew current line\nis printed\n\\*c\n.fi\nT}\n.TE\n.sp 0.5v\n.TS\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\nT{\n\\fBedit \\fIfile\\fP\n.br\n\\fBedit! \\fIfile\\fP\nT}\tT{\ne\n.br\ne!\nT}\tT{\n.fi\n\\fRClears the editor buffer and then\ncopies into it the named \\fIfile\\fR,\nwhich becomes the current file.\nThis is a way of shifting to a different\nfile\nwithout leaving the editor.\nThe editor issues a warning\nmessage if this command is used before\nsaving changes\nmade to the file already in the buffer;\nusing the form \\fBe!\\fR overrides this protective mechanism.\nT}\tT{\n.nf\n\\fR:\\fBe ch10\\fR\nNo write since\nlast change\n:\\fBe! ch10\\fR\n\"ch10\" 3 lines,\n62 characters\n\\*c\n.fi\nT}\n.SP\n\\fBfile \\fIname\\fR\tf\tT{\n\\fRIf followed by a \\fIname\\fR, renames\nthe current file to \\fIname\\fR.\nIf used without \\fIname\\fR, prints\nthe name of the current file.\nT}\tT{\n.nf\n\\fR:\\fBf ch9\\fP\n\"ch9\" [Modified]\n3 lines ...\n:\\fBf\\fP\n\"ch9\" [Modified]\n3 lines ...\n\\*c\n.fi\nT}\n.SP\n(1,$)\\fBglobal\tg\t\\fBglobal/\\fIpattern\\fB/\\fIcommands\tT{\n.nf\n:\\fBg/nonsense/d\n\\fR\\*c\n.fi\nT}\n\\fR(1,$)\\fBglobal!\tg!\\fR or \\fBv\tT{\nSearches the entire buffer (unless a smaller\nrange is specified by line-number prefixes) and\nexecutes \\fIcommands\\fR on every line with\nan expression matching \\fIpattern\\fR.\nThe second form, abbreviated\neither \\fBg!\\fR or \\fBv\\fR,\nexecutes \\fIcommands\\fR on lines that \\fIdo\nnot\\fR contain the expression \\fIpattern\\fR.\nT}\t\\^\n.SP\n\\fR(.)\\fBinsert\ti\tT{\nInserts new lines of text immediately before the specified line.\nDiffers from\n.I append\nonly in that text is placed before, rather than after, the indicated line.\nIn other words, \\fB1i\\fR has the same effect as \\fB0a\\fR.\nT}\tT{\n.nf\n:\\fB1i\nThese lines of text\nwill be added prior\nto line 1.\n\\&.\n\\fR:\n.fi\nT}\n.SP\n\\fR(.,.+1)\\fBjoin\tj\tT{\nJoin lines together, adjusting whitespace (spaces\nand tabs) as necessary.\nT}\tT{\n.nf\n:\\fB2,5j\\fR\nResulting line is\nprinted\n:\n.fi\nT}\n.TE\n.bp\n.TS\ncp10 cp10 cp10 cp10\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\nName\tAbbr\tDescription\tExamples\n.sp 1.75v\n\\fR(.,.)\\fBlist\tl\tT{\n\\fRPrints lines in a more\nunambiguous way than the \\fIprint\\fR\ncommand does. The end of a line,\nfor example, is marked with a ``$'',\nand tabs printed as ``^I''.\nT}\tT{\n.nf\n:\\fB9l\n\\fRThis is line 9$\n\\*c\n.fi\nT}\n.TE\n.sp 0.5v\n.TS\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\n\\fR(.,.)\\fBmove \\fIaddr\\fB\tm\tT{\n\\fRMoves the specified lines\nto a position after the line\nindicated by \\fIaddr\\fR.\nT}\tT{\n.nf\n\\fR:\\fB12,15m 25\\fR\nNew current line is\nprinted\n\\*c\n.fi\nT}\n.SP\n\\fR(.,.)\\fBnumber\tnu\tT{\nPrints each line preceded\nby its buffer line number.\nT}\tT{\n.nf\n\\fR:\\fBnu\n\\0\\0\\fR10\\0 This is line 10\n\\*c\n.fi\nT}\n.\\\" .SP\n.\\\" \\fR(.)\\fBopen\to\tT{\n.\\\" Too involved to discuss here,\n.\\\" but if you enter open mode\n.\\\" accidentally, press\n.\\\" the \\s-2ESC\\s0 key followed by\n.\\\" \\fBq\\fR to\n.\\\" get back into normal editor\n.\\\" command mode.\n.\\\" \\fIEdit\\fP is designed to\n.\\\" prevent accidental use of\n.\\\" the open command.\n.\\\" T}\n.SP\n\\fBpreserve\tpre\tT{\nSaves a copy of the current buffer contents as though the system had\njust crashed.  This is for use in an emergency when a\n.I write\ncommand has failed and you don't know how else to save your work.*\nT}\tT{\n.nf\n:\\fBpreserve\\fR\nFile preserved.\n:\n.fi\nT}\n.SP\n\\fR(.,.)\\fBprint\tp\tPrints the text of line(s).\tT{\n.nf\n:\\fB+2,+3p\\fR\nThe second and third lines\nafter the current line\n:\n.fi\nT}\n.TE\n.FS\n.ll 6.5i\n* You should seek assistance from a system administrator as soon as\npossible after saving a file with the\n.I preserve\ncommand, because the preserved copy of the file is saved in a\ndirectory used to store temporary files, and thus, the preserved\ncopy may only be available for a short period of time.\n.FE\n.SP\n.nf\n.TS\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\nT{\n.nf\n\\fBquit\nquit!\n.fi\nT}\tT{\n.nf\nq\nq!\nT}\tT{\n.fi\n\\fREnds the editing session.\nYou will receive a\nwarning if you have changed the buffer\nsince last writing its contents\nto the file. In this event you\nmust either type \\fBw\\fR to write,\nor type \\fBq!\\fR to exit from\nthe editor without saving your changes.\nT}\tT{\n.nf\n\\fR:\\fBq\n\\fRNo write since last change\n:\\fBq!\n\\fR%\n.fi\nT}\n.SP\n\\fR(.)\\fBread \\fIfile\\fP\tr\tT{\n.fi\n\\fRPlaces a copy of \\fIfile\\fR in the\nbuffer after the specified line.\nAddress 0 is permissible and causes\nthe copy of \\fIfile\\fR to be placed\nat the beginning of the buffer.\nThe \\fIread\\fP command does not\nerase any text already in the buffer.\nIf no line number is specified,\n\\fIfile\\fR is placed after the\ncurrent line.\nT}\tT{\n.nf\n\\fR:\\fB0r newfile\n\\fR\"newfile\" 5 lines, 86 characters\n\\*c\n.fi\nT}\n.SP\n\\fBrecover \\fIfile\\fP\trec\tT{\n.fi\nRetrieves a copy of the editor buffer\nafter a system crash, editor crash,\nphone line disconnection, or\n\\fIpreserve\\fR command.\nT}\n.SP\n\\fR(.,.)\\fBsubstitute\ts\tT{\n.nf\n\\fBsubstitute/\\fIpattern\\fB/\\fIreplacement\\fB/\nsubstitute/\\fIpattern\\fB/\\fIreplacement\\fB/gc\n.fi\n\\fRReplaces the first occurrence of \\fIpattern\\fR\non a line\nwith \\fIreplacement\\fP.\nIncluding a \\fBg\\fR after the command\nchanges all occurrences of \\fIpattern\\fP\non the line.\nThe \\fBc\\fR option allows the user to\nconfirm each substitution before it is\nmade; see the manual for details.\nT}\tT{\n.nf\n:\\fB3p\n\\fRLine 3 contains a misstake\n:\\fBs/misstake/mistake/\n\\fRLine 3 contains a mistake\n\\*c\n.fi\nT}\n.TE\n.bp\n.TS\ncp10 cp10 cp10 cp10\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\nName\tAbbr\tDescription\tExamples\n.sp 1.75\n\\fBundo\tu\tT{\n.fi\n\\fRReverses the changes made in\nthe buffer by the last buffer-editing\ncommand.\nNote that this example contains\na notification about the number of\nlines affected.\nT}\tT{\n.nf\n\\fR:\\fB1,15d\n\\fR15 lines deleted\nnew line number 1\nis printed\n:\\fBu\n\\fR15 more lines in file ...\nold line number 1\nis printed\n\\*c\n.fi\nT}\n.SP\n\\fR(1,$)\\fBwrite \\fIfile\\fR\tw\tT{\n.fi\n\\fRCopies data from the buffer onto\na permanent file. If no \\fIfile\\fR\nis named, the current filename\nis used.\nThe file is automatically created\nif it does not yet exist.\nA response containing the number of\nlines and characters in the file\nindicates that the write\nhas been completed successfully.\nThe editor's built-in protections\nagainst overwriting existing files\nwill in some circumstances\ninhibit a write.\nThe form \\fBw!\\fR forces the\nwrite, confirming that\nan existing file is to be overwritten.\nT}\tT{\n.nf\n\\fR:\\fBw\n\\fR\"file7\" 64 lines, 1122 characters\n:\\fBw file8\n\\fR\"file8\" File exists ...\n:\\fBw! file8\n\\fR\"file8\" 64 lines, 1122 characters\n\\*c\n.fi\nT}\n\\fR(1,$)\\fBwrite! \\fIfile\\fP\tw!\t\\^\t\\^\n.TE\n.sp 0.5v\n.TS\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\n\\fR(.)\\fBz \\fIcount\\fP\tz\tT{\n.fi\n\\fRPrints a screen full of text starting\nwith the line indicated;\nor, if \\fIcount\\fR is specified,\nprints that number of lines.\nVariants of the \\fIz\\fR command\nare described in the manual.\nT}\n.SP\n\\fB!\\fIcommand\t\tT{\n.fi\nExecutes the remainder of the line\nafter \\fB!\\fR as a \\*U command.\nThe buffer is unchanged by this, and\ncontrol is returned to the editor when\nthe execution of \\fIcommand\\fR is complete.\nT}\tT{\n.nf\n\\fR:\\fB!date\n\\fRFri Jun 9 12:15:11 PDT 1978\n!\n\\*c\n.fi\nT}\n.SP\n\\fRcontrol-d\t\tT{\n.fi\nPrints the next \\fIscroll\\fR of text,\nnormally half of a screen. See the\nmanual for details of the \\fIscroll\\fR\noption.\nT}\n.SP\n\\fR(.+1)<cr>\t\tT{\n.fi\nAn address alone followed by a carriage\nreturn causes the line to be printed.\nA carriage return by itself prints the\nline following the current line.\nT}\tT{\n.nf\n:\\fR<cr>\nthe line after the current line\n\\*c\n.fi\nT}\n.TE\n.sp 0.5v\n.TS\nltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i).\n\\fB/\\fIpattern\\fB/\t\tT{\n.fi\n\\fRSearches for the next line in which\n\\fIpattern\\fR occurs and prints it.\nT}\tT{\n.nf\n\\fR:\\fB/This pattern/\n\\fRThis pattern next occurs here.\n\\*c\n.fi\nT}\n.SP\n\\fB//\t\tT{\nRepeats the most recent search.\nT}\tT{\n.nf\n\\fR:\\fB//\n\\fRThis pattern also occurs here.\n\\*c\n.fi\nT}\n.SP\n\\fB?\\fIpattern\\fB?\t\tT{\nSearches in the reverse direction\nfor \\fIpattern\\fP.\nT}\n.SP\n\\fB??\t\tT{\nRepeats the most recent search,\nmoving in the reverse direction\nthrough the buffer.\nT}\n.TE\n"
  },
  {
    "path": "docs/USD.doc/re_format/vi_regex.7",
    "content": ".\\\"         $OpenBSD: re_format.7,v 1.23 2021/07/07 11:21:55 martijn Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1997, Phillip F Knaack\n.\\\" Copyright (c) 1992, 1993, 1994 Henry Spencer\n.\\\" Copyright (c) 1992, 1993, 1994 The Regents of the University of California\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" This code is derived from software contributed to Berkeley by\n.\\\" Henry Spencer.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"         @(#)re_format.7         8.3 (Berkeley) 3/20/94\n.\\\"\n.Dd $Mdocdate: July 7 2021 $\n.Dt VI_REGEX 7\n.Os\n.Sh NAME\n.Nm vi_regex\n.Nd POSIX regular expression support\n.Sh DESCRIPTION\nRegular expressions (REs),\nas defined in\n.St -p1003.1-2004 ,\ncome in two forms:\nbasic regular expressions\n(BREs)\nand extended regular expressions\n(EREs).\n.Pp\nPOSIX leaves some aspects of RE syntax and semantics open;\n.Sq **\nmarks decisions on these aspects that\nmay not be fully portable to other POSIX implementations.\n.Pp\nThis manual page first describes regular expressions in general,\nspecifically extended regular expressions,\nand then discusses differences between them and basic regular expressions.\n.Sh EXTENDED REGULAR EXPRESSIONS\nAn ERE is one** or more non-empty**\n.Em branches ,\nseparated by\n.Sq | .\nIt matches anything that matches one of the branches.\n.Pp\nA branch is one** or more\n.Em pieces ,\nconcatenated.\nIt matches a match for the first, followed by a match for the second, etc.\n.Pp\nA piece is an\n.Em atom\npossibly followed by a single**\n.Sq * ,\n.Sq + ,\n.Sq ?\\& ,\nor\n.Em bound .\nAn atom followed by\n.Sq *\nmatches a sequence of 0 or more matches of the atom.\nAn atom followed by\n.Sq +\nmatches a sequence of 1 or more matches of the atom.\nAn atom followed by\n.Sq ?\\&\nmatches a sequence of 0 or 1 matches of the atom.\n.Pp\nA bound is\n.Sq {\nfollowed by an unsigned decimal integer,\npossibly followed by\n.Sq ,\\&\npossibly followed by another unsigned decimal integer,\nalways followed by\n.Sq } .\nThe integers must lie between 0 and\n.Dv RE_DUP_MAX\n(255**) inclusive,\nand if there are two of them, the first may not exceed the second.\nAn atom followed by a bound containing one integer\n.Ar i\nand no comma matches\na sequence of exactly\n.Ar i\nmatches of the atom.\nAn atom followed by a bound\ncontaining one integer\n.Ar i\nand a comma matches\na sequence of\n.Ar i\nor more matches of the atom.\nAn atom followed by a bound\ncontaining two integers\n.Ar i\nand\n.Ar j\nmatches a sequence of\n.Ar i\nthrough\n.Ar j\n(inclusive) matches of the atom.\n.Pp\nAn atom is a regular expression enclosed in\n.Sq ()\n(matching a part of the regular expression),\nan empty set of\n.Sq ()\n(matching the null string)**,\na\n.Em bracket expression\n(see below),\n.Sq .\\&\n(matching any single character),\n.Sq ^\n(matching the null string at the beginning of a line),\n.Sq $\n(matching the null string at the end of a line),\na\n.Sq \\e\nfollowed by one of the characters\n.Sq ^.[$()|*+?{\\e\n(matching that character taken as an ordinary character),\na\n.Sq \\e\nfollowed by any other character**\n(matching that character taken as an ordinary character,\nas if the\n.Sq \\e\nhad not been present**),\nor a single character with no other significance (matching that character).\nA\n.Sq {\nfollowed by a character other than a digit is an ordinary character,\nnot the beginning of a bound**.\nIt is illegal to end an RE with\n.Sq \\e .\n.Pp\nA bracket expression is a list of characters enclosed in\n.Sq [] .\nIt normally matches any single character from the list (but see below).\nIf the list begins with\n.Sq ^ ,\nit matches any single character\n.Em not\nfrom the rest of the list\n(but see below).\nIf two characters in the list are separated by\n.Sq - ,\nthis is shorthand for the full\n.Em range\nof characters between those two (inclusive) in the\ncollating sequence, e.g.\\&\n.Sq [0-9]\nin ASCII matches any decimal digit.\nIt is illegal** for two ranges to share an endpoint, e.g.\\&\n.Sq a-c-e .\nRanges are very collating-sequence-dependent,\nand portable programs should avoid relying on them.\n.Pp\nTo include a literal\n.Sq ]\\&\nin the list, make it the first character\n(following a possible\n.Sq ^ ) .\nTo include a literal\n.Sq - ,\nmake it the first or last character,\nor the second endpoint of a range.\nTo use a literal\n.Sq -\nas the first endpoint of a range,\nenclose it in\n.Sq [.\nand\n.Sq .]\nto make it a collating element (see below).\nWith the exception of these and some combinations using\n.Sq \\&[\n(see next paragraphs),\nall other special characters, including\n.Sq \\e ,\nlose their special significance within a bracket expression.\n.Pp\nWithin a bracket expression, a collating element\n(a character,\na multi-character sequence that collates as if it were a single character,\nor a collating-sequence name for either)\nenclosed in\n.Sq [.\nand\n.Sq .]\nstands for the sequence of characters of that collating element.\nThe sequence is a single element of the bracket expression's list.\nA bracket expression containing a multi-character collating element\ncan thus match more than one character,\ne.g. if the collating sequence includes a\n.Sq ch\ncollating element,\nthen the RE\n.Sq [[.ch.]]*c\nmatches the first five characters of\n.Sq chchcc .\n.Pp\nWithin a bracket expression, a collating element enclosed in\n.Sq [=\nand\n.Sq =]\nis an equivalence class, standing for the sequences of characters\nof all collating elements equivalent to that one, including itself.\n(If there are no other equivalent collating elements,\nthe treatment is as if the enclosing delimiters were\n.Sq [.\nand\n.Sq .] . )\nFor example, if\n.Sq x\nand\n.Sq y\nare the members of an equivalence class,\nthen\n.Sq [[=x=]] ,\n.Sq [[=y=]] ,\nand\n.Sq [xy]\nare all synonymous.\nAn equivalence class may not** be an endpoint of a range.\n.Pp\nWithin a bracket expression, the name of a\n.Em character class\nenclosed\nin\n.Sq [:\nand\n.Sq :]\nstands for the list of all characters belonging to that class.\nStandard character class names are:\n.Bd -literal -offset indent\nalnum\tdigit\tpunct\nalpha\tgraph\tspace\nblank\tlower\tupper\ncntrl\tprint\txdigit\n.Ed\n.Pp\nThese stand for the character classes defined in\n.Xr isalnum 3 ,\n.Xr isalpha 3 ,\nand so on.\nA character class may not be used as an endpoint of a range.\n.Pp\nThere are two special cases** of bracket expressions:\nthe bracket expressions\n.Sq [[:<:]]\nand\n.Sq [[:>:]]\nmatch the null string at the beginning and end of a word, respectively.\nA word is defined as a sequence of\ncharacters starting and ending with a word character\nwhich is neither preceded nor followed by\nword characters.\nA word character is an\n.Em alnum\ncharacter (as defined by\n.Xr isalnum 3 )\nor an underscore.\nThis is an extension,\ncompatible with but not specified by POSIX,\nand should be used with\ncaution in software intended to be portable to other systems.\nThe additional word delimiters\n.Ql \\e<\nand\n.Ql \\e>\nare provided to ease compatibility with traditional SVR4\nsystems but are not portable and should be avoided.\n.Pp\nIn the event that an RE could match more than one substring of a given\nstring,\nthe RE matches the one starting earliest in the string.\nIf the RE could match more than one substring starting at that point,\nit matches the longest.\nSubexpressions also match the longest possible substrings, subject to\nthe constraint that the whole match be as long as possible,\nwith subexpressions starting earlier in the RE taking priority over\nones starting later.\nNote that higher-level subexpressions thus take priority over\ntheir lower-level component subexpressions.\n.Pp\nMatch lengths are measured in characters, not collating elements.\nA null string is considered longer than no match at all.\nFor example,\n.Sq bb*\nmatches the three middle characters of\n.Sq abbbc ;\n.Sq (wee|week)(knights|nights)\nmatches all ten characters of\n.Sq weeknights ;\nwhen\n.Sq (.*).*\nis matched against\n.Sq abc ,\nthe parenthesized subexpression matches all three characters;\nand when\n.Sq (a*)*\nis matched against\n.Sq bc ,\nboth the whole RE and the parenthesized subexpression match the null string.\n.Pp\nIf case-independent matching is specified,\nthe effect is much as if all case distinctions had vanished from the\nalphabet.\nWhen an alphabetic that exists in multiple cases appears as an\nordinary character outside a bracket expression, it is effectively\ntransformed into a bracket expression containing both cases,\ne.g.\\&\n.Sq x\nbecomes\n.Sq [xX] .\nWhen it appears inside a bracket expression,\nall case counterparts of it are added to the bracket expression,\nso that, for example,\n.Sq [x]\nbecomes\n.Sq [xX]\nand\n.Sq [^x]\nbecomes\n.Sq [^xX] .\n.Pp\nNo particular limit is imposed on the length of REs**.\nPrograms intended to be portable should not employ REs longer\nthan 256 bytes,\nas an implementation can refuse to accept such REs and remain\nPOSIX-compliant.\n.Pp\nThe following is a list of extended regular expressions:\n.Bl -tag -width Ds\n.It Ar c\nAny character\n.Ar c\nnot listed below matches itself.\n.It \\e Ns Ar c\nAny backslash-escaped character\n.Ar c\nmatches itself.\n.It \\&.\nMatches any single character that is not a newline\n.Pq Sq \\en .\n.It Bq Ar char-class\nMatches any single character in\n.Ar char-class .\nTo include a\n.Ql \\&]\nin\n.Ar char-class ,\nit must be the first character.\nA range of characters may be specified by separating the end characters\nof the range with a\n.Ql - ;\ne.g.\\&\n.Ar a-z\nspecifies the lower case characters.\nThe following literal expressions can also be used in\n.Ar char-class\nto specify sets of characters:\n.Bd -unfilled -offset indent\n[:alnum:] [:cntrl:] [:lower:] [:space:]\n[:alpha:] [:digit:] [:print:] [:upper:]\n[:blank:] [:graph:] [:punct:] [:xdigit:]\n.Ed\n.Pp\nIf\n.Ql -\nappears as the first or last character of\n.Ar char-class ,\nthen it matches itself.\nAll other characters in\n.Ar char-class\nmatch themselves.\n.Pp\nPatterns in\n.Ar char-class\nof the form\n.Eo [.\n.Ar col-elm\n.Ec .]\\&\nor\n.Eo [=\n.Ar col-elm\n.Ec =]\\& ,\nwhere\n.Ar col-elm\nis a collating element, are interpreted according to\n.Xr setlocale 3\n.Pq not currently supported .\n.It Bq ^ Ns Ar char-class\nMatches any single character, other than newline, not in\n.Ar char-class .\n.Ar char-class\nis defined as above.\n.It ^\nIf\n.Sq ^\nis the first character of a regular expression, then it\nanchors the regular expression to the beginning of a line.\nOtherwise, it matches itself.\n.It $\nIf\n.Sq $\nis the last character of a regular expression,\nit anchors the regular expression to the end of a line.\nOtherwise, it matches itself.\n.It [[:<:]]\nAnchors the single character regular expression or subexpression\nimmediately following it to the beginning of a word.\n.It [[:>:]]\nAnchors the single character regular expression or subexpression\nimmediately preceding it to the end of a word.\n.It Pq Ar re\nDefines a subexpression\n.Ar re .\nAny set of characters enclosed in parentheses\nmatches whatever the set of characters without parentheses matches\n(that is a long-winded way of saying the constructs\n.Sq (re)\nand\n.Sq re\nmatch identically).\n.It *\nMatches the single character regular expression or subexpression\nimmediately preceding it zero or more times.\nIf\n.Sq *\nis the first character of a regular expression or subexpression,\nthen it matches itself.\nThe\n.Sq *\noperator sometimes yields unexpected results.\nFor example, the regular expression\n.Ar b*\nmatches the beginning of the string\n.Qq abbb\n(as opposed to the substring\n.Qq bbb ) ,\nsince a null match is the only leftmost match.\n.It +\nMatches the singular character regular expression\nor subexpression immediately preceding it\none or more times.\n.It ?\nMatches the singular character regular expression\nor subexpression immediately preceding it\n0 or 1 times.\n.Sm off\n.It Xo\n.Pf { Ar n , m No }\\ \\&\n.Pf { Ar n , No }\\ \\&\n.Pf { Ar n No }\n.Xc\n.Sm on\nMatches the single character regular expression or subexpression\nimmediately preceding it at least\n.Ar n\nand at most\n.Ar m\ntimes.\nIf\n.Ar m\nis omitted, then it matches at least\n.Ar n\ntimes.\nIf the comma is also omitted, then it matches exactly\n.Ar n\ntimes.\n.It |\nUsed to separate patterns.\nFor example,\nthe pattern\n.Sq cat|dog\nmatches either\n.Sq cat\nor\n.Sq dog .\n.El\n.Sh BASIC REGULAR EXPRESSIONS\nBasic regular expressions differ in several respects:\n.Bl -bullet -offset 3n\n.It\nThe delimiters for bounds are\n.Sq \\e{\nand\n.Sq \\e} ,\nwith\n.Sq {\nand\n.Sq }\nby themselves ordinary characters.\n.It\n.Sq | ,\n.Sq + ,\nand\n.Sq ?\\&\nare ordinary characters.\n.Sq \\e{1,\\e}\nis equivalent to\n.Sq + .\n.Sq \\e{0,1\\e}\nis equivalent to\n.Sq ?\\& .\nThere is no equivalent for\n.Sq | .\n.It\nThe parentheses for nested subexpressions are\n.Sq \\e(\nand\n.Sq \\e) ,\nwith\n.Sq \\&(\nand\n.Sq )\\&\nby themselves ordinary characters.\n.It\n.Sq ^\nis an ordinary character except at the beginning of the\nRE or** the beginning of a parenthesized subexpression.\n.It\n.Sq $\nis an ordinary character except at the end of the\nRE or** the end of a parenthesized subexpression.\n.It\n.Sq *\nis an ordinary character if it appears at the beginning of the\nRE or the beginning of a parenthesized subexpression\n(after a possible leading\n.Sq ^ ) .\n.It\nFinally, there is one new type of atom, a\n.Em back-reference :\n.Sq \\e\nfollowed by a non-zero decimal digit\n.Ar d\nmatches the same sequence of characters matched by the\n.Ar d Ns th\nparenthesized subexpression\n(numbering subexpressions by the positions of their opening parentheses,\nleft to right),\nso that, for example,\n.Sq \\e([bc]\\e)\\e1\nmatches\n.Sq bb\\&\nor\n.Sq cc\nbut not\n.Sq bc .\n.El\n.Pp\nThe following is a list of basic regular expressions:\n.Bl -tag -width Ds\n.It Ar c\nAny character\n.Ar c\nnot listed below matches itself.\n.It \\e Ns Ar c\nAny backslash-escaped character\n.Ar c ,\nexcept for\n.Sq { ,\n.Sq } ,\n.Sq \\&( ,\nand\n.Sq \\&) ,\nmatches itself.\n.It \\&.\nMatches any single character that is not a newline\n.Pq Sq \\en .\n.It Bq Ar char-class\nMatches any single character in\n.Ar char-class .\nTo include a\n.Ql \\&]\nin\n.Ar char-class ,\nit must be the first character.\nA range of characters may be specified by separating the end characters\nof the range with a\n.Ql - ;\ne.g.\\&\n.Ar a-z\nspecifies the lower case characters.\nThe following literal expressions can also be used in\n.Ar char-class\nto specify sets of characters:\n.Bd -unfilled -offset indent\n[:alnum:] [:cntrl:] [:lower:] [:space:]\n[:alpha:] [:digit:] [:print:] [:upper:]\n[:blank:] [:graph:] [:punct:] [:xdigit:]\n.Ed\n.Pp\nIf\n.Ql -\nappears as the first or last character of\n.Ar char-class ,\nthen it matches itself.\nAll other characters in\n.Ar char-class\nmatch themselves.\n.Pp\nPatterns in\n.Ar char-class\nof the form\n.Eo [.\n.Ar col-elm\n.Ec .]\\&\nor\n.Eo [=\n.Ar col-elm\n.Ec =]\\& ,\nwhere\n.Ar col-elm\nis a collating element, are interpreted according to\n.Xr setlocale 3\n.Pq not currently supported .\n.It Bq ^ Ns Ar char-class\nMatches any single character, other than newline, not in\n.Ar char-class .\n.Ar char-class\nis defined as above.\n.It ^\nIf\n.Sq ^\nis the first character of a regular expression, then it\nanchors the regular expression to the beginning of a line.\nOtherwise, it matches itself.\n.It $\nIf\n.Sq $\nis the last character of a regular expression,\nit anchors the regular expression to the end of a line.\nOtherwise, it matches itself.\n.It [[:<:]]\nAnchors the single character regular expression or subexpression\nimmediately following it to the beginning of a word.\n.It [[:>:]]\nAnchors the single character regular expression or subexpression\nimmediately following it to the end of a word.\n.It \\e( Ns Ar re Ns \\e)\nDefines a subexpression\n.Ar re .\nSubexpressions may be nested.\nA subsequent backreference of the form\n.Pf \\e Ar n ,\nwhere\n.Ar n\nis a number in the range [1,9], expands to the text matched by the\n.Ar n Ns th\nsubexpression.\nFor example, the regular expression\n.Ar \\e(.*\\e)\\e1\nmatches any string consisting of identical adjacent substrings.\nSubexpressions are ordered relative to their left delimiter.\n.It *\nMatches the single character regular expression or subexpression\nimmediately preceding it zero or more times.\nIf\n.Sq *\nis the first character of a regular expression or subexpression,\nthen it matches itself.\nThe\n.Sq *\noperator sometimes yields unexpected results.\nFor example, the regular expression\n.Ar b*\nmatches the beginning of the string\n.Qq abbb\n(as opposed to the substring\n.Qq bbb ) ,\nsince a null match is the only leftmost match.\n.Sm off\n.It Xo\n.Pf \\e{ Ar n , m No \\e}\\ \\&\n.Pf \\e{ Ar n , No \\e}\\ \\&\n.Pf \\e{ Ar n No \\e}\n.Xc\n.Sm on\nMatches the single character regular expression or subexpression\nimmediately preceding it at least\n.Ar n\nand at most\n.Ar m\ntimes.\nIf\n.Ar m\nis omitted, then it matches at least\n.Ar n\ntimes.\nIf the comma is also omitted, then it matches exactly\n.Ar n\ntimes.\n.El\n.Sh SEE ALSO\n.Xr regex 3\n.Sh STANDARDS\n.St -p1003.1-2004 :\nBase Definitions, Chapter 9 (Regular Expressions).\n.Sh BUGS\nHaving two kinds of REs is a botch.\n.Pp\nThe current POSIX spec says that\n.Sq )\\&\nis an ordinary character in the absence of an unmatched\n.Sq \\&( ;\nthis was an unintentional result of a wording error,\nand change is likely.\nAvoid relying on it.\n.Pp\nBack-references are a dreadful botch,\nposing major problems for efficient implementations.\nThey are also somewhat vaguely defined\n(does\n.Sq a\\e(\\e(b\\e)*\\e2\\e)*d\nmatch\n.Sq abbbd ? ) .\nAvoid using them.\n.Pp\nPOSIX's specification of case-independent matching is vague.\nThe\n.Dq one case implies all cases\ndefinition given above\nis the current consensus among implementers as to the right interpretation.\n.Pp\nThe syntax for word boundaries is incredibly ugly.\n"
  },
  {
    "path": "docs/USD.doc/vi.man/vi.1",
    "content": ".\\\"        $OpenBSD: vi.1,v 1.83 2023/01/29 09:28:57 otto Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1994\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 1994, 1995, 1996\n.\\\"        Keith Bostic.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" The vi program is freely redistributable.\n.\\\" You are welcome to copy, modify and share it with others\n.\\\" under the conditions listed in the LICENSE.md file.\n.\\\"\n.\\\" If any company (not individual!) finds vi sufficiently useful\n.\\\" that you would have purchased it, or if any company wishes to\n.\\\" redistribute it, contributions to the authors would be appreciated.\n.\\\"\n.\\\"        @(#)vi.1        8.51 (Berkeley) 10/10/96\n.\\\"\n.Dd $Mdocdate: January 29 2023 $\n.Dt VI 1\n.Os\n.Sh NAME\n.Nm ex , vi , view\n.Nd text editors\n.Sh SYNOPSIS\n.Nm ex\n.Op Fl FRrSsv\n.Op Fl c Ar cmd\n.Op Fl t Ar tag\n.Op Fl w Ar size\n.Op Ar\n.Nm vi\\ \\&\n.Op Fl eFRrS\n.Op Fl c Ar cmd\n.Op Fl t Ar tag\n.Op Fl w Ar size\n.Op Ar\n.Nm view\n.Op Fl eFrS\n.Op Fl c Ar cmd\n.Op Fl t Ar tag\n.Op Fl w Ar size\n.Op Ar\n.Sh DESCRIPTION\n.Nm ex\nis a line-oriented text editor;\n.Nm vi\nis a screen-oriented text editor.\n.Nm ex\nand\n.Nm vi\nare different interfaces to the same program,\nand it is possible to switch back and forth during an edit session.\n.Nm view\nis the equivalent of using the\n.Fl R\n.Pq read-only\noption of\n.Nm vi .\n.Pp\nThis manual page is the one provided with the\n.Nm oex Ns / Ns Nm ovi\nversions of the\n.Nm oex Ns / Ns Nm vi\ntext editors.\n.Nm oex Ns / Ns Nm ovi\nare intended as enhanced but compatible replacements for the original\nFourth Berkeley Software Distribution\n.Pq 4BSD\n.Nm ex\nand\n.Nm vi\nprograms.\nFor the rest of this manual page,\n.Nm oex Ns / Ns Nm ovi\nis used only when it's necessary to distinguish it from the historic\nimplementations of\n.Nm ex Ns / Ns Nm vi .\n.Pp\nThis manual page is intended for users already familiar with\n.Nm ex Ns / Ns Nm vi .\nAnyone else should almost certainly read a good tutorial on the\neditor before this manual page.\nIf you're in an unfamiliar environment,\nand you absolutely have to get work done immediately,\nread the section after the options description, entitled\n.Sx FAST STARTUP .\nIt's probably enough to get you going.\n.Pp\nThe following options are available:\n.Bl -tag -width \"-w size \"\n.It Fl c Ar cmd\nExecute\n.Ar cmd\non the first file loaded.\nParticularly useful for initial positioning in the file, although\n.Ar cmd\nis not limited to positioning commands.\nThis is the POSIX 1003.2 interface for the historic\n.Dq +cmd\nsyntax.\n.Nm oex Ns / Ns Nm ovi\nsupports both the old and new syntax.  This option is mutally\nexclusive with the\n.Dq -C\noption.\n.It Fl C Ar cmd\nExecute\n.Ar cmd\nwhen starting, like using\n.Dq -c\nbut the command is executed even\nif a file is not loaded, just after processing any startup commands\nfrom the\n.Pa $HOME/.nexrc\nor\n.Pa $HOME/.exrc\nfiles.  This option is mutually exclusive with the\n.Dq -c\noption.\n.It Fl e\nStart editing in ex mode, as if the command name were\n.Nm ex .\n.It Fl F\nDon't copy the entire file when first starting to edit.\n(The default is to make a copy in case someone else modifies\nthe file during your edit session.)\n.It Fl R\nStart editing in read-only mode, as if the command name was\n.Nm view ,\nor the\n.Cm readonly\noption was set.\n.It Fl r\nRecover the specified files or, if no files are specified,\nlist the files that could be recovered.\nIf no recoverable files by the specified name exist,\nthe file is edited as if the\n.Fl r\noption had not been specified.\n.It Fl S\nRun with the\n.Cm secure\nedit option set, disallowing all access to external programs.\n.It Fl s\nEnter batch mode; applicable only to\n.Nm ex\nedit sessions.\nBatch mode is useful when running\n.Nm ex\nscripts.\nPrompts, informative messages and other user oriented messages are turned off,\nand no startup files or environment variables are read.\nThis is the POSIX 1003.2 interface for the historic\n.Dq -\nargument.\n.Nm oex Ns / Ns Nm ovi\nsupports both the old and new syntax.\n.It Fl t Ar tag\nStart editing at the specified\n.Ar tag\n(see\n.Xr ctags 1 ) .\n.It Fl v\nStart editing in vi mode, as if the command name was\n.Nm vi .\n.It Fl w Ar size\nSet the initial window size to the specified number of lines.\n.El\n.Pp\nCommand input for\n.Nm ex Ns / Ns Nm vi\nis read from the standard input.\nIn the\n.Nm vi\ninterface, it is an error if standard input is not a terminal.\nIn the\n.Nm ex\ninterface, if standard input is not a terminal,\n.Nm ex\nwill read commands from it regardless; however, the session will be a\nbatch mode session, exactly as if the\n.Fl s\noption had been specified.\n.Sh FAST STARTUP\nThis section will tell you the minimum amount that you need to\ndo simple editing tasks using\n.Nm vi .\nIf you've never used any screen editor before,\nyou're likely to have problems even with this simple introduction.\nIn that case you should find someone that already knows\n.Nm vi\nand have them walk you through this section.\n.Pp\n.Nm vi\nis a screen editor.\nThis means that it takes up almost the entire screen,\ndisplaying part of the file on each screen line,\nexcept for the last line of the screen.\nThe last line of the screen is used for you to give commands to\n.Nm vi ,\nand for\n.Nm vi\nto give information to you.\n.Pp\nThe other fact that you need to understand is that\n.Nm vi\nis a modeful editor,\ni.e. you are either entering text or you are executing commands,\nand you have to be in the right mode to do one or the other.\nYou will be in command mode when you first start editing a file.\nThere are commands that switch you into input mode.\nThere is only one key that takes you out of input mode,\nand that is the\n.Aq escape\nkey.\n.Pp\nKey names are written using angle brackets, e.g.\\&\n.Aq escape\nmeans the\n.Dq escape\nkey, usually labeled\n.Dq Esc\non your\nterminal's keyboard.\nIf you're ever confused as to which mode you're in,\nkeep entering the\n.Aq escape\nkey until\n.Nm vi\nbeeps at you.\nGenerally,\n.Nm vi\nwill beep at you if you try and do something that's not allowed.\nIt will also display error messages.\n.Pp\nTo start editing a file, enter the following command:\n.Pp\n.Dl $ vi file\n.Pp\nThe command you should enter as soon as you start editing is:\n.Pp\n.Dl :set verbose showmode\n.Pp\nThis will make the editor give you verbose error messages and display\nthe current mode at the bottom of the screen.\n.Pp\nThe commands to move around the file are:\n.Bl -tag -width Ds\n.It Cm h\nMove the cursor left one character.\n.It Cm j\nMove the cursor down one line.\n.It Cm k\nMove the cursor up one line.\n.It Cm l\nMove the cursor right one character.\n.It Aq Cm cursor-arrows\nThe cursor arrow keys should work, too.\n.It Cm / Ns text\nSearch for the string\n.Dq text\nin the file,\nand move the cursor to its first character.\n.El\n.Pp\nThe commands to enter new text are:\n.Bl -tag -width \"<escape>\"\n.It Cm a\nAppend new text, after the cursor.\n.It Cm i\nInsert new text, before the cursor.\n.It Cm O\nOpen a new line above the line the cursor is on, and start entering text.\n.It Cm o\nOpen a new line below the line the cursor is on, and start entering text.\n.It Aq Cm escape\nOnce you've entered input mode using one of the\n.Cm a ,\n.Cm i ,\n.Cm O\nor\n.Cm o\ncommands, use\n.Aq Cm escape\nto quit entering text and return to command mode.\n.El\n.Pp\nThe commands to copy text are:\n.Bl -tag -width Ds\n.It Cm p\nAppend the copied line after the line the cursor is on.\n.It Cm yy\nCopy the line the cursor is on.\n.El\n.Pp\nThe commands to delete text are:\n.Bl -tag -width Ds\n.It Cm dd\nDelete the line the cursor is on.\n.It Cm x\nDelete the character the cursor is on.\n.El\n.Pp\nThe commands to write the file are:\n.Bl -tag -width Ds\n.It Cm :w\nWrite the file back to the file with the name that you originally used\nas an argument on the\n.Nm vi\ncommand line.\n.It Cm :w Ar file_name\nWrite the file back to the file with the name\n.Ar file_name .\n.El\n.Pp\nThe commands to quit editing and exit the editor are:\n.Bl -tag -width Ds\n.It Cm :q\nQuit editing and leave\n.Nm vi\n(if you've modified the file, but not saved your changes,\n.Nm vi\nwill refuse to quit).\n.It Cm :q!\nQuit, discarding any modifications that you may have made.\n.El\n.Pp\nOne final caution:\nUnusual characters can take up more than one column on the screen,\nand long lines can take up more than a single screen line.\nThe above commands work on\n.Dq physical\ncharacters and lines,\ni.e. they affect the entire line no matter how many screen lines it takes up\nand the entire character no matter how many screen columns it takes up.\n.Sh REGULAR EXPRESSIONS\n.Nm ex Ns / Ns Nm vi\nsupports regular expressions\n.Pq REs ,\nas documented in\n.Xr re_format 7 ,\nfor line addresses, as the first part of the\n.Nm ex Cm substitute ,\n.Cm global\nand\n.Cm v\ncommands, and in search patterns.\nBasic regular expressions\n.Pq BREs\nare enabled by default;\nextended regular expressions\n.Pq EREs\nare used if the\n.Cm extended\noption is enabled.\nThe use of regular expressions can be largely disabled using the\n.Cm magic\noption.\n.Pp\nThe following strings have special meanings in the\n.Nm ex Ns / Ns Nm vi\nversion of regular expressions:\n.Bl -bullet -offset 6u\n.It\nAn empty regular expression is equivalent to the last regular expression used.\n.It\n.Sq \\e<\nmatches the beginning of the word.\n.It\n.Sq \\e>\nmatches the end of the word.\n.It\n.Sq ~\nmatches the replacement part of the last\n.Cm s\ncommand.\n.El\n.Sh BUFFERS\nA buffer is an area where commands can save changed or deleted text\nfor later use.\n.Nm vi\nbuffers are named with a single character preceded by a double quote,\nfor example\n.Pf \\&\" Aq c ;\n.Nm ex\nbuffers are the same,\nbut without the double quote.\n.Nm oex Ns / Ns Nm ovi\npermits the use of any character without another meaning in the position where\na buffer name is expected.\n.Pp\nAll buffers are either in\n.Em line mode\nor\n.Em character mode .\nInserting a buffer in line mode into the text creates new lines for each of the\nlines it contains, while a buffer in character mode creates new lines for any\nlines\n.Em other\nthan the first and last lines it contains.\nThe first and last lines are inserted at the current cursor position, becoming\npart of the current line.\nIf there is more than one line in the buffer,\nthe current line itself will be split.\nAll\n.Nm ex\ncommands which store text into buffers do so in line mode.\nThe behaviour of\n.Nm vi\ncommands depend on their associated motion command:\n.Bl -bullet -offset 6u\n.It\n.Aq Cm control-A ,\n.Cm h ,\n.Cm l ,\n.Cm ,\\& ,\n.Cm 0 ,\n.Cm B ,\n.Cm E ,\n.Cm F ,\n.Cm T ,\n.Cm W ,\n.Cm ^ ,\n.Cm b ,\n.Cm e ,\n.Cm f\nand\n.Cm t\nmake the destination buffer character-oriented.\n.It\n.Cm j ,\n.Aq Cm control-M ,\n.Cm k ,\n.Cm ' ,\n.Cm - ,\n.Cm G ,\n.Cm H ,\n.Cm L ,\n.Cm M ,\n.Cm _\nand\n.Cm |\\&\nmake the destination buffer line-oriented.\n.It\n.Cm $ ,\n.Cm % ,\n.Cm ` ,\n.Cm (\\& ,\n.Cm )\\& ,\n.Cm / ,\n.Cm ?\\& ,\n.Cm [[ ,\n.Cm ]] ,\n.Cm {\nand\n.Cm }\nmake the destination buffer character-oriented, unless the starting and\nend positions are the first and last characters on a line.\nIn that case, the buffer is line-oriented.\n.El\n.Pp\nThe\n.Nm ex\ncommand\n.Cm display buffers\ndisplays the current mode for each buffer.\n.Pp\nBuffers named\n.Sq a\nthrough\n.Sq z\nmay be referred to using their uppercase equivalent, in which case new content\nwill be appended to the buffer, instead of replacing it.\n.Pp\nBuffers named\n.Sq 1\nthrough\n.Sq 9\nare special.\nA region of text modified using the\n.Cm c\n.Pq change\nor\n.Cm d\n.Pq delete\ncommands is placed into the numeric buffer\n.Sq 1\nif no other buffer is specified and if it meets one of the following conditions:\n.Bl -bullet -offset 6u\n.It\nIt includes characters from more than one line.\n.It\nIt is specified using a line-oriented motion.\n.It\nIt is specified using one of the following motion commands:\n.Aq Cm control-A ,\n.Cm ` Ns Aq Cm character ,\n.Cm n ,\n.Cm N ,\n.Cm % ,\n.Cm / ,\n.Cm { ,\n.Cm } ,\n.Cm \\&( ,\n.Cm \\&) ,\nand\n.Cm \\&? .\n.El\n.Pp\nBefore this copy is done, the previous contents of buffer\n.Sq 1\nare moved into buffer\n.Sq 2 ,\n.Sq 2\ninto buffer\n.Sq 3 ,\nand so on.\nThe contents of buffer\n.Sq 9\nare discarded.\nNote that this rotation occurs\n.Em regardless\nof the user specifying another buffer.\nIn\n.Nm vi ,\ntext may be explicitly stored into the numeric buffers.\nIn this case, the buffer rotation occurs before the replacement of the buffer's\ncontents.\nThe numeric buffers are only available in\n.Nm vi\nmode.\n.Sh VI COMMANDS\nThe following section describes the commands available in the command\nmode of the\n.Nm vi\neditor.\nThe following words have a special meaning in the commands\ndescription:\n.Pp\n.Bl -tag -width bigword -compact -offset 3u\n.It Ar bigword\nA set of non-whitespace characters.\n.It Ar buffer\nTemporary area where commands may place text.\nIf not specified, the default buffer is used.\nSee also\n.Sx BUFFERS ,\nabove.\n.It Ar count\nA positive number used to specify the desired number of iterations\nof a command.\nIt defaults to 1 if not specified.\n.It Ar motion\nA cursor movement command which indicates the other end of the affected region\nof text, the first being the current cursor position.\nRepeating the command character makes it affect the whole\ncurrent line.\n.It Ar word\nA sequence of letters, digits or underscores.\n.El\n.Pp\n.Ar buffer\nand\n.Ar count ,\nif both present, may be specified in any order.\n.Ar motion\nand\n.Ar count ,\nif both present, are effectively multiplied together\nand considered part of the motion.\n.Pp\n.Bl -tag -width Ds -compact\n.It Xo\n.Aq Cm control-A\n.Xc\nSearch forward\nfor the word starting at the cursor position.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-B\n.Xc\nPage backwards\n.Ar count\nscreens.\nTwo lines of overlap are maintained, if possible.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-D\n.Xc\nScroll forward\n.Ar count\nlines.\nIf\n.Ar count\nis not given, scroll forward the number of lines specified by the last\n.Aq Cm control-D\nor\n.Aq Cm control-U\ncommand.\nIf this is the first\n.Aq Cm control-D\ncommand, scroll half the number of lines in the current screen.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-E\n.Xc\nScroll forward\n.Ar count\nlines, leaving the current line and column as is, if possible.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-F\n.Xc\nPage forward\n.Ar count\nscreens.\nTwo lines of overlap are maintained, if possible.\n.Pp\n.It Aq Cm control-G\nDisplay the following file information:\nthe file name (as given to\n.Nm vi ) ;\nwhether the file has been modified since it was last written;\nif the file is readonly;\nthe current line number;\nthe total number of lines in the file;\nand the current line number as a percentage of the total lines in the file.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-H\n.Xc\n.It Xo\n.Op Ar count\n.Cm h\n.Xc\nMove the cursor back\n.Ar count\ncharacters in the current line.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-J\n.Xc\n.It Xo\n.Op Ar count\n.Aq Cm control-N\n.Xc\n.It Xo\n.Op Ar count\n.Cm j\n.Xc\nMove the cursor down\n.Ar count\nlines without changing the current column.\n.Pp\n.It Aq Cm control-L\n.It Aq Cm control-R\nRepaint the screen.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-M\n.Xc\n.It Xo\n.Op Ar count\n.Cm +\n.Xc\nMove the cursor down\n.Ar count\nlines to the first non-blank character of that line.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-P\n.Xc\n.It Xo\n.Op Ar count\n.Cm k\n.Xc\nMove the cursor up\n.Ar count\nlines, without changing the current column.\n.Pp\n.It Aq Cm control-T\nReturn to the most recent tag context.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-U\n.Xc\nScroll backwards\n.Ar count\nlines.\nIf\n.Ar count\nis not given, scroll backwards the number of lines specified by the last\n.Aq Cm control-D\nor\n.Aq Cm control-U\ncommand.\nIf this is the first\n.Aq Cm control-U\ncommand, scroll half the number of lines in the current screen.\n.Pp\n.It Aq Cm control-W\nSwitch to the next lower screen in the window,\nor to the first screen if there are no lower screens in the window.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm control-Y\n.Xc\nScroll backwards\n.Ar count\nlines, leaving the current line and column as is, if possible.\n.Pp\n.It Aq Cm control-Z\nSuspend the current editor session.\n.Pp\n.It Aq Cm escape\nExecute the\n.Nm ex\ncommand being entered, or cancel it if it is only partial.\n.Pp\n.It Aq Cm control-]\nPush a tag reference onto the tag stack.\n.Pp\n.It Aq Cm control-^\nSwitch to the most recently edited file.\n.Pp\n.It Xo\n.Op Ar count\n.Aq Cm space\n.Xc\n.It Xo\n.Op Ar count\n.Cm l\n.Xc\nMove the cursor forward\n.Ar count\ncharacters without changing the current line.\n.Pp\n.It Xo\n.Op Ar count\n.Cm !\\&\n.Ar motion shell-argument(s)\n.Aq Li carriage-return\n.Xc\nReplace the lines spanned by\n.Ar count\nand\n.Ar motion\nwith the output\n.Pq standard output and standard error\nof the program named by the\n.Cm shell\noption, called with a\n.Fl c\nflag followed by the\n.Ar shell-argument(s)\n.Pq bundled into a single argument .\nWithin\n.Ar shell-argument(s) ,\nthe\n.Sq % ,\n.Sq #\nand\n.Sq !\\&\ncharacters are expanded to the current file name,\nthe previous current file name,\nand the command text of the previous\n.Cm !\\&\nor\n.Cm :!\ncommands, respectively.\nThe special meaning of\n.Sq % ,\n.Sq #\nand\n.Sq !\\&\ncan be overridden by escaping them with a backslash.\n.Pp\n.It Xo\n.Op Ar count\n.Cm #\n.Sm off\n.Cm # | + | -\n.Sm on\n.Xc\nIncrement\n.Pq trailing So # Sc or So + Sc\nor decrement\n.Pq trailing Sq -\nthe number under the cursor by\n.Ar count ,\nstarting at the cursor position or at the first non-blank\ncharacter following it.\nNumbers with a leading\n.Sq 0x\nor\n.Sq 0X\nare interpreted as hexadecimal numbers.\nNumbers with a leading\n.Sq 0\nare interpreted as octal numbers unless they contain a non-octal digit.\nOther numbers may be prefixed with a\n.Sq +\nor\n.Sq -\nsign.\n.Pp\n.It Xo\n.Op Ar count\n.Cm $\n.Xc\nMove the cursor to the end of a line.\nIf\n.Ar count\nis specified, additionally move the cursor down\n.Ar count\n\\- 1 lines.\n.Pp\n.It Cm %\nMove to the parenthesis, square bracket or curly brace matching\nthe one found at the cursor position or the closest to the right of it.\n.Pp\n.It Cm &\nRepeat the previous substitution command on the current line.\n.Pp\n.It Xo\n.Cm ' Ns Aq Ar character\n.Xc\n.It Xo\n.Cm ` Ns Aq Ar character\n.Xc\nReturn to the cursor position marked by the character\n.Ar character ,\nor, if\n.Ar character\nis\n.Sq '\nor\n.Sq ` ,\nto the position of the cursor before the last of the following commands:\n.Aq Cm control-A ,\n.Aq Cm control-T ,\n.Aq Cm control-] ,\n.Cm % ,\n.Cm ' ,\n.Cm ` ,\n.Cm (\\& ,\n.Cm )\\& ,\n.Cm / ,\n.Cm ?\\& ,\n.Cm G ,\n.Cm H ,\n.Cm L ,\n.Cm [[ ,\n.Cm ]] ,\n.Cm { ,\n.Cm } .\nThe first form returns to the first non-blank character of the line marked by\n.Ar character .\nThe second form returns to the line and column marked by\n.Ar character .\n.Pp\n.It Xo\n.Op Ar count\n.Cm \\&(\n.Xc\n.It Xo\n.Op Ar count\n.Cm \\&)\n.Xc\nMove\n.Ar count\nsentences backward or forward, respectively.\nA sentence is an area of text that begins with the first nonblank character\nfollowing the previous sentence, paragraph, or section\nboundary and continues until the next period, exclamation mark,\nor question mark character, followed by any number of closing parentheses,\nbrackets, double or single quote characters, followed by\neither an end-of-line or two whitespace characters.\nGroups of empty lines\n.Pq or lines containing only whitespace characters\nare treated as a single sentence.\n.Pp\n.It Xo\n.Op Ar count\n.Cm ,\\&\n.Xc\nReverse find character\n(i.e. the last\n.Cm F ,\n.Cm f ,\n.Cm T\nor\n.Cm t\ncommand)\n.Ar count\ntimes.\n.Pp\n.It Xo\n.Op Ar count\n.Cm -\n.Xc\nMove to the first non-blank character of the previous line,\n.Ar count\ntimes.\n.Pp\n.It Xo\n.Op Ar count\n.Cm .\\&\n.Xc\nRepeat the last\n.Nm vi\ncommand that modified text.\n.Ar count\nreplaces both the\n.Ar count\nargument of the repeated command and that of the associated\n.Ar motion .\nIf the\n.Cm .\\&\ncommand repeats the\n.Cm u\ncommand, the change log is rolled forward or backward, depending on the action\nof the\n.Cm u\ncommand.\n.Pp\n.It Xo\n.Pf / Ar RE\n.Aq Li carriage-return\n.Xc\n.It Xo\n.Pf / Ar RE Ns /\n.Op Ar offset\n.Op Cm z\n.Aq Li carriage-return\n.Xc\n.It Xo\n.Pf ? Ar RE\n.Aq Li carriage-return\n.Xc\n.It Xo\n.Pf ? Ar RE ? Op Ar offset\n.Op Cm z\n.Aq Li carriage-return\n.Xc\n.It Cm N\n.It Cm n\nSearch forward\n.Pq Sq /\nor backward\n.Pq Sq ?\\&\nfor a regular expression.\n.Cm n\nand\n.Cm N\nrepeat the last search in the same or opposite directions, respectively.\nIf\n.Ar RE\nis empty, the last search regular expression is used.\nIf\n.Ar offset\nis specified, the cursor is placed\n.Ar offset\nlines before or after the matched regular expression.\nIf either\n.Cm n\nor\n.Cm N\ncommands are used as motion components for the\n.Cm !\\&\ncommand, there will be no prompt for the text of the command and the previous\n.Cm !\\&\nwill be executed.\nMultiple search patterns may be grouped together by delimiting them with\nsemicolons and zero or more whitespace characters.\nThese patterns are evaluated from left to right with the final cursor position\ndetermined by the last search pattern.\nA\n.Cm z\ncommand may be appended to the closed search expressions to reposition the\nresult line.\n.Pp\n.It Cm 0\nMove to the first character in the current line.\n.Pp\n.It Cm :\\&\nExecute an\n.Nm ex\ncommand.\n.Pp\n.It Xo\n.Op Ar count\n.Cm ;\\&\n.Xc\nRepeat the last character find\n(i.e. the last\n.Cm F ,\n.Cm f ,\n.Cm T\nor\n.Cm t\ncommand)\n.Ar count\ntimes.\n.Pp\n.It Xo\n.Op Ar count\n.Cm <\n.Ar motion\n.Xc\n.It Xo\n.Op Ar count\n.Cm >\n.Ar motion\n.Xc\nShift\n.Ar count\nlines left or right, respectively, by an amount of\n.Cm shiftwidth .\n.Pp\n.It Cm @ Ar buffer\nExecute a named\n.Ar buffer\nas\n.Nm vi\ncommands.\nThe buffer may include\n.Nm ex\ncommands too, but they must be expressed as a\n.Cm \\&:\ncommand.\nIf\n.Ar buffer\nis\n.Sq @\nor\n.Sq * ,\nthen the last buffer executed shall be used.\n.Pp\n.It Xo\n.Op Ar count\n.Cm A\n.Xc\nEnter input mode, appending the text after the end of the line.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\- 1 times after input mode is exited.\n.Pp\n.It Xo\n.Op Ar count\n.Cm B\n.Xc\nMove backwards\n.Ar count\nbigwords.\n.Pp\n.It Xo\n.Op Ar buffer\n.Cm C\n.Xc\nChange text from the current position to the end-of-line.\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe deleted text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar buffer\n.Cm D\n.Xc\nDelete text from the current position to the end-of-line.\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe deleted text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar count\n.Cm E\n.Xc\nMove forward\n.Ar count\nend-of-bigwords.\n.Pp\n.It Xo\n.Op Ar count\n.Cm F Aq Ar character\n.Xc\nSearch\n.Ar count\ntimes backward through the current line for\n.Ar character .\n.Pp\n.It Xo\n.Op Ar count\n.Cm G\n.Xc\nMove to line\n.Ar count ,\nor the last line of the file if\n.Ar count\nis not specified.\n.Pp\n.It Xo\n.Op Ar count\n.Cm H\n.Xc\nMove to the screen line\n.Ar count\n\\- 1 lines below the top of the screen.\n.Pp\n.It Xo\n.Op Ar count\n.Cm I\n.Xc\nEnter input mode, inserting the text at the beginning of the line.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\- 1 more times.\n.Pp\n.It Xo\n.Op Ar count\n.Cm J\n.Xc\nJoin\n.Ar count\nlines with the current line.\nThe spacing between two joined lines is set to two whitespace characters if the\nformer ends with a question mark, a period or an exclamation mark.\nIt is set to one whitespace character otherwise.\n.Pp\n.It Xo\n.Op Ar count\n.Cm L\n.Xc\nMove to the screen line\n.Ar count\n\\- 1 lines above the bottom of the screen.\n.Pp\n.It Cm M\nMove to the screen line in the middle of the screen.\n.Pp\n.It Xo\n.Op Ar count\n.Cm O\n.Xc\nEnter input mode, appending text in a new line above the current line.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\- 1 more times.\n.Pp\n.It Xo\n.Op Ar buffer\n.Cm P\n.Xc\nInsert text from\n.Ar buffer\nbefore the current column if\n.Ar buffer\nis character-oriented or before the current line if it is line-oriented.\n.Pp\n.It Cm Q\nExit\n.Nm vi\n.Pq or visual\nmode and switch to\n.Nm ex\nmode.\n.Pp\n.It Xo\n.Op Ar count\n.Cm R\n.Xc\nEnter input mode, replacing the characters in the current line.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\- 1 more times upon exit from insert mode.\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm S\n.Xc\nSubstitute\n.Ar count\nlines.\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe deleted text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar count\n.Cm T\n.Aq Ar character\n.Xc\nSearch backwards,\n.Ar count\ntimes, through the current line for the character after the specified\n.Ar character .\n.Pp\n.It Cm U\nRestore the current line to its state before the cursor last moved to it.\n.Pp\n.It Xo\n.Op Ar count\n.Cm W\n.Xc\nMove forward\n.Ar count\nbigwords.\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm X\n.Xc\nDelete\n.Ar count\ncharacters before the cursor, on the current line.\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe deleted text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm Y\n.Xc\nCopy (or\n.Dq yank )\n.Ar count\nlines into\n.Ar buffer .\n.Pp\n.It Cm ZZ\nWrite the file and exit\n.Nm vi\nif there are no more files to edit.\nEntering two\n.Dq quit\ncommands in a row ignores any remaining file to edit.\n.Pp\n.It Xo\n.Op Ar count\n.Cm [[\n.Xc\nBack up\n.Ar count\nsection boundaries.\n.Pp\n.It Xo\n.Op Ar count\n.Cm ]]\n.Xc\nMove forward\n.Ar count\nsection boundaries.\n.Pp\n.It Cm ^\nMove to the first non-blank character on the current line.\n.Pp\n.It Xo\n.Op Ar count\n.Cm _\n.Xc\nMove down\n.Ar count\n\\- 1 lines, to the first non-blank character.\n.Pp\n.It Xo\n.Op Ar count\n.Cm a\n.Xc\nEnter input mode, appending the text after the cursor.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\-1 more times.\n.Pp\n.It Xo\n.Op Ar count\n.Cm b\n.Xc\nMove backwards\n.Ar count\nwords.\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm c\n.Ar motion\n.Xc\nChange the region of text described by\n.Ar count\nand\n.Ar motion .\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe changed text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm d\n.Ar motion\n.Xc\nDelete the region of text described by\n.Ar count\nand\n.Ar motion .\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe deleted text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar count\n.Cm e\n.Xc\nMove forward\n.Ar count\nend-of-words.\n.Pp\n.It Xo\n.Op Ar count\n.Cm f Aq Ar character\n.Xc\nSearch forward,\n.Ar count\ntimes, through the rest of the current line for\n.Aq Ar character .\n.Pp\n.It Xo\n.Op Ar count\n.Cm i\n.Xc\nEnter input mode, inserting the text before the cursor.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\-1 more times.\n.Pp\n.It Xo\n.Cm m\n.Aq Ar character\n.Xc\nSave the current context\n.Pq line and column\nas\n.Aq Ar character .\n.Pp\n.It Xo\n.Op Ar count\n.Cm o\n.Xc\nEnter input mode, appending text in a new line under the current line.\nIf a\n.Ar count\nargument is given,\nthe characters input are repeated\n.Ar count\n\\- 1 more times.\n.Pp\n.It Xo\n.Op Ar buffer\n.Cm p\n.Xc\nAppend text from\n.Ar buffer .\nText is appended after the current column if\n.Ar buffer\nis character oriented, or the after current line otherwise.\n.Pp\n.It Xo\n.Op Ar count\n.Cm r\n.Aq Ar character\n.Xc\nReplace\n.Ar count\ncharacters by\n.Ar character .\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm s\n.Xc\nSubstitute\n.Ar count\ncharacters in the current line starting with the current character.\nIf\n.Ar buffer\nis specified,\n.Dq yank\nthe substituted text into\n.Ar buffer .\n.Pp\n.It Xo\n.Op Ar count\n.Cm t\n.Aq Ar character\n.Xc\nSearch forward,\n.Ar count\ntimes, through the current line for the character immediately before\n.Aq Ar character .\n.Pp\n.It Cm u\nUndo the last change made to the file.\nIf repeated, the\n.Cm u\ncommand alternates between these two states.\nThe\n.Cm .\\&\ncommand, when used immediately after\n.Cm u ,\ncauses the change log to be rolled forward or backward, depending on the action\nof the\n.Cm u\ncommand.\n.Pp\n.It Xo\n.Op Ar count\n.Cm w\n.Xc\nMove forward\n.Ar count\nwords.\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm x\n.Xc\nDelete\n.Ar count\ncharacters at the current cursor position, but no more than there are till the\nend of the line.\n.Pp\n.It Xo\n.Op Ar buffer\n.Op Ar count\n.Cm y\n.Ar motion\n.Xc\nCopy (or\n.Dq yank )\na text region specified by\n.Ar count\nand\n.Ar motion\ninto a buffer.\n.Pp\n.It Xo\n.Op Ar count1\n.Cm z\n.Op Ar count2\n.Cm type\n.Xc\nRedraw, optionally repositioning and resizing the screen.\nIf\n.Ar count2\nis specified, limit the screen size to\n.Ar count2\nlines.\nThe following\n.Cm type\ncharacters may be used:\n.Bl -tag -width Ds\n.It Cm +\nIf\n.Ar count1\nis specified, place the line\n.Ar count1\nat the top of the screen.\nOtherwise, display the screen after the current screen.\n.It Aq Cm carriage-return\nPlace the line\n.Ar count1\nat the top of the screen.\n.It Cm .\\&\nPlace the line\n.Ar count1\nin the center of the screen.\n.It Cm -\nPlace the line\n.Ar count1\nat the bottom of the screen.\n.It Cm ^\nIf\n.Ar count1\nis given,\ndisplay the screen before the screen before\n.Ar count1\n.Pq i.e. 2 screens before .\nOtherwise, display the screen before the current screen.\n.El\n.Pp\n.It Xo\n.Op Ar count\n.Cm {\\&\n.Xc\nMove backward\n.Ar count\nparagraphs.\n.Pp\n.It Xo\n.Op Ar column\n.Cm |\\&\n.Xc\nMove to a specific\n.Ar column\nposition on the current line.\nIf\n.Ar column\nis omitted,\nmove to the start of the current line.\n.Pp\n.It Xo\n.Op Ar count\n.Cm }\\&\n.Xc\nMove forward\n.Ar count\nparagraphs.\n.Pp\n.It Xo\n.Op Ar count\n.Cm ~\n.Ar motion\n.Xc\nIf the\n.Cm tildeop\noption is not set, reverse the case of the next\n.Ar count\ncharacter(s) and no\n.Ar motion\ncan be specified.\nOtherwise\n.Ar motion\nis mandatory and\n.Cm ~\nreverses the case of the characters in a text region specified by the\n.Ar count\nand\n.Ar motion .\n.Pp\n.It Aq Cm interrupt\nInterrupt the current operation.\nThe\n.Aq interrupt\ncharacter is usually\n.Aq control-C .\n.El\n.Sh VI TEXT INPUT COMMANDS\nThe following section describes the commands available in the text input mode\nof the\n.Nm vi\neditor.\n.Pp\n.Bl -tag -width Ds -compact\n.It Aq Cm nul\nReplay the previous input.\n.Pp\n.It Aq Cm control-D\nErase to the previous\n.Ar shiftwidth\ncolumn boundary.\n.Pp\n.It Cm ^ Ns Aq Cm control-D\nErase all of the autoindent characters.\n.Pp\n.It Cm 0 Ns Aq Cm control-D\nErase all of the autoindent characters, and reset the autoindent level.\n.Pp\n.It Aq Cm control-T\nInsert sufficient\n.Aq tab\nand\n.Aq space\ncharacters to move forward to the next\n.Ar shiftwidth\ncolumn boundary.\nIf the\n.Cm expandtab\noption is set, only insert\n.Aq space\ncharacters.\n.Pp\n.It Aq Cm erase\n.It Aq Cm control-H\nErase the last character.\n.Pp\n.It Aq Cm literal next\nEscape the next character from any special meaning.\nThe\n.Aq literal\\ \\&next\ncharacter is usually\n.Aq control-V .\n.Pp\n.It Aq Cm escape\nResolve all text input into the file, and return to command mode.\n.Pp\n.It Aq Cm line erase\nErase the current line.\n.Pp\n.It Aq Cm control-W\n.It Aq Cm word erase\nErase the last word.\nThe definition of word is dependent on the\n.Cm altwerase\nand\n.Cm ttywerase\noptions.\n.Pp\n.Sm off\n.It Xo\n.Aq Cm control-X\n.Bq Cm 0-9A-Fa-f\n.Cm +\n.Xc\n.Sm on\nInsert a character with the specified hexadecimal value into the text.\n.Pp\n.It Aq Cm interrupt\nInterrupt text input mode, returning to command mode.\nThe\n.Aq interrupt\ncharacter is usually\n.Aq control-C .\n.El\n.Sh EX COMMANDS\nThe following section describes the commands available in the\n.Nm ex\neditor.\nIn each entry below, the tag line is a usage synopsis for the command.\n.Pp\n.Bl -tag -width Ds -compact\n.It Aq Cm end-of-file\nScroll the screen.\n.Pp\n.It Cm !\\& Ar argument(s)\n.It Xo\n.Op Ar range\n.Cm !\\&\n.Ar argument(s)\n.Xc\nExecute a shell command, or filter lines through a shell command.\n.Pp\n.It Cm \\&\"\nA comment.\n.Pp\n.It Xo\n.Op Ar range\n.Cm nu Ns Op Cm mber\n.Op Ar count\n.Op Ar flags\n.Xc\n.It Xo\n.Op Ar range\n.Cm #\n.Op Ar count\n.Op Ar flags\n.Xc\nDisplay the selected lines, each preceded with its line number.\n.Pp\n.It Cm @ Ar buffer\n.It Cm * Ar buffer\nExecute a buffer.\n.Pp\n.It Xo\n.Op Ar range\n.Cm < Ns Op Cm < ...\n.Op Ar count\n.Op Ar flags\n.Xc\nShift lines left.\n.Pp\n.It Xo\n.Op Ar line\n.Cm =\n.Op Ar flags\n.Xc\nDisplay the line number of\n.Ar line .\nIf\n.Ar line\nis not specified, display the line number of the last line in the file.\n.Pp\n.It Xo\n.Op Ar range\n.Cm > Ns Op Cm > ...\n.Op Ar count\n.Op Ar flags\n.Xc\nShift lines right.\n.Pp\n.It Xo\n.Cm ab Ns Op Cm breviate\n.Ar lhs rhs\n.Xc\n.Nm vi\nonly.\nAdd\n.Ar lhs\nas an abbreviation for\n.Ar rhs\nto the abbreviation list.\n.Pp\n.It Xo\n.Op Ar line\n.Cm a Ns Op Cm ppend Ns\n.Op Cm !\\&\n.Xc\nThe input text is appended after the specified line.\n.Pp\n.It Cm ar Ns Op Cm gs\nDisplay the argument list.\n.Pp\n.It Cm bg\n.Nm vi\nonly.\nBackground the current screen.\n.Pp\n.It Xo\n.Op Ar range\n.Cm c Ns Op Cm hange Ns\n.Op Cm !\\&\n.Op Ar count\n.Xc\nThe input text replaces the specified range.\n.Pp\n.It Xo\n.Cm chd Ns Op Cm ir Ns\n.Op Cm !\\&\n.Op Ar directory\n.Xc\n.It Xo\n.Cm cd Ns Op Cm !\\&\n.Op Ar directory\n.Xc\nChange the current working directory.\n.Pp\n.It Xo\n.Op Ar range\n.Cm co Ns Op Cm py\n.Ar line\n.Op Ar flags\n.Xc\n.It Xo\n.Op Ar range\n.Cm t\n.Ar line\n.Op Ar flags\n.Xc\nCopy the specified lines after the destination\n.Ar line .\n.Pp\n.It Xo\n.Op Ar range\n.Cm d Ns Op Cm elete\n.Op Ar buffer\n.Op Ar count\n.Op Ar flags\n.Xc\nDelete the lines from the file.\n.Pp\n.It Xo\n.Cm di Ns Op Cm splay\n.Cm b Ns Oo Cm uffers Oc |\n.Cm s Ns Oo Cm creens Oc |\n.Cm t Ns Op Cm ags\n.Xc\nDisplay buffers, screens or tags.\n.Pp\n.It Xo\n.Cm e Ns Op Cm dit Ns | Ns Cm x Ns\n.Op Cm !\\&\n.Op Ar +cmd\n.Op Ar file\n.Xc\nEdit a different file. The capitalized command opens a new screen below the\ncurrent screen.\n.Pp\n.It Xo\n.Cm exu Ns Op Cm sage\n.Op Ar command\n.Xc\nDisplay usage for an\n.Nm ex\ncommand.\n.Pp\n.It Xo\n.Cm f Ns Op Cm ile\n.Op Ar file\n.Xc\nDisplay and optionally change the file name.\n.Pp\n.It Cm fg Op Ar name\n.Nm vi\nonly.\nForeground the specified screen. The capitalized command opens a new screen\nbelow the current screen.\n.Pp\n.It Xo\n.Op Ar range\n.Cm g Ns Op Cm lobal\n.No / Ns Ar pattern Ns /\n.Op Ar commands\n.Xc\n.It Xo\n.Op Ar range\n.Cm v\n.No / Ns Ar pattern Ns /\n.Op Ar commands\n.Xc\nApply commands to lines matching\n.Pq Sq global\nor not matching\n.Pq Sq v\na pattern.\n.Pp\n.It Cm he Ns Op Cm lp\nDisplay a help message.\n.Pp\n.It Xo\n.Op Ar line\n.Cm i Ns Op Cm nsert Ns\n.Op Cm !\\&\n.Xc\nThe input text is inserted before the specified line.\n.Pp\n.It Xo\n.Op Ar range\n.Cm j Ns Op Cm oin Ns\n.Op Cm !\\&\n.Op Ar count\n.Op Ar flags\n.Xc\nJoin lines of text together.\n.Pp\n.It Xo\n.Op Ar range\n.Cm l Ns Op Cm ist\n.Op Ar count\n.Op Ar flags\n.Xc\nDisplay the lines unambiguously.\n.Pp\n.It Xo\n.Cm map Ns Op Cm !\\&\n.Op Ar lhs rhs\n.Xc\n.Nm vi\nonly.\nDefine or display maps.\n.Pp\n.It Xo\n.Op Ar line\n.Cm ma Ns Op Cm rk\n.Aq Ar character\n.Xc\n.It Xo\n.Op Ar line\n.Cm k Aq Ar character\n.Xc\nMark the line with the mark\n.Aq Ar character .\n.Pp\n.It Xo\n.Op Ar range\n.Cm m Ns Op Cm ove\n.Ar line\n.Xc\nMove the specified lines after the target line.\n.Pp\n.It Xo\n.Cm mk Ns Op Cm exrc Ns\n.Op Cm !\\&\n.Ar file\n.Xc\nWrite the abbreviations, editor options and maps to the specified\n.Ar file .\n.Pp\n.It Xo\n.Cm n Ns Op Cm ext Ns\n.Op Cm !\\&\n.Op Ar file ...\n.Xc\nEdit the next file from the argument list. The capitalized command opens a\nnew screen below the current screen.\n.\\\" .Pp\n.\\\" .It Xo\n.\\\" .Op Ar line\n.\\\" .Cm o Ns Op Cm pen\n.\\\" .No / Ns Ar pattern Ns /\n.\\\" .Op Ar flags\n.\\\" .Xc\n.\\\" Enter open mode.\n.Pp\n.It Cm pre Ns Op Cm serve\nSave the file in a form that can later be recovered using the\n.Nm ex\n.Fl r\noption.\n.Pp\n.It Cm prev Ns Oo Cm ious Oc Ns Op Cm !\\&\nEdit the previous file from the argument list. The capitalized command opens\na new screen below the current screen.\n.Pp\n.It Xo\n.Op Ar range\n.Cm p Ns Op Cm rint\n.Op Ar count\n.Op Ar flags\n.Xc\nDisplay the specified lines.\n.Pp\n.It Xo\n.Op Ar line\n.Cm pu Ns Op Cm t\n.Op Ar buffer\n.Xc\nAppend buffer contents to the current line.\n.Pp\n.It Xo\n.Cm q Ns Op Cm uit Ns\n.Op Cm !\\&\n.Xc\nEnd the editing session.\nIn split-screen mode, only close the current screen\nand switch to the previous one.\n.Pp\n.It Xo\n.Op Ar line\n.Cm r Ns Op Cm ead Ns\n.Op Cm !\\&\n.Op Ar file\n.Xc\nRead a file.\n.Pp\n.It Xo\n.Cm rec Ns Op Cm over\n.Ar file\n.Xc\nRecover\n.Ar file\nif it was previously saved.\n.Pp\n.It Xo\n.Cm res Ns Op Cm ize\n.Op Cm + Ns | Ns Cm - Ns\n.Ar lines\n.Xc\n.Nm vi\nmode only.\nGrow or shrink the current screen.\n.Pp\n.It Xo\n.Cm rew Ns Op Cm ind Ns\n.Op Cm !\\&\n.Xc\nRewind the argument list.\n.Pp\n.It Xo\n.Op Ar range\n.Sm off\n.Cm s\n.Oo Cm / Ar pattern Cm / Ar replace Cm /\n.Op Ar options\n.Op Ar count\n.Op Ar flags\n.Oc\n.Sm on\n.Xc\n.It Xo\n.Op Ar range\n.Sm off\n.Cm &\n.Op Ar options\n.Op Ar count\n.Op Ar flags\n.Sm on\n.Xc\n.It Xo\n.Op Ar range\n.Sm off\n.Cm ~\n.Op Ar options\n.Op Ar count\n.Op Ar flags\n.Sm on\n.Xc\nSubstitute the regular expression\n.Ar pattern\nwith\n.Ar replace .\nWhen invoked as\n.Cm & ,\nor if\n.Cm / Ns Ar pattern Ns Cm / Ns Ar replace Ns Cm /\nis omitted,\n.Ar pattern\nand\n.Ar replace\nfrom the most recent\n.Cm s\ncommand are used.\n.Cm ~\nbehaves like\n.Cm & ,\nexcept the pattern used is the most recent regular expression used by any\ncommand.\n.Pp\nThe\n.Ar replace\nfield may contain any of the following sequences:\n.Bl -tag -width Ds\n.It Sq &\nThe text matched by\n.Ar pattern .\n.It Sq \\(a~\nThe replacement part of the previous\n.Cm s\ncommand.\n.It Sq %\nIf this is the entire\n.Ar replace\npattern, the replacement part of the previous\n.Cm s\ncommand.\n.It Sq \\e#\nWhere\n.Sq #\nis an integer from 1 to 9, the text matched by the #'th subexpression in\n.Ar pattern .\n.It Sq \\eL\nCauses the characters up to the end of the line of the next occurrence of\n.Sq \\eE\nor\n.Sq \\ee\nto be converted to lowercase.\n.It Sq \\el\nCauses the next character to be converted to lowercase.\n.It Sq \\eU\nCauses the characters up to the end of the line of the next occurrence of\n.Sq \\eE\nor\n.Sq \\ee\nto be converted to uppercase.\n.It Sq \\eu\nCauses the next character to be converted to uppercase.\n.El\n.Pp\nThe\n.Ar options\nfield may contain any of the following characters:\n.Bl -tag -width Ds\n.It Sq c\nPrompt for confirmation before each replacement is done.\n.It Sq g\nReplace all instances of\n.Ar pattern\nin a line, not just the first.\n.El\n.Pp\n.It Xo\n.Cm se Ns Op Cm t\n.Sm off\n.Op option Oo = Oo value Oc Oc \\ \\&...\n.Sm on\n.Pf \\ \\& Op nooption ...\n.Op option? ...\n.Op Ar all\n.Xc\nDisplay or set the editor options described in\n.Sx SET OPTIONS .\n.Pp\n.It Cm sh Ns Op Cm ell\nRun a shell program.\n.Pp\n.It Xo\n.Cm so Ns Op Cm urce\n.Ar file\n.Xc\nRead and execute\n.Nm ex\ncommands from a file.\n.Pp\n.It Xo\n.Cm su Ns Op Cm spend Ns\n.Op Cm !\\&\n.Xc\n.It Xo\n.Cm st Ns Op Cm op Ns\n.Op Cm !\\&\n.Xc\n.It Aq Cm suspend\nSuspend the edit session.\nThe\n.Aq suspend\ncharacter is usually\n.Aq control-Z .\n.Pp\n.It Xo\n.Cm ta Ns Op Cm g Ns\n.Op Cm !\\&\n.Ar tagstring\n.Xc\nEdit the file containing the specified tag. The capitalized command opens a\nnew screen below the current screen.\n.Pp\n.It Xo\n.Cm tagn Ns Op Cm ext Ns\n.Op Cm !\\&\n.Xc\nEdit the file containing the next context for the current tag.\n.Pp\n.It Xo\n.Cm tagp Ns Op Cm op Ns\n.Op Cm !\\&\n.Op Ar file | number\n.Xc\nPop to the specified tag in the tags stack.\n.Pp\n.It Xo\n.Cm tagpr Ns Op Cm ev Ns\n.Op Cm !\\&\n.Xc\nEdit the file containing the previous context for the current tag.\n.Pp\n.It Xo\n.Cm tagt Ns Op Cm op Ns\n.Op Cm !\\&\n.Xc\nPop to the least recent tag on the tags stack, clearing the stack.\n.Pp\n.It Xo\n.Cm una Ns Op Cm bbreviate\n.Ar lhs\n.Xc\n.Nm vi\nonly.\nDelete an abbreviation.\n.Pp\n.It Cm u Ns Op Cm ndo\nUndo the last change made to the file.\n.Pp\n.It Xo\n.Cm unm Ns Op Cm ap Ns\n.Op Cm !\\&\n.Ar lhs\n.Xc\nUnmap a mapped string.\n.Pp\n.It Cm ve Ns Op Cm rsion\nDisplay the version of the\n.Nm ex Ns / Ns Nm vi\neditor.\n.Pp\n.It Xo\n.Op Ar line\n.Cm vi Ns Op Cm sual\n.Op Ar type\n.Op Ar count\n.Op Ar flags\n.Xc\n.Nm ex\nonly.\nEnter\n.Nm vi .\n.Pp\n.It Xo\n.Cm vi Ns\n.Op Cm sual Ns\n.Op Cm !\\&\n.Op Ar +cmd\n.Op Ar file\n.Xc\n.Nm vi\nmode only. Edit a different file by opening a new screen below the current\nscreen.\n.Pp\n.It Xo\n.Cm viu Ns Op Cm sage\n.Op Ar command\n.Xc\nDisplay usage for a\n.Nm vi\ncommand.\n.Pp\n.It Xo\n.Op Ar range\n.Cm w Ns Op Cm rite Ns\n.Op Cm !\\&\n.Op >>\n.Op Ar file\n.Xc\nWrite the file.\n.Pp\n.It Xo\n.Op Ar range\n.Cm wn Ns Op Cm !\\&\n.Op >>\n.Op Ar file\n.Xc\nWrite the file and edit the next file from the argument list.\n.Pp\n.It Xo\n.Op Ar range\n.Cm wq Ns Op Cm !\\&\n.Op >>\n.Op Ar file\n.Xc\nWrite the file and exit the editor.\nIn split-screen mode, close the current screen\nand switch to the previous one.\n.Pp\n.It Xo\n.Op Ar range\n.Cm x Ns Op Cm it Ns\n.Op Cm !\\&\n.Op Ar file\n.Xc\nExit the editor,\nwriting the file if it has been modified.\nIn split-screen mode, close the current screen\nand switch to the previous one.\n.Pp\n.It Xo\n.Op Ar range\n.Cm ya Ns Op Cm nk\n.Op Ar buffer\n.Op Ar count\n.Xc\nCopy the specified lines to a buffer.\n.Pp\n.It Xo\n.Op Ar line\n.Cm z\n.Op Ar type\n.Op Ar count\n.Op Ar flags\n.Xc\nAdjust the window.\n.El\n.Pp\nFor\n.Cm e ,\n.Cm fg ,\n.Cm n ,\n.Cm prev ,\n.Cm ta ,\nand\n.Cm vi ,\nif the first letter of the command is capitalized, the current screen is\nsplit and the new file is displayed in addition to the current screen.\nThis feature is only available in\n.Nm vi ,\nnot in\n.Nm ex .\n.Sh SET OPTIONS\nThere are a large number of options that can\nchange the editor's behavior,\nusing the\n.Cm set\ncommand.\nThis section describes the options, their abbreviations and their\ndefault values.\n.Pp\nIn each entry below, the first part of the tag line is the full name\nof the option, followed by any equivalent abbreviations.\nThe part in square brackets is the default value of the option.\nMost of the options are boolean, i.e. they are either on or off,\nand do not have an associated value.\n.Pp\nOptions apply to both\n.Nm ex\nand\n.Nm vi\nmodes, unless otherwise specified.\n.Bl -tag -width Ds\n.It Cm altnotation , an Bq off\nDisplay most control characters less than 0x20 using <C-char> notation.\nCarriage feed, escape, and delete are displayed as <Ret>, <Esc>, and <Del>,\nrespectively.\n.It Cm altwerase Bq off\n.Nm vi\nonly.\nSelect an alternate word erase algorithm.\n.It Cm autoindent , ai Bq off\nAutomatically indent new lines.\n.It Cm autoprint , ap Bq on\n.Nm ex\nonly.\nDisplay the current line automatically.\n.It Cm autowrite , aw Bq off\nWrite modified files automatically when changing files or suspending the editor\nsession.\n.It Cm backup Bq \\&\"\\&\"\nBack up files before they are overwritten.\n.It Cm beautify , bf Bq off\nDiscard control characters.\n.It Cm bserase , bse Bq off\n.Nm vi\nonly.\nImmediately erase backspaced characters from the screen.\n.It Cm cdpath Bq \"environment variable CDPATH, or current directory\"\nThe directory paths used as path prefixes for the\n.Cm cd\ncommand.\n.It Cm cedit Bq no default\nSet the character to edit the colon command-line history.\n.It Cm columns , co Bq 80\nSet the number of columns in the screen.\n.It Cm comment Bq off\n.Nm vi\nonly.\nSkip leading comments in shell, C and C++ language files.\n.It Cm edcompatible , ed Bq off\nRemember the values of the\n.Sq c\nand\n.Sq g\nsuffixes to the\n.Cm s , &\nand\n.Cm ~\ncommands, instead of initializing them as unset for each new command.\n.It Cm escapetime Bq 2\nThe tenths of a second\n.Nm ex Ns / Ns Nm vi\nwaits for a subsequent key to complete an\n.Aq escape\nkey mapping.\n.It Cm errorbells , eb Bq off\n.Nm ex\nonly.\nAnnounce error messages with a bell.\n.It Cm expandtab , et Bq off\nExpand\n.Aq tab\ncharacters to\n.Aq space\nwhen inserting, replacing or shifting text, autoindenting,\nindenting with\n.Aq Ic control-T ,\noutdenting with\n.Aq Ic control-D ,\nor\nwhen filtering lines with the\n.Cm !\\&\ncommand.\n.It Cm exrc , ex Bq off\nRead the startup files in the local directory.\n.It Cm extended Bq off\nUse extended regular expressions\n.Pq EREs\nrather than basic regular expressions\n.Pq BREs .\nSee\n.Xr re_format 7\nfor more information on regular expressions.\n.It Cm filec Bq Aq tab\nSet the character to perform file path completion on the colon command line.\n.It Cm flash Bq off\nFlash the screen instead of beeping the keyboard on error.\n.It Cm hardtabs , ht Bq 0\nSet the spacing between hardware tab settings.\nThis option currently has no effect.\n.It Cm iclower Bq off\nMakes all regular expressions case-insensitive,\nas long as an upper-case letter does not appear in the search string.\n.It Cm ignorecase , ic Bq off\nIgnore case differences in regular expressions.\n.It Cm imctrl Bq off\nControl input method using escape sequences compatible with\nTera Term and RLogin.\nThe state of the input method commands specified by imkey option is\nsaved and restored automatically.\nThis input method is deactivated upon returning to command mode.\nIf the terminal in use does not accept these escape sequences, the\nscreen display may be corrupted.\n.It Cm imkey [/?aioAIO]\nSet commands which the state of input method is restored and saved on\nentering and leaving, respectively.\n.It Cm keytime Bq 6\nThe tenths of a second\n.Nm ex Ns / Ns Nm vi\nwaits for a subsequent key to complete a key mapping.\n.It Cm leftright Bq off\n.Nm vi\nonly.\nDo left-right scrolling.\n.It Cm lines , li Bq 24\n.Nm vi\nonly.\nSet the number of lines in the screen.\n.It Cm list Bq off\nDisplay lines in an unambiguous fashion.\n.It Cm lock Bq on\nAttempt to get an exclusive lock on any file being edited, read or written.\n.It Cm magic Bq on\nWhen turned off, all regular expression characters except for\n.Sq ^\nand\n.Sq $\nare treated as ordinary characters.\nPreceding individual characters by\n.Sq \\e\nre-enables them.\n.It Cm matchtime Bq 7\n.Nm vi\nonly.\nThe tenths of a second\n.Nm ex Ns / Ns Nm vi\npauses on the matching character when the\n.Cm showmatch\noption is set.\n.It Cm mesg Bq on\nPermit messages from other users.\n.It Cm noprint Bq \\&\"\\&\"\nCharacters that are never handled as printable characters.\n.It Cm number , nu Bq off\nPrecede each line displayed with its current line number.\n.It Cm octal Bq off\nDisplay unknown characters as octal numbers, instead of the default\nhexadecimal.\n.It Cm open Bq on\n.Nm ex\nonly.\nIf this option is not set, the\n.Cm open\nand\n.Cm visual\ncommands are disallowed.\n.It Cm paragraphs , para Bq \"IPLPPPQPP LIpplpipbpBlBdPpLpIt\"\n.Nm vi\nonly.\nDefine additional paragraph boundaries for the\n.Cm {\\&\nand\n.Cm }\\&\ncommands.\n.It Cm path Bq \\&\"\\&\"\nDefine additional directories to search for files being edited.\n.It Cm print Bq \\&\"\\&\"\nCharacters that are always handled as printable characters.\n.It Cm prompt Bq on\n.Nm ex\nonly.\nDisplay a command prompt.\n.It Cm readonly , ro Bq off\nMark the file and session as read-only.\n.It Cm recdir Bq /var/tmp/vi.recover\nThe directory where recovery files are stored.\n.It Cm remap Bq on\nRemap keys until resolved.\n.It Cm report Bq 5\nSet the number of lines about which the editor reports changes or yanks.\n.It Cm ruler Bq off\n.Nm vi\nonly.\nDisplay a row/column/percentage ruler on the colon command line.\n.It Cm scroll , scr Bq \"($LINES \\- 1) / 2\"\nSet the number of lines scrolled.\n.It Cm searchincr Bq off\nMakes the\n.Cm /\nand\n.Cm ?\\&\ncommands incremental.\n.It Cm sections , sect Bq \"NHSHH HUnhshShSs\"\n.Nm vi\nonly.\nDefine additional section boundaries for the\n.Cm [[\nand\n.Cm ]]\ncommands.\n.It Cm secure\nTurns off all access to external programs.\nOnce set, this option can't be disabled.\n.It Cm shell , sh Bq \"environment variable SHELL, or /bin/sh\"\nSelect the shell used by the editor.\n.It Cm shellmeta Bq ~{[*?$`'\\&\"\\e\nSet the meta characters checked to determine if file name expansion\nis necessary.\n.It Cm shiftwidth , sw Bq 8\nSet the autoindent and shift command indentation width.\n.It Cm showmatch , sm Bq off\n.Nm vi\nonly.\nNote matching\n.Sq {\nand\n.Sq \\&(\nfor\n.Sq }\nand\n.Sq )\\&\ncharacters.\n.It Cm showfilename Bq off\n.Nm vi\nonly.\nDisplay the file name on the colon command line.\n.It Cm showmode , smd Bq off\n.Nm vi\nonly.\nDisplay the current editor mode and a\n.Dq modified\nflag.\n.It Cm sidescroll Bq 16\n.Nm vi\nonly.\nSet the amount a left-right scroll will shift.\n.It Cm tabstop , ts Bq 8\nThis option sets tab widths for the editor display.\n.It Cm taglength , tl Bq 0\nSet the number of significant characters in tag names.\n.It Cm tags , tag Bq tags\nSet the list of tags files.\n.It Xo\n.Cm term , ttytype , tty\n.Bq \"environment variable TERM\"\n.Xc\nSet the terminal type.\n.It Cm terse Bq off\nThis option has historically made editor messages less verbose.\nIt has no effect in this implementation.\n.It Cm tildeop Bq off\nModify the\n.Cm ~\ncommand to take an associated motion.\n.It Cm timeout , to Bq on\nTime out on keys which may be mapped.\n.It Cm ttywerase Bq off\n.Nm vi\nonly.\nSelect an alternate erase algorithm.\n.It Cm verbose Bq off\n.Nm vi\nonly.\nDisplay an error message for every error.\n.It Cm visibletab , vt Bq off\n.Nm vi\nonly.\nDisplays tabs visibly while editing.\n.It Cm w300 Bq no default\n.Nm vi\nonly.\nSet the window size if the baud rate is less than 1200 baud.\n.It Cm w1200 Bq no default\n.Nm vi\nonly.\nSet the window size if the baud rate is equal to 1200 baud.\n.It Cm w9600 Bq no default\n.Nm vi\nonly.\nSet the window size if the baud rate is greater than 1200 baud.\n.It Cm warn Bq on\n.Nm ex\nonly.\nThis option causes a warning message to be printed on the terminal\nif the file has been modified since it was last written, before a\n.Cm !\\&\ncommand.\n.It Xo\n.Cm window , w , wi\n.Bq \"environment variable LINES \\- 1\"\n.Xc\nSet the window size for the screen. Note that POSIX 1003.2 requires\nhonoring the environment variable value, even if the terminal has been\nresized.\n.It Cm windowname Bq off\nChange the icon/window name to the current file name even if it can't\nbe restored on editor exit.\n.It Cm wraplen , wl Bq 0\n.Nm vi\nonly.\nBreak lines automatically,\nthe specified number of columns from the left-hand margin.\nIf both the\n.Cm wraplen\nand\n.Cm wrapmargin\nedit options are set, the\n.Cm wrapmargin\nvalue is used.\n.It Cm wrapmargin , wm Bq 0\n.Nm vi\nonly.\nBreak lines automatically,\nthe specified number of columns from the right-hand margin.\nIf both the\n.Cm wraplen\nand\n.Cm wrapmargin\nedit options are set, the\n.Cm wrapmargin\nvalue is used.\n.It Cm wrapscan , ws Bq on\nSet searches to wrap around the end or beginning of the file.\n.It Cm writeany , wa Bq off\nTurn off file-overwriting checks.\n.El\n.Sh ENVIRONMENT\n.Bl -tag -width \"COLUMNS\"\n.It Ev COLUMNS\nThe number of columns on the screen.\nThis value overrides any system or terminal specific values, as POSIX\n1003.2 requires honoring this environment variable value, even if the\nterminal is later resized.\nIf the\n.Ev COLUMNS\nenvironment variable is not set when\n.Nm ex Ns / Ns Nm vi\nruns, or the\n.Cm columns\noption is explicitly reset by the user,\n.Nm ex Ns / Ns Nm vi\nenters the value into the environment.\n.It Ev EXINIT\nA list of\n.Nm ex\nstartup commands, read after\n.Pa /etc/vi.exrc\nunless the variable\n.Ev NEXINIT\nis also set.\n.It Ev HOME\nThe user's home directory, used as the initial directory path for the startup\n.Pa $HOME/.nexrc\nand\n.Pa $HOME/.exrc\nfiles.\nThis value is also used as the default directory for the\n.Nm vi\n.Cm cd\ncommand.\n.It Ev LINES\nThe number of rows on the screen.\nThis value overrides any system or terminal specific values as POSIX 1003.2\nrequires honoring the environment variable value, even if the terminal is\nlater resized.\nIf the\n.Ev LINES\nenvironment variable is not set when\n.Nm ex Ns / Ns Nm vi\nruns, or the\n.Cm lines\noption is explicitly reset by the user,\n.Nm ex Ns / Ns Nm vi\nenters the value into the environment.\n.It Ev NEXINIT\nA list of\n.Nm ex\nstartup commands, read after\n.Pa /etc/vi.exrc .\n.It Ev SHELL\nThe user's shell of choice (see also the\n.Cm shell\noption).\n.It Ev TERM\nThe user's terminal type.\nThe default is the type\n.Dq vt100 .\nIf the\n.Ev TERM\nenvironment variable is not set when\n.Nm ex Ns / Ns Nm vi\nruns, or the\n.Cm term\noption is explicitly reset by the user,\n.Nm ex Ns / Ns Nm vi\nenters the value into the environment.\n.El\n.Sh ASYNCHRONOUS EVENTS\n.Bl -tag -width \"SIGWINCH\" -compact\n.It Dv SIGALRM\n.Nm vi Ns / Ns Nm ex\nuses this signal for periodic backups of file modifications and to display\n.Dq busy\nmessages when operations are likely to take a long time.\n.Pp\n.It Dv SIGHUP\n.It Dv SIGTERM\nIf the current buffer has changed since it was last written in its entirety,\nthe editor attempts to save the modified file so it can be later recovered.\nSee the\n.Nm vi Ns / Ns Nm ex\nreference manual section\n.Sx Recovery\nfor more information.\n.Pp\n.It Dv SIGINT\n.It Dv SIGQUIT\nWhen an interrupt occurs, the current operation is halted\nand the editor returns to the command level.\nIf interrupted during text input,\nthe text already input is resolved into the file as if the text\ninput had been normally terminated.\n.Pp\n.It Dv SIGWINCH\nThe screen is resized.\nSee the\n.Nm vi Ns / Ns Nm ex\nreference manual section\n.Sx Sizing the Screen\nfor more information.\n.\\\" .Pp\n.\\\" .It Dv SIGCONT\n.\\\" .It Dv SIGTSTP\n.\\\" .Nm vi Ns / Ns Nm ex\n.\\\" ignores these signals.\n.El\n.Sh FILES\n.Bl -tag -width \"/var/tmp/vi.recover\"\n.It Pa /bin/sh\nThe default user shell.\n.It Pa /etc/vi.exrc\nSystem-wide\n.Nm vi\nstartup file.\nIt is read for\n.Nm ex\ncommands first in the startup sequence.\nMust be owned by root or the user,\nand writable only by the owner.\n.It Pa /tmp\nTemporary file directory.\n.It Pa /var/tmp/vi.recover\nThe default recovery file directory.\n.It Pa $HOME/.nexrc\nFirst choice for user's home directory startup file, read for\n.Nm ex\ncommands right after\n.Pa /etc/vi.exrc\nunless either\n.Ev NEXINIT\nor\n.Ev EXINIT\nare set.\nMust be owned by root or the user,\nand writable only by the owner.\n.It Pa $HOME/.exrc\nSecond choice for user's home directory startup file, read for\n.Nm ex\ncommands under the same conditions as\n.Pa $HOME/.nexrc .\n.It Pa .nexrc\nFirst choice for local directory startup file, read for\n.Nm ex\ncommands at the end of the startup sequence if the\n.Cm exrc\noption was turned on earlier.\nMust be owned by the user\nand writable only by the owner.\n.It Pa .exrc\nSecond choice for local directory startup file, read for\n.Nm ex\ncommands under the same conditions as\n.Pa .nexrc .\n.El\n.Sh EXIT STATUS\nThe\n.Nm ex\nand\n.Nm vi\nutilities exit 0 on success,\nand >0 if an error occurs.\n.Sh SEE ALSO\n.Xr ctags 1 ,\n.Xr re_format 7\n.Rs\n.Sh STANDARDS\n.Nm nex Ns / Ns Nm nvi\nis close to\n.St -p1003.1-2008 .\nIt deviates in the following respects:\n.Bl -bullet\n.It\nThe\n.Ic s\n.Nm ex\ncommand may not be called as\n.Ic substitute .\n.It\nThe\n.Ic directory , redraw\nand\n.Ic slowopen\nsettings are not implemented.\n.It\nThe\n.Ic paragraphs\nand\n.Ic sections\nsettings default to values useful for editing\n.Xr mdoc 7\nmanuals.\n.It\nThe\n.Ev TMPDIR\nenvironment variable is ignored.\n.It\nIn insert mode, entering\n.Aq Ic control-H ,\n.Aq Ic erase ,\nor\n.Aq Ic kill\nfollowing a backslash will not embed the control character in the text.\n.El\n.Sh HISTORY\nThe\n.Nm ex\neditor first appeared in\n.Bx 1 .\nThe\n.Nm nex Ns / Ns Nm nvi\nreplacements for the\n.Nm ex Ns / Ns Nm vi\neditor first appeared in\n.Bx 4.4 .\n.Sh AUTHORS\n.An Bill Joy\nwrote the original version of\n.Nm ex\nin 1977.\n"
  },
  {
    "path": "docs/USD.doc/vitut/vi.apwh.ms",
    "content": ".\\\"        $OpenBSD: vi.apwh.ms,v 1.5 2004/01/24 12:29:13 jmc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)vi.apwh.ms        8.2 (Berkeley) 8/18/96\n.\\\"\n.if n \\{\\\n.po 5n\n.ll 70n\n.\\}\n.nr LL 6.5i\n.nr FL 6.5i\n.TL\nVi Command & Function Reference\n.AU CB 2675\nAlan P.W. Hewett\n.sp\nRevised for version 2.12 by Mark Horton\n.\\\" .CB\n.NH 1\nAuthor's Disclaimer\n.LP\nThis document does not claim to be 100% complete.  There are a\nfew commands listed in the original document that I was unable\nto test either because I do not speak \\fBlisp\\fR, because they\nrequired programs we don't have, or because I wasn't able to make\nthem work.  In these cases I left the command out.  The commands\nlisted in this document have been tried and are known to work.\nIt is expected that prospective users of this document will read\nit once to get the flavor of everything that \\fBvi\\fR can do\nand then use it as a reference document.  Experimentation is\nrecommended.  If you don't understand a command, try it and\nsee what happens.\n.LP\n[Note: In revising this document, I have attempted to make it\ncompletely reflect version 2.12 of\n.B vi .\nIt does not attempt to document the VAX version (version 3),\nbut with one or two exceptions (wrapmargin, arrow keys)\neverything said about 2.12 should apply to 3.1.\n.I \"Mark Horton\" ]\n.NH 1\nNotation\n.LP\n\\fB[option]\\fR is used to denote optional parts of a command.\nMany \\fBvi\\fR commands have an optional count.  \\fB[cnt]\\fR\nmeans that an optional number may precede the command to\nmultiply or iterate the command.\n\\fB{variable item}\\fR is used to denote parts of the command\nwhich must appear, but can take a number of different values.\n\\fB<character [-character]>\\fR means that the character or\none of the characters in the range described between the\ntwo angle brackets is to be typed.\nFor example \\fB<esc>\\fR means\nthe \\fBescape\\fR key is to be typed.  \\fB<a-z>\\fR means that a\nlower case letter is to be typed.  \\fB^<character>\\fR means that\nthe character is to be typed as a \\fBcontrol\\fR character, that is,\nwith the \\fB<cntl>\\fR key held down while simultaneously typing\nthe specified character.  In this document control characters will\nbe denoted using the \\fIupper case\\fR character, but\n^<uppercase chr> and ^<lowercase chr> are equivalent.  That is, for\nexample, \\fB<^D>\\fR is equal to \\fB<^d>\\fR.\nThe most common character abbreviations\nused in this list are as follows:\n.RS\n.IP <esc> 8\nescape, octal 033\n.IP <cr> 8\ncarriage return, ^M, octal 015\n.IP <lf> 8\nlinefeed ^J, octal 012\n.IP <nl> 8\nnewline, ^J, octal 012 (same as linefeed)\n.IP <bs> 8\nbackspace, ^H, octal 010\n.IP <tab> 8\ntab, ^I, octal 011\n.IP <bell> 8\nbell, ^G, octal 07\n.IP <ff> 8\nformfeed, ^L, octal 014\n.IP <sp> 8\nspace, octal 040\n.IP <del> 8\ndelete, octal 0177\n.RE\n.sp 1\n.NH 1\nBasics\n.LP\nTo run \\fBvi\\fR the shell variable \\fBTERM\\fR must be defined and\nexported to your environment.\nHow you do this depends on which shell you are using.\nYou can tell which shell you have by the character it\nprompts you for commands with.\nThe Bourne shell prompts with `$', and the C shell prompts with `%'.\nFor these examples, we will suppose\nthat you are using an HP 2621 terminal, whose termcap name is ``2621''.\n.NH 2\nBourne Shell\n.LP\nTo manually set your terminal type to 2621 you would type:\n.DS\nexport TERM=2621\n.DE\n.PP\nThere are various ways of having this automatically or\nsemi-automatically done when you log in.\nSuppose you usually dial in on a 2621.\nYou want to tell this to the machine, but still have it\nwork when you use a hardwired terminal.\nThe recommended way, if you have the\n.B tset\nprogram, is to use the sequence\n.DS\ntset \\-s \\-d 2621 > tset$$\n\\&. tset$$\nrm tset$$\n.DE\nin your .login (for csh) or the same thing using `.' instead of `source'\nin your .profile (for sh).\nThe above line says that if you are dialing in you are on a 2621,\nbut if you are on a hardwired terminal it figures out your terminal\ntype from an on-line list.\n.NH 2\nThe C Shell\n.LP\nTo manually set your terminal type to 2621 you would type:\n.DS\nsetenv TERM 2621\n.DE\n.PP\nThere are various ways of having this automatically or\nsemi-automatically done when you log in.\nSuppose you usually dial in on a 2621.\nYou want to tell this to the machine, but still have it\nwork when you use a hardwired terminal.\nThe recommended way, if you have the\n.B tset\nprogram, is to use the sequence\n.DS\ntset \\-s \\-d 2621 > tset$$\nsource tset$$\nrm tset$$\n.DE\nin your .login.*\n.FS\n* On a version 6 system\nwithout environments, the invocation of tset\nis simpler, just add the line ``tset \\-d 2621''\nto your .login or .profile.\n.FE\nThe above line says that if you are dialing in you are on a 2621,\nbut if you are on a hardwired terminal it figures out your terminal\ntype from an on-line list.\n.NH 1\nNormal Commands\n.LP\n\\fBVi\\fR is a visual editor with a window on the file.  What\nyou see on the screen is \\fBvi\\fR's current notion of\nwhat your file will contain,\n(at this point in the file),\nwhen it is written out.\nMost commands do not cause any change in the screen until the\ncomplete command is typed.  Should you get confused while\ntyping a command, you can abort the command by typing an\n<del> character.  You will know you are back to command level\nwhen you hear a <bell>.  Usually typing an <esc> will produce the\nsame result.  When \\fBvi\\fR gets an improperly formatted command\nit rings the <bell>.\nFollowing are the \\fBvi\\fR commands broken down by function.\n.NH 2\nEntry and Exit\n.LP\nTo enter\n.B vi\non a particular\n.I file ,\ntype\n.DS\n\\fBvi\\fP \\fIfile\\fP\n.DE\nThe file will be read in and the cursor will be placed at the beginning\nof the first line.\nThe first screenfull of the file will be displayed on the terminal.\n.PP\nTo get out of the editor, type\n.DS\nZZ\n.DE\nIf you are in some special mode, such as input mode\nor the middle of a multi-keystroke command, it may\nbe necessary to type <esc> first.\n.NH 2\nCursor and Page Motion\n.LP\n.RS\n.B NOTE:\nThe arrow keys (see the next four commands)\non certain kinds of terminals will not work with the\nPDP-11 version of vi.  The control versions or the hjkl versions will\nwork on any terminal.  Experienced users prefer the hjkl keys because\nthey are always right under their fingers.  Beginners often prefer\nthe arrow keys, since they do not require memorization of which hjkl\nkey is which.\nThe mnemonic value of hjkl is clear from looking at the keyboard of an adm3a.\n.sp\n.IP \"[cnt]h or [cnt]^H\" 16\n.br\nMove the cursor to the left one character.  Cursor stops at the left\nmargin of the page.\nIf cnt is given, these commands move that many spaces.\n.IP \"[cnt]^N or [cnt]j or [cnt]^J\" 16\n.br\nMove down one line.\nMoving off the screen scrolls the window to force a new line\nonto the screen.\nMnemonic: \\fBN\\fRext\n.IP \"[cnt]^P or [cnt]k\" 16\n.br\nMove up one line.\nMoving off the top of the screen forces new text onto the screen.\nMnemonic: \\fBP\\fRrevious\n.IP \"[cnt]<sp> or [cnt]l\" 16\n.br\nMove to the right one character.\nCursor will not go beyond the end of the line.\n.IP [cnt]- 16\nMove the cursor up the screen to the beginning of the next line.\nScroll if necessary.\n.IP \"[cnt]+ or [cnt]<cr>\" 16\n.sp 1\nMove the cursor down the screen to the beginning of the next line.\nScroll up if necessary.\n.IP \"[cnt]$\" 16\nMove the cursor to the end of the line.\nIf there is a count, move to the end of the line \"cnt\" lines\nforward in the file.\n.IP \"^\" 16\nMove the cursor to the beginning of the first word on the line.\n.IP \"0\" 16\nMove the cursor to the left margin of the current line.\n.IP \"[cnt]|\" 16\nMove the cursor to the column specified by the count.  The default is\ncolumn zero.\n.IP \"[cnt]w\" 16\nMove the cursor to the beginning of the next word. If there\nis a count, then move forward that many words and\nposition the cursor at the beginning of the word.\nMnemonic: next-\\fBw\\fRord\n.IP \"[cnt]W\" 16\nMove the cursor to the beginning of the next word which follows\na \"whitespace\" (<sp>,<tab>, or <nl>).  Ignore other punctuation.\n.IP \"[cnt]b\" 16\nMove the cursor to the preceding word.  Mnemonic: \\fBb\\fRackup-word\n.IP \"[cnt]B\" 16\nMove the cursor to the preceding word that is separated from the\ncurrent word by a \"whitespace\" (<sp>,<tab>, or <nl>).\n.IP \"[cnt]e\" 16\nMove the cursor to the end of the current word or the end of the\n\"cnt\"'th word hence.  Mnemonic: \\fBe\\fRnd-of-word\n.IP \"[cnt]E\" 16\nMove the cursor to the end of the current word which is delimited by\n\"whitespace\" (<sp>,<tab>, or <nl>).\n.IP \"[line number]G\" 16\n.br\nMove the cursor to the line specified.  Of particular use are the\nsequences \"1G\" and \"G\", which move the cursor to the beginning and\nthe end of the file respectively.  Mnemonic: \\fBG\\fRo-to\n.LP\n.B NOTE:\nThe next four commands (^D, ^U, ^F, ^B)\nare not true motion commands, in that they\ncannot be used as the object of commands such as delete or change.\n.IP \"[cnt]^D\" 16\nMove the cursor down in the file by \"cnt\" lines (or the last \"cnt\"\nif a new count isn't given.  The initial default is half a page.)  The\nscreen is simultaneously scrolled up.  Mnemonic: \\fBD\\fRown\n.IP \"[cnt]^U\" 16\nMove the cursor up in the file by \"cnt\" lines.  The screen is simultaneously\nscrolled down.  Mnemonic: \\fBU\\fRp\n.IP \"[cnt]^F\" 16\nMove the cursor to the next page.  A count moves that many pages.\nTwo lines of the previous page are kept on the screen for continuity if\npossible.  Mnemonic: \\fBF\\fRorward-a-page\n.IP \"[cnt]^B\" 16\nMove the cursor to the previous page.  Two lines of the current page\nare kept if possible.  Mnemonic: \\fBB\\fRackup-a-page\n.IP \"[cnt](\" 16\nMove the cursor to the beginning of the next sentence.\nA sentence is defined as ending with a \".\", \"!\", or \"?\"\nfollowed by two spaces or a <nl>.\n.IP \"[cnt])\" 16\nMove the cursor backwards to the beginning of a sentence.\n.IP \"[cnt]}\" 16\nMove the cursor to the beginning of the next paragraph.  This command\nworks best inside \\fBnroff\\fR documents.  It understands two sets of\n\\fBnroff\\fR macros, \\fB\\-ms\\fR and \\fB\\-mm\\fR, for which the\ncommands \".IP\", \".LP\", \".PP\", \".QP\", \"P\", as well as the nroff command \".bp\"\nare considered to be paragraph delimiters.\nA blank line also delimits a paragraph.\nThe \\fBnroff\\fR macros that it accepts as paragraph delimiters is\nadjustable.  See \\fBparagraphs\\fR under the \\fBSet Commands\\fR section.\n.IP \"[cnt]{\" 16\nMove the cursor backwards to the beginning of a paragraph.\n.IP \"]]\" 16\nMove the cursor to the next \"section\", where a section is defined by\ntwo sets of \\fBnroff\\fR macros, \\fB\\-ms\\fR and \\fB\\-mm\\fR, in which\n\".NH\", \".SH\", and \".H\" delimit a section.  A line beginning with a <ff><nl>\nsequence, or a line beginning with a \"{\" are also considered to\nbe section delimiters.  The last option makes it\nuseful for finding the beginnings of C functions.\nThe \\fBnroff\\fR macros that are used for section delimiters can be adjusted.\nSee \\fBsections\\fR under the \\fBSet Commands\\fR section.\n.IP \"[[\" 16\nMove the cursor backwards to the beginning of a section.\n.IP \"%\" 16\nMove the cursor to the matching parenthesis\nor brace.  This is very useful in C or lisp code.  If the\ncursor is sitting on a \\fB( ) {\\fR or \\fB}\\fR the cursor\nis moved to the matching character at the other end of the\nsection.  If the cursor is not sitting on a brace or a\nparenthesis, \\fBvi\\fR searches forward until it finds one\nand then jumps to the match mate.\n.IP \"[cnt]H\" 16\nIf there is no count move the cursor to the top left position on the screen.\nIf there is a count, then move the cursor to the beginning of the line\n\"cnt\" lines from the top of the screen.  Mnemonic:  \\fBH\\fRome\n.IP \"[cnt]L\" 16\nIf there is no count move the cursor to the beginning\nof the last line on the screen.\nIf there is a count, then move the cursor to the beginning of the line\n\"cnt\" lines from the bottom of the screen.  Mnemonic: \\fBL\\fRast\n.IP \"M\" 16\nMove the cursor to the beginning of the middle line on the screen.\nMnemonic: \\fBM\\fRiddle\n.IP \"m<a-z>\" 16\nThis command does not move the cursor, but it \\fBmarks\\fR the place\nin the file and the character \"<a-z>\" becomes the label for referring\nto this location in the file.  See the next two commands.  Mnemonic:\n\\fBm\\fRark\n.B NOTE:\nThe mark command is not a motion, and cannot be used as the target\nof commands such as delete.\n.IP \"\\(aa<a-z>\" 16\nMove the cursor to the beginning of the line that is marked with the label\n\"<a-z>\".\n.IP \"\\(ga<a-z>\" 16\nMove the cursor to the exact position on the line that was marked with\nwith the label \"<a-z>\".\n.IP \"\\(aa\\(aa\" 16\nMove the cursor back to the beginning of the line where it was before the\nlast \"non-relative\" move.  A \"non-relative\" move is something such as a\nsearch or a jump to a specific line in the file, rather than moving the\ncursor or scrolling the screen.\n.IP \"\\(ga\\(ga\" 16\nMove the cursor back to the exact spot on the line where it was located\nbefore the last \"non-relative\" move.\n.RE\n.NH 2\nSearches\n.LP\nThe following commands allow you to search for items in a file.\n.RS\n.IP [cnt]f{chr} 16\n.sp 1\nSearch forward on the line for the next or \"cnt\"'th occurrence of\nthe character \"chr\".  The cursor is placed \\fBat\\fR the character\nof interest.  Mnemonic: \\fBf\\fRind character\n.IP [cnt]F{chr} 16\n.sp 1\nSearch backwards on the line for the next or \"cnt\"'th occurrence of\nthe character \"chr\".  The cursor is placed \\fBat\\fR the character\nof interest.\n.IP [cnt]t{chr} 16\n.sp 1\nSearch forward on the line for the next or \"cnt\"'th occurrence of\nthe character \"chr\".  The cursor is placed \\fBjust preceding\\fR\nthe character of interest.  Mnemonic: move cursor up \\fBt\\fRo character\n.IP [cnt]T{chr} 16\n.sp 1\nSearch backwards on the line for the next or \"cnt\"'th occurrence of\nthe character \"chr\".  The cursor is placed \\fBjust preceding\\fR\nthe character of interest.\n.IP \"[cnt];\" 16\nRepeat the last \"f\", \"F\", \"t\" or \"T\" command.\n.IP \"[cnt],\" 16\nRepeat the last \"f\", \"F\", \"t\" or \"T\" command, but in the opposite\nsearch direction.  This is useful if you overshoot.\n.IP \"[cnt]/[string]/<nl>\" 16\n.br\nSearch forward for the next occurrence of \"string\".\nWrap around at the end of the file\ndoes occur.\nThe final \\fB</>\\fR is not required.\n.IP \"[cnt]?[string]?<nl>\" 16\n.br\nSearch backwards for the next occurrence of \"string\".  If a count is\nspecified, the count becomes the new window size.  Wrap around at the beginning\nof the file does occur.\nThe final \\fB<?>\\fR is not required.\n.IP n 16\nRepeat the last /[string]/ or ?[string]? search.  Mnemonic: \\fBn\\fRext\noccurrence.\n.IP N 16\nRepeat the last /[string]/ or ?[string]? search, but in the reverse\ndirection.\n.IP \":g/[string]/[editor command]<nl>\" 16\n.sp 1\nUsing the \\fB:\\fR syntax it is possible to do global searches ala the\nstandard UNIX \"ed\" editor.\n.RE\n.NH 2\nText Insertion\n.LP\nThe following commands allow for the insertion of text.  All multicharacter\ntext insertions are terminated with an <esc> character.\nThe last change\ncan always be \\fBundone\\fR by typing a \\fBu\\fR.\nThe text insert in insertion mode can contain newlines.\n.RS\n.IP a{text}<esc> 16\nInsert text immediately following the cursor position.\nMnemonic: \\fBa\\fRppend\n.IP A{text}<esc> 16\nInsert text at the end of the current line.\nMnemonic: \\fBA\\fRppend\n.IP i{text}<esc> 16\nInsert text immediately preceding the cursor position.\nMnemonic: \\fBi\\fRnsert\n.IP I{text}<esc> 16\nInsert text at the beginning of the current line.\n.IP o{text}<esc> 16\nInsert a new line after the line on which the cursor appears and\ninsert text there.  Mnemonic:  \\fBo\\fRpen new line\n.IP O{text}<esc> 16\nInsert a new line preceding the line on which the cursor appears\nand insert text there.\n.RE\n.NH 2\nText Deletion\n.LP\nThe following commands allow the user to delete text in various ways.\nAll changes can always be \\fBundone\\fR by typing the \\fBu\\fR command.\n.RS\n.IP \"[cnt]x\" 16\nDelete the character or characters starting at the cursor position.\n.IP \"[cnt]X\" 16\nDelete the character or characters starting at the character preceding\nthe cursor position.\n.IP \"D\" 16\nDeletes the remainder of the line starting at the cursor.\nMnemonic: \\fBD\\fRelete the rest of line\n.IP \"[cnt]d{motion}\" 16\n.br\nDeletes one or more occurrences of the specified motion.\nAny motion from sections 4.1 and 4.2 can be used here.\nThe d can be stuttered (e.g. [cnt]dd) to delete cnt lines.\n.RE\n.NH 2\nText Replacement\n.LP\nThe following commands allow the user to simultaneously delete and\ninsert new text.  All such actions can be \\fBundone\\fR by typing\n\\fBu\\fR following the command.\n.RS\n.IP \"r<chr>\" 16\nReplaces the character at the current cursor position with <chr>.  This\nis a one character replacement.  No <esc> is required for termination.\nMnemonic:  \\fBr\\fReplace character\n.IP \"R{text}<esc>\" 16\nStarts overlaying the characters on the screen with whatever you type.\nIt does not stop until an <esc> is typed.\n.IP \"[cnt]s{text}<esc>\" 16\nSubstitute for \"cnt\" characters beginning at the current cursor\nposition.  A \"$\" will appear at the position in the text where the\n\"cnt\"'th character appears so you will know how much you are erasing.\nMnemonic: \\fBs\\fRubstitute\n.IP \"[cnt]S{text}<esc>\" 16\nSubstitute for the entire current line (or lines).  If no count is given,\na \"$\" appears at the end of the current line.  If a count of more than\n1 is given, all the lines to be replaced are deleted before the insertion\nbegins.\n.IP \"[cnt]c{motion}{text}<esc>\" 16\n.br\nChange the specified \"motion\" by replacing it with the\ninsertion text.  A \"$\" will appear at the end of the last item\nthat is being deleted unless the deletion involves whole lines.\nMotion's can be any motion from sections 4.1 or 4.2.\nStuttering the c (e.g. [cnt]cc) changes cnt lines.\n.RE\n.NH 2\nMoving Text\n.LP\n\\fBVi\\fR provides a number of ways of moving chunks of text around.\nThere are nine buffers into which each piece of text which is deleted\nor \"yanked\" is put in addition to the \"undo\" buffer.\nThe most recent deletion or yank is in the \"undo\" buffer and also\nusually in buffer\n1, the next most recent in buffer 2, and so forth.  Each new deletion\npushes down all the older deletions.  Deletions older than 9\ndisappear.  There is also\na set of named registers, a-z, into which text can optionally\nbe placed.  If any delete or replacement type command is preceded\nby \\fB\"<a-z>\\fR, that named buffer will contain the text deleted\nafter the command is executed.  For example, \\fB\"a3dd\\fR will delete\nthree lines starting at the current line and put them in buffer \\fB\"a\\fR.*\n.FS\n* Referring to an upper case letter as a buffer name (A-Z) is the\nsame as referring to the lower case letter, except that text placed\nin such a buffer is appended to it instead of replacing it.\n.FE\nThere are two more basic commands and\nsome variations useful in getting and putting text into a file.\n.RS\n.IP [\"<a-z>][cnt]y{motion} 16\n.sp 1\nYank the specified item or \"cnt\" items and put in the \"undo\" buffer or\nthe specified buffer.  The variety of \"items\" that can be yanked\nis the same as those that can be deleted with the \"d\" command or\nchanged with the \"c\" command.  In the same way that \"dd\" means\ndelete the current line and \"cc\" means replace the current line,\n\"yy\" means yank the current line.\n.IP [\"<a-z>][cnt]Y 16\nYank the current line or the \"cnt\" lines starting from the current\nline.  If no buffer is specified, they will go into the \"undo\" buffer,\nlike any delete would.  It is equivalent to \"yy\".\nMnemonic:  \\fBY\\fRank\n.IP [\"<a-z>]p 16\nPut \"undo\" buffer or the specified buffer down \\fBafter\\fR the cursor.\nIf whole lines were yanked or deleted into the buffer, then they will be\nput down on the line following the line the cursor is on.  If\nsomething else was deleted, like a word or sentence, then it will\nbe inserted immediately following the cursor.\nMnemonic:  \\fBp\\fRut buffer\n.IP\nIt should be noted that text in the named buffers remains there when you\nstart editing a new file with the \\fB:e file<esc>\\fR command.  Since\nthis is so, it is possible to copy or delete text from one file and\ncarry it over to another file in the buffers.\nHowever, the undo buffer and the ability to undo are lost when\nchanging files.\n.IP [\"<a-z>]P 16\nPut \"undo\" buffer or the specified buffer down \\fBbefore\\fR the cursor.\nIf whole lines where yanked or deleted into the buffer, then they will be\nput down on the line preceding the line the cursor is on.  If\nsomething else was deleted, like a word or sentence, then it will\nbe inserted immediately preceding the cursor.\n.IP [cnt]>{motion} 16\nThe shift operator will right shift all the text from the line on which\nthe cursor is located to the line where the \\fBmotion\\fR is located.\nThe text is shifted by one \\fBshiftwidth\\fR.  (See section 6.)\n\\fB>>\\fR means right shift the current line or lines.\n.IP [cnt]<{motion} 16\nThe shift operator will left shift all the text from the line on which\nthe cursor is located to the line where the \\fBitem\\fR is located.\nThe text is shifted by one \\fBshiftwidth\\fR.  (See section 6.)\n\\fB<<\\fR means left shift the current line or lines.\nOnce the line has reached the left margin it is not further affected.\n.IP [cnt]={motion} 16\n.\\\" Prettyprints the indicated area according to\n.\\\" .B lisp\n.\\\" conventions.\n.\\\" The area should be a lisp s-expression.\nDisplays the line number.\nIf no prefix is specified,the line number of the last line in the file\nis displayed.\n.RE\n.NH 2\nMiscellaneous Commands\n.LP\n\\fBVi\\fR has a number of miscellaneous commands that are very\nuseful.  They are:\n.RS\n.IP ZZ 16\nThis is the normal way to exit from vi.\nIf any changes have been made, the file is written out.\nThen you are returned to the shell.\n.IP ^L 16\nRedraw the current screen.  This is useful if someone \"write\"s you\nwhile you are in \"vi\" or if for any reason garbage gets onto the\nscreen.\n.IP ^R 16\nOn dumb terminals, those not having the \"delete line\" function\n(the vt100 is such a terminal), \\fBvi\\fR saves redrawing the\nscreen when you delete a line by just marking the line with an\n\"@\" at the beginning and blanking the line.  If you want to\nactually get rid of the lines marked with \"@\" and see what the\npage looks like, typing a ^R will do this.\n.IP \\s+4.\\s0 16\n\"Dot\" is a particularly useful command.  It repeats the last\ntext modifying command.  Therefore you can type a command once and\nthen to another place and repeat it by just typing \".\".\n.IP u 16\nPerhaps the most important command in the editor,\nu undoes the last command that changed the buffer.\nMnemonic:  \\fBu\\fRndo\n.IP U 16\nUndo all the text modifying commands performed on the current line\nsince the last time you moved onto it.\n.IP [cnt]J 16\nJoin the current line and the following line.  The <nl> is deleted\nand the two lines joined, usually with a space between the\nend of the first line and the beginning of what was the second\nline.  If the first line ended with a \"period\", then two spaces\nare inserted.\nA count joins the next cnt lines.\nMnemonic: \\fBJ\\fRoin lines\n.IP Q 16\nSwitch to \\fBex\\fR editing mode.\nIn this mode \\fBvi\\fR will behave very much like \\fBed\\fR.\nThe editor in this mode will operate on single lines normally and\nwill not attempt to keep the \"window\" up to date.\n.\\\" Once in this mode it is also possible to switch to the \\fBopen\\fR\n.\\\" mode of editing.  By entering the command \\fB[line number]open<nl>\\fR\n.\\\" you enter this mode.  It is similar to the normal visual mode\n.\\\" except the window is only \\fBone\\fR line long.\nMnemonic: \\fBQ\\fRuit visual mode\n.IP ^] 16\nAn abbreviation for a tag command.\nThe cursor should be positioned at the beginning of a word.\nThat word is taken as a tag name, and the tag with that\nname is found as if it had been typed in a :tag command.\n.IP [cnt]!{motion}{UNIX\\ cmd}<nl> 16\n.br\nAny UNIX filter\n(e.g. command that reads the standard input and outputs something\nto the standard output) can be sent a section of the current file and\nhave the output of the command replace the original text.  Useful\nexamples are programs like \\fBsort\\fR and\n\\fBnroff\\fR.  For instance, using \\fBsort\\fR it would be possible to\nsort a section of the current file into a new list.\nUsing \\fB!!\\fR means take a line or lines starting at the line the\ncursor is currently on and pass them to the UNIX command.\n.B NOTE:\nTo just escape to the shell for one command,\nuse :!{cmd}<nl>, see section 5.\n.IP z{cnt}<nl> 16\nThis resets the current window size to \"cnt\" lines and redraws the screen.\n.RE\n.NH 2\nSpecial Insert Characters\n.LP\nThere are some characters that have special meanings during\ninsert modes.  They are:\n.RS\n.IP ^V 16\nDuring inserts, typing a ^V allows you to quote control characters\ninto the file.  Any character typed after the ^V will be inserted\ninto the file.\n.IP [^]^D\\ or\\ [0]^D 16\n<^D> without any argument backs up one \\fBshiftwidth\\fR.  This is necessary\nto remove indentation that was inserted by the \\fBautoindent\\fR feature.\n^<^D> temporarily removes all the autoindentation, thus placing the cursor\nat the left margin.  On the next line, the previous indent level will be\nrestored.  This is useful for putting \"labels\" at the left margin.\n0<^D> says remove all autoindents and stay that way.  Thus the cursor\nmoves to the left margin and stays there on successive lines until\n<tab>'s are typed.  As with the <tab>, the <^D> is only effective before\nany other \"non-autoindent\" controlling characters are typed.\nMnemonic: \\fBD\\fRelete a shiftwidth\n.IP ^W 16\nIf the cursor is sitting on a word, <^W> moves the cursor back to the beginning\nof the word, thus erasing the word from the insert.\nMnemonic: erase \\fBW\\fRord\n.IP <bs> 16\nThe backspace always serves as an erase during insert modes in addition\nto your normal \"erase\" character.  To insert a <bs> into your file, use\nthe <^V> to quote it.\n.RE\n.NH 1\n\\fB:\\fR Commands\n.LP\nTyping a \":\" during command mode causes \\fBvi\\fR to put the cursor at\nthe bottom on the screen in preparation for a command.  In the\n\":\" mode, \\fBvi\\fR can be given most \\fBed\\fR commands.  It is\nalso from this mode that you exit from \\fBvi\\fR or switch to different\nfiles.  All commands of this variety are terminated by a <nl>, <cr>,\nor <esc>.\n.RS\n.IP \":w[!] [file]\" 16\nCauses \\fBvi\\fR to write out the current text to the disk.  It is\nwritten to the file you are editing unless \"file\" is supplied.  If\n\"file\" is supplied, the write is directed to that file instead.  If\nthat file already exists, \\fBvi\\fR will not perform the write unless\nthe \"!\" is supplied indicating you\n.I really\nwant to destroy the older copy of the file.\n.IP :q[!] 16\nCauses \\fBvi\\fR to exit.  If you have modified the file you are\nlooking at currently and haven't written it out, \\fBvi\\fR will\nrefuse to exit unless the \"!\" is supplied.\n.IP \":e[!] [+[cmd]] [file]\" 16\n.sp 1\nStart editing a new file called \"file\" or start editing the current\nfile over again.  The command \":e!\" says \"ignore the changes I've made\nto this file and start over from the beginning\".  It is useful if\nyou really mess up the file.  The optional \"+\" says instead of starting\nat the beginning, start at the \"end\", or,\nif \"cmd\" is supplied, execute \"cmd\" first.\nUseful cases of this are where cmd is \"n\" (any integer) which starts\nat line number n,\nand \"/text\", which searches for \"text\" and starts at the line where\nit is found.\n.IP \"^^\" 16\nSwitch back to the place you were before your last tag command.\nIf your last tag command stayed within the file, ^^ returns to that tag.\nIf you have no recent tag command, it will return to the\nsame place in the previous file that it was showing when you switched\nto the current file.\n.IP \":n[!]\" 16\nStart editing the next file in the argument list.  Since \\fBvi\\fR\ncan be called with multiple file names, the \":n\" command tells it to\nstop work on the current file and switch to the next file.  If the\ncurrent file was modifies, it has to be written out before the \":n\"\nwill work or else the \"!\" must be supplied, which says discard the\nchanges I made to the current file.\n.IP \":n[!] file [file file ...]\" 16\n.sp\nReplace the current argument list with a new list of files and start\nediting the first file in this new list.\n.IP \":r file\" 16\nRead in a copy of \"file\" on the line after the cursor.\n.IP \":r !cmd\" 16\nExecute the \"cmd\" and take its output and put it into the file after\nthe current line.\n.IP \":!cmd\" 16\nExecute any UNIX shell command.\n.IP \":ta[!] tag\" 16\n.B Vi\nlooks in the file named\n.B tags\nin the current directory.\n.B Tags\nis a file of lines in the format:\n.sp 1\n.ti +8\ntag filename \\fBvi\\fR-search-command\n.sp 1\nIf \\fBvi\\fR finds the tag you specified in the \\fB:ta\\fR command,\nit stops editing the current file if necessary and if the current file is\nup to date on the disk and switches to the file specified and uses the\nsearch pattern specified to find the \"tagged\" item of interest.  This\nis particularly useful when editing multi-file C programs such as the\noperating system.  There is a program called \\fBctags\\fR which will\ngenerate an appropriate \\fBtags\\fR file for C and f77\nprograms so that by saying\n\\fB:ta function<nl>\\fR you will be switched to that function.\nIt could also be useful when editing multi-file documents, though the\n\\fBtags\\fR file would have to be generated manually.\n.RE\n.NH 1\nSpecial Arrangements for Startup\n.PP\n\\fBVi\\fR takes the value of \\fB$TERM\\fR and looks up the characteristics\nof that terminal in the file \\fB/etc/termcap\\fR.\nIf you don't know \\fBvi\\fR's name for the terminal you are working\non, look in \\fB/etc/termcap\\fR.\n.PP\nWhen \\fBvi\\fR starts, it attempts to read the variable EXINIT\nfrom your environment.*\nIf that exists, it takes the values in it as the default values\nfor certain of its internal constants.  See the section on \"Set Values\"\nfor further details.\nIf EXINIT doesn't exist you will get all the normal defaults.\n.FS\n* On version 6 systems\nInstead of EXINIT, put the startup commands in the file .exrc\nin your home directory.\n.FE\n.PP\nShould you inadvertently hang up the phone while inside\n.B vi ,\nor should the computer crash,\nall may not be lost.\nUpon returning to the system, type:\n.DS\nvi \\-r file\n.DE\nThis will normally recover the file.  If there is more than one\ntemporary file for a specific file name, \\fBvi\\fR recovers the\nnewest one.  You can get an older version by recovering the\nfile more than once.\nThe command \"vi -r\" without a file name gives you the list of files\nthat were saved in the last system crash\n(but\n.I not\nthe file just saved when the phone was hung up).\n.NH 1\nSet Commands\n.LP\n\\fBVi\\fR has a number of internal variables and switches which can be\nset to achieve special affects.\nThese options come in three forms, those that are switches, which toggle\nfrom off to on and back, those that require a numeric value, and those\nthat require an alphanumeric string value.\nThe toggle options are set by a command of the form:\n.DS\n:set option<nl>\n.DE\nand turned off with the command:\n.DS\n:set nooption<nl>\n.DE\nCommands requiring a value are set with a command of the form:\n.DS\n:set option=value<nl>\n.DE\nTo display the value of a specific option type:\n.DS\n:set option?<nl>\n.DE\nTo display only those that you have changed type:\n.DS\n:set<nl>\n.DE\nand to display the long table of all the settable parameters and\ntheir current values type:\n.DS\n:set all<nl>\n.DE\n.PP\nMost of the options have a long form and an abbreviation.  Both are\nlisted in the following table as well as the normal default value.\n.PP\nTo arrange to have values other than the default used every time you\nenter\n.B vi ,\nplace the appropriate\n.B set\ncommand in EXINIT in your environment, e.g.\n.DS\nEXINIT='set ai aw terse sh=/bin/csh'\nexport EXINIT\n.DE\nor\n.DS\nsetenv EXINIT 'set ai aw terse sh=/bin/csh'\n.DE\nfor\n.B sh\nand\n.B csh ,\nrespectively.\nThese are usually placed in your .profile or .login.\nIf you are running a system without environments (such as version 6)\nyou can place the set command in the file .exrc in your home\ndirectory.\n.RS\n.IP autoindent\\ ai 16\nDefault: noai Type: toggle\n.br\nWhen in autoindent mode, vi helps you indent code by starting each\nline in the same column as the preceding line.\nTabbing to the right with <tab> or <^T> will move this boundary to\nthe right, and it can be moved to the left with <^D>.\n.IP autoprint\\ ap 16\nDefault: ap Type: toggle\n.br\nCauses the current line to be printed after each ex text modifying command.\nThis is not of much interest in the normal \\fBvi\\fR visual mode.\n.IP autowrite\\ aw 16\nDefault: noaw type: toggle\n.br\nAutowrite causes an automatic write to be done if there are unsaved\nchanges before certain commands which change files or otherwise\ninteract with the outside world.\nThese commands are :!, :tag, :next, :rewind, ^^, and ^].\n.IP beautify\\ bf 16\nDefault: nobf Type: toggle\n.br\nCauses all control characters except <tab>, <nl>, and <ff> to be discarded.\n.IP directory\\ dir 16\nDefault: dir=/tmp Type: string\n.br\nThis is the directory in which \\fBvi\\fR puts its temporary file.\n.IP errorbells\\ eb 16\nDefault: noeb Type: toggle\n.br\nError messages are preceded by a <bell>.\n.IP hardtabs\\ ht 16\nDefault: hardtabs=0 Type: numeric\n.br\nThis option contains the value of hardware tabs in your terminal, or\nof software tabs expanded by the Unix system.\n.IP ignorecase\\ ic 16\nDefault: noic Type: toggle\n.br\nAll upper case characters are mapped to lower case in regular expression\nmatching.\n.\\\" .IP lisp 16\n.\\\" Default: nolisp Type: toggle\n.\\\" .br\n.\\\" Autoindent for \\fBlisp\\fR code.  The commands \\fB( ) [[\\fR and \\fB]]\\fR\n.\\\" are modified appropriately to affect s-expressions and functions.\n.IP list 16\nDefault: nolist Type: toggle\n.br\nAll printed lines have the <tab> and <nl> characters displayed visually.\n.IP magic 16\nDefault: magic Type: toggle\n.br\nEnable the metacharacters for matching.  These include \\fB. * < > [string]\n[^string]\\fR and \\fB[<chr>-<chr>]\\fR.\n.IP number\\ nu 16\nDefault: nonu Type: toggle\n.br\nEach line is displayed with its line number.\n.IP open 16\nDefault: open Type: toggle\n.br\nWhen set, prevents entering open or visual modes from ex or edit.\nNot of interest from vi.\n.IP optimize\\ opt 16\nDefault: opt Type: toggle\n.br\nBasically of use only when using the \\fBex\\fR capabilities.  This\noption prevents automatic <cr>s from taking place,\nand speeds up output of indented lines,\nat the expense of losing typeahead on some versions of UNIX.\n.IP paragraphs\\ para 16\nDefault: para=IPLPPPQPP LIpplpipbp Type: string\n.br\nEach pair of characters in the string indicate \\fBnroff\\fR macros\nwhich are to be treated as the beginning of a paragraph for the\n\\fB{\\fR and \\fB}\\fR commands.  The default string is for the \\fB-ms\\fR\nand \\fB-mm\\fR macros.\nTo indicate one letter \\fBnroff\\fR macros, such as \\fB.P\\fR or \\fB.H\\fR,\nquote a space in for the second character position.  For example:\n.sp 1\n.ti +8\n:set paragraphs=P\\e bp<nl>\n.sp 1\nwould cause \\fBvi\\fR to consider \\fB.P\\fR and \\fB.bp\\fR as paragraph\ndelimiters.\n.IP prompt 16\nDefault: prompt Type: toggle\n.br\nIn\n.B ex\ncommand mode the prompt character \\fB:\\fR will be printed when\n\\fBex\\fR is waiting for a command.  This is not of interest from vi.\n.IP redraw 16\nDefault: noredraw Type: toggle\n.br\nOn dumb terminals, force the screen to always be up to date,\nby sending great amounts of output.  Useful only at high speeds.\n.IP report 16\nDefault: report=5 Type: numeric\n.br\nThis sets the threshold for the number of lines modified.  When\nmore than this number of lines are modified, removed, or yanked,\n\\fBvi\\fR will report the number of lines changed at the bottom of\nthe screen.\n.IP scroll 16\nDefault: scroll={1/2 window} Type: numeric\n.br\nThis is the number of lines that the screen scrolls up or down when\nusing the <^U> and <^D> commands.\n.IP sections 16\nDefault: sections=NHSHH HUnhsh Type: string\n.br\nEach two character pair of this string specify \\fBnroff\\fR macro names\nwhich are to be treated as the beginning of a section by the\n\\fB]]\\fR and \\fB[[\\fR commands.  The default string is for the \\fB-ms\\fR\nand \\fB-mm\\fR macros.\nTo enter one letter \\fBnroff\\fR macros, use a quoted space as the\nsecond character.\nSee \\fBparagraphs\\fR for a fuller explanation.\n.IP shell\\ sh 16\nDefault: sh=from environment SHELL or /bin/sh   Type: string\n.br\nThis is the name of the \\fBsh\\fR to be used for \"escaped\" commands.\n.IP shiftwidth\\ sw 16\nDefault: sw=8 Type: numeric\n.br\nThis is the number of spaces that a <^T> or <^D> will move over for\nindenting, and the amount < and > shift by.\n.IP showmatch\\ sm 16\nDefault: nosm Type: toggle\n.br\nWhen a \\fB)\\fR or \\fB}\\fR is typed, show the matching \\fB(\\fR or \\fB{\\fR\nby moving the cursor to it for one second if it is on the current screen.\n.\\\" .IP slowopen\\ slow 16\n.\\\" Default: terminal dependent Type: toggle\n.\\\" .br\n.\\\" On terminals that are slow and unintelligent, this option prevents the\n.\\\" updating of the screen some of the time to improve speed.\n.IP tabstop\\ ts 16\nDefault: ts=8 Type: numeric\n.br\n<tab>s are expanded to boundaries that are multiples of this value.\n.IP taglength\\ tl 16\nDefault: tl=0 Type: numeric\n.br\nIf nonzero, tag names are only significant to this many characters.\n.IP term 16\nDefault: (from environment \\fBTERM\\fP, else dumb) Type: string\n.br\nThis is the terminal and controls the visual displays.  It cannot be\nchanged when in \"visual\" mode,\nyou have to Q to command mode, type a\nset term command, and do ``vi.'' to get back into visual.\nOr exit vi, fix $TERM, and reenter.\nThe definitions that drive a particular\nterminal type are found in the file \\fB/etc/termcap\\fR.\n.\\\" .IP terse 16\n.\\\" Default: terse Type: toggle\n.\\\" .br\n.\\\" When set, the error diagnostics are short.\n.IP warn 16\nDefault: warn Type: toggle\n.br\nThe user is warned if she/he tries to escape to\nthe shell without writing out the current changes.\n.IP window 16\nDefault: window={8 at 600 baud or less, 16 at 1200 baud, and screen\nsize \\- 1 at 2400 baud or more} Type: numeric\n.br\nThis is the number of lines in the window whenever \\fBvi\\fR must redraw\nan entire screen.  It is useful to make this size smaller if you are\non a slow line.\n.IP w300,\\ w1200,\\ w9600\n.br\nThese set window, but only within the corresponding speed ranges.\nThey are useful in an EXINIT to fine tune window sizes.\nFor example,\n.DS\nset w300=4 w1200=12\n.DE\ncauses a 4 lines window at speed up to 600 baud, a 12 line window at 1200\nbaud, and a full screen (the default) at over 1200 baud.\n.IP wrapscan\\ ws 16\nDefault: ws Type: toggle\n.br\nSearches will wrap around the end of the file when is option is set.  When\nit is off, the search will terminate when it reaches the end or the\nbeginning of the file.\n.IP wrapmargin\\ wm 16\nDefault: wm=0 Type: numeric\n.br\n\\fBVi\\fR will automatically insert a <nl> when it finds a natural\nbreak point (usually a <sp> between words) that occurs within\n\"wm\" spaces of the right margin.\nTherefore with \"wm=0\" the option is off.  Setting it to 10 would\nmean that any time you are within 10 spaces of the right margin\n\\fBvi\\fR would be looking for a <sp> or <tab> which it could\nreplace with a <nl>.  This is convenient for people who forget\nto look at the screen while they type.\n(In version 3, wrapmargin behaves more like nroff, in that the\nboundary specified by the distance from the right edge of the screen\nis taken as the rightmost edge of the area where a break is allowed,\ninstead of the leftmost edge.)\n.IP writeany\\ wa 16\nDefault: nowa Type: toggle\n.br\n\\fBVi\\fR normally makes a number of checks before it writes out a file.\nThis prevents the user from inadvertently destroying a file.  When the\n\"writeany\" option is enabled, \\fBvi\\fR no longer makes these checks.\n.RE\n"
  },
  {
    "path": "docs/USD.doc/vitut/vi.chars",
    "content": ".\\\"        $OpenBSD: vi.chars,v 1.6 2004/11/29 06:20:03 jsg Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)vi.chars        8.3 (Berkeley) 6/27/96\n.\\\"\n.\\\" .bd S 3\n.pn 21\n.de iP\n.IP \"\\fB\\\\$1\\fR\" \\\\$2\n..\n.SH\nAppendix: character functions\n.PP\nThis appendix gives the uses the editor makes of each character.  The\ncharacters are presented in their order in the \\s-2ASCII\\s0 character\nset:  Control characters come first, then most special characters, then\nthe digits, upper and then lower case characters.\n.PP\nFor each character we tell a meaning it has as a command and any meaning it\nhas during an insert.\nIf it has only meaning as a command, then only this is discussed.\nSection numbers in parentheses indicate where the character is discussed;\na `f' after the section number means that the character is mentioned\nin a footnote.\n.iP \"^@\" 15\nNot a command character.\nIf typed as the first character of an insertion it is replaced with the\nlast text inserted, and the insert terminates.  Only 128 characters are\nsaved from the last insert; if more characters were inserted the mechanism\nis not available.\nA \\fB^@\\fR cannot be part of the file due to the editor implementation\n(7.5f).\n.iP \"^A\" 15\nSearch forward for the current word.\n.iP \"^B\" 15\nBackward window.\nA count specifies repetition.\nTwo lines of continuity are kept if possible (2.1, 6.1, 7.2).\n.iP \"^C\" 15\nUnused.\n.iP \"^D\" 15\nAs a command, scrolls down a half-window of text.\nA count gives the number of (logical) lines to scroll, and is remembered\nfor future \\fB^D\\fR and \\fB^U\\fR commands (2.1, 7.2).\nDuring an insert, backtabs over \\fIautoindent\\fR whitespace at the beginning\nof a line (6.6, 7.5); this whitespace cannot be backspaced over.\n.iP \"^E\" 15\nExposes one more line below the current screen in the file, leaving\nthe cursor where it is if possible.\n(Version 3 only.)\n.iP \"^F\" 15\nForward window.  A count specifies repetition.\nTwo lines of continuity are kept if possible (2.1, 6.1, 7.2).\n.iP \"^G\" 15\nEquivalent to \\fB:f\\fR\\s-2CR\\s0, printing the current file, whether\nit has been modified, the current line number and the number of lines\nin the file, and the percentage of the way through the file that you\nare.\n.iP \"^H (\\fR\\s-2BS\\s0\\fP)\" 15\nSame as\n.B \"left arrow\" .\n(See\n.B h ).\nDuring an insert, eliminates the last input character, backing over it\nbut not erasing it; it remains so you can see what you typed if you\nwish to type something only slightly different (3.1, 7.5).\n.iP \"^I\\ (\\fR\\s-2TAB\\s0\\fP)\" 15\nNot a command character.\nWhen inserted it prints as some\nnumber of spaces.\nWhen the cursor is at a tab character it rests at the last of the spaces\nwhich represent the tab.\nThe spacing of tabstops is controlled by the \\fItabstop\\fR option (4.1, 6.6).\n.iP \"^J\\ (\\fR\\s-2LF\\s0\\fP)\" 15\nSame as\n.B \"down arrow\"\n(see\n.B j ).\n.iP \"^K\" 15\nUnused.\n.iP \"^L\" 15\nThe \\s-2ASCII\\s0 formfeed character, this causes the screen to be cleared\nand redrawn.  This is useful after a transmission error, if characters\ntyped by a program other than the editor scramble the screen,\nor after output is stopped by an interrupt (5.4, 7.2f).\n.ne 1i\n.iP \"^M\\ (\\fR\\s-2CR\\s0\\fP)\" 15\nA carriage return advances to the next line, at the first non-white position\nin the line.  Given a count, it advances that many lines (2.3).\nDuring an insert, a \\s-2CR\\s0 causes the insert to continue onto\nanother line (3.1).\n.iP \"^N\" 15\nSame as\n.B \"down arrow\"\n(see\n.B j ).\n.iP \"^O\" 15\nUnused.\n.iP \"^P\" 15\nSame as\n.B \"up arrow\"\n(see\n.B k ).\n.iP \"^Q\" 15\nNot a command character.\nIn input mode,\n.B ^Q\nquotes the next character, the same as\n.B ^V ,\nexcept that some teletype drivers will eat the\n.B ^Q\nso that the editor never sees it.\n.iP \"^R\" 15\nRedraws the current screen, eliminating logical lines not corresponding\nto physical lines (lines with only a single @ character on them)\n.\\\" On hardcopy terminals in \\fIopen\\fR mode, retypes the current line\n(5.4, 7.2, 7.8).\n.iP \"^S\" 15\nUnused.  Some teletype drivers use\n.B ^S\nto suspend output until\n.B ^Q\nis pressed.\n.iP \"^T\" 15\nNot a command character.\nDuring an insert, with \\fIautoindent\\fR set and at the beginning of the\nline, inserts \\fIshiftwidth\\fR whitespace.\n.iP \"^U\" 15\nScrolls the screen up, inverting \\fB^D\\fR which scrolls down.  Counts work as\nthey do for \\fB^D\\fR, and the previous scroll amount is common to both.\nOn a dumb terminal, \\fB^U\\fR will often necessitate clearing and redrawing\nthe screen further back in the file (2.1, 7.2).\n.iP \"^V\" 15\nNot a command character.\nIn input mode, quotes the next character so that it is possible\nto insert non-printing and special characters into the file (4.2, 7.5).\n.iP \"^W\" 15\nNot a command character.\nDuring an insert, backs up as \\fBb\\fR would in command mode; the deleted\ncharacters remain on the display (see \\fB^H\\fR) (7.5).\n.iP \"^X\" 15\nUnused.\n.iP \"^Y\" 15\nExposes one more line above the current screen, leaving the cursor where\nit is if possible.  (No mnemonic value for this key; however, it is next\nto \\fB^U\\fR which scrolls up a bunch.)\n(Version 3 only.)\n.iP \"^Z\" 15\nIf supported by the Unix system,\nstops the editor, exiting to the top level shell.\nSame as \\fB:stop\\fP\\s-2CR\\s0.\nOtherwise, unused.\n.iP \"^[\\ (\\fR\\s-2ESC\\s0\\fP)\" 15\nCancels a partially formed command, such as a \\fBz\\fR when no following\ncharacter has yet been given; terminates inputs on the last line (read\nby commands such as \\fB: /\\fR and \\fB?\\fR); ends insertions of new text\ninto the buffer.\nIf an \\s-2ESC\\s0 is given when quiescent in command state, the editor\nrings the bell or flashes the screen.  You can thus hit \\s-2ESC\\s0 if\nyou don't know what is happening till the editor rings the bell.\nIf you don't know if you are in insert mode you can type \\s-2ESC\\s0\\fBa\\fR,\nand then material to be input; the material will be inserted correctly\nwhether or not you were in insert mode when you started (1.5, 3.1, 7.5).\n.iP \"^\\e\" 15\nUnused.\n.iP \"^]\" 15\nSearches for the word which is after the cursor as a tag.  Equivalent\nto typing \\fB:ta\\fR, this word, and then a \\s-2CR\\s0.\nMnemonically, this command is ``go right to'' (7.3).\n.iP \"^^\" 15\nEquivalent to \\fB:e #\\fR\\s-2CR\\s0, returning to the previous position\nin the last edited file, or editing a file which you specified if you\ngot a `No write since last change diagnostic' and do not want to have\nto type the file name again (7.3).\n(You have to do a \\fB:w\\fR before \\fB^^\\fR\nwill work in this case.  If you do not wish to write the file you should\ndo \\fB:e!\\ #\\fR\\s-2CR\\s0 instead.)\n.iP \"^_\" 15\nUnused.\nReserved as the command character for the\nTektronix 4025 and 4027 terminal.\n.iP \"\\fR\\s-2SPACE\\s0\\fP\" 15\nSame as\n.B \"right arrow\"\n(see\n.B l ).\n.iP \"!\" 15\nAn operator, which processes lines from the buffer with reformatting commands.\nFollow \\fB!\\fR with the object to be processed, and then the command name\nterminated by \\s-2CR\\s0.  Doubling \\fB!\\fR and preceding it by a count\ncauses count lines to be filtered; otherwise the count\nis passed on to the object after the \\fB!\\fR.  Thus \\fB2!}\\fR\\fIfmt\\fR\\s-2CR\\s0\nreformats the next two paragraphs by running them through the program\n\\fIfmt\\fR.  If you are working on \\s-2LISP\\s0,\nthe command \\fB!%\\fR\\fIgrind\\fR\\s-2CR\\s0,*\n.FS\n*Both\n.I fmt\nand\n.I grind\nare Berkeley programs and may not be present at all installations.\n.FE\ngiven at the beginning of a\nfunction, will run the text of the function through the \\s-2LISP\\s0 grinder\n(6.7, 7.3).\nTo read a file or the output of a command into the buffer use \\fB:r\\fR (7.3).\nTo simply execute a command use \\fB:!\\fR (7.3).\n.tr \u0007\"\n.iP \u0007 15\nPrecedes a named buffer specification.  There are named buffers \\fB1\\-9\\fR\nused for saving deleted text and named buffers \\fBa\\-z\\fR into which you can\nplace text (4.3, 6.3)\n.tr \u0007\u0007\n.iP \"#\" 15\nThe macro character which, when followed by a number, will substitute\nfor a function key on terminals without function keys (6.9).\nIn input mode,\nif this is your erase character, it will delete the last character\nyou typed in input mode, and must be preceded with a \\fB\\e\\fR to insert\nit, since it normally backs over the last input character you gave.\n.iP \"$\" 15\nMoves to the end of the current line.  If you \\fB:se list\\fR\\s-2CR\\s0,\nthen the end of each line will be shown by printing a \\fB$\\fR after the\nend of the displayed text in the line.  Given a count, advances to the\ncount'th following end of line; thus \\fB2$\\fR advances to the end of the\nfollowing line.\n.iP \"%\" 15\nMoves to the parenthesis or brace \\fB{ }\\fR which balances the parenthesis\nor brace at the current cursor position.\n.iP \"&\" 15\nA synonym for \\fB:&\\fR\\s-2CR\\s0, by analogy with the\n.I ex\n.B &\ncommand.\n.iP \"\\(aa\" 15\nWhen followed by a \\fB\\(aa\\fR returns to the previous context at the\nbeginning of a line.  The previous context is set whenever the current\nline is moved in a non-relative way.\nWhen followed by a letter \\fBa\\fR\\-\\fBz\\fR, returns to the line which\nwas marked with this letter with a \\fBm\\fR command, at the first non-white\ncharacter in the line. (2.2, 5.3).\nWhen used with an operator such as \\fBd\\fR, the operation takes place\nover complete lines; if you use \\fB\\(ga\\fR, the operation takes place\nfrom the exact marked place to the current cursor position within the\nline.\n.iP \"(\" 15\nRetreats to the beginning of a sentence.\n.\\\" or to the beginning of a \\s-2LISP\\s0 s-expression\n.\\\" if the \\fIlisp\\fR option is set.\nA sentence ends at a \\fB. !\\fR or \\fB?\\fR which is followed by either\nthe end of a line or by two spaces.  Any number of closing \\fB) ] \"\\fR\nand \\fB\\(aa\\fR characters may appear after the \\fB. !\\fR or \\fB?\\fR,\nand before the spaces or end of line.  Sentences also begin\nat paragraph and section boundaries\n(see \\fB{\\fR and \\fB[[\\fR below).\nA count advances that many sentences (4.2, 6.8).\n.iP \")\" 15\nAdvances to the beginning of a sentence.\nA count repeats the effect.\nSee \\fB(\\fR above for the definition of a sentence (4.2, 6.8).\n.iP \"*\" 15\nUnused.\n.iP \"+\" 15\nSame as \\s-2CR\\s0 when used as a command.\n.iP \",\" 15\nReverse of the last \\fBf F t\\fR or \\fBT\\fR command, looking the other way\nin the current line.  Especially useful after hitting too many \\fB;\\fR\ncharacters.  A count repeats the search.\n.iP \"\\-\" 15\nRetreats to the previous line at the first non-white character.\nThis is the inverse of \\fB+\\fR and \\s-2RETURN\\s0.\nIf the line moved to is not on the screen, the screen is scrolled, or\ncleared and redrawn if this is not possible.\nIf a large amount of scrolling would be required the screen is also cleared\nand redrawn, with the current line at the center (2.3).\n.iP \"\\&.\" 15\nRepeats the last command which changed the buffer.  Especially useful\nwhen deleting words or lines; you can delete some words/lines and then\nhit \\fB.\\fR to delete more and more words/lines.\nGiven a count, it passes it on to the command being repeated.  Thus after\na \\fB2dw\\fR, \\fB3.\\fR deletes three words (3.3, 6.3, 7.2, 7.4).\n.iP \"/\" 15\nReads a string from the last line on the screen, and scans forward for\nthe next occurrence of this string.  The normal input editing sequences may\nbe used during the input on the bottom line.\n.\\\" an returns to command state without ever searching.\nThe search begins when you hit \\s-2CR\\s0 to terminate the pattern;\nthe cursor moves to the beginning of the last line to indicate that the search\nis in progress; the search may then\nbe terminated with a \\s-2DEL\\s0 or \\s-2RUB\\s0, or by backspacing when\nat the beginning of the bottom line, returning the cursor to\nits initial position.\nSearches normally wrap end-around to find a string\nanywhere in the buffer.\n.IP\nWhen used with an operator the enclosed region is normally affected.\nBy mentioning an\noffset from the line matched by the pattern you can force whole lines\nto be affected.  To do this give a pattern with a closing\na closing \\fB/\\fR and then an offset \\fB+\\fR\\fIn\\fR or \\fB\\-\\fR\\fIn\\fR.\n.IP\nTo include the character \\fB/\\fR in the search string, you must escape\nit with a preceding \\fB\\e\\fR.\nA \\fB^\\fR at the beginning of the pattern forces the match to occur\nat the beginning of a line only; this speeds the search.  A \\fB$\\fR at\nthe end of the pattern forces the match to occur at the end of a line\nonly.\nMore extended pattern matching is available, see section 7.4;\nunless you set \\fBnomagic\\fR in your \\fI\\&.exrc\\fR file you will have\nto precede the characters \\fB. [ *\\fR and \\fB~\\fR in the search pattern\nwith a \\fB\\e\\fR to get them to work as you would naively expect (1.5, 2,2,\n6.1, 7.2, 7.4).\n.iP \"0\" 15\nMoves to the first character on the current line.\nAlso used, in forming numbers, after an initial \\fB1\\fR\\-\\fB9\\fR.\n.iP \"1\\-9\" 15\nUsed to form numeric arguments to commands (2.3, 7.2).\n.iP \":\" 15\nA prefix to a set of commands for file and option manipulation and escapes\nto the system.  Input is given on the bottom line and terminated with\nan \\s-2CR\\s0, and the command then executed.  You can return to where\nyou were by hitting \\s-2DEL\\s0 or \\s-2RUB\\s0 if you hit \\fB:\\fR accidentally\n(see primarily 6.2 and 7.3).\n.iP \";\" 15\nRepeats the last single character find which used \\fBf F t\\fR or \\fBT\\fR.\nA count iterates the basic scan (4.1).\n.iP \"<\" 15\nAn operator which shifts lines left one \\fIshiftwidth\\fR, normally 8\nspaces.  Like all operators, affects lines when repeated, as in\n\\fB<<\\fR.  Counts are passed through to the basic object, thus \\fB3<<\\fR\nshifts three lines (6.6, 7.2).\n.iP \"=\" 15\n.\\\" Reindents line for \\s-2LISP\\s0, as though they were typed in with \\fIlisp\\fR\n.\\\" and \\fIautoindent\\fR set (6.8).\nDisplay the line number.\nIf a numerical prefix is specified, display its line number;\notherwise display the line number of the last line in the file.\n.iP \">\" 15\nAn operator which shifts lines right one \\fIshiftwidth\\fR, normally 8\nspaces.  Affects lines when repeated as in \\fB>>\\fR.  Counts repeat the\nbasic object (6.6, 7.2).\n.iP \"?\" 15\nScans backwards, the opposite of \\fB/\\fR.  See the \\fB/\\fR description\nabove for details on scanning (2.2, 6.1, 7.4).\n.iP \"@\" 15\nA macro character (6.9).  If this is your kill character, you must escape it with a \\e\nto type it in during input mode, as it normally backs over the input you\nhave given on the current line (3.1, 3.4, 7.5).\n.iP \"A\" 15\nAppends at the end of line, a synonym for \\fB$a\\fR (7.2).\n.iP \"B\" 15\nBacks up a word, where words are composed of non-blank sequences, placing\nthe cursor at the beginning of the word.  A count repeats the effect\n(2.4).\n.iP \"C\" 15\nChanges the rest of the text on the current line; a synonym for \\fBc$\\fR.\n.iP \"D\" 15\nDeletes the rest of the text on the current line; a synonym for \\fBd$\\fR.\n.iP \"E\" 15\nMoves forward to the end of a word, defined as blanks and non-blanks,\nlike \\fBB\\fR and \\fBW\\fR.  A count repeats the effect.\n.iP \"F\" 15\nFinds a single following character, backwards in the current line.\nA count repeats this search that many times (4.1).\n.iP \"G\" 15\nGoes to the line number given as preceding argument, or the end of the\nfile if no preceding count is given.  The screen is redrawn with the\nnew current line in the center if necessary (7.2).\n.iP \"H\" 15\n.B \"Home arrow\" .\nHomes the cursor to the top line on the screen.  If a count is given,\nthen the cursor is moved to the count'th line on the screen.\nIn any case the cursor is moved to the first non-white character on the\nline.  If used as the target of an operator, full lines are affected\n(2.3, 3.2).\n.iP \"I\" 15\nInserts at the beginning of a line; a synonym for \\fB^i\\fR.\n.iP \"J\" 15\nJoins together lines, supplying appropriate whitespace: one space between\nwords, two spaces after a \\fB.\\fR, and no spaces at all if the first\ncharacter of the joined on line is \\fB)\\fR.  A count causes that many\nlines to be joined rather than the default two (6.5, 7.1f).\n.iP \"K\" 15\nUnused.\n.iP \"L\" 15\nMoves the cursor to the first non-white character of the last line on\nthe screen.  With a count, to the first non-white of the count'th line\nfrom the bottom.  Operators affect whole lines when used with \\fBL\\fR\n(2.3).\n.iP \"M\" 15\nMoves the cursor to the middle line on the screen, at the first non-white\nposition on the line (2.3).\n.iP \"N\" 15\nScans for the next match of the last pattern given to\n\\fB/\\fR or \\fB?\\fR, but in the reverse direction; this is the reverse\nof \\fBn\\fR.\n.iP \"O\" 15\nOpens a new line above the current line and inputs text there up to an\n\\s-2ESC\\s0.  A count can be used on dumb terminals to specify a number\nof lines to be opened.\n.\\\" this is generally obsolete, as the \\fIslowopen\\fR option works better (3.1).\n.iP \"P\" 15\nPuts the last deleted text back before/above the cursor.  The text goes\nback as whole lines above the cursor if it was deleted as whole lines.\nOtherwise the text is inserted between the characters before and at the\ncursor.  May be preceded by a named buffer specification \\fB\"\\fR\\fIx\\fR\nto retrieve the contents of the buffer; buffers \\fB1\\fR\\-\\fB9\\fR contain\ndeleted material, buffers \\fBa\\fR\\-\\fBz\\fR are available for general\nuse (6.3).\n.iP \"Q\" 15\nQuits from \\fIvi\\fR to \\fIex\\fR command mode.  In this mode, whole lines\nform commands, ending with a \\s-2RETURN\\s0.  You can give all the \\fB:\\fR\ncommands; the editor supplies the \\fB:\\fR as a prompt (7.7).\n.iP \"R\" 15\nReplaces characters on the screen with characters you type (overlay fashion).\nTerminates with an \\s-2ESC\\s0.\n.iP \"S\" 15\nChanges whole lines, a synonym for \\fBcc\\fR.  A count substitutes for\nthat many lines.  The lines are saved in the numeric buffers, and erased\non the screen before the substitution begins.\n.iP \"T\" 15\nTakes a single following character, locates the character before the\ncursor in the current line, and places the cursor just after that character.\nA count repeats the effect.  Most useful with operators such as \\fBd\\fR\n(4.1).\n.iP \"U\" 15\nRestores the current line to its state before you started changing it\n(3.5).\n.iP \"V\" 15\nUnused.\n.iP \"W\" 15\nMoves forward to the beginning of a word in the current line,\nwhere words are defined as sequences of blank/non-blank characters.\nA count repeats the effect (2.4).\n.iP \"X\" 15\nDeletes the character before the cursor.  A count repeats the effect,\nbut only characters on the current line are deleted.\n.iP \"Y\" 15\nYanks a copy of the current line into the unnamed buffer, to be put back\nby a later \\fBp\\fR or \\fBP\\fR; a very useful synonym for \\fByy\\fR.\nA count yanks that many lines.  May be preceded by a buffer name to put\nlines in that buffer (7.4).\n.iP \"ZZ\" 15\nExits the editor.\n(Same as \\fB:x\\fP\\s-2CR\\s0.)\nIf any changes have been made, the buffer is written out to the current file.\nThen the editor quits.\n.iP \"[[\" 15\nBacks up to the previous section boundary.  A section begins at each\nmacro in the \\fIsections\\fR option,\nnormally a `.NH' or `.SH' and also at lines which which start\nwith a formfeed \\fB^L\\fR.  Lines beginning with \\fB{\\fR also stop \\fB[[\\fR;\nthis makes it useful for looking backwards, a function at a time, in C\nprograms\n.\\\" If the option \\fIlisp\\fR is set, stops at each \\fB(\\fR at the\n.\\\" beginning of a line, and is thus useful for moving backwards at the top\n.\\\" level \\s-2LISP\\s0 objects.\n(4.2, 6.1, 6.6, 7.2).\n.iP \"\\e\" 15\nUnused.\n.iP \"]]\" 15\nForward to a section boundary; see \\fB[[\\fR for a definition (4.2, 6.1,\n6.6, 7.2).\n.iP \"^\" 15\nMoves to the first non-white position on the current line (4.4).\n.iP \"_\" 15\nUnused.\n.iP \"\\(ga\" 15\nWhen followed by a \\fB\\(ga\\fR, returns to the previous context.\nThe previous context is set whenever the current\nline is moved in a non-relative way.\nWhen followed by a letter \\fBa\\fR\\-\\fBz\\fR, returns to the position which\nwas marked with this letter with a \\fBm\\fR command.\nWhen used with an operator such as \\fBd\\fR, the operation takes place\nfrom the exact marked place to the current position within the line;\nif you use \\fB\\(aa\\fR, the operation takes place over complete lines\n(2.2, 5.3).\n.iP \"a\" 15\nAppends arbitrary text after the current cursor position; the insert\ncan continue onto multiple lines by using \\s-2RETURN\\s0 within the insert.\nA count causes the inserted text to be replicated, but only if the inserted\ntext is all on one line.\nThe insertion terminates with an \\s-2ESC\\s0 (3.1, 7.2).\n.iP \"b\" 15\nBacks up to the beginning of a word in the current line.  A word is a\nsequence of alphanumerics, or a sequence of special characters.\nA count repeats the effect (2.4).\n.iP \"c\" 15\nAn operator which changes the following object, replacing it with the\nfollowing input text up to an \\s-2ESC\\s0.  If more than part of a single\nline is affected, the text which is changed away is saved in the numeric named\nbuffers.  If only part of the current line is affected, then the last\ncharacter to be changed away is marked with a \\fB$\\fR.\nA count causes that many objects to be affected, thus both\n\\fB3c)\\fR and \\fBc3)\\fR change the following three sentences (7.4).\n.iP \"d\" 15\nAn operator which deletes the following object.  If more than part of\na line is affected, the text is saved in the numeric buffers.\nA count causes that many objects to be affected; thus \\fB3dw\\fR is the\nsame as \\fBd3w\\fR (3.3, 3.4, 4.1, 7.4).\n.iP \"e\" 15\nAdvances to the end of the next word, defined as for \\fBb\\fR and \\fBw\\fR.\nA count repeats the effect (2.4, 3.1).\n.iP \"f\" 15\nFinds the first instance of the next character following the cursor on\nthe current line.  A count repeats the find (4.1).\n.iP \"g\" 15\nUnused.\n.sp\nArrow keys\n.B h ,\n.B j ,\n.B k ,\n.B l ,\nand\n.B H .\n.iP \"h\" 15\n.B \"Left arrow\" .\nMoves the cursor one character to the left.\nLike the other arrow keys, either\n.B h ,\nthe\n.B \"left arrow\"\nkey, or one of the synonyms (\\fB^H\\fP) has the same effect.\nOn v2 editors, arrow keys on certain kinds of terminals\n(those which send escape sequences, such as vt52, c100, or hp)\ncannot be used.\nA count repeats the effect (3.1, 7.5).\n.iP \"i\" 15\nInserts text before the cursor, otherwise like \\fBa\\fR (7.2).\n.iP \"j\" 15\n.B \"Down arrow\" .\nMoves the cursor one line down in the same column.\nIf the position does not exist,\n.I vi\ncomes as close as possible to the same column.\nSynonyms include\n.B ^J\n(linefeed) and\n.B ^N .\n.iP \"k\" 15\n.B \"Up arrow\" .\nMoves the cursor one line up.\n.B ^P\nis a synonym.\n.iP \"l\" 15\n.B \"Right arrow\" .\nMoves the cursor one character to the right.\n\\s-2SPACE\\s0 is a synonym.\n.iP \"m\" 15\nMarks the current position of the cursor in the mark register which is\nspecified by the next character \\fBa\\fR\\-\\fBz\\fR.  Return to this position\nor use with an operator using \\fB\\(ga\\fR or \\fB\\(aa\\fR (5.3).\n.iP \"n\" 15\nRepeats the last \\fB/\\fR or \\fB?\\fR scanning commands (2.2).\n.iP \"o\" 15\nOpens new lines below the current line; otherwise like \\fBO\\fR (3.1).\n.iP \"p\" 15\nPuts text after/below the cursor; otherwise like \\fBP\\fR (6.3).\n.iP \"q\" 15\nUnused.\n.iP \"r\" 15\nReplaces the single character at the cursor with a single character you\ntype.  The new character may be a \\s-2RETURN\\s0; this is the easiest\nway to split lines.  A count replaces each of the following count characters\nwith the single character given; see \\fBR\\fR above which is the more\nusually useful iteration of \\fBr\\fR (3.2).\n.iP \"s\" 15\nChanges the single character under the cursor to the text which follows\nup to an \\s-2ESC\\s0; given a count, that many characters from the current\nline are changed.  The last character to be changed is marked with \\fB$\\fR\nas in \\fBc\\fR (3.2).\n.iP \"t\" 15\nAdvances the cursor upto the character before the next character typed.\nMost useful with operators such as \\fBd\\fR and \\fBc\\fR to delete the\ncharacters up to a following character.  You can use \\fB.\\fR to delete\nmore if this doesn't delete enough the first time (4.1).\n.iP \"u\" 15\nUndoes the last change made to the current buffer.  If repeated, will\nalternate between these two states, thus is its own inverse. When used\nafter an insert which inserted text on more than one line, the lines are\nsaved in the numeric named buffers (3.5).\n.iP \"v\" 15\nUnused.\n.iP \"w\" 15\nAdvances to the beginning of the next word, as defined by \\fBb\\fR (2.4).\n.iP \"x\" 15\nDeletes the single character under the cursor.  With a count deletes\ndeletes that many characters forward from the cursor position, but only\non the current line (6.5).\n.iP \"y\" 15\nAn operator, yanks the following object into the unnamed temporary buffer.\nIf preceded by a named buffer specification, \\fB\"\\fR\\fIx\\fR, the text\nis placed in that buffer also.  Text can be recovered by a later \\fBp\\fR\nor \\fBP\\fR (7.4).\n.iP \"z\" 15\nRedraws the screen with the current line placed as specified by the following\ncharacter: \\s-2RETURN\\s0 specifies the top of the screen, \\fB.\\fR the\ncenter of the screen, and \\fB\\-\\fR at the bottom of the screen.\nA count may be given after the \\fBz\\fR and before the following character\nto specify the new screen size for the redraw.\nA count before the \\fBz\\fR gives the number of the line to place in the\ncenter of the screen instead of the default current line. (5.4)\n.iP \"{\" 15\nRetreats to the beginning of the beginning of the preceding paragraph.\nA paragraph begins at each macro in the \\fIparagraphs\\fR option, normally\n`.IP', `.LP', `.PP', `.QP' and `.bp'.\nA paragraph also begins after a completely\nempty line, and at each section boundary (see \\fB[[\\fR above) (4.2, 6.8,\n7.6).\n.iP \"|\" 15\nPlaces the cursor on the character in the column specified\nby the count (7.1, 7.2).\n.iP \"}\" 15\nAdvances to the beginning of the next paragraph.  See \\fB{\\fR for the\ndefinition of paragraph (4.2, 6.8, 7.6).\n.iP \"~\" 15\nUnused.\n.iP \"^?\\ (\\s-2\\fRDEL\\fP\\s0)\" 15\nInterrupts the editor, returning it to command accepting state (1.5,\n7.5).\n"
  },
  {
    "path": "docs/USD.doc/vitut/vi.in",
    "content": ".\\\"        $OpenBSD: vi.in,v 1.9 2022/12/26 19:16:03 jmc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"         @(#)vi.in        8.5 (Berkeley) 8/18/96\n.\\\"\n.if n \\{\\\n.po 5n\n.ll 70n\n.\\}\n.nr LL 6.5i\n.nr FL 6.5i\n.EH 'USD:11-%''An Introduction to Display Editing with Vi'\n.OH 'An Introduction to Display Editing with Vi''USD:11-%'\n.\\\" .bd S 3\n.if t .ds dg \\(dg\n.if n .ds dg +\n.if t .ds dd \\(dd\n.if n .ds dd ++\n.\\\".RP\n.TL\nAn Introduction to Display Editing with Vi\n.AU\nWilliam Joy\n.AU\nMark Horton\n.AI\nComputer Science Division\nDepartment of Electrical Engineering and Computer Science\nUniversity of California, Berkeley\nBerkeley, Ca.  94720\n.AB\n.PP\n.I Vi\n(visual) is a display oriented interactive text editor.\nWhen using\n.I vi\nthe screen of your terminal acts as a window into the file which you\nare editing.  Changes which you make to the file are reflected\nin what you see.\n.PP\nUsing\n.I vi\nyou can insert new text any place in the file quite easily.\nMost of the commands to\n.I vi\nmove the cursor around in the file.\nThere are commands to move the cursor\nforward and backward in units of characters, words,\nsentences and paragraphs.\nA small set of operators, like\n.B d\nfor delete and\n.B c\nfor change, are combined with the motion commands to form operations\nsuch as delete word or change paragraph, in a simple and natural way.\nThis regularity and the mnemonic assignment of commands to keys makes the\neditor command set easy to remember and to use.\n.PP\n.I Vi\nwill work on a large number of display terminals,\nand new terminals are easily driven after editing a terminal description file.\nWhile it is advantageous to have an intelligent terminal which can locally\ninsert and delete lines and characters from the display, the editor will\nfunction quite well on dumb terminals over slow phone lines.\nThe editor makes allowance for the low bandwidth in these situations\nand uses smaller window sizes and\ndifferent display updating algorithms to make best use of the\nlimited speed available.\n.PP\nIt is also possible to use the command set of\n.I vi\non hardcopy terminals, storage tubes and ``glass tty's'' using a one-line\nediting window; thus\n.I vi 's\ncommand set is available on all terminals.\nThe full command set of the more traditional, line\noriented editor\n.I ex\nis available within\n.I vi ;\nit is quite simple to switch between the two modes of editing.\n.AE\n.NH 1\nGetting started\n.PP\n.FS\nThe financial support of an \\s-2IBM\\s0 Graduate Fellowship and the\nNational Science Foundation under grants MCS74-07644-A03 and MCS78-07291\nis gratefully acknowledged.\n.FE\nThis document provides a quick introduction to\n.I vi .\n(Pronounced \\fIvee-eye\\fP.)\nYou should be running\n.I vi\non a file you are familiar with while you are reading this.\nThe first part of this document (sections 1 through 5)\ndescribes the basics of using\n.I vi .\nSome topics of special interest are presented in section 6, and\nsome nitty-gritty details of how the editor functions are saved for section\n7 to avoid cluttering the presentation here.\n.PP\nThere is also a short appendix here, which gives for each character the\nspecial meanings which this character has in \\fIvi\\fR.  Attached to\nthis document should be a quick reference card.\nThis card summarizes the commands of\n.I vi\nin a very compact format.  You should have the card handy while you are\nlearning\n.I vi .\n.NH 2\nSpecifying terminal type\n.PP\nBefore you start\n.I vi\nyou can tell the system what kind of terminal you are using.\nHere is a (necessarily incomplete) list of terminal type codes.\nIf your terminal does not appear here, you should consult with one of\nthe staff members on your system to find out the code for your terminal.\nIf your terminal does not have a code, one can be assigned and a description\nfor the terminal can be created.\n.LP\n.TS\ncenter;\nab ab ab\na a a.\nCode\tFull name\tType\n_\n2621\tHewlett-Packard 2621A/P\tIntelligent\n2645\tHewlett-Packard 264x\tIntelligent\nact4\tMicroterm ACT-IV\tDumb\nact5\tMicroterm ACT-V\tDumb\nadm3a\tLear Siegler ADM-3a\tDumb\nadm31\tLear Siegler ADM-31\tIntelligent\nc100\tHuman Design Concept 100\tIntelligent\ndm1520\tDatamedia 1520\tDumb\ndm2500\tDatamedia 2500\tIntelligent\ndm3025\tDatamedia 3025\tIntelligent\nfox\tPerkin-Elmer Fox\tDumb\nh1500\tHazeltine 1500\tIntelligent\nh19\tHeathkit h19\tIntelligent\ni100\tInfoton 100\tIntelligent\nmime\tImitating a smart act4\tIntelligent\nt1061\tTeleray 1061\tIntelligent\nvt52\tDec VT-52\tDumb\n.TE\n.PP\nSuppose for example that you have a Hewlett-Packard HP2621A\nterminal.  The code used by the system for this terminal is `2621'.\nIn this case you can use one of the following commands to tell the system\nthe type of your terminal:\n.DS I\n% setenv TERM 2621\n.DE\nThis command works with the\n.I csh\nshell.\nIf you are using the standard Bourne shell\n.I sh\nthen you should give the command:\n.DS I\n$ export TERM=2621\n.DE\n.PP\nIf you want to arrange to have your terminal type set up automatically\nwhen you log in, you can use the\n.I tset (1)\nprogram.\nIf you dial in on a\n.I mime ,\nbut often use hardwired ports, a typical line for your\n.I .login\nfile (if you use csh) would be\n.DS I\n% setenv TERM `tset - -d mime`\n.DE\nor for your\n.I .profile\nfile (if you use sh)\n.DS I\n$ export TERM=`tset - -d mime`\n.DE\n.I Tset\nknows which terminals are hardwired to each port\nand needs only to be told that when you dial in you\nare probably on a\n.I mime .\n.I Tset\nis usually used to change the erase and kill characters, too.\n.NH 2\nEditing a file\n.PP\nAfter telling the system which kind of terminal you have, you should\nmake a copy of a file you are familiar with, and run\n.I vi\non this file, giving the command\n.DS I\n% vi name\n.DE\nreplacing \\fIname\\fR with the name of the copy file you just created.\nThe screen should clear and the text of your file should appear on the\nscreen.  If something else happens refer to the footnote.\\*(dd\n.FS\n\\*(dd If you gave the system an incorrect terminal type code then the\neditor may have just made a mess out of your screen.  This happens when\nit sends control codes for one kind of terminal to some other\nkind of terminal.  In this case hit\nthe keys \\fB:q\\fR (colon and the q key) and then hit the \\s-2RETURN\\s0 key.\nThis should get you back to the command level interpreter.\nFigure out what you did wrong (ask someone else if necessary) and try again.\n     Another thing which can go wrong is that you typed the wrong file name and\nthe editor just printed an error diagnostic.  In this case you should\nfollow the above procedure for getting out of the editor and try again,\nthis time spelling the file name correctly.\n     If the editor doesn't seem to respond to the commands which you type\nhere, try sending an interrupt to it by hitting the \\s-2DEL\\s0 or \\s-2RUB\\s0\nkey on your terminal, and then hitting the \\fB:q\\fR command again followed\nby a carriage return.\n.sp\n.FE\n.NH 2\nThe editor's copy: the buffer\n.PP\nThe editor does not directly modify the file which you are editing.\nRather, the editor makes a copy of this file, in a place called the\n.I buffer ,\nand remembers the file's\nname.  You do not affect the contents of the file unless and until you\nwrite the changes you make back into the original file.\n.NH 2\nNotational conventions\n.PP\nIn our examples, input which must be typed as is will be presented in\n\\fBbold face\\fR. Text which should be replaced with appropriate input\nwill be given in \\fIitalics\\fR.  We will represent special characters\nin \\s-2SMALL CAPITALS\\s0.\n.NH 2\nArrow keys\n.PP\nThe editor command set is independent of the terminal\nyou are using.  On most terminals with cursor positioning keys, these keys\nwill also work within the editor.\nIf you don't have cursor positioning keys, or even if you do, you can use\nthe \\fBh j k\\fR and \\fBl\\fR keys as cursor positioning\nkeys (these are labelled with arrows on an\n.I adm3a). *\n.PP\n(Particular note for the HP2621: on this terminal the function keys\nmust be \\fIshifted\\fR (ick) to send to the machine, otherwise they\nonly act locally.  Unshifted use will leave the cursor positioned\nincorrectly.)\n.FS\n* As we will see later,\n.B h\nmoves back to the left (like control-h which is a backspace),\n.B j\nmoves down (in the same column),\n.B k\nmoves up (in the same column),\nand\n.B l\nmoves to the right.\n.FE\n.NH 2\nSpecial characters: \\s-2ESC\\s0, \\s-2CR\\s0 and \\s-2DEL\\s0\n.PP\nSeveral of these special characters are very important, so be sure to\nfind them right now.  Look on your keyboard for a key labelled \\s-2ESC\\s0\nor \\s-2ALT\\s0.  It should be near the upper left corner of your terminal.\nTry hitting this key a few times.  The editor will ring the bell\nto indicate that it is in a quiescent state.\\*(dd\n.FS\n\\*(dd On smart terminals where it is possible, the editor will quietly\nflash the screen rather than ringing the bell.\n.FE\nPartially formed commands are cancelled by \\s-2ESC\\s0, and when you insert\ntext in the file you end the text insertion\nwith \\s-2ESC\\s0.  This key is a fairly\nharmless one to hit, so you can just hit it if you don't know\nwhat is going on until the editor rings the bell.\n.PP\nThe \\s-2CR\\s0 or \\s-2RETURN\\s0 key is important because it is used\nto terminate certain commands.\nIt is usually at the right side of the keyboard,\nand is the same command used at the end of each shell command.\n.PP\nAnother very useful key is the \\s-2DEL\\s0 or \\s-2RUB\\s0 key, which generates\nan interrupt, telling the editor to stop what it is doing.\nIt is a forceful way of making the editor listen\nto you, or to return it to the quiescent state if you don't know or don't\nlike what is going on.  Try hitting the `/' key on your terminal.  This\nkey is used when you want to specify a string to be searched for.  The\ncursor should now be positioned at the bottom line of the terminal after\na `/' printed as a prompt.  You can get the cursor back to the current\nposition by hitting the \\s-2DEL\\s0 or \\s-2RUB\\s0 key; try this now.*\n.FS\n* Backspacing over the `/' will also cancel the search.\n.FE\nFrom now on we will simply refer to hitting the \\s-2DEL\\s0 or \\s-2RUB\\s0\nkey as ``sending an interrupt.''**\n.FS\n** On some systems, this interruptibility comes at a price: you cannot type\nahead when the editor is computing with the cursor on the bottom line.\n.FE\n.PP\nThe editor often echoes your commands on the last line of the terminal.\nIf the cursor is on the first position of this last line, then the editor\nis performing a computation, such as computing a new position in the\nfile after a search or running a command to reformat part of the buffer.\nWhen this is happening you can stop the editor by\nsending an interrupt.\n.NH 2\nGetting out of the editor\n.PP\nAfter you have worked with this introduction for a while, and you wish\nto do something else, you can give the command \\fBZZ\\fP\nto the editor.\nThis will write the contents of the editor's buffer back into\nthe file you are editing, if you made any changes, and then quit from\nthe editor.  You can also end an editor\nsession by giving the command \\fB:q!\\fR\\s-2CR\\s0;\\*(dg\n.FS\n\\*(dg All commands which read from the last display line can also be\nterminated with a \\s-2ESC\\s0 as well as an \\s-2CR\\s0.\n.FE\nthis is a dangerous but occasionally essential\ncommand which ends the editor session and discards all your changes.\nYou need to know about this command in case you change the editor's\ncopy of a file you wish only to look at.  Be very careful\nnot to give this command when you really want to save\nthe changes you have made.\n.NH 1\nMoving around in the file\n.NH 2\nScrolling and paging\n.PP\nThe editor has a number of commands for moving around in the file.\nThe most useful of these is generated by hitting the control and D keys\nat the same time, a control-D or `^D'.  We will use this two character\nnotation for referring to these control keys from now on.\n.\\\" You may have a key labelled `^' on your terminal;\n.\\\" `^' is exclusively used as part of the `^x' notation for control characters.\n.PP\nAs you know now if you tried hitting \\fB^D\\fR, this command scrolls down in\nthe file.  The \\fBD\\fR thus stands for down.  Many editor commands are mnemonic\nand this makes them much easier to remember.  For instance the command\nto scroll up is \\fB^U\\fR.  Many dumb terminals can't scroll up at all, in which\ncase hitting \\fB^U\\fR clears the screen and refreshes it\nwith a line which is farther back in the file at the top.\n.PP\nIf you want to see more of the file below where you are, you can\nhit \\fB^E\\fR to expose one more line at the bottom of the screen,\nleaving the cursor where it is.\nThe command \\fB^Y\\fR (which is hopelessly non-mnemonic, but next to \\fB^U\\fR\non the keyboard) exposes one more line at the top of the screen.\n.PP\nThere are other ways to move around in the file; the keys \\fB^F\\fR and \\fB^B\\fR\nmove forward and backward a page,\nkeeping a couple of lines of continuity between screens\nso that it is possible to read through a file using these rather than\n\\fB^D\\fR and \\fB^U\\fR if you wish.\n.PP\nNotice the difference between scrolling and paging.  If you are trying\nto read the text in a file, hitting \\fB^F\\fR to move forward a page\nwill leave you only a little context to look back at.  Scrolling on the\nother hand leaves more context, and happens more smoothly.  You can continue\nto read the text as scrolling is taking place.\n.NH 2\nSearching, goto, and previous context\n.PP\nAnother way to position yourself in the file is by giving the editor a string\nto search for.  Type the character \\fB/\\fR followed by a string of characters\nterminated by \\s-2CR\\s0.  The editor will position the cursor\nat the next occurrence of this string.\nTry hitting \\fBn\\fR to then go to the next occurrence of this string.\nThe character \\fB?\\fR will search backwards from where you are, and is\notherwise like \\fB/\\fR.\\*(dg\n.FS\n\\*(dg These searches will normally wrap around the end of the file, and thus\nfind the string even if it is not on a line in the direction you search\nprovided it is anywhere else in the file.  You can disable this wraparound\nin scans by giving the command \\fB:se nowrapscan\\fR\\s-2CR\\s0,\nor more briefly \\fB:se nows\\fR\\s-2CR\\s0.\n.FE\n.PP\nIf the search string you give the editor is not present in the\nfile, the editor will print\na diagnostic on the last line of the screen, and the cursor will be returned\nto its initial position.\n.PP\nIf you wish the search to match only at the beginning of a line, begin\nthe search string with an \\fB^\\fR.  To match only at the end of\na line, end the search string with a \\fB$\\fR.\nThus \\fB/^search\\fR\\s-2CR\\s0 will search for the word `search' at\nthe beginning of a line, and \\fB/last$\\fR\\s-2CR\\s0 searches for the\nword `last' at the end of a line.*\n.FS\n*Actually, the string you give to search for here can be a\n.I \"regular expression\"\nin the sense of the editors\n.I ex (1)\nand\n.I ed (1).\nIf you don't wish to learn about this yet, you can disable this more\ngeneral facility by doing\n\\fB:se\\ nomagic\\fR\\s-2CR\\s0;\nby putting this command in\nEXINIT\nin your environment, you can have this always be in effect (more\nabout\n.I EXINIT\nlater.)\n.FE\n.PP\nThe command \\fBG\\fR, when preceded by a number will position the cursor\nat that line in the file.\nThus \\fB1G\\fR will move the cursor to\nthe first line of the file.  If you give \\fBG\\fR no count, then it moves\nto the end of the file.\n.PP\nIf you are near the end of the file, and the last line is not at the bottom\nof the screen, the editor will place only the character `~' on each remaining\nline.  This indicates that the last line in the file is on the screen;\nthat is, the `~' lines are past the end of the file.\n.PP\nYou can find out the state of the file you are editing by typing a \\fB^G\\fR.\nThe editor will show you the name of the file you are editing, the number\nof the current line, the number of lines in the buffer, and the percentage\nof the way through the buffer which you are.\nTry doing this now, and remember the number of the line you are on.\nGive a \\fBG\\fR command to get to the end and then another \\fBG\\fR command\nto get back where you were.\n.PP\nYou can also get back to a previous position by using the command\n\\fB\\(ga\\(ga\\fR (two back quotes).\nThis is often more convenient than \\fBG\\fR because it requires no advance\npreparation.\nTry giving a \\fBG\\fR or a search with \\fB/\\fR or \\fB?\\fR and then a\n\\fB\\(ga\\(ga\\fR to get back to where you were.  If you accidentally hit\n\\fBn\\fR or any command which moves you far away from a context of interest, you\ncan quickly get back by hitting \\fB\\(ga\\(ga\\fR.\n.NH 2\nMoving around on the screen\n.PP\nNow try just moving the cursor around on the screen.\nIf your terminal has arrow keys (4 or 5 keys with arrows\ngoing in each direction) try them and convince yourself\nthat they work.\nIf you don't have working arrow keys, you can always use\n.B h ,\n.B j ,\n.B k ,\nand\n.B l .\nExperienced users of\n.I vi\nprefer these keys to arrow keys,\nbecause they are usually right underneath their fingers.\n.PP\nHit the \\fB+\\fR key.  Each time you do, notice that the cursor\nadvances to the next line in the file, at the first non-white position\non the line.  The \\fB\\-\\fR key is like \\fB+\\fR but goes the other way.\n.PP\nThese are very common keys for moving up and down lines in the file.\nNotice that if you go off the bottom or top with these keys then the\nscreen will scroll down (and up if possible) to bring a line at a time\ninto view.  The \\s-2RETURN\\s0 key has the same effect as the \\fB+\\fR\nkey.\n.PP\n.I Vi\nalso has commands to take you to the top, middle and bottom of the screen.\n\\fBH\\fR will take you to the top (home) line on the screen.\nTry preceding it with a\nnumber as in \\fB3H\\fR.\nThis will take you to the third line on the screen.\nMany\n.I vi\ncommands take preceding numbers and do interesting things with them.\nTry \\fBM\\fR,\nwhich takes you to the middle line on the screen,\nand \\fBL\\fR,\nwhich takes you to the last line on the screen.\n\\fBL\\fR also takes counts, thus\n\\fB5L\\fR will take you to the fifth line from the bottom.\n.NH 2\nMoving within a line\n.PP\nNow try picking a word on some line on the screen, not the\nfirst word on the line.\nmove the cursor using \\s-2RETURN\\s0 and \\fB\\-\\fR to be on the line where\nthe word is.\nTry hitting the \\fBw\\fR key.  This will advance the cursor to the\nnext word on the line.\nTry hitting the \\fBb\\fR key to back up words\nin the line.\nAlso try the \\fBe\\fR key which advances you to the end of the current\nword rather than to the beginning of the next word.\nAlso try \\s-2SPACE\\s0 (the space bar) which moves right one character\nand the \\s-2BS\\s0 (backspace or \\fB^H\\fR) key which moves left one character.\nThe key \\fBh\\fR works as \\fB^H\\fR does and is useful if you don't have\na \\s-2BS\\s0 key.\n(Also, as noted just above, \\fBl\\fR will move to the right.)\n.PP\nIf the line had punctuation in it you may have noticed that\nthat the \\fBw\\fR and \\fBb\\fR\nkeys stopped at each group of punctuation.  You can also go back and\nforwards words without stopping at punctuation by using \\fBW\\fR and \\fBB\\fR\nrather than the lower case equivalents.  Think of these as bigger words.\nTry these on a few lines with punctuation to see how they differ from\nthe lower case \\fBw\\fR and \\fBb\\fR.\n.PP\nThe word keys wrap around the end of line,\nrather than stopping at the end.  Try moving to a word on a line below\nwhere you are by repeatedly hitting \\fBw\\fR.\n.NH 2\nSummary\n.IP\n.TS\nlw(.50i)b a.\n\\fR\\s-2SPACE\\s0\\fP\tadvance the cursor one position\n^B\tbackwards to previous page\n^D\tscrolls down in the file\n^E\texposes another line at the bottom\n^F\tforward to next page\n^G\ttell what is going on\n^H\tbackspace the cursor\n^N\tnext line, same column\n^P\tprevious line, same column\n^U\tscrolls up in the file\n^Y\texposes another line at the top\n+\tnext line, at the beginning\n\\-\tprevious line, at the beginning\n/\tscan for a following string forwards\n?\tscan backwards\nB\tback a word, ignoring punctuation\nG\tgo to specified line, last default\nH\thome screen line\nM\tmiddle screen line\nL\tlast screen line\nW\tforward a word, ignoring punctuation\nb\tback a word\ne\tend of current word\nn\tscan for next instance of \\fB/\\fR or \\fB?\\fR pattern\nw\tword after this word\n.TE\n.NH 2\nView\n.PP\nIf you want to use the editor to look at a file,\nrather than to make changes,\ninvoke it as\n.I view\ninstead of\n.I vi .\nThis will set the\n.I readonly\noption which will prevent you from\naccidentally overwriting the file.\n.sp\n.NH 1\nMaking simple changes\n.NH 2\nInserting\n.PP\nOne of the most useful commands is the\n\\fBi\\fR (insert) command.\nAfter you type \\fBi\\fR, everything you type until you hit \\s-2ESC\\s0\nis inserted into the file.\nTry this now; position yourself to some word in the file and try inserting\ntext before this word.\nIf you are on an dumb terminal it will seem, for a minute,\nthat some of the characters in your line have been overwritten, but they will\nreappear when you hit \\s-2ESC\\s0.\n.PP\nNow try finding a word which can, but does not, end in an `s'.\nPosition yourself at this word and type \\fBe\\fR (move to end of word), then\n\\fBa\\fR for append and then `s\\s-2ESC\\s0' to terminate the textual insert.\nThis sequence of commands can be used to easily pluralize a word.\n.PP\nTry inserting and appending a few times to make sure you understand how\nthis works; \\fBi\\fR placing text to the left of the cursor, \\fBa\\fR to\nthe right.\n.PP\nIt is often the case that you want to add new lines to the file you are\nediting, before or after some specific line in the file.  Find a line\nwhere this makes sense and then give the command \\fBo\\fR to create a\nnew line after the line you are on, or the command \\fBO\\fR to create\na new line before the line you are on.  After you create a new line in\nthis way, text you type up to an \\s-2ESC\\s0 is inserted on the new line.\n.PP\nMany related editor commands\nare invoked by the same letter key and differ only in that one is given\nby a lower\ncase key and the other is given by\nan upper case key.  In these cases, the\nupper case key often differs from the lower case key in its sense of\ndirection, with\nthe upper case key working backward and/or up, while the lower case\nkey moves forward and/or down.\n.PP\nWhenever you are typing in text, you can give many lines of input or\njust a few characters.\nTo type in more than one line of text,\nhit a \\s-2RETURN\\s0 at the middle of your input.  A new line will be created\nfor text, and you can continue to type.  If you are on a slow\nand dumb terminal the editor may choose to wait to redraw the\ntail of the screen, and will let you type over the existing screen lines.\nThis avoids the lengthy delay which would occur if the editor attempted\nto keep the tail of the screen always up to date.  The tail of the screen will\nbe fixed up, and the missing lines will reappear, when you hit \\s-2ESC\\s0.\n.PP\nWhile you are inserting new text, you can use the characters you normally use\nat the system command level (usually \\fB^H\\fR or \\fB#\\fR) to backspace\nover the last\ncharacter which you typed, and the character which you use to kill input lines\n(usually \\fB@\\fR, \\fB^X\\fR, or \\fB^U\\fR)\nto erase the input you have typed on the current line.\\*(dg\n.FS\n\\*(dg In fact, the character \\fB^H\\fR (backspace) always works to erase the\nlast input character here, regardless of what your erase character is.\n.FE\nThe character \\fB^W\\fR\nwill erase a whole word and leave you after the space after the previous\nword; it is useful for quickly backing up in an insert.\n.PP\nNotice that when you backspace during an insertion the characters you\nbackspace over are not erased; the cursor moves backwards, and the characters\nremain on the display.  This is often useful if you are planning to type\nin something similar.  In any case the characters disappear when when\nyou hit \\s-2ESC\\s0; if you want to get rid of them immediately, hit an\n\\s-2ESC\\s0 and then \\fBa\\fR again.\n.PP\nNotice also that you can't erase characters which you didn't insert, and that\nyou can't backspace around the end of a line.  If you need to back up\nto the previous line to make a correction, just hit \\s-2ESC\\s0 and move\nthe cursor back to the previous line.  After making the correction you\ncan return to where you were and use the insert or append command again.\n.sp .5\n.NH 2\nMaking small corrections\n.PP\nYou can make small corrections in existing text quite easily.\nFind a single character which is wrong or just pick any character.\nUse the arrow keys to find the character, or\nget near the character with the word motion keys and then either\nbackspace (hit the \\s-2BS\\s0 key or \\fB^H\\fR or even just \\fBh\\fR) or\n\\s-2SPACE\\s0 (using the space bar)\nuntil the cursor is on the character which is wrong.\nIf the character is not needed then hit the \\fBx\\fP key; this deletes\nthe character from the file.  It is analogous to the way you \\fBx\\fP\nout characters when you make mistakes on a typewriter (except it's not\nas messy).\n.PP\nIf the character\nis incorrect, you can replace it with the correct character by giving\nthe command \\fBr\\fR\\fIc\\fR,\nwhere \\fIc\\fR is replaced by the correct character.\nFinally if the character which is incorrect should be replaced\nby more than one character, give the command \\fBs\\fR which substitutes\na string of characters, ending with \\s-2ESC\\s0, for it.\nIf there are a small number of characters\nwhich are wrong you can precede \\fBs\\fR with a count of the number of\ncharacters to be replaced.  Counts are also useful with \\fBx\\fR to specify\nthe number of characters to be deleted.\n.NH 2\nMore corrections: operators\n.PP\nYou already know almost enough to make changes at a higher level.\nAll you need to know now is that the\n.B d\nkey acts as a delete operator.  Try the command\n.B dw\nto delete a word.\nTry hitting \\fB.\\fR a few times.  Notice that this repeats the effect\nof the \\fBdw\\fR.  The command \\fB.\\fR repeats the last command which\nmade a change.  You can remember it by analogy with an ellipsis `\\fB...\\fR'.\n.PP\nNow try\n\\fBdb\\fR.\nThis deletes a word backwards, namely the preceding word.\nTry\n\\fBd\\fR\\s-2SPACE\\s0.  This deletes a single character, and is equivalent\nto the \\fBx\\fR command.\n.PP\nAnother very useful operator is\n.B c\nor change.  The command\n.B cw\nthus changes the text of a single word.\nYou follow it by the replacement text ending with an \\s-2ESC\\s0.\nFind a word which you can change to another, and try this\nnow.\nNotice that the end of the text to be changed was marked with the character\n`$' so that you can see this as you are typing in the new material.\n.sp .5\n.NH 2\nOperating on lines\n.PP\nIt is often the case that you want to operate on lines.\nFind a line which you want to delete, and type\n\\fBdd\\fR,\nthe\n.B d\noperator twice.  This will delete the line.\nIf you are on a dumb terminal, the editor may just erase the line on\nthe screen, replacing it with a line with only an @ on it.  This line\ndoes not correspond to any line in your file, but only acts as a place\nholder.  It helps to avoid a lengthy redraw of the rest of the screen\nwhich would be necessary to close up the hole created by the deletion\non a terminal without a delete line capability.\n.PP\nTry repeating the\n.B c\noperator twice; this will change a whole line, erasing its previous contents and\nreplacing them with text you type up to an \\s-2ESC\\s0.\\*(dg\n.FS\n\\*(dg The command \\fBS\\fR is a convenient synonym for for \\fBcc\\fR, by\nanalogy with \\fBs\\fR.  Think of \\fBS\\fR as a substitute on lines, while\n\\fBs\\fR is a substitute on characters.\n.FE\n.PP\nYou can delete or change more than one line by preceding the\n.B dd\nor\n.B cc\nwith a count, i.e. \\fB5dd\\fR deletes 5 lines.\nYou can also give a command like \\fBdL\\fR to delete all the lines up to\nand including\nthe last line on the screen, or \\fBd3L\\fR to delete through the third from\nthe bottom line.  Try some commands like this now.*\n.FS\n* One subtle point here involves using the \\fB/\\fR search after a \\fBd\\fR.\nThis will normally delete characters from the current position to the\npoint of the match.  If what is desired is to delete whole lines\nincluding the two points, give the pattern as \\fB/pat/+0\\fR, a line address.\n.FE\nNotice that the editor lets you know when you change a large number of\nlines so that you can see the extent of the change.\nThe editor will also always tell you when a change you make affects text which\nyou cannot see.\n.NH 2\nUndoing\n.PP\nNow suppose that the last change which you made was incorrect;\nyou could use the insert, delete and append commands to put the correct\nmaterial back.  However, since it is often the case that we regret a\nchange or make a change incorrectly, the editor provides a\n.B u\n(undo) command to reverse the last change which you made.\nTry this a few times, and give it twice in a row to notice that a\n.B u\nalso undoes a\n.B u .\n.PP\nThe undo command lets you reverse only a single change.  After you make\na number of changes to a line, you may decide that you would rather have\nthe original state of the line back.  The\n.B U\ncommand restores the current line to the state before you started changing\nit.\nAdditionally, an unlimited number of changes may be reversed by following a\n.B u\nwith a\n.B . .\nEach subsequent\n.B .\nwill undo one more change.\n.PP\nYou can recover text which you delete, even if\nundo will not bring it back; see the section on recovering lost text\nbelow.\n.NH 2\nSummary\n.IP\n.TS\nlw(.50i)b a.\n\\fB\\s-2SPACE\\s0\\fP\tadvance the cursor one position\n^H\tbackspace the cursor\n\\fBwerase\\fP\t(usually ^W), erase a word during an insert\n\\fBerase\\fP\t(usually DEL or ^H), erases a character during an insert\n\\fBkill\\fP\t(usually ^U), kills the insert on this line\n\\fB.\\fP\trepeats the changing command\nO\topens and inputs new lines, above the current\nU\tundoes the changes you made to the current line\na\tappends text after the cursor\nc\tchanges the object you specify to the following text\nd\tdeletes the object you specify\ni\tinserts text before the cursor\no\topens and inputs new lines, below the current\nu\tundoes the last change\n.TE\n.NH 1\nMoving about; rearranging and duplicating text\n.NH 2\nLow level character motions\n.PP\nNow move the cursor to a line where there is a punctuation or a bracketing\ncharacter such as a parenthesis or a comma or period.  Try the command\n\\fBf\\fR\\fIx\\fR, where \\fIx\\fR is this character.  This command finds\nthe next \\fIx\\fR character to the right of the cursor in the current\nline.  Try then hitting a \\fB;\\fR, which finds the next instance of the\nsame character.  By using the \\fBf\\fR command and then a sequence of\n\\fB;\\fR's you can often\nget to a particular place in a line much faster than with a sequence\nof word motions or \\s-2SPACE\\s0s.\nThere is also an \\fBF\\fR command, which is like \\fBf\\fR, but searches\nbackward.  The \\fB;\\fR command repeats \\fBF\\fR also.\n.PP\nWhen you are operating on the text in a line it is often desirable to\ndeal with the characters up to, but not including, the first instance of\na character.  Try \\fBdf\\fR\\fIx\\fR for some \\fIx\\fR now and\nnotice that the text up to (and including) the \\fIx\\fR character is deleted.\nUndo this with \\fBu\\fR and then try \\fBdt\\fR\\fIx\\fR;\nthe \\fBt\\fR here stands for to, i.e.\ndelete up to the next \\fIx\\fR, but not the \\fIx\\fR.  The command \\fBT\\fR\nis the reverse of \\fBt\\fR.\n.PP\nWhen working with the text of a single line, a \\fB^\\fR moves the\ncursor to the first non-white position on the line, and a\n\\fB$\\fR moves it to the end of the line.  Thus \\fB$a\\fR will append new\ntext at the end of the current line.\n.PP\nYour file may have tab (\\fB^I\\fR) characters in it.  These\ncharacters are represented as a number of spaces expanding to a tab stop,\nwhere tab stops are every 8 positions.*\n.FS\n* This is settable by a command of the form \\fB:se ts=\\fR\\fIx\\fR\\s-2CR\\s0,\nwhere \\fIx\\fR is 4 to set tabstops every four columns.  This has an\neffect on the screen representation within the editor.\n.FE\nWhen the cursor is at a tab, it sits on the last of the several spaces\nwhich represent that tab.  Try moving the cursor back and forth over\ntabs so you understand how this works.\n.PP\nOn rare occasions, your file may have nonprinting characters in it.\nThese characters are displayed in the same way they are represented in\nthis document, that is with a two character code, the first character\nof which is `^'.  On the screen non-printing characters resemble a `^'\ncharacter adjacent to another, but spacing or backspacing over the character\nwill reveal that the two characters are, like the spaces representing\na tab character, a single character.\n.PP\nThe editor sometimes discards control characters,\ndepending on the character and the setting of the\n.I beautify\noption,\nif you attempt to insert them in your file.\nYou can get a control character in the file by beginning\nan insert and then typing a \\fB^V\\fR before the control\ncharacter.  The\n\\fB^V\\fR quotes the following character, causing it to be\ninserted directly into the file.\n.PP\n.NH 2\nHigher level text objects\n.PP\nIn working with a document it is often advantageous to work in terms\nof sentences, paragraphs, and sections.  The operations \\fB(\\fR and \\fB)\\fR\nmove to the beginning of the previous and next sentences respectively.\nThus the command \\fBd)\\fR will delete the rest of the current sentence;\nlikewise \\fBd(\\fR will delete the previous sentence if you are at the\nbeginning of the current sentence, or the current sentence up to where\nyou are if you are not at the beginning of the current sentence.\n.PP\nA sentence is defined to end at a `.', `!' or `?' which is followed by\neither the end of a line, or by two spaces.  Any number of closing `)',\n`]', `\"' and `\\(aa' characters may appear after the `.', `!' or `?' before\nthe spaces or end of line.\n.PP\nThe operations \\fB{\\fR and \\fB}\\fR move over paragraphs and the operations\n\\fB[[\\fR and \\fB]]\\fR move over sections.\\*(dg\n.FS\n\\*(dg The \\fB[[\\fR and \\fB]]\\fR operations\nrequire the operation character to be doubled because they can move the\ncursor far from where it currently is.  While it is easy to get back\nwith the command \\fB\\(ga\\(ga\\fP,\nthese commands would still be frustrating\nif they were easy to hit accidentally.\n.FE\n.PP\nA paragraph begins after each empty line, and also\nat each of a set of paragraph macros, specified by the pairs of characters\nin the definition of the string valued option \\fIparagraphs\\fR.\nThe default setting for this option defines the paragraph macros of the\n\\fI\\-ms\\fR and \\fI\\-mm\\fR macro packages, i.e. the `.IP', `.LP', `.PP'\nand `.QP', `.P' and `.LI' macros.\\*(dd\n.FS\n\\*(dd You can easily change or extend this set of macros by assigning a\ndifferent string to the \\fIparagraphs\\fR option in your EXINIT.\nSee section 6.2 for details.\nThe `.bp' directive is also considered to start a paragraph.\n.FE\nEach paragraph boundary is also a sentence boundary.  The sentence\nand paragraph commands can\nbe given counts to operate over groups of sentences and paragraphs.\n.PP\nSections in the editor begin after each macro in the \\fIsections\\fR option,\nnormally `.NH', `.SH', `.H' and `.HU', and each line with a formfeed \\fB^L\\fR\nin the first column.\nSection boundaries are always line and paragraph boundaries also.\n.PP\nTry experimenting with the sentence and paragraph commands until you are\nsure how they work.  If you have a large document, try looking through\nit using the section commands.\nThe section commands interpret a preceding count as a different window size in\nwhich to redraw the screen at the new location, and this window size\nis the base size for newly drawn windows until another size is specified.\nThis is very useful\nif you are on a slow terminal and are looking for a particular section.\nYou can give the first section command a small count to then see each successive\nsection heading in a small window.\n.NH 2\nRearranging and duplicating text\n.PP\nThe editor has a single unnamed buffer where the last deleted or\nchanged away text is saved, and a set of named buffers \\fBa\\fR\\-\\fBz\\fR\nwhich you can use to save copies of text and to move text around in\nyour file and between files.\n.PP\nThe operator\n.B y\nyanks a copy of the object which follows into the unnamed buffer.\nIf preceded by a buffer name, \\fB\"\\fR\\fIx\\fR\\|\\fBy\\fR, where\n\\fIx\\fR here is replaced by a letter \\fBa\\-z\\fR, it places the text in the named\nbuffer.  The text can then be put back in the file with the commands\n.B p\nand\n.B P ;\n\\fBp\\fR puts the text after or below the cursor, while \\fBP\\fR puts the text\nbefore or above the cursor.\n.PP\nIf the text which you\nyank forms a part of a line, or is an object such as a sentence which\npartially spans more than one line, then when you put the text back,\nit will be placed after the cursor (or before if you\nuse \\fBP\\fR).  If the yanked text forms whole lines, they will be put\nback as whole lines, without changing the current line.  In this case,\nthe put acts much like an \\fBo\\fR or \\fBO\\fR command.\n.PP\nTry the command \\fBYP\\fR.  This makes a copy of the current line and\nleaves you on this copy, which is placed before the current line.\nThe command \\fBY\\fR is a convenient abbreviation for \\fByy\\fR.\nThe command \\fBYp\\fR will also make a copy of the current line, and place\nit after the current line.  You can give \\fBY\\fR a count of lines to\nyank, and thus duplicate several lines; try \\fB3YP\\fR.\n.PP\nTo move text within the buffer, you need to delete it in one place, and\nput it back in another.  You can precede a delete operation by the\nname of a buffer in which the text is to be stored as in \\fB\"a5dd\\fR\ndeleting 5 lines into the named buffer \\fIa\\fR.  You can then move the\ncursor to the eventual resting place of these lines and do a \\fB\"ap\\fR\nor \\fB\"aP\\fR to put them back.\nIn fact, you can switch and edit another file before you put the lines\nback, by giving a command of the form \\fB:e \\fR\\fIname\\fR\\s-2CR\\s0 where\n\\fIname\\fR is the name of the other file you want to edit.  You will\nhave to write back the contents of the current editor buffer (or discard\nthem) if you have made changes before the editor will let you switch\nto the other file.\nAn ordinary delete command saves the text in the unnamed buffer,\nso that an ordinary put can move it elsewhere.\nHowever, the unnamed buffer is lost when you change files,\nso to move text from one file to another you should use a named buffer.\n.NH 2\nSummary.\n.IP\n.TS\nlw(.50i)b a.\n^\tfirst non-white on line\n$\tend of line\n)\tforward sentence\n}\tforward paragraph\n]]\tforward section\n(\tbackward sentence\n{\tbackward paragraph\n[[\tbackward section\nf\\fIx\\fR\tfind \\fIx\\fR forward in line\np\tput text back, after cursor or below current line\ny\tyank operator, for copies and moves\nt\\fIx\\fR\tup to \\fIx\\fR forward, for operators\nF\\fIx\\fR\tf backward in line\nP\tput text back, before cursor or above current line\nT\\fIx\\fR\tt backward in line\n.TE\n.ne 1i\n.NH 1\nHigh level commands\n.NH 2\nWriting, quitting, editing new files\n.PP\nSo far we have seen how to enter\n.I vi\nand to write out our file using either\n\\fBZZ\\fR or \\fB:w\\fR\\s-2CR\\s0. The first exits from\nthe editor,\n(writing if changes were made),\nthe second writes and stays in the editor.\n.PP\nIf you have changed the editor's copy of the file but do not wish to\nsave your changes, either because you messed up the file or decided that the\nchanges are not an improvement to the file, then you can give the command\n\\fB:q!\\fR\\s-2CR\\s0 to quit from the editor without writing the changes.\nYou can also reedit the same file (starting over) by giving the command\n\\fB:e!\\fR\\s-2CR\\s0.  These commands should be used only rarely, and with\ncaution, as it is not possible to recover the changes you have made after\nyou discard them in this manner.\n.PP\nYou can edit a different file without leaving the editor by giving the\ncommand \\fB:e\\fR\\ \\fIname\\fR\\s-2CR\\s0.  If you have not written out\nyour file before you try to do this, then the editor will tell you this,\nand delay editing the other file.  You can then give the command\n\\fB:w\\fR\\s-2CR\\s0 to save your work and then the \\fB:e\\fR\\ \\fIname\\fR\\s-2CR\\s0\ncommand again, or carefully give the command \\fB:e!\\fR\\ \\fIname\\fR\\s-2CR\\s0,\nwhich edits the other file discarding the changes you have made to the\ncurrent file.\nTo have the editor automatically save changes,\ninclude\n.I \"set autowrite\"\nin your EXINIT,\nand use \\fB:n\\fP instead of \\fB:e\\fP.\n.NH 2\nEscaping to a shell\n.PP\nYou can get to a shell to execute a single command by giving a\n.I vi\ncommand of the form \\fB:!\\fIcmd\\fR\\s-2CR\\s0.\nThe system will run the single command\n.I cmd\nand when the command finishes, the editor will ask you to hit a \\s-2RETURN\\s0\nto continue.  When you have finished looking at the output on the screen,\nyou should hit \\s-2RETURN\\s0 and the editor will clear the screen and\nredraw it.  You can then continue editing.\nYou can also give another \\fB:\\fR command when it asks you for a \\s-2RETURN\\s0;\nin this case the screen will not be redrawn.\n.PP\nIf you wish to execute more than one command in the shell, then you can\ngive the command \\fB:sh\\fR\\s-2CR\\s0.\nThis will give you a new shell, and when you finish with the shell, ending\nit by typing a \\fB^D\\fR, the editor will clear the screen and continue.\n.PP\nOn systems which support it, \\fB^Z\\fP will suspend the editor\nand return to the (top level) shell.\nWhen the editor is resumed, the screen will be redrawn.\n.NH 2\nMarking and returning\n.PP\nThe command \\fB\\(ga\\(ga\\fR returned to the previous place\nafter a motion of the cursor by a command such as \\fB/\\fR, \\fB?\\fR or\n\\fBG\\fR.  You can also mark lines in the file with single letter tags\nand return to these marks later by naming the tags.  Try marking the\ncurrent line with the command \\fBm\\fR\\fIx\\fR, where you should pick some\nletter for \\fIx\\fR, say `a'.  Then move the cursor to a different line\n(any way you like) and hit \\fB\\(gaa\\fR.  The cursor will return to the\nplace which you marked.\nMarks last only until you edit another file.\n.PP\nWhen using operators such as\n.B d\nand referring to marked lines, it is often desirable to delete whole lines\nrather than deleting to the exact position in the line marked by \\fBm\\fR.\nIn this case you can use the form \\fB\\(aa\\fR\\fIx\\fR rather than\n\\fB\\(ga\\fR\\fIx\\fR.  Used without an operator, \\fB\\(aa\\fR\\fIx\\fR will move to\nthe first non-white character of the marked line; similarly \\fB\\(aa\\(aa\\fR\nmoves to the first non-white character of the line containing the previous\ncontext mark \\fB\\(ga\\(ga\\fR.\n.NH 2\nAdjusting the screen\n.PP\nIf the screen image is messed up because of a transmission error to your\nterminal, or because some program other than the editor wrote output\nto your terminal, you can hit a \\fB^L\\fR, the \\s-2ASCII\\s0 form-feed\ncharacter, to cause the screen to be refreshed.\n.PP\nOn a dumb terminal, if there are @ lines in the middle of the screen\nas a result of line deletion, you may get rid of these lines by typing\n\\fB^R\\fR to cause the editor to retype the screen, closing up these holes.\n.PP\nFinally, if you wish to place a certain line on the screen at the top,\nmiddle, or bottom of the screen, you can position the cursor to that line,\nand then give a \\fBz\\fR command.\nYou should follow the \\fBz\\fR command with a \\s-2RETURN\\s0 if you want\nthe line to appear at the top of the window, a \\fB.\\fR if you want it\nat the center, or a \\fB\\-\\fR if you want it at the bottom.\n.NH 1\nSpecial topics\n.NH 2\nEditing on slow terminals\n.PP\nWhen you are on a slow terminal, it is important to limit the amount\nof output which is generated to your screen so that you will not suffer\nlong delays, waiting for the screen to be refreshed.  We have already\npointed out how the editor optimizes the updating of the screen during\ninsertions on dumb terminals to limit the delays, and how the editor erases\nlines to @ when they are deleted on dumb terminals.\n.\\\" .PP\n.\\\" The use of the slow terminal insertion mode is controlled by the\n.\\\" .I slowopen\n.\\\" option.  You can force the editor to use this mode even on faster terminals\n.\\\" by giving the command \\fB:se slow\\fR\\s-2CR\\s0.  If your system is sluggish,\n.\\\" this helps lessen the amount of output coming to your terminal.\n.\\\" You can disable this option by \\fB:se noslow\\fR\\s-2CR\\s0.\n.\\\" .PP\n.\\\" The editor can simulate an intelligent terminal on a dumb one.  Try\n.\\\" giving the command \\fB:se redraw\\fR\\s-2CR\\s0.  This simulation generates\n.\\\" a great deal of output and is generally tolerable only on lightly loaded\n.\\\" systems and fast terminals.  You can disable this by giving the command\n.\\\"  \\fB:se noredraw\\fR\\s-2CR\\s0.\n.PP\nThe editor also makes editing more pleasant at low speed by starting\nediting in a small window, and letting the window expand as you edit.\nThis works particularly well on intelligent terminals.  The editor can\nexpand the window easily when you insert in the middle of the screen\non these terminals.  If possible, try the editor on an intelligent terminal\nto see how this works.\n.PP\nYou can control the size of the window which is redrawn each time the\nscreen is cleared by giving window sizes as argument to the commands\nwhich cause large screen motions:\n.DS\n.B \":  /  ?  [[  ]]  \\(ga  \\(aa\"\n.DE\nThus if you are searching for a particular instance of a common string\nin a file, you can precede the first search command by a small number,\nsay 3, and the editor will draw three line windows around each instance\nof the string which it locates.\n.PP\nYou can easily expand or contract the window, placing the current line\nas you choose, by giving a number on a \\fBz\\fR command, after the \\fBz\\fR\nand before the following \\s-2RETURN\\s0, \\fB.\\fR or \\fB\\-\\fR.  Thus the\ncommand \\fBz5.\\fR redraws the screen with the current line in the center\nof a five line window.\\*(dg\n.FS\n\\*(dg Note that the command \\fB5z.\\fR has an entirely different effect,\nplacing line 5 in the center of a new window.\n.FE\n.PP\nIf the editor is redrawing or otherwise updating large portions of the\ndisplay, you can interrupt this updating by hitting a \\s-2DEL\\s0 or \\s-2RUB\\s0\nas usual.  If you do this you may partially confuse the editor about\nwhat is displayed on the screen.  You can still edit the text on\nthe screen if you wish; clear up the confusion\nby hitting a \\fB^L\\fR; or move or search again, ignoring the\ncurrent state of the display.\n.\\\" .PP\n.\\\" See section 7.8 on \\fIopen\\fR mode for another way to use the\n.\\\" .I vi\n.\\\" command set on slow terminals.\n.NH 2\nOptions, set, and editor startup files\n.PP\nThe editor has a set of options, some of which have been mentioned above.\nThe most useful options are given in the following table.\n.PP\nThe options are of three kinds:  numeric options, string options, and\ntoggle options.  You can set numeric and string options by a statement\nof the form\n.DS\n\\fBset\\fR \\fIopt\\fR\\fB=\\fR\\fIval\\fR\n.DE\nand toggle options can be set or unset by statements of one of the forms\n.DS\n\\fBset\\fR \\fIopt\\fR\n\\fBset\\fR \\fBno\\fR\\fIopt\\fR\n.DE\n.KF\n.TS\nlb lb lb lb\nl l l a.\nName\tDefault\tDescription\n_\nautoindent\tnoai\tSupply indentation automatically\nautowrite\tnoaw\tAuto write before \\fB:n\\fR, \\fB:ta\\fR, \\fB^^\\fR, \\fB!\\fR\nignorecase\tnoic\tIgnore case in searching\nlist\tnolist\tTabs print as ^I; end of lines $\nmagic\tmagic\t. [ and * are special in scans\nnumber\tnonu\tLines prefixed with line numbers\nparagraphs\tpara=IPLPPPQPP LIpplpipbp\tMacros which start paragraphs\nruler\tnoruler\tDisplay a row/column/percentage ruler.\nsections\tsect=NHSHH HUnhsh\tMacros which start new sections\nshiftwidth\tsw=8\tShift distance for <, >, \\fB^D\\fP and \\fB^T\\fR\nshowmatch\tnosm\tShow matching \\fB(\\fP or \\fB{\\fP\nterm\t$TERM\tThe kind of terminal being used\n.TE\n.KE\nThese statements can be placed in your EXINIT in your environment,\nor given while you are running\n.I vi\nby preceding them with a \\fB:\\fR and following them with a \\s-2CR\\s0.\n.PP\nYou can get a list of all options which you have changed by the\ncommand \\fB:set\\fR\\s-2CR\\s0, or the value of a single option by the\ncommand \\fB:set\\fR \\fIopt\\fR\\fB?\\fR\\s-2CR\\s0.\nA list of all possible options and their values is generated by\n\\fB:set all\\fP\\s-2CR\\s0.\nSet can be abbreviated \\fBse\\fP.\nMultiple options can be placed on one line, e.g.\n\\fB:se ai aw nu\\fP\\s-2CR\\s0.\n.PP\nOptions set by the \\fBset\\fP command only last\nwhile you stay in the editor.\nIt is common to want to have certain options set whenever you\nuse the editor.\nThis can be accomplished by creating a list of \\fIex\\fP commands\\*(dg\n.FS\n\\*(dg\nAll commands which start with\n.B :\nare \\fIex\\fP commands.\n.FE\nwhich are to be run every time you start up \\fIex\\fP\nor \\fIvi\\fP.\nA typical list includes a \\fBset\\fP command, and possibly a few\n\\fBmap\\fP commands.\nSince it is advisable to get these commands on one line, they can\nbe separated with the | character, for example:\n.DS\n\\fBset\\fP ai aw terse|\\fBmap\\fP @ dd|\\fBmap\\fP # x\n.DE\nwhich sets the options \\fIautoindent\\fP, \\fIautowrite\\fP, \\fIterse\\fP\n(the\n.B set\ncommand),\nmakes @ delete a line\n(the first\n.B map ),\nand makes # delete a character\n(the second\n.B map ).\n(See section 6.9 for a description of the \\fBmap\\fP command.)\nThis string should be placed in the variable EXINIT in your environment.\nIf you use the shell \\fIcsh\\fP,\nput this line in the file\n.I .login\nin your home directory:\n.DS I\nsetenv EXINIT 'set ai aw terse|map @ dd|map # x'\n.DE\nIf you use the standard shell \\fIsh\\fP,\nput these lines in the file\n.I .profile\nin your home directory:\n.DS\nexport EXINIT='set ai aw terse|map @ dd|map # x'\n.DE\nOf course, the particulars of the line would depend on which options\nyou wanted to set.\n.NH 2\nRecovering lost lines\n.PP\nYou might have a serious problem if you delete a number of lines and then\nregret that they were deleted.  Despair not, the editor saves the last\n9 deleted blocks of text in a set of numbered registers 1\\-9.\nYou can get the \\fIn\\fR'th previous deleted text back in your file by\nthe command\n\"\\fR\\fIn\\fR\\|\\fBp\\fR.\nThe \"\\fR here says that a buffer name is to follow,\n\\fIn\\fR is the number of the buffer you wish to try\n(use the number 1 for now),\nand\n.B p\nis the put command, which puts text in the buffer after the cursor.\nIf this doesn't bring back the text you wanted, hit\n.B u\nto undo this and then\n\\fB\\&.\\fR\n(period)\nto repeat the put command.\nIn general the\n\\fB\\&.\\fR\ncommand will repeat the last change you made.\nAs a special case, when the last command refers to a numbered text buffer,\nthe \\fB.\\fR command increments the number of the buffer before repeating\nthe command.  Thus a sequence of the form\n.DS\n\\fB\"1pu.u.u.\\fR\n.DE\nwill, if repeated long enough, show you all the deleted text which has\nbeen saved for you.\nYou can omit the\n.B u\ncommands here to gather up all this text in the buffer, or stop after any\n\\fB\\&.\\fR command to keep just the then recovered text.\nThe command\n.B P\ncan also be used rather than\n.B p\nto put the recovered text before rather than after the cursor.\n.NH 2\nRecovering lost files\n.PP\nIf the system crashes, you can recover the work you were doing\nto within a few changes.  You will normally receive mail when you next\nlogin giving you the name of the file which has been saved for you.\nYou should then change to the directory where you were when the system\ncrashed and give a command of the form:\n.DS\n% vi -r name\n.DE\nreplacing \\fIname\\fR with the name of the file which you were editing.\nThis will recover your work to a point near where you left off.\\*(dg\n.FS\n\\*(dg In rare cases, some of the lines of the file may be lost.  The\neditor will give you the numbers of these lines and the text of the lines\nwill be replaced by the string `LOST'.  These lines will almost always\nbe among the last few which you changed.  You can either choose to discard\nthe changes which you made (if they are easy to remake) or to replace\nthe few lost lines by hand.\n.FE\n.PP\nYou can get a listing of the files which are saved for you by giving\nthe command:\n.DS I\n% vi -r\n.DE\nIf there is more than one instance of a particular file saved, the editor\ngives you the newest instance each time you recover it.  You can thus\nget an older saved copy back by first recovering the newer copies.\n.PP\nFor this feature to work,\n.I vi\nmust be correctly installed by a super user on your system,\nand the\n.I mail\nprogram must exist to receive mail.\nThe invocation ``\\fIvi -r\\fP'' will not always list all saved files,\nbut they can be recovered even if they are not listed.\n.NH 2\nContinuous text input\n.PP\nWhen you are typing in large amounts of text it is convenient to have\nlines broken near the right margin automatically.  You can cause this\nto happen by giving the command\n\\fB:se wm=10\\fR\\s-2CR\\s0.\nThis causes all lines to be broken at a space at least 10 columns\nfrom the right hand edge of the screen.\n.PP\nIf the editor breaks an input line and you wish to put it back together\nyou can tell it to join the lines with \\fBJ\\fR.  You can give \\fBJ\\fR\na count of the number of lines to be joined as in \\fB3J\\fR to join 3\nlines.  The editor supplies whitespace, if appropriate,\nat the juncture of the joined\nlines, and leaves the cursor at this whitespace.\nYou can kill the whitespace with \\fBx\\fR if you don't want it.\n.NH 2\nFeatures for editing programs\n.PP\nThe editor has a number of commands for editing programs.\nThe thing that most distinguishes editing of programs from editing of text\nis the desirability of maintaining an indented structure to the body of\nthe program.  The editor has an\n.I autoindent\nfacility for helping you generate correctly indented programs.\n.PP\nTo enable this facility you can give the command \\fB:se ai\\fR\\s-2CR\\s0.\nNow try opening a new line with \\fBo\\fR and type some characters on the\nline after a few tabs.  If you now start another line, notice that the\neditor supplies whitespace at the beginning of the line to line it up\nwith the previous line.  You cannot backspace over this indentation,\nbut you can use \\fB^D\\fR key to backtab over the supplied indentation.\n.PP\nEach time you type \\fB^D\\fR you back up one position, normally to an\n8 column boundary.  This amount is settable; the editor has an option\ncalled\n.I shiftwidth\nwhich you can set to change this value.\nTry giving the command \\fB:se sw=4\\fR\\s-2CR\\s0\nand then experimenting with autoindent again.\n.PP\nFor shifting lines in the program left and right, there are operators\n.B <\nand\n.B >.\nThese shift the lines you specify right or left by one\n.I shiftwidth .\nTry\n.B <<\nand\n.B >>\nwhich shift one line left or right, and\n.B <L\nand\n.B >L\nshifting the rest of the display left and right.\n.PP\nIf you have a complicated expression and wish to see how the parentheses\nmatch, put the cursor at a left or right parenthesis and hit \\fB%\\fR.\nThis will show you the matching parenthesis.\nThis works also for braces { and }, and brackets [ and ].\n.PP\nIf you are editing C programs, you can use the \\fB[[\\fR and \\fB]]\\fR keys\nto advance or retreat to a line starting with a \\fB{\\fR, i.e. a function\ndeclaration at a time.  When \\fB]]\\fR is used with an operator it stops\nafter a line which starts with \\fB}\\fR; this is sometimes useful with\n\\fBy]]\\fR.\n.NH 2\nFiltering portions of the buffer\n.PP\nYou can run system commands over portions of the buffer using the operator\n\\fB!\\fR.\nYou can use this to sort lines in the buffer, or to reformat portions\nof the buffer with a pretty-printer.\nTry typing in a list of random words, one per line, and ending them\nwith a blank line.  Back up to the beginning of the list, and then give\nthe command \\fB!}sort\\fR\\s-2CR\\s0.  This says to sort the next paragraph\nof material, and the blank line ends a paragraph.\n.\\\" .NH 2\n.\\\" Commands for editing \\s-2LISP\\s0\n.\\\" .PP\n.\\\" If you are editing a \\s-2LISP\\s0 program you should set the option\n.\\\" .I lisp\n.\\\" by doing\n.\\\" \\fB:se\\ lisp\\fR\\s-2CR\\s0.\n.\\\" This changes the \\fB(\\fR and \\fB)\\fR commands to move backward and forward\n.\\\" over s-expressions.\n.\\\" The \\fB{\\fR and \\fB}\\fR commands are like \\fB(\\fR and \\fB)\\fR but don't\n.\\\" stop at atoms.  These can be used to skip to the next list, or through\n.\\\" a comment quickly.\n.\\\" .PP\n.\\\" The\n.\\\" .I autoindent\n.\\\" option works differently for \\s-2LISP\\s0, supplying indent to align at\n.\\\" the first argument to the last open list.  If there is no such argument\n.\\\" then the indent is two spaces more than the last level.\n.\\\" .PP\n.\\\" There is another option which is useful for typing in \\s-2LISP\\s0, the\n.\\\" .I showmatch\n.\\\" option.\n.\\\" Try setting it with\n.\\\" \\fB:se sm\\fR\\s-2CR\\s0\n.\\\" and then try typing a `(' some words and then a `)'.  Notice that the\n.\\\" cursor shows the position of the `(' which matches the `)' briefly.\n.\\\" This happens only if the matching `(' is on the screen, and the cursor\n.\\\" stays there for at most one second.\n.\\\" .PP\n.\\\" The editor also has an operator to realign existing lines as though they\n.\\\" had been typed in with\n.\\\" .I lisp\n.\\\" and\n.\\\" .I autoindent\n.\\\" set.  This is the \\fB=\\fR operator.\n.\\\" Try the command \\fB=%\\fR at the beginning of a function.  This will realign\n.\\\" all the lines of the function declaration.\n.\\\" .PP\n.\\\" When you are editing \\s-2LISP\\s0,, the \\fB[[\\fR and \\fR]]\\fR advance\n.\\\" and retreat to lines beginning with a \\fB(\\fR, and are useful for dealing\n.\\\" with entire function definitions.\n.NH 2\nMacros\n.PP\n.I Vi\nhas a parameterless macro facility, which lets you set it up so that\nwhen you hit a single keystroke, the editor will act as though\nyou had hit some longer sequence of keys.  You can set this up if\nyou find yourself typing the same sequence of commands repeatedly.\n.PP\nBriefly, there are two flavors of macros:\n.IP a)\nOnes where you put the macro body in a buffer register, say \\fIx\\fR.\nYou can then type \\fB@x\\fR to invoke the macro.  The \\fB@\\fR may be followed\nby another \\fB@\\fR to repeat the last macro.\n.IP b)\nYou can use the\n.I map\ncommand from\n.I vi\n(typically in your\n.I EXINIT )\nwith a command of the form:\n.DS\n:map \\fIlhs\\fR \\fIrhs\\fR\\s-2CR\n.DE\nmapping\n.I lhs\ninto\n.I rhs.\nThere are restrictions:\n.I lhs\nshould be one keystroke (either 1 character or one function key)\nsince it must be entered within one second\n(unless\n.I notimeout\nis set, in which case you can type it as slowly as you wish,\nand\n.I vi\nwill wait for you to finish it before it echoes anything).\nThe\n.I lhs\ncan be no longer than 10 characters, the\n.I rhs\nno longer than 100.\nTo get a space, tab or newline into\n.I lhs\nor\n.I rhs\nyou should escape them with a \\fB^V\\fR.\n(It may be necessary to double the \\fB^V\\fR if the map\ncommand is given inside\n.I vi ,\nrather than in\n.I ex .)\nSpaces and tabs inside the\n.I rhs\nneed not be escaped.\n.PP\nThus to make the \\fBq\\fR key write and exit the editor, you can give\nthe command\n.DS I\n:map q :wq\\fB^V^V\\fP\\s-2CR CR\\s0\n.DE\nwhich means that whenever you type \\fBq\\fR, it will be as though you\nhad typed the four characters \\fB:wq\\fR\\s-2CR\\s0.\nA \\fB^V\\fR's is needed because without it the \\s-2CR\\s0 would end the\n\\fB:\\fR command, rather than becoming part of the\n.I map\ndefinition.\nThere are two\n.B ^V 's\nbecause from within\n.I vi ,\ntwo\n.B ^V 's\nmust be typed to get one.\nThe first \\s-2CR\\s0 is part of the\n.I rhs ,\nthe second terminates the : command.\n.PP\nMacros can be deleted with\n.DS I\nunmap lhs\n.DE\n.PP\nIf the\n.I lhs\nof a macro is ``#0'' through ``#9'', this maps the particular function key\ninstead of the 2 character ``#'' sequence.  So that terminals without\nfunction keys can access such definitions, the form ``#x'' will mean function\nkey\n.I x\non all terminals (and need not be typed within one second).\nThe character ``#'' can be changed by using a macro in the usual way:\n.DS\n:map \\fB^V^V^I\\fP #\n.DE\nto use tab, for example.  (This won't affect the\n.I map\ncommand, which still uses\n.B #,\nbut just the invocation from visual mode.)\n.PP\nThe\n.I undo\ncommand reverses an entire macro call as a unit,\nif it made any changes.\n.PP\nPlacing a `!' after the word\n.B map\ncauses the mapping to apply\nto input mode, rather than command mode.\nThus, to arrange for \\fB^T\\fP to be the same as 4 spaces in input mode,\nyou can type:\n.DS\n:map \\fB^T\\fP \\fB^V\\fP\\o'b/'\\o'b/'\\o'b/'\\o'b/'\n.DE\nwhere\n.B \\o'b/'\nis a blank.\nThe \\fB^V\\fP is necessary to prevent the blanks from being taken as\nwhitespace between the\n.I lhs\nand\n.I rhs .\n.NH\nWord Abbreviations\n.PP\nA feature similar to macros in input mode is word abbreviation.\nThis allows you to type a short word and have it expanded into\na longer word or words.\nThe commands are\n.B :abbreviate\nand\n.B :unabbreviate\n(\\fB:ab\\fP\nand\n.B :una )\nand have the same syntax as\n.B :map .\nFor example:\n.DS\n:ab eecs Electrical Engineering and Computer Sciences\n.DE\ncauses the word `eecs' to always be changed into the\nphrase `Electrical Engineering and Computer Sciences'.\nWord abbreviation is different from macros in that\nonly whole words are affected.\nIf `eecs' were typed as part of a larger word, it would\nbe left alone.\nAlso, the partial word is echoed as it is typed.\nThere is no need for an abbreviation to be a single keystroke,\nas it should be with a macro.\n.NH 2\nAbbreviations\n.PP\nThe editor has a number of short\ncommands which abbreviate longer commands which we\nhave introduced here.  You can find these commands easily\non the quick reference card.\nThey often save a bit of typing and you can learn them as convenient.\n.NH 1\nNitty-gritty details\n.NH 2\nLine representation in the display\n.PP\nThe editor folds long logical lines onto many physical lines in the display.\nCommands which advance lines advance logical lines and will skip\nover all the segments of a line in one motion.  The command \\fB|\\fR moves\nthe cursor to a specific column, and may be useful for getting near the\nmiddle of a long line to split it in half.  Try \\fB80|\\fR on a line which\nis more than 80 columns long.\\*(dg\n.FS\n\\*(dg You can make long lines very easily by using \\fBJ\\fR to join together\nshort lines.\n.FE\n.PP\nThe editor only puts full lines on the display; if there is not enough\nroom on the display to fit a logical line, the editor leaves the physical\nline empty, placing only an @ on the line as a place holder.  When you\ndelete lines on a dumb terminal, the editor will often just clear the\nlines to @ to save time (rather than rewriting the rest of the screen.)\nYou can always maximize the information on the screen by giving the \\fB^R\\fR\ncommand.\n.PP\nIf you wish, you can have the editor place line numbers before each line\non the display.  Give the command \\fB:se nu\\fR\\s-2CR\\s0 to enable\nthis, and the command \\fB:se nonu\\fR\\s-2CR\\s0 to turn it off.\nYou can have tabs represented as \\fB^I\\fR and the ends of lines indicated\nwith `$' by giving the command \\fB:se list\\fR\\s-2CR\\s0;\n\\fB:se nolist\\fR\\s-2CR\\s0 turns this off.\n.PP\nFinally, lines consisting of only the character `~' are displayed when\nthe last line in the file is in the middle of the screen.  These represent\nphysical lines which are past the logical end of file.\n.NH 2\nCounts\n.PP\nMost\n.I vi\ncommands will use a preceding count to affect their behavior in some way.\nThe following table gives the common ways in which the counts are used:\n.DS\n.TS\nl lb.\nnew window size\t:  /  ?  [[  ]]  \\`  \\'\nscroll amount\t^D  ^U\nline/column number\tz  G  |\nrepeat effect\t\\fRmost of the rest\\fP\n.TE\n.DE\n.PP\nThe editor maintains a notion of the current default window size.\nOn terminals which run at speeds greater than 1200 baud\nthe editor uses the full terminal screen.\nOn terminals which are slower than 1200 baud\n(most dialup lines are in this group)\nthe editor uses 8 lines as the default window size.\nAt 1200 baud the default is 16 lines.\n.PP\nThis size is the size used when the editor clears and refills the screen\nafter a search or other motion moves far from the edge of the current window.\nThe commands which take a new window size as count all often cause the\nscreen to be redrawn.  If you anticipate this, but do not need as large\na window as you are currently using, you may wish to change the screen\nsize by specifying the new size before these commands.\nIn any case, the number of lines used on the screen will expand if you\nmove off the top with a \\fB\\-\\fR or similar command or off the bottom\nwith a command such as \\s-2RETURN\\s0 or \\fB^D\\fR.\nThe window will revert to the last specified size the next time it is\ncleared and refilled.\\*(dg\n.FS\n\\*(dg But not by a \\fB^L\\fR which just redraws the screen as it is.\n.FE\n.PP\nThe scroll commands \\fB^D\\fR and \\fB^U\\fR likewise remember the amount\nof scroll last specified, using half the basic window size initially.\nThe simple insert commands use a count to specify a repetition of the\ninserted text.  Thus \\fB10a+\\-\\-\\-\\-\\fR\\s-2ESC\\s0 will insert a grid-like\nstring of text.\nA few commands also use a preceding count as a line or column number.\n.PP\nExcept for a few commands which ignore any counts (such as \\fB^R\\fR),\nthe rest of the editor commands use a count to indicate a simple repetition\nof their effect.  Thus \\fB5w\\fR advances five words on the current line,\nwhile \\fB5\\fR\\s-2RETURN\\s0 advances five lines.  A very useful instance\nof a count as a repetition is a count given to the \\fB.\\fR command, which\nrepeats the last changing command.  If you do \\fBdw\\fR and then \\fB3.\\fR,\nyou will delete first one and then three words.  You can then delete\ntwo more words with \\fB2.\\fR.\n.NH 2\nMore file manipulation commands\n.PP\nThe following table lists the file manipulation commands which you can\nuse when you are in\n.I vi .\n.KF\n.DS\n.TS\nlb l.\n:w\twrite back changes\n:wq\twrite and quit\n:x\twrite (if necessary) and quit (same as ZZ)\n:e \\fIname\\fP\tedit file \\fIname\\fR\n:e!\treedit, discarding changes\n:e + \\fIname\\fP\tedit, starting at end\n:e +\\fIn\\fP\tedit, starting at line \\fIn\\fP\n:e #\tedit alternate file\n:w \\fIname\\fP\twrite file \\fIname\\fP\n:w! \\fIname\\fP\toverwrite file \\fIname\\fP\n:\\fIx,y\\fPw \\fIname\\fP\twrite lines \\fIx\\fP through \\fIy\\fP to \\fIname\\fP\n:r \\fIname\\fP\tread file \\fIname\\fP into buffer\n:r !\\fIcmd\\fP\tread output of \\fIcmd\\fP into buffer\n:n\tedit next file in argument list\n:n!\tedit next file, discarding changes to current\n:n \\fIargs\\fP\tspecify new argument list\n:ta \\fItag\\fP\tedit file containing tag \\fItag\\fP, at \\fItag\\fP\n.TE\n.DE\n.KE\nAll of these commands are followed by a \\s-2CR\\s0 or \\s-2ESC\\s0.\nThe most basic commands are \\fB:w\\fR and \\fB:e\\fR.\nA normal editing session on a single file will end with a \\fBZZ\\fR command.\nIf you are editing for a long period of time you can give \\fB:w\\fR commands\noccasionally after major amounts of editing, and then finish\nwith a \\fBZZ\\fR.   When you edit more than one file, you can finish\nwith one with a \\fB:w\\fR and start editing a new file by giving a \\fB:e\\fR\ncommand,\nor set\n.I autowrite\nand use \\fB:n\\fP <file>.\n.PP\nIf you make changes to the editor's copy of a file, but do not wish to\nwrite them back, then you must give an \\fB!\\fR after the command you\nwould otherwise use; this forces the editor to discard any changes\nyou have made.  Use this carefully.\n.ne 1i\n.PP\nThe \\fB:e\\fR command can be given a \\fB+\\fR argument to start at the\nend of the file, or a \\fB+\\fR\\fIn\\fR argument to start at line \\fIn\\fR\\^.\nIn actuality, \\fIn\\fR may be any editor command not containing a space,\nusefully a scan like \\fB+/\\fIpat\\fR or \\fB+?\\fIpat\\fR.\nIn forming new names to the \\fBe\\fR command, you can use the character\n\\fB%\\fR which is replaced by the current file name, or the character\n\\fB#\\fR which is replaced by the alternate file name.\nThe alternate file name is generally the last name you typed other than\nthe current file.  Thus if you try to do a \\fB:e\\fR and get a diagnostic\nthat you haven't written the file, you can give a \\fB:w\\fR command and\nthen a \\fB:e #\\fR command to redo the previous \\fB:e\\fR.\n.PP\nYou can write part of the buffer to a file by finding out the lines\nthat bound the range to be written using \\fB^G\\fR, and giving these\nnumbers after the \\fB:\\fR\nand before the \\fBw\\fP, separated by \\fB,\\fR's.\nYou can also mark these lines with \\fBm\\fR and\nthen use an address of the form \\fB\\(aa\\fR\\fIx\\fR\\fB,\\fB\\(aa\\fR\\fIy\\fR\non the \\fBw\\fR command here.\n.PP\nYou can read another file into the buffer after the current line by using\nthe \\fB:r\\fR command.\nYou can similarly read in the output from a command, just use \\fB!\\fR\\fIcmd\\fR\ninstead of a file name.\n.PP\nIf you wish to edit a set of files in succession, you can give all the\nnames on the command line, and then edit each one in turn using the command\n\\fB:n\\fR.  It is also possible to respecify the list of files to be edited\nby giving the \\fB:n\\fR command a list of file names, or a pattern to\nbe expanded as you would have given it on the initial\n.I vi\ncommand.\n.PP\nIf you are editing large programs, you will find the \\fB:ta\\fR command\nvery useful.  It utilizes a data base of function names and their locations,\nwhich can be created by programs such as\n.I ctags ,\nto quickly find a function whose name you give.\nIf the \\fB:ta\\fR command requires the editor to switch files, then\nyou must \\fB:w\\fR or abandon any changes before switching.  You can repeat\nthe \\fB:ta\\fR command without any arguments to look for the same tag\nagain.\n.NH 2\nMore about searching for strings\n.PP\nWhen you are searching for strings in the file with \\fB/\\fR and \\fB?\\fR,\nthe editor normally places you at the next or previous occurrence\nof the string.  If you are using an operator such as \\fBd\\fR,\n\\fBc\\fR or \\fBy\\fR, then you may well wish to affect lines up to the\nline before the line containing the pattern.  You can give a search of\nthe form \\fB/\\fR\\fIpat\\fR\\fB/\\-\\fR\\fIn\\fR to refer to the \\fIn\\fR'th line\nbefore the next line containing \\fIpat\\fR, or you can use \\fB+\\fR instead\nof \\fB\\-\\fR to refer to the lines after the one containing \\fIpat\\fR.\nIf you don't give a line offset, then the editor will affect characters\nup to the match place, rather than whole lines; thus use ``+0'' to affect\nto the line which matches.\n.PP\nYou can have the editor ignore the case of words in the searches it does\nby giving the command \\fB:se ic\\fR\\s-2CR\\s0.\nThe command \\fB:se noic\\fR\\s-2CR\\s0 turns this off.\n.ne 1i\n.PP\nStrings given to searches may actually be regular expressions.\nIf you do not want or need this facility, you should\n.DS\nset nomagic\n.DE\nin your EXINIT.\nIn this case,\nonly the characters \\fB^\\fR and \\fB$\\fR are special in patterns.\nThe character \\fB\\e\\fR is also then special (as it is most everywhere in\nthe system), and may be used to get at the\nan extended pattern matching facility.\nIt is also necessary to use a \\e before a\n\\fB/\\fR in a forward scan or a \\fB?\\fR in a backward scan, in any case.\nThe following table gives the extended forms when \\fBmagic\\fR is set.\n.DS\n.TS\nlb l.\n^\tat beginning of pattern, matches beginning of line\n$\tat end of pattern, matches end of line\n\\fB\\&.\\fR\tmatches any character\n\\e<\tmatches the beginning of a word\n\\e>\tmatches the end of a word\n[\\fIstr\\fP]\tmatches any single character in \\fIstr\\fP\n[^\\fIstr\\fP]\tmatches any single character not in \\fIstr\\fP\n[\\fIx\\fP\\-\\fIy\\fP]\tmatches any character between \\fIx\\fP and \\fIy\\fP\n*\tmatches any number of the preceding pattern\n.TE\n.DE\nIf you use \\fBnomagic\\fR mode, then\nthe \\fB. [\\fR and \\fB*\\fR primitives are given with a preceding\n\\e.\n.NH 2\nMore about input mode\n.PP\nThere are a number of characters which you can use to make corrections\nduring input mode.  These are summarized in the following table.\n.sp .5\n.DS\n.TS\nlb l.\n^H\tdeletes the last input character\n^W\tdeletes the last input word, defined as by \\fBb\\fR\nerase\tyour erase character, same as \\fB^H\\fP\nkill\tyour kill character, deletes the input on this line\n\\e\tescapes a following \\fB^H\\fP and your erase and kill\n\\s-2ESC\\s0\tends an insertion\n\\s-2DEL\\s0\tinterrupts an insertion, terminating it abnormally\n\\s-2CR\\s0\tstarts a new line\n^D\tbacktabs over \\fIautoindent\\fP\n0^D\tkills all the \\fIautoindent\\fP\n^^D\tsame as \\fB0^D\\fP, but restores indent next line\n^V\tquotes the next non-printing character into the file\n.TE\n.DE\n.sp .5\n.PP\nThe most usual way of making corrections to input is by typing \\fB^H\\fR\nto correct a single character, or by typing one or more \\fB^W\\fR's to\nback over incorrect words.  If you use \\fB#\\fR as your erase character\nin the normal system, it will work like \\fB^H\\fR.\n.PP\nYour system kill character, normally \\fB@\\fR, \\fB^X\\fP or \\fB^U\\fR,\nwill erase all\nthe input you have given on the current line.\nIn general, you can neither\nerase input back around a line boundary nor can you erase characters\nwhich you did not insert with this insertion command.  To make corrections\non the previous line after a new line has been started you can hit \\s-2ESC\\s0\nto end the insertion, move over and make the correction, and then return\nto where you were to continue.  The command \\fBA\\fR which appends at the\nend of the current line is often useful for continuing.\n.PP\nIf you wish to type in your erase or kill character (say # or @) then\nyou must precede it with a \\fB\\e\\fR, just as you would do at the normal\nsystem command level.  A more general way of typing non-printing characters\ninto the file is to precede them with a \\fB^V\\fR.  The \\fB^V\\fR echoes\nas a \\fB^\\fR character on which the cursor rests.  This indicates that\nthe editor expects you to type a control character.  In fact you may\ntype any character and it will be inserted into the file at that point.*\n.FS\n* This is not quite true.  The implementation of the editor does\nnot allow the \\s-2NULL\\s0 (\\fB^@\\fR) character to appear in files.  Also\nthe \\s-2LF\\s0 (linefeed or \\fB^J\\fR) character is used by the editor\nto separate lines in the file, so it cannot appear in the middle of a\nline.  You can insert any other character, however, if you wait for the\neditor to echo the \\fB^\\fR before you type the character.  In fact,\nthe editor will treat a following letter as a request for the corresponding\ncontrol character.  This is the only way to type \\fB^S\\fR or \\fB^Q\\fP,\nsince the system normally uses them to suspend and resume output\nand never gives them to the editor to process.\n.FE\n.PP\nIf you are using \\fIautoindent\\fR you can backtab over the indent which\nit supplies by typing a \\fB^D\\fR.  This backs up to a \\fIshiftwidth\\fR\nboundary.\nThis only works immediately after the supplied \\fIautoindent\\fR.\n.PP\nWhen you are using \\fIautoindent\\fR you may wish to place a label at\nthe left margin of a line.  The way to do this easily is to type \\fB^\\fR\nand then \\fB^D\\fR.  The editor will move the cursor to the left margin\nfor one line, and restore the previous indent on the next.  You can also\ntype a \\fB0\\fR followed immediately by a \\fB^D\\fR if you wish to kill\nall the indent and not have it come back on the next line.\n.NH 2\nUpper case only terminals\n.PP\nIf your terminal has only upper case, you can still use\n.I vi\nby using the normal\nsystem convention for typing on such a terminal.\nCharacters which you normally type are converted to lower case, and you\ncan type upper case letters by preceding them with a \\e.\nThe characters { ~ } | \\(ga are not available on such terminals, but you\ncan escape them as \\e( \\e^ \\e) \\e! \\e\\(aa.\nThese characters are represented on the display in the same way they\nare typed.\\*(dd\n.FS\n\\*(dd The \\e character you give will not echo until you type another\nkey.\n.FE\n.NH 2\nVi and ex\n.PP\n.I Vi\nis actually one mode of editing within the editor\n.I ex .\nWhen you are running\n.I vi\nyou can escape to the line oriented editor of\n.I ex\nby giving the command\n\\fBQ\\fR.\nAll of the\n.B :\ncommands which were introduced above are available in\n.I ex.\nLikewise, most\n.I ex\ncommands can be invoked from\n.I vi\nusing \\fB:\\fR.\nJust give them without the \\fB:\\fR and follow them with a \\s-2CR\\s0.\n.PP\nIn rare instances, an internal error may occur in\n.I vi .\nIn this case you will get a diagnostic and be left in the command mode of\n.I ex .\nYou can then save your work and quit if you wish by giving a command\n\\fBx\\fR after the \\fB:\\fR which \\fIex\\fR prompts you with, or you can\nreenter \\fIvi\\fR by giving\n.I ex\na\n.I vi\ncommand.\n.PP\nThere are a number of things which you can do more easily in\n.I ex\nthan in\n.I vi.\nSystematic changes in line oriented material are particularly easy.\nYou can read the advanced editing documents for the editor\n.I ed\nto find out a lot more about this style of editing.\nExperienced\nusers often mix their use of\n.I ex\ncommand mode and\n.I vi\ncommand mode to speed the work they are doing.\n.\\\" .NH 2\n.\\\" Open mode: vi on hardcopy terminals and ``glass tty's''\n.\\\" \\(dd\n.\\\" .PP\n.\\\" If you are on a hardcopy terminal or a terminal which does not have a cursor\n.\\\" which can move off the bottom line, you can still use the command set of\n.\\\" .I vi,\n.\\\" but in a different mode.\n.\\\" When you give a\n.\\\" .I vi\n.\\\" command, the editor will tell you that it is using\n.\\\" .I open\n.\\\" mode.\n.\\\" This name comes from the\n.\\\" .I open\n.\\\" command in\n.\\\" .I ex,\n.\\\" which is used to get into the same mode.\n.\\\" .PP\n.\\\" The only difference between\n.\\\" .I visual\n.\\\" mode\n.\\\" and\n.\\\" .I open\n.\\\" mode is the way in which the text is displayed.\n.\\\" .PP\n.\\\" In\n.\\\" .I open\n.\\\" mode the editor uses a single line window into the file, and moving backward\n.\\\" and forward in the file causes new lines to be displayed, always below the\n.\\\" current line.\n.\\\" Two commands of\n.\\\" .I vi\n.\\\" work differently in\n.\\\" .I open:\n.\\\" .B z\n.\\\" and\n.\\\" \\fB^R\\fR.\n.\\\" The\n.\\\" .B z\n.\\\" command does not take parameters, but rather draws a window of context around\n.\\\" the current line and then returns you to the current line.\n.\\\" .PP\n.\\\" If you are on a hardcopy terminal,\n.\\\" the\n.\\\" .B ^R\n.\\\" command will retype the current line.\n.\\\" On such terminals, the editor normally uses two lines to represent the\n.\\\" current line.\n.\\\" The first line is a copy of the line as you started to edit it, and you work\n.\\\" on the line below this line.\n.\\\" When you delete characters, the editor types a number of \\e's to show\n.\\\" you the characters which are deleted.  The editor also reprints the current\n.\\\" line soon after such changes so that you can see what the line looks\n.\\\" like again.\n.\\\" .PP\n.\\\" It is sometimes useful to use this mode on very slow terminals which\n.\\\" can support\n.\\\" .I vi\n.\\\" in the full screen mode.\n.\\\" You can do this by entering\n.\\\" .I ex\n.\\\" and using an\n.\\\" .I open\n.\\\" command.\n.\\\" .LP\n.SH\nAcknowledgements\n.PP\nBruce Englar encouraged the early development of this display editor.\nPeter Kessler helped bring sanity to version 2's command layout.\nBill Joy wrote versions 1 and 2.0 through 2.7,\nand created the framework that users see in the present editor.\nMark Horton added macros and other features and made the\neditor work on a large number of terminals and Unix systems.\n"
  },
  {
    "path": "docs/USD.doc/vitut/vi.summary",
    "content": ".\\\"        $OpenBSD: vi.summary,v 1.6 2004/01/24 12:29:13 jmc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1980, 1993\n.\\\"        The Regents of the University of California.  All rights reserved.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"        @(#)vi.summary        8.3 (Berkeley) 8/18/96\n.\\\"\n.ds CH\n.ds CF\n.de TS\n.br\n.if !\\\\n(1T .RT\n.ul 0\n.ti \\\\n(.iu\n.if t .sp 0.25\n.if n .sp\n.if \u0007\\\\$1\u0007H\u0007 .TQ\n.nr IX 1\n..\n.nr PS 9\n.ps 9\n.nr VS 11\n.vs 11\n.nr HM .50i\n.nr FM .25i\n.nr PO 1.0i\n.po 1.0i\n.nr LL 4.5i\n.ll 4.5i\n.de nc\n.bp\n..\n.de h\n.LG\n.B\n\\\\$1\n.R\n.NL\n..\n.LG\n.LG\n.B\n.ce\nEx Quick Reference\n.R\n.NL\n.LP\n.LP\n.h \"Entering/leaving ex\"\n.TS\naw(1.4i)b aw(1.8i).\n% ex \\fIname\\fP\tedit \\fIname\\fP, start at end\n% ex +\\fIn\\fP \\fIname\\fP\t... at line \\fIn\\fP\n% ex \\-t \\fItag\\fP\tstart at \\fItag\\fP\n% ex \\-r\tlist saved files\n% ex \\-r \\fIname\\fP\trecover file \\fIname\\fP\n% ex \\fIname\\fP ...\tedit first; rest via \\fB:n\\fP\n% ex \\-R \\fIname\\fP \tread only mode\n: x\texit, saving changes\n: q!\texit, discarding changes\n.TE\n.h \"Ex states\"\n.TS\nlw(1i) lw(2.0i).\nCommand\tT{\nNormal and initial state.  Input prompted for by \\fB:\\fP.\nYour kill character cancels partial command.\nT}\nInsert\tT{\nEntered by \\fBa\\fP \\fBi\\fP and \\fBc\\fP.\nArbitrary text then terminates with line having only \\fB.\\fP\ncharacter on it or abnormally with interrupt.\nT}\nOpen/visual\tT{\nEntered by \\fBopen\\fP or \\fBvi\\fP, terminates with \\fBQ\\fP\nor ^\\e.\nT}\n.TE\n.h \"Ex commands\"\n.TS\nlw(.45i) lw(.08i)b lw(.45i) lw(.08i)b lw(.45i) lw(.08i)b.\nabbrev\tab\tnext\tn\tunabbrev\tuna\nappend\ta\tnumber\tnu\tundo\tu\nargs\tar\topen\to\tunmap\tunm\nchange\tc\tpreserve\tpre\tversion\tve\ncopy\tco\tprint\tp\tvisual\tvi\ndelete\td\tput\tpu\twrite\tw\nedit\te\tquit\tq\txit\tx\nfile\tf\tread\tre\tyank\tya\nglobal\tg\trecover\trec\t\\fIwindow\\fP\tz\ninsert\ti\trewind\trew\t\\fIescape\\fP\t!\njoin\tj\tset\tse\t\\fIlshift\\fP\t<\nlist\tl\tshell\tsh\t\\fIprint next\\fP\t\\fRCR\\fP\nmap\t\tsource\tso\t\\fIresubst\\fP\t&\nmark\tma\tstop\tst\t\\fIrshift\\fP\t>\nmove\tm\tsubstitute\ts\t\\fIscroll\\fP\t^D\n.TE\n.h \"Ex command addresses\"\n.TS\nlw(.3i)b lw(0.8i) lw(.3i)b lw(0.8i).\n\\fIn\\fP\tline \\fIn\\fP\t/\\fIpat\\fP\tnext with \\fIpat\\fP\n\\&.\tcurrent\t?\\fIpat\\fP\tprevious with \\fIpat\\fP\n$\tlast\t\\fIx\\fP-\\fIn\\fP\t\\fIn\\fP before \\fIx\\fP\n+\tnext\t\\fIx\\fP,\\fIy\\fP\t\\fIx\\fP through \\fIy\\fP\n\\-\tprevious\t\\(aa\\fIx\\fP\tmarked with \\fIx\\fP\n+\\fIn\\fP\t\\fIn\\fP forward\t\\(aa\\(aa\tprevious context\n%\t1,$\n.TE\n.nc\n.h \"Specifying terminal type\"\n.TS\naw(1.7i)b aw(1.5i).\n% setenv TERM \\fItype\\fP\t\\fIcsh\\fP and all version 6\n$ export TERM=\\fItype\\fP\t\\fIsh\\fP in Version 7\n\\fRSee also\\fP \\fItset\\fR(1)\n.TE\n.h \"Some terminal types\"\n.TS\nlw(.4i) lw(.4i) lw(.4i) lw(.4i) lw(.4i).\n2621\t43\tadm31\tdw1\th19\n2645\t733\tadm3a\tdw2\ti100\n300s\t745\tc100\tgt40\tmime\n33\tact4\tdm1520\tgt42\towl\n37\tact5\tdm2500\th1500\tt1061\n4014\tadm3\tdm3025\th1510\tvt52\n.TE\n.h \"Initializing options\"\n.TS\nlw(.9i)b aw(1.5i).\nEXINIT\tplace \\fBset\\fP's here in environment var\nset \\fIx\\fP\tenable option\nset no\\fIx\\fP\tdisable option\nset \\fIx\\fP=\\fIval\\fP\tgive value \\fIval\\fP\nset\tshow changed options\nset all\tshow all options\nset \\fIx\\fP?\tshow value of option \\fIx\\fP\n.TE\n.h \"Useful options\"\n.TS\nlw(.9i)b lw(.3i) lw(1.0i).\nautoindent\tai\tsupply indent\nautowrite\taw\twrite before changing files\nignorecase\tic\tin scanning\n.\\\" lisp\t\t\\fB( ) { }\\fP are s-exp's\nlist\t\tprint ^I for tab, $ at end\nmagic\t\t\\fB. [ *\\fP special in patterns\nnumber\tnu\tnumber lines\nparagraphs\tpara\tmacro names which start ...\nredraw\t\tsimulate smart terminal\nscroll\t\tcommand mode lines\nsections\tsect\tmacro names ...\nshiftwidth\tsw\tfor \\fB< >\\fP, and input \\fB^D\\fP\nshowmatch\tsm\tto \\fB)\\fP and \\fB}\\fP as typed\n.\\\" slowopen\tslow\tchoke updates during insert\nwindow\t\tvisual mode lines\nwrapscan\tws\taround end of buffer?\nwrapmargin\twm\tautomatic line splitting\n.TE\n.LP\n.h \"Scanning pattern formation\"\n.TS\naw(.9i)b aw(1.0i).\n^\tbeginning of line\n$\tend of line\n\\fB.\\fR\tany character\n\\e<\tbeginning of word\n\\e>\tend of word\n[\\fIstr\\fP]\tany char in \\fIstr\\fP\n[^\\fIstr\\fP]\t... not in \\fIstr\\fP\n[\\fIx\\-y\\fP]\t... between \\fIx\\fP and \\fIy\\fP\n*\tany number of preceding\n.TE\n.nc\n.LP\n.LG\n.LG\n.B\n.ce\nVi Quick Reference\n.NL\n.R\n.LP\n.LP\n.h \"Entering/leaving vi\"\n.TS\naw(1.4i)b aw(1.8i).\n% vi \\fIname\\fP\tedit \\fIname\\fP at top\n% vi +\\fIn\\fP \\fIname\\fP\t... at line \\fIn\\fP\n% vi + \\fIname\\fP\t... at end\n% vi \\-r\tlist saved files\n% vi \\-r \\fIname\\fP\trecover file \\fIname\\fP\n% vi \\fIname\\fP ...\tedit first; rest via \\fB:n\\fP\n% vi \\-t \\fItag\\fP\tstart at \\fItag\\fP\n% vi +/\\fIpat\\fP \\fIname\\fP\tsearch for \\fIpat\\fP\n% view \\fIname\\fP\tread only mode\nZZ\texit from vi, saving changes\n^Z\tstop vi for later resumption\n.TE\n.h \"The display\"\n.TS\nlw(.75i) lw(2.2i).\nLast line\tT{\nError messages, echoing input to \\fB: / ?\\fP and \\fB!\\fR,\nfeedback about i/o and large changes.\nT}\n@ lines\tOn screen only, not in file.\n~ lines\tLines past end of file.\n^\\fIx\\fP\tControl characters, ^? is delete.\ntabs\tExpand to spaces, cursor at last.\n.TE\n.LP\n.h \"Vi states\"\n.TS\nlw(.75i) lw(2.2i).\nCommand\tT{\nNormal and initial state.  Others return here.\nESC (escape) cancels partial command.\nT}\nInsert\tT{\nEntered by \\fBa i A I o O c C s S\\fP \\fBR\\fP.\nArbitrary text then terminates with ESC character,\nor abnormally with interrupt.\nT}\nLast line\tT{\nReading input for \\fB: / ?\\fP or \\fB!\\fP; terminate\nwith ESC or CR to execute, interrupt to cancel.\nT}\n.TE\n.h \"Counts before vi commands\"\n.TS\nlw(1.5i) lw(1.7i)b.\nline/column number\tz  G  |\nscroll amount\t^D  ^U\nreplicate insert\ta  i  A  I\nrepeat effect\t\\fRmost rest\\fP\n.TE\n.h \"Simple commands\"\n.TS\nlw(1.5i)b lw(1.7i).\ndw\tdelete a word\nde\t... leaving punctuation\ndd\tdelete a line\n3dd\t... 3 lines\ni\\fItext\\fP\\fRESC\\fP\tinsert text \\fIabc\\fP\ncw\\fInew\\fP\\fRESC\\fP\tchange word to \\fInew\\fP\nea\\fIs\\fP\\fRESC\\fP\tpluralize word\nxp\ttranspose characters\n.TE\n.nc\n.h \"Interrupting, cancelling\"\n.TS\naw(0.75i)b aw(1.6i).\nESC\tend insert or incomplete cmd\n^?\t(delete or rubout) interrupts\n^L\treprint screen if \\fB^?\\fR scrambles it\n.TE\n.h \"File manipulation\"\n.TS\naw(0.75i)b aw(1.6i).\n:w\twrite back changes\n:wq\twrite and quit\n:q\tquit\n:q!\tquit, discard changes\n:e \\fIname\\fP\tedit file \\fIname\\fP\n:e!\treedit, discard changes\n:e + \\fIname\\fP\tedit, starting at end\n:e +\\fIn\\fR\tedit starting at line \\fIn\\fR\n:e #\tedit alternate file\n^^\tsynonym for \\fB:e #\\fP\n:w \\fIname\\fP\twrite file \\fIname\\fP\n:w! \\fIname\\fP\toverwrite file \\fIname\\fP\n:sh\trun shell, then return\n:!\\fIcmd\\fP\trun \\fIcmd\\fR, then return\n:n\tedit next file in arglist\n:n \\fIargs\\fP\tspecify new arglist\n:f\tshow current file and line\n^G\tsynonym for \\fB:f\\fP\n:ta \\fItag\\fP\tto tag file entry \\fItag\\fP\n^]\t\\fB:ta\\fP, following word is \\fItag\\fP\n.TE\n.h \"Positioning within file\"\n.TS\naw(0.75i)b aw(1.6i).\n^F\tforward screenfull\n^B\tbackward screenfull\n^D\tscroll down half screen\n^U\tscroll up half screen\nG\tgoto line (end default)\n/\\fIpat\\fR\tnext line matching \\fIpat\\fR\n?\\fIpat\\fR\tprev line matching \\fIpat\\fR\nn\trepeat last \\fB/\\fR or \\fB?\\fR\nN\treverse last \\fB/\\fR or \\fB?\\fR\n/\\fIpat\\fP/+\\fIn\\fP\tn'th line after \\fIpat\\fR\n?\\fIpat\\fP?\\-\\fIn\\fP\tn'th line before \\fIpat\\fR\n]]\tnext section/function\n[[\tprevious section/function\n%\tfind matching \\fB( ) {\\fP or \\fB}\\fP\n.TE\n.h \"Adjusting the screen\"\n.TS\naw(0.75i)b aw(1.6i).\n^L\tclear and redraw\n^R\tretype, eliminate @ lines\nz\\fRCR\\fP\tredraw, current at window top\nz\\-\t... at bottom\nz\\|.\t... at center\n/\\fIpat\\fP/z\\-\t\\fIpat\\fP line at bottom\nz\\fIn\\fP\\|.\tuse \\fIn\\fP line window\n^E\tscroll window down 1 line\n^Y\tscroll window up 1 line\n.TE\n.nc\n.h \"Marking and returning\n.TS\naw(0.5i)b aw(2.0i).\n\\(ga\\(ga\tprevious context\n\\(aa\\(aa\t... at first non-white in line\nm\\fIx\\fP\tmark position with letter \\fIx\\fP\n\\(ga\\fIx\\fP\tto mark \\fIx\\fP\n\\(aa\\fIx\\fP\t... at first non-white in line\n.TE\n.h \"Line positioning\"\n.TS\naw(0.5i)b aw(2.0i).\nH\thome window line\nL\tlast window line\nM\tmiddle window line\n+\tnext line, at first non-white\n\\-\tprevious line, at first non-white\n\\fRCR\\fP\treturn, same as +\n^J \\fRor\\fP j\tnext line, same column\n^ \\fRor\\fP k\tprevious line, same column\n.TE\n.h \"Character positioning\"\n.TS\naw(0.5i)b aw(2.0i).\n^\tfirst non white\n0\tbeginning of line\n$\tend of line\nh \\fRor\\fP \\(->\tforward\nl \\fRor\\fP \\(<-\tbackwards\n^H\tsame as \\fB\\(<-\\fP\n\\fRspace\\fP\tsame as \\fB\\(->\\fP\nf\\fIx\\fP\tfind \\fIx\\fP forward\nF\\fIx\\fP\t\\fBf\\fR backward\nt\\fIx\\fP\tupto \\fIx\\fP forward\nT\\fIx\\fP\tback upto \\fIx\\fP\n;\trepeat last \\fBf F t\\fP or \\fBT\\fP\n,\tinverse of \\fB;\\fP\n|\tto specified column\n%\tfind matching \\fB( { )\\fP or \\fB}\\fR\n.TE\n.h \"Words, sentences, paragraphs\"\n.TS\naw(0.5i)b aw(2.0i).\nw\tword forward\nb\tback word\ne\tend of word\n)\tto next sentence\n}\tto next paragraph\n(\tback sentence\n{\tback paragraph\nW\tblank delimited word\nB\tback \\fBW\\fP\nE\tto end of \\fBW\\fP\n.TE\n.h \"Commands for \\s-2LISP\\s0\"\n.TS\naw(0.5i)b aw(2.0i).\n)\tForward s-expression\n}\t... but don't stop at atoms\n(\tBack s-expression\n{\t... but don't stop at atoms\n.TE\n.nc\n.h \"Corrections during insert\"\n.TS\naw(.5i)b aw(2.0i).\n^H\terase last character\n^W\terases last word\n\\fRerase\\fP\tyour erase, same as \\fB^H\\fP\n\\fRkill\\fP\tyour kill, erase input this line\n\\e\tescapes \\fB^H\\fR, your erase and kill\n\\fRESC\\fP\tends insertion, back to command\n^?\tinterrupt, terminates insert\n^D\tbacktab over \\fIautoindent\\fP\n^^D\tkill \\fIautoindent\\fP, save for next\n0^D\t... but at margin next also\n^V\tquote non-printing character\n.TE\n.h \"Insert and replace\"\n.TS\naw(.5i)b aw(2.0i).\na\tappend after cursor\ni\tinsert before\nA\tappend at end of line\nI\tinsert before first non-blank\no\topen line below\nO\topen above\nr\\fIx\\fP\treplace single char with \\fIx\\fP\nR\treplace characters\n.TE\n.h \"Operators (double to affect lines)\"\n.TS\naw(0.5i)b aw(2.0i).\nd\tdelete\nc\tchange\n<\tleft shift\n>\tright shift\n!\tfilter through command\n\\&=\tindent for \\s-2LISP\\s0\ny\tyank lines to buffer\n.TE\n.h \"Miscellaneous operations\"\n.TS\naw(0.5i)b aw(2.0i).\nC\tchange rest of line\nD\tdelete rest of line\ns\tsubstitute chars\nS\tsubstitute lines\nJ\tjoin lines\nx\tdelete characters\nX\t... before cursor\nY\tyank lines\n.TE\n.h \"Yank and put\"\n.TS\naw(0.5i)b aw(2.0i).\np\tput back lines\nP\tput before\n\"\\fIx\\fPp\tput from buffer \\fIx\\fP\n\"\\fIx\\fPy\tyank to buffer \\fIx\\fP\n\"\\fIx\\fPd\tdelete into buffer \\fIx\\fP\n.TE\n.h \"Undo, redo, retrieve\"\n.TS\naw(0.5i)b aw(2.0i).\nu\tundo last change\nU\trestore current line\n\\fB.\\fP\trepeat last change\n\"\\fId\\fP\\|p\tretrieve \\fId\\fP'th last delete\n.TE\n"
  },
  {
    "path": "docs/ev",
    "content": "Ev:     Vi:     Result:\n<CK>    <CK>    (Cursor keys).  Move around the file.\n\nMeta key commands:\n^A<#>   <#>G    Goto line #.\n^A$      G      Goto the end of the file.\n^A/      /      Prompt and execute a forward search.\n^A:      :      Prompt and execute an ex command.\n^A?      ?      Prompt and execute a backward search.\n^Ac      y'<c>  Copy to mark in line mode (or copy the current line).\n^AC      y`<c>  Copy to mark in character mode.\n^Ad      d'<c>  Delete to mark in line mode (or delete the current line).\n^AD      d`<c>  Delete to mark in character mode.\n^Aj      J      Join lines.\n^Am      m<c>   Mark the current cursor position.\n^AN      N      Repeat search in the reverse direction.\n^An     ^A      Search for the word under the cursor.\n^Ar      u      Redo a command.\n^Au      u      Undo a command.\n\nSingle key commands:\n^B      ^B      Page up a screen.\n^C      ^C      Interrupt long-running commands.\n^D      ^D      Page down a half-screen.\n^E       $      End of line.\n^F      ^F      Page down a screen.\n^G      ^G      File status/information.\n^H       X      Delete the character to the left of the cursor.\n^I (TAB)\n^J       j      Cursor down one line.\n^K       k      Cursor up one line.\n^L      ^L      Redraw the screen.\n^M (CR) ^M      In insert mode, split the line at the current cursor,\n                creating a new line.\n                In overwrite mode, cursor down one line.\n^N       n      Repeat previous search, in previous direction.\n^O (UNUSED)\n^P       p      Paste the cut text at the cursor position.\n^Q (XON/XOFF)\n^R (UNUSED)\n^S (XON/XOFF)\n^T       D      Truncate the line at the cursor position.\n^U      ^U      Page up a half-screen.\n^V<c>   ^V<c>   Insert/overwrite with a literal next character.\n^W       w      Move forward one whitespace separated word.\n^X       x      Delete the current character.\n^Y (UNUSED)\n^Z      ^Z      Suspend.\n\nNew ex mode commands:\n\n^A:set ov[erwrite]      Toggle \"insert\" mode, so that input keys overwrite\n                        the existing characters.\n"
  },
  {
    "path": "docs/ev.license",
    "content": "SPDX-License-Identifier: BSD-3-Clause\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/help",
    "content": "MOVING THE CURSOR:\n k - cursor up    ^F - page forward            /<pattern><CR> - search forward\n j - cursor down  ^B - page backward           ?<pattern><CR> - search backward\n h - cursor left   w - move forward a \"word\"   n - repeat the last search\n l - cursor right  b - move backward a \"word\"\n\nENTERING TEXT:\na - append after the cursor.             Use the <escape> key to return to\ni - insert before the cursor.            command mode.\no - open a new line below the cursor.\nO - open new line above the cursor.\n\nWRITING AND EXITING:\n:w<Enter>  - write the file\n:q<Enter>  - exit the file\n:q!<Enter> - exit without writing the file\n:#<Enter>  - move to a line (e.g., :35<Enter> moves to line 35)\n\nMISCELLANEOUS:\n^G - display the file name\n J - join two lines (use i<Enter><escape> to split a line)\n u - undo the last change (enter . after a 'u' to undo more than one change)\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nVI COMMANDS:\n        ^A search forward for cursor word\n        ^B scroll up by screens\n        ^C interrupt an operation (e.g. read, write, search)\n        ^D scroll down by half screens (setting count)\n        ^E scroll down by lines\n        ^F scroll down by screens\n        ^G file status\n        ^H move left by characters\n        ^J move down by lines\n        ^L redraw screen\n        ^M move down by lines (to first non-blank)\n        ^N move down by lines\n        ^P move up by lines\n        ^R redraw screen\n        ^T tag pop\n        ^U half page up (set count)\n        ^V input a literal character\n        ^W move to next screen\n        ^Y page up by lines\n        ^Z suspend editor\n        ^[ <escape> exit input mode, cancel partial commands\n        ^\\ switch to ex mode\n        ^] tag push cursor word\n        ^^ switch to previous file\n   <space> move right by columns\n         ! filter through command(s) to motion\n         # number increment/decrement\n         $ move to last column\n         % move to match\n         & repeat substitution\n         ' move to mark (to first non-blank)\n         ( move back sentence\n         ) move forward sentence\n         + move down by lines (to first non-blank)\n         , reverse last F, f, T or t search\n         - move up by lines (to first non-blank)\n         . repeat the last command\n         / search forward\n         0 move to first character\n         : ex command\n         ; repeat last F, f, T or t search\n         < shift lines left to motion\n         > shift lines right to motion\n         ? search backward\n         @ execute buffer\n         A append to the line\n         B move back bigword\n         C change to end-of-line\n         D delete to end-of-line\n         E move to end of bigword\n         F character in line backward search\n         G move to line\n         H move to count lines from screen top\n         I insert before first nonblank\n         J join lines\n         L move to screen bottom\n         M move to screen middle\n         N reverse last search\n         O insert above line\n         P insert before cursor from buffer\n         Q switch to ex mode\n         R replace characters\n         S substitute for the line(s)\n         T before character in line backward search\n         U Restore the current line\n         W move to next bigword\n         X delete character before cursor\n         Y copy line\n        ZZ save file and exit\n        [[ move back section\n        ]] move forward section\n         ^ move to first non-blank\n         _ move to first non-blank\n         ` move to mark\n         a append after cursor\n         b move back word\n         c change to motion\n         d delete to motion\n         e move to end of word\n         f character in line forward search\n         h move left by columns\n         i insert before cursor\n         j move down by lines\n         k move up by lines\n         l move right by columns\n         m set mark\n         n repeat last search\n         o append after line\n         p insert after cursor from buffer\n         r replace character\n         s substitute character\n         t before character in line forward search\n         u undo last change\n         w move to next word\n         x delete character\n         y copy text to motion into a cut buffer\n         z re-position the screen\n         { move back paragraph\n         | move to column\n         } move forward paragraph\n         ~ reverse case\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nEX COMMANDS:\n          ^D: scroll lines\n           !: filter lines through commands or run commands\n           #: display numbered lines\n           &: repeat the last substitution\n           *: execute a buffer\n           <: shift lines left\n           =: display line number\n           >: shift lines right\n           @: execute a buffer\n      append: append input to a line\n  abbreviate: specify an input abbreviation\n        args: display file argument list\n          bg: background the current screen\n      change: change lines to input\n          cd: change the current directory\n       chdir: change the current directory\n        copy: copy lines elsewhere in the file\n      delete: delete lines from the file\n     display: display buffers, screens or tags\n     [Ee]dit: begin editing another file\n       [Ee]x: begin editing another file\n     exusage: display ex command usage statement\n        file: display (and optionally set) file name\n          fg: switch the current screen and a backgrounded screen\n      global: execute a global command on lines matching an RE\n        help: display help statement\n      insert: insert input before a line\n        join: join lines into a single line\n           k: mark a line position\n        list: display lines in an unambiguous form\n        move: move lines elsewhere in the file\n        mark: mark a line position\n         map: map input or commands to one or more keys\n      mkexrc: write a .exrc file\n     [Nn]ext: edit (and optionally specify) the next file\n      number: change display to number lines\n        open: enter \"open\" mode (not implemented)\n       print: display lines\n    preserve: preserve an edit session for recovery\n [Pp]revious: edit the previous file in the file argument list\n         put: append a cut buffer to the line\n        quit: exit ex/vi\n        read: append input from a command or file to the line\n     recover: recover a saved file\n      resize: grow or shrink the current screen\n      rewind: re-edit all the files in the file argument list\n           s: substitute on lines matching an RE\n      script: run a shell in a screen\n         set: set options (use \":set all\" to see all options)\n       shell: suspend editing and run a shell\n      source: read a file of ex commands\n        stop: suspend the edit session\n     suspend: suspend the edit session\n           t: copy lines elsewhere in the file\n      [Tt]ag: edit the file containing the tag\n     tagnext: move to the next tag\n      tagpop: return to the previous group of tags\n     tagprev: move to the previous tag\n      tagtop: discard all tags\n        undo: undo the most recent change\nunabbreviate: delete an abbreviation\n       unmap: delete an input or command map\n           v: execute a global command on lines NOT matching an RE\n     version: display the program version information\n      visual: enter visual (vi) mode from ex mode\n   [Vv]isual: edit another file (from vi mode only)\n     viusage: display vi key usage statement\n       write: write the file\n          wn: write the file and switch to the next file\n          wq: write the file and exit\n         xit: exit\n        yank: copy lines to a cut buffer\n           z: display different screens of the file\n           ~: replace previous RE with previous replacement string,\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nEdit options:\nnoaltwerase     noerrorbells    nolist          remap           noterse\nnoautoindent    noexpandtab     lock            report=5        notildeop\nautoprint       noexrc          magic           noruler         timeout\nnoautowrite     noextended      matchtime=7     scroll=21       nottywerase\nbackup=\"\"       filec=\" \"       mesg            nosearchincr    noverbose\nnobeautify      noflash         noprint=\"\"      nosecure        novisibletab\nnobserase       hardtabs=0      nonumber        shiftwidth=8    warn\ncdpath=\":\"      noiclower       nooctal         noshowmatch     window=42\ncedit=\"\"        noignorecase    open            noshowmode      nowindowname\ncolumns=86      noimctrl        path=\"\"         sidescroll=16   wraplen=0\nnocomment       keytime=6       print=\"\"        tabstop=8       wrapmargin=0\nnoedcompatible  noleftright     prompt          taglength=0     wrapscan\nescapetime=2    lines=43        noreadonly      tags=\"tags\"     nowriteany\ndirectory=\"/tmp\"\nimkey=\"/?aioAIO\"\nparagraphs=\"iplpppqpp lipplpipbp\"\nrecdir=\"/var/tmp/vi.recover\"\nsections=\"NHSHH HUnhsh\"\nshell=\"/bin/sh\"\nshellmeta=\"~{[*?$`'\"\\\"\nterm=\"vt100\"\n"
  },
  {
    "path": "docs/help.license",
    "content": "SPDX-License-Identifier: BSD-3-Clause\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/internals/autowrite",
    "content": "Vi autowrite behavior, the fields with *'s are \"don't cares\".\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nCommands that are affected only by autowrite:\n\nCommand File            Autowrite?      Action:\n        modified?\n-----------------------------------------------\n^Z      Y               Y               Write file and suspend.\n^Z      Y               N               Suspend.\n^Z      N               *               Suspend.\n\n# This behavior is NOT identical to :edit.\n^^      Y               Y               Write file and jump.\n^^      Y               N               Error.\n^^      N               *               Jump.\n\n# The new nvi command ^T (:tagpop) behaves identically to ^].\n# This behavior is identical to :tag, :tagpop, and :tagpush with\n# force always set to N.\n^]      Y               Y               Write file and jump.\n^]      Y               N               Error.\n^]      N               *               Jump.\n\n# There's no way to specify a force flag to the '!' command.\n:!      Y               Y               Write file and execute.\n:!      Y               N               Warn (if warn option) and execute.\n:!      N               *               Execute.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nCommands that are affected by both autowrite and force:\n\nNOTE: the \"force\" flag is never passed on, i.e. the write\nto the file caused by the autowrite flag is never forced.\n\nCommand File            Autowrite?      Force?  Action:\n        modified?                       (!)\n-------------------------------------------------------\n# The first rule (YYY) is historic practice, but seems wrong.\n# In nvi, :next and :prev commands behave identically to :rewind.\n:next   Y               Y               Y       Write changes and jump.\n:next   Y               Y               N       Write changes and jump.\n:next   Y               N               Y       Abandon changes and jump.\n:next   Y               N               N       Error.\n:next   N               *               *       Jump.\n\n:rewind Y               Y               Y       Abandon changes and jump.\n:rewind Y               Y               N       Write changes and jump.\n:rewind Y               N               Y       Abandon changes and jump.\n:rewind Y               N               N       Error.\n:rewind N               *               *       Jump.\n\n# The new nvi commands, :tagpop and :tagtop, behave identically to :tag.\n# Note, this behavior is the same as :rewind and friends, as well.\n:tag    Y               Y               Y       Abandon changes and jump.\n:tag    Y               Y               N       Write changes and jump.\n:tag    Y               N               Y       Abandon changes and jump.\n:tag    Y               N               N       Error.\n:tag    N               *               *       Jump.\n\n# The command :suspend behaves identically to :stop.\n:stop   Y               Y               Y       Suspend.\n:stop   Y               Y               N       Write changes and suspend.\n:stop   Y               N               Y       Suspend.\n:stop   Y               N               N       Suspend.\n:stop   N               *               *       Suspend.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nCommands that might be affected by autowrite, but aren't:\n\nCommand File            Autowrite?      Force?  Action:\n        modified?                       (!)\n-------------------------------------------------------\n#:ex, and :vi (executed while in vi mode) behave identically to :edit.\n:edit   Y               *               Y       Abandon changes and jump.\n:edit   Y               *               N       Error.\n:edit   N               *               *       Jump.\n\n:quit   Y               *               Y       Quit.\n:quit   Y               *               N       Error.\n:quit   N               *               *       Quit.\n\n:shell  *               *               *       Execute shell.\n\n:xit    Y               *               *       Write changes and exit.\n:xit    N               *               *       Exit.\n"
  },
  {
    "path": "docs/internals/autowrite.license",
    "content": "# $OpenBSD: autowrite,v 1.3 2001/01/29 01:58:37 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# @(#)autowrite   8.3 (Berkeley) 2/17/95\n\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/internals/context",
    "content": "In historic vi, the previous context mark was always set:\n\nex address:\n    any number, <question-mark>, <slash>, <dollar-sign>,\n    <single-quote>, <backslash>\n\nex commands: undo, \"z.\", global, v\n\nvi commands: (, ), {, }, %, [[, ]], ^]\n\nnvi adds the vi command ^T to this list.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nIn historic vi, the previous context mark was set if the\nline changed:\n\nvi commands: '<mark>, G, H, L, M, z\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nIn historic vi, the previous context mark was set if the\nline or column changed:\n\nvi commands: `<mark>, /, ?, N, n\n\nnvi adds the vi command ^A to this list.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\nIn historic vi, the previous context mark was set in non-visual\nmode for ^R and ^L if the line changed, but I have yet to figure\nout how the line could change.\n"
  },
  {
    "path": "docs/internals/context.license",
    "content": "# $OpenBSD: context,v 1.3 2001/01/29 01:58:37 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# @(#)context     8.6 (Berkeley) 10/14/94\n\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/internals/gdb.script",
    "content": "# $OpenBSD: gdb.script,v 1.3 2001/01/29 01:58:38 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 1996, 1997, 1998, 1999, 2000 Keith Bostic\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n# @(#)gdb.script  8.5 (Berkeley) 5/4/96\n\n# display the VI screen map\n# usage dmap(sp)\ndefine  dmap\n        set $h = ((VI_PRIVATE *)$arg0->vi_private)->h_smap\n        set $t = ((VI_PRIVATE *)$arg0->vi_private)->t_smap\n        while ($h <= $t)\n                printf \"lno: %2d; soff %d coff %d \", \\\n                    (int)$h->lno, (int)$h->soff, (int)$h->coff\n                if ($h->c_ecsize == 0)\n                        printf \"flushed\\n\"\n                else\n                        printf \"\\n\\tsboff %d; scoff %d\\n\", \\\n                            (int)$h->c_sboff, (int)$h->c_scoff\n                        printf \"\\teboff %d; eclen %d; ecsize %d\\n\", \\\n                            (int)$h->c_eboff, (int)$h->c_eclen, \\\n                            (int)$h->c_ecsize\n                end\n                set $h = $h + 1\n        end\nend\n\n# display the tail of the VI screen map\ndefine  tmap\n        set $h = ((VI_PRIVATE *)$arg0->vi_private)->h_smap\n        set $t = ((VI_PRIVATE *)$arg0->vi_private)->t_smap\n        while ($t >= $h)\n                printf \"lno: %2d; soff %d coff %d \", \\\n                    (int)$t->lno, (int)$t->soff, (int)$t->coff\n                if ($t->c_ecsize == 0)\n                        printf \"flushed\\n\"\n                else\n                        printf \"\\n\\tsboff %d; scoff %d\\n\", \\\n                            (int)$t->c_sboff, (int)$t->c_scoff\n                        printf \"\\teboff %d; eclen %d; ecsize %d\\n\", \\\n                            (int)$t->c_eboff, (int)$t->c_eclen, \\\n                            (int)$t->c_ecsize\n                end\n                set $t = $t - 1\n        end\nend\n\n# display the private structures\ndefine  clp\n        print *((CL_PRIVATE *)sp->gp->cl_private)\nend\ndefine  vip\n        print *((VI_PRIVATE *)sp->vi_private)\nend\ndefine  exp\n        print *((EX_PRIVATE *)sp->ex_private)\nend\n\n# display the marks\ndefine  markp\n        set $h = sp->ep->marks.next\n        set $t = &sp->ep->marks\n        while ($h != 0 && $h != $t)\n                printf \"key %c lno: %d cno: %d flags: %x\\n\", \\\n                    ((MARK *)$h)->name, ((MARK *)$h)->lno, \\\n                    ((MARK *)$h)->cno, ((MARK *)$h)->flags\n                set $h = ((MARK *)$h)->next\n        end\nend\n\n# display the tags\ndefine  tagp\n        set $h = sp->taghdr.next\n        set $t = &sp->taghdr\n        while ($h != 0 && $h != $t)\n                printf \"tag: %s lno %d cno %d\\n\", ((TAG *)$h)->frp->fname, \\\n                    ((TAG *)$h)->lno, ((TAG *)$h)->cno\n                set $h= ((TAG *)$h)->next\n        end\nend\n"
  },
  {
    "path": "docs/internals/input",
    "content": "MAPS, EXECUTABLE BUFFERS AND INPUT IN EX/VI:\n\nThe basic rule is that input in ex/vi is a stack.  Every time a key which\ngets expanded is encountered, it is expanded and the expansion is treated\nas if it were input from the user.  So, maps and executable buffers are\nsimply pushed onto the stack from which keys are returned.  The exception\nis that if the \"remap\" option is turned off, only a single map expansion\nis done.  I intend to be fully backward compatible with this.\n\nHistorically, if the mode of the editor changed (ex to vi or vice versa),\nany queued input was silently discarded.  I don't see any reason to either\nsupport or not support this semantic.  I intend to retain the queued input,\nmostly because it's simpler than throwing it away.\n\nHistorically, neither the initial command on the command line (the + flag)\nor the +cmd associated with the ex and edit commands was subject to mapping.\nAlso, while the +cmd appears to be subject to \"@buffer\" expansion, once\nexpanded it doesn't appear to work correctly.  I don't see any reason to\neither support or not support these semantics, so, for consistency, I intend\nto pass both the initial command and the command associated with ex and edit\ncommands through the standard mapping and @ buffer expansion.\n\nOne other difference between the historic ex/vi and nex/nvi is that nex\ndisplays the executed buffers as it executes them.  This means that if\nthe file is:\n\n        set term=xterm\n        set term=yterm\n        set term=yterm\n\nthe user will see the following during a typical edit session:\n\n        nex testfile\n        testfile: unmodified: line 3\n        :1,$yank a\n        :@a\n        :set term=zterm\n        :set term=yterm\n        :set term=xterm\n        :q!\n\nThis seems like a feature and unlikely to break anything, so I don't\nintend to match historic practice in this area.\n\nThe rest of this document is a set of conclusions as to how I believe\nthe historic maps and @ buffers work.  The summary is as follows:\n\n1: For buffers that are cut in \"line mode\", or buffers that are not cut\n   in line mode but which contain portions of more than a single line, a\n   trailing <newline> character appears in the input for each line in the\n   buffer when it is executed.  For buffers not cut in line mode and which\n   contain portions of only a single line, no additional characters\n   appear in the input.\n\n2: Executable buffers that execute other buffers don't load their\n   contents until they execute them.\n\n3: Maps and executable buffers are copied when they are executed --\n   they can be modified by the command but that does not change their\n   actions.\n\n4: Historically, executable buffers are discarded if the editor\n   switches between ex and vi modes.\n\n5: Executable buffers inside of map commands are expanded normally.\n   Maps inside of executable buffers are expanded normally.\n\n6: If an error is encountered while executing a mapped command or buffer,\n   the rest of the mapped command/buffer is discarded.  No user input\n   characters are discarded.\n\n7: Characters in executable buffers are remapped.\n\n8: Characters in executable buffers are not quoted.\n\nIndividual test cases follow.  Note, in the test cases, control characters\nare not literal and will have to be replaced to make the test cases work.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n1: For buffers that are cut in \"line mode\", or buffers that are not cut\n   in line mode but which contain portions of more than a single line, a\n   trailing <newline> character appears in the input for each line in the\n   buffer when it is executed.  For buffers not cut in line mode and which\n   contain portions of only a single line, no additional characters\n   appear in the input.\n\n===   test file   ===\n3Gw\nw\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\n=== end test file ===\n\n   If the first line is loaded into 'a' and executed:\n\n1G\"ayy@a\n\n   The cursor ends up on the '2', a result of pushing \"3Gw^J\" onto\n   the stack.\n\n   If the first two lines are loaded into 'a' and executed:\n\n1G2\"ayy@a\n\n   The cursor ends up on the 'f' in \"foo\" in the fifth line of the\n   file, a result of pushing \"3Gw^Jw^J\" onto the stack.\n\n   If the first line is loaded into 'a', but not using line mode,\n   and executed:\n\n1G\"ay$@a\n\n   The cursor ends up on the '1', a result of pushing \"3Gw\" onto\n   the stack\n\n   If the first two lines are loaded into 'a', but not using line mode,\n   and executed:\n\n1G2\"ay$@a\n\n   The cursor ends up on the 'f' in \"foo\" in the fifth line of the\n   file, a result of pushing \"3Gw^Jw^J\" onto the stack.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n2: Executable buffers that execute other buffers don't load their\n   contents until they execute them.\n\n===   test file   ===\ncwLOAD B^[\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\n@a@b\n\"byy\n=== end test file ===\n\n   The command is loaded into 'e', and then executed.  'e' executes\n   'a', which loads 'b', then 'e' executes 'b'.\n\n5G\"eyy6G\"ayy1G@e\n\n   The output should be:\n\n===   output file   ===\ncwLOAD B^[\nLOAD B 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\n@a@b\n\"byy\n=== end output file ===\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n3: Maps and executable buffers are copied when they are executed --\n   they can be modified by the command but that does not change their\n   actions.\n\n   Executable buffers:\n\n===   test file   ===\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\n@a@b\n\"eyy\ncwEXECUTE B^[\n=== end test file ===\n\n4G\"eyy5G\"ayy6G\"byy1G@eG\"ep\n\n   The command is loaded into 'e', and then executed.  'e' executes\n   'a', which loads 'e', then 'e' executes 'b' anyway.\n\n   The output should be:\n\n===   output file   ===\nline 1 foo bar baz\nEXECUTE B 2 foo bar baz\nline 3 foo bar baz\n@a@b\n\"eyy\ncwEXECUTE B^[\nline 1 foo bar baz\n=== end output file ===\n\n   Maps:\n\n===   test file   ===\nCine 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\n=== end test file ===\n\n   Entering the command ':map = :map = rB^V^MrA^M1G==' shows that\n   the first time the '=' is entered the '=' map is set and the\n   character is changed to 'A', the second time the character is\n   changed to 'B'.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n4: Historically, executable buffers are discarded if the editor\n   switches between ex and vi modes.\n\n===   test file   ===\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\ncwCHANGE^[Q:set\nset|visual|1Gwww\n=== end test file ===\n\nvi testfile\n4G\"ayy@a\n\nex testfile\n$p\nyank a\n@a\n\n   In vi, the command is loaded into 'a' and then executed.  The command\n   subsequent to the 'Q' is (historically, silently) discarded.\n\n   In ex, the command is loaded into 'a' and then executed.  The command\n   subsequent to the 'visual' is (historically, silently) discarded.  The\n   first set command is output by ex, although refreshing the screen usually\n   causes it not to be seen.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n5: Executable buffers inside of map commands are expanded normally.\n   Maps inside of executable buffers are expanded normally.\n\n   Buffers inside of map commands:\n\n===   test file   ===\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\ncwREPLACE BY A^[\n=== end test file ===\n\n4G\"ay$:map x @a\n1Gx\n\n   The output should be:\n\n===   output file   ===\nREPLACE BY A 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\ncwREPLACE BY A^[\n=== end output file ===\n\n   Maps commands inside of executable buffers:\n\n===   test file   ===\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\nX\n=== end test file ===\n\n:map X cwREPLACE BY XMAP^[\n4G\"ay$1G@a\n\n   The output should be:\n\n===   output file   ===\nREPLACE BY XMAP 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\nX\n=== end output file ===\n\n   Here's a test that does both, repeatedly.\n\n===   test file   ===\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\nX\nY\ncwREPLACED BY C^[\nblank line\n=== end test file ===\n\n:map x @a\n4G\"ay$\n:map X @b\n5G\"by$\n:map Y @c\n6G\"cy$\n1Gx\n\n   The output should be:\n\n===   output file   ===\nREPLACED BY C 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\nX\nY\ncwREPLACED BY C^[\nblank line\n=== end output file ===\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n6: If an error is encountered while executing a mapped command or\n   a buffer, the rest of the mapped command/buffer is discarded.  No\n   user input characters are discarded.\n\n===   test file   ===\nline 1 foo bar baz\nline 2 foo bar baz\nline 3 foo bar baz\n:map = 10GcwREPLACMENT^V^[^[\n=== end test file ===\n\n   The above mapping fails, however, if the 10G is changed to 1, 2,\n   or 3G, it will succeed.\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n7: Characters in executable buffers are remapped.\n\n===   test file   ===\nabcdefghijklmnnop\nggg\n=== end test file ===\n\n:map g x\n2G\"ay$1G@a\n\n   The output should be:\n\n===   output file   ===\ndefghijklmnnop\nggg\n=== end output file ===\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n8: Characters in executable buffers are not quoted.\n\n===   test file   ===\niFOO^[\n\n=== end test file ===\n\n1G\"ay$2G@a\n\n   The output should be:\n\n===   output file   ===\niFOO^[\nFOO\n=== end output file ===\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"
  },
  {
    "path": "docs/internals/input.license",
    "content": "# $OpenBSD: input,v 1.2 2001/01/29 01:58:38 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# @(#)input       5.5 (Berkeley) 7/2/94\n\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/internals/openmode",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# @(#)openmode    8.1 (Berkeley) 10/29/94\n\nOpen mode has the following special behaviors:\n\nz, ^F, ^B:\n        If count is not specified, it shall default to the window\n        edit option - 2.\n\n        Write lines from the edit buffer starting at:\n\n                (the current line) - ((count - 2) / 2)\n\n        until:\n\n                (((count + 1) / 2) * 2) - 1\n\n        lines, or the last line in the edit buffer has been written.  A\n        line consisting of the smaller of the number of columns in the\n        display divided by two or 40 ``-'' characters shall be written\n        immediately before and after the specified is written.  These two\n        lines shall count against the total number of lines to be written.\n        A blank line shall be written after the last line is written.\n\n        z, ^F and ^B all behave identically.\n\n^D:     Display the next scroll value lines, change the current line.\n\n^U:     Change the current line, do nothing else.\n\n^E, ^Y: Do nothing.\n\n^L:     Clear the screen and re-display the current line.\n\nH, L, M:\n        Move to the first nonblank of the current line and do nothing\n        else.\n"
  },
  {
    "path": "docs/internals/openmode.license",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# @(#)openmode    8.1 (Berkeley) 10/29/94\n\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/internals/quoting",
    "content": "QUOTING IN EX/VI:\n\nThere are four escape characters in historic ex/vi:\n\n        \\ (backslashes)\n        ^V\n        ^Q (assuming it wasn't used for IXON/IXOFF)\n        The terminal literal next character.\n\nVi did not use the lnext character, it always used ^V (or ^Q).\n^V and ^Q were equivalent in all cases for vi.\n\nThere are four different areas in ex/vi where escaping characters\nis interesting:\n\n        1: In vi text input mode.\n        2: In vi command mode.\n        3: In ex command and text input modes.\n        4: In the ex commands themselves.\n\n1: Vi text input mode (a, i, o, :colon commands, etc.):\n\n   The set of characters that users might want to escape are as follows:\n\n   As ^L and ^Z were not special in input mode, they are not listed.\n\n        carriage return (^M)\n        escape          (^[)\n        autoindents     (^D, 0, ^, ^T)\n        erase           (^H)\n        word erase      (^W)\n        line erase      (^U)\n        newline         (^J)            (not historic practice)\n\n   Historic practice was that ^V was the only way to escape any\n   of these characters, and that whatever character followed\n   the ^V was taken literally, e.g. ^V^V is a single ^V.  I\n   don't see any strong reason to make it possible to escape\n   ^J, so I'm going to leave that alone.\n\n   One comment regarding the autoindent characters.  In historic\n   vi, if you entered \"^V0^D\" autoindent erasure was still\n   triggered, although it wasn't if you entered \"0^V^D\".  In\n   nvi, if you escape either character, autoindent erasure is\n   not triggered.\n\n   Abbreviations were not performed if the non-word character\n   that triggered the abbreviation was escaped by a ^V.  Input\n   maps were not triggered if any part of the map was escaped\n   by a ^V.\n\n   The historic vi implementation for the 'r' command requires\n   two leading ^V's to replace a character with a literal\n   character.  This is obviously a bug, and should be fixed.\n\n2: Vi command mode\n\n   Command maps were not triggered if the second or later\n   character of a map was escaped by a ^V.\n\n   The obvious extension is that ^V should keep the next command\n   character from being mapped, so you can do \":map x xxx\" and\n   then enter ^Vx to delete a single character.\n\n3: Ex command and text input modes.\n\n   As ex ran in canonical mode, there was little work that it\n   needed to do for quoting.  The notable differences between\n   ex and vi are that it was possible to escape a <newline> in\n   the ex command and text input modes, and ex used the \"literal\n   next\" character, not control-V/control-Q.\n\n4: The ex commands:\n\n   Ex commands are delimited by '|' or newline characters.\n   Within the commands, whitespace characters delimit the\n   arguments.  Backslash will generally escape any following\n   character.  In the abbreviate, unabbreviate, map and unmap\n   commands, control-V escapes the next character, instead.\n\n   This is historic behavior in vi, although there are special\n   cases where it's impossible to escape a character, generally\n   a whitespace character.\n\n   Escaping characters in file names in ex commands:\n\n        :cd [directory]                         (directory)\n        :chdir [directory]                      (directory)\n        :edit [+cmd] [file]                     (file)\n        :ex [+cmd] [file]                       (file)\n        :file [file]                            (file)\n        :next [file ...]                        (file ...)\n        :read [!cmd | file]                     (file)\n        :source [file]                          (file)\n        :write [!cmd | file]                    (file)\n        :wq [file]                              (file)\n        :xit [file]                             (file)\n\n   Since file names are also subject to word expansion, the\n   underlying shell had better be doing the correct backslash\n   escaping.  This is NOT historic behavior in vi, making it\n   impossible to insert a whitespace, newline or carriage return\n   character into a file name.\n\n4: Escaping characters in non-file arguments in ex commands:\n\n        :abbreviate word string                 (word, string)\n*       :edit [+cmd] [file]                     (+cmd)\n*       :ex [+cmd] [file]                       (+cmd)\n        :map word string                        (word, string)\n*       :set [option ...]                       (option)\n*       :tag string                             (string)\n        :unabbreviate word                      (word)\n        :unmap word                             (word)\n\n   These commands use whitespace to delimit their arguments, and use\n   ^V to escape those characters.  The exceptions are starred in the\n   above list, and are discussed below.\n\n   In general, I intend to treat a ^V in any argument, followed by\n   any character, as that literal character.  This will permit\n   editing of files name \"foo|\", for example, by using the string\n   \"foo\\^V|\", where the literal next character protects the pipe\n   from the ex command parser and the backslash protects it from the\n   shell expansion.\n\n   This is backward compatible with historical vi, although there\n   were a number of special cases where vi wasn't consistent.\n\n4.1: The edit/ex commands:\n\n   The edit/ex commands are a special case because | symbols may\n   occur in the \"+cmd\" field, for example:\n\n        :edit +10|s/abc/ABC/ file.c\n\n   In addition, the edit and ex commands have historically\n   ignored literal next characters in the +cmd string, so that\n   the following command won't work.\n\n        :edit +10|s/X/^V / file.c\n\n   I intend to handle the literal next character in edit/ex consistently\n   with how it is handled in other commands.\n\n   More fun facts to know and tell:\n        The acid test for the ex/edit commands:\n\n                date > file1; date > file2\n                vi\n                :edit +1|s/./XXX/|w file1| e file2|1 | s/./XXX/|wq\n\n        No version of vi, of which I'm aware, handles it.\n\n4.2: The set command:\n\n   The set command treats ^V's as literal characters, so the\n   following command won't work.  Backslashes do work in this\n   case, though, so the second version of the command does work.\n\n        set tags=tags_file1^V tags_file2\n        set tags=tags_file1\\ tags_file2\n\n   I intend to continue permitting backslashes in set commands,\n   but to also permit literal next characters to work as well.\n   This is backward compatible, but will also make set\n   consistent with the other commands.  I think it's unlikely\n   to break any historic .exrc's, given that there are probably\n   very few files with ^V's in their name.\n\n4.3: The tag command:\n\n   The tag command ignores ^V's and backslashes; there's no way to\n   get a space into a tag name.\n\n   I think this is a don't care, and I don't intend to fix it.\n\n5: Regular expressions:\n\n        :global /pattern/ command\n        :substitute /pattern/replace/\n        :vglobal /pattern/ command\n\n   I intend to treat a backslash in the pattern, followed by the\n   delimiter character or a backslash, as that literal character.\n\n   This is historic behavior in vi.  It would get rid of a fairly\n   hard-to-explain special case if we could just use the character\n   immediately following the backslash in all cases, or, if we\n   changed nvi to permit using the literal next character as a\n   pattern escape character, but that would probably break historic\n   scripts.\n\n   There is an additional escaping issue for regular expressions.\n   Within the pattern and replacement, the '|' character did not\n   delimit ex commands.  For example, the following is legal.\n\n        :substitute /|/PIPE/|s/P/XXX/\n\n   This is a special case that I will support.\n\n6: Ending anything with an escape character:\n\n   In all of the above rules, an escape character (either ^V or a\n   backslash) at the end of an argument or file name is not handled\n   specially, but used as a literal character.\n"
  },
  {
    "path": "docs/internals/quoting.license",
    "content": "# $OpenBSD: quoting,v 1.3 2001/01/29 01:58:39 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# @(#)quoting     5.5 (Berkeley) 11/12/94\n\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/internals/structures",
    "content": "There are three major data structures in this package, plus a single data\nstructure per screen type.  The first is a single global structure (GS)\nwhich contains information common to all files and screens.  It hold\nglobal things like the input key queues, and functions as a single place\nto hang things.  For example, interrupt routines have to be able to find\nscreen structures, and they can only do this if they have a starting\npoint.  The number of globals in nvi is dependent on the screen type, but\nevery screen type will have at least one global, __global_list, which\nreferences the GS structure.\n\nThe GS structure contains linked lists of screen (SCR) structures.\nEach SCR structure normally references a file (EXF) structure.\n\nThe GS structure has a set of functions which update the screen and/or\nreturn information about the screen from the underlying screen package.\nThe GS structure never goes away.  The SCR structure persists over\ninstances of screens, and the EXF structure persists over references to\nfiles.\n\nFile names have different properties than files themselves, so the name\ninformation for a file is held in an FREF structure which is chained from\nthe SCR structure.\n\nIn general, functions are always passed an SCR structure, which usually\nreferences an underlying EXF structure.  The SCR structure is necessary\nfor any routine that wishes to talk to the screen, the EXF structure is\nnecessary for any routine that wants to modify the file.  The relationship\nbetween an SCR structure and its underlying EXF structure is not fixed,\nand various ex commands will substitute a new EXF in place of the current\none, and there's no way to detect this.\n\nThe naming of the structures is consistent across the program.  (Macros\neven depend on it, so don't try and change it!)  The global structure is\n\"gp\", the screen structure is \"sp\", and the file structure is \"ep\".\n\nA few other data structures:\n\nTEXT    In nvi/cut.h.  This structure describes a portion of a line,\n        and is used by the input routines and as the \"line\" part of a\n        cut buffer.\n\nCB      In nvi/cut.h.   A cut buffer.  A cut buffer is a place to\n        hang a list of TEXT structures.\n\nCL      The curses screen private data structure.  Everything to\n        do standalone curses screens.\n\nMARK    In nvi/mark.h.  A cursor position, consisting of a line number\n        and a column number.\n\nMSG     In nvi/msg.h.  A chain of messages for the user.\n\nSEQ     In nvi/seq.h.  An abbreviation or a map entry.\n\nEXCMD   In nvi/ex/ex.h.  The structure that gets passed around to the\n        functions that implement the ex commands.  (The main ex command\n        loop (see nvi/ex/ex.c) builds this up and then passes it to the\n        ex functions.)\n\nVICMD   In nvi/vi/vi.h.  The structure that gets passed around to the\n        functions that implement the vi commands.  (The main vi command\n        loop (see nvi/vi/vi.c) builds this up and then passes it to the\n        vi functions.)\n"
  },
  {
    "path": "docs/internals/structures.license",
    "content": "# $OpenBSD: structures,v 1.3 2001/01/29 01:58:39 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# @(#)structures  5.4 (Berkeley) 10/4/95\n\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/tutorial/vi.advanced",
    "content": "Section 26: Index to the rest of the tutorial\n\nThe remainder of the tutorial can be perused at your leisure.  Simply find the\ntopic of interest in the following list, and {/Section xx:/^M} to get to the\nappropriate section.  (Remember that ^M means the return key)\n\nThe material in the following sections is not necessarily in a bottom up\norder.  It should be fairly obvious that if a section mentions something with\nwhich you are not familiar, say, buffers, you might {/buffer/^M} followed by\nseveral {n} to do a keyword search of the file for more details on that item.\nAnother point to remember is that commands are surrounded by curly-braces and\ncan therefore be found rather easily.  To see where, say, the X command is\nused try {/{X}/^M}.  Subsequent {n} will show you other places the command was\nused.  We have tried to maintain the convention of placing the command letter\nsurrounded by curly-braces on the section line where that command is\nmentioned.\n\nFinally, you should have enough 'savvy' at this point to be able to do your\nown experimentation with commands without too much hand-holding on the part of\nthe tutorial.  Experimentation is the best way to learn the effects of the\ncommands.\n\n Section      Topic - description\n -------      -------------------\n(Sections 1 through 25 are located in the file vi.beginner.)\n    1         introduction: {^F} {ZZ}\n    2         introduction (cont'd) and positioning: {^F} {^B}\n    3         introduction (cont'd) and positioning: {^F} {^B}\n    4         positioning: {^F} {^B} {^M} (return key)\n    5         quitting: {:q!} {^M} (return key)\n    6         marking, cursor and screen positioning: {m} {G} {'} {z}\n    7         marking, cursor and screen positioning: {m} {G} {'} {z}\n    8         marking, cursor and screen positioning: {z} {m} {'}\n    9         marking and positioning: {m} {''}\n   10         line positioning: {^M} {-}\n   11         scrolling with {^M}\n   12         scrolling with {-} and screen adjustment {z}\n   13         notes on use of tutorial\n   14         other scrolling and positioning commands: {^E} {^Y} {^D} {^U}\n   15         searching: {/ .. /^M}\n   16         searching: {? .. ?^M} {n} (in search strings ^ $)\n   17         searching: \\ and magic-characters in search strings\n   18         colon commands, exiting: {:} {ZZ}\n   19         screen positioning: {H} {M} {L}\n   20         character positioning: {w} {b} {0} {W} {B} {e} {E} {'} {`}\n   21         cursor positioning: {l} {k} {j} {h}\n   22         adding text: {i} {a} {I} {A} {o} {O} {^[} (escape key)\n   23         character manipulation: {f} {x} {X} {w} {l} {r} {R} {s} {S} {J}\n   24         undo: {u} {U}\n   25         review\n(The following sections are in this file.)\n   26         Index to the rest of the tutorial ******** YOU ARE HERE *******\n   27         discussion of repeat counts and the repeat command: {.}\n   28         more on low-level character motions: {t} {T} {|}\n   29         advanced correction operators: {d} {c}\n   30         updating the screen: {^R}\n   31         text buffers: {\"}\n   32         rearranging and duplicating text: {p} {P} {y} {Y}\n   33         recovering lost lines\n   34         advanced file manipulation with vi\n   34.1          more than one file at a time: {:n}\n   34.2          reading files and command output: {:r}\n   34.3          invoking vi from within vi: {:e} {:vi}\n   34.4          escaping to a shell: {:sh} {:!}\n   34.5          writing parts of a file: {:w}\n   34.6          filtering portions of text: {!}\n   35         advanced searching: magic patterns\n   36         advanced substitution: {:s}\n   37         advanced line addressing: {:p} {:g} {:v}\n   38         higher level text objects and nroff: ( ) { } [[ ]]\n   39         more about inserting text\n   40         more on operators: {d} {c} {<} {>} {!} {=} {y}\n   41         abbreviations: {:ab}\n   42         vi's relationship with the ex editor: {:}\n   43         vi on hard-copy terminals and dumb terminals: open mode\n   44         options: {:set} {setenv EXINIT}\n   44.1          autoindent\n   44.2          autoprint\n   44.3          autowrite\n   44.4          beautify\n   44.5          directory\n   44.6          edcompatible\n   44.7          errorbells\n   44.8          hardtabs\n   44.9          ignorecase\n   44.10         lisp\n   44.11         list\n   44.12         magic\n   44.13         mesg\n   44.14         number\n   44.15         open\n   44.16         optimize\n   44.17         paragraphs\n   44.18         prompt\n   44.19         readonly\n   44.20         redraw\n   44.21         remap\n   44.22         report\n   44.23         scroll\n   44.24         sections\n   44.25         shell\n   44.26         shiftwidth\n   44.27         showmatch\n   44.28         slowopen\n   44.29         tabstop\n   44.30         tags\n   44.31         taglength\n   44.32         term\n   44.33         terse\n   44.34         timeout\n   44.35         ttytype\n   44.36         warn\n   44.37         window\n   44.38         wrapscan\n   44.39         wrapmargin\n   44.40         writeany\n   44.41         w300, w1200, w9600\n\nSection 27: repetition counts and the repeat command {.}\n\nMost vi commands will use a preceding count to affect their behavior in some\nway.  We have already seen how {3x} deletes three characters, and {22G} moves\nus to line 22 of the file.  For almost all of the commands, one can survive by\nthinking of these leading numbers as a 'repeat count' specifying that the\ncommand is to be repeated so many number of times.\n\nOther commands use the repeat count slightly differently, like the {G} command\nwhich use it as a line number.\n\nFor example:\n\n{3^D} means scroll down in the file three lines.  Subsequent {^D} OR {^U} will\nscroll only three lines in their respective directions!\n\n{3z^M} says put line three of the file at the top of the screen, while {3z.}\nsays put line three as close to the middle of the screen as possible.\n\n{50|} moves the cursor to column fifty in the current line.\n\n{3^F} says move forward 3 screen-fulls.  This is a repetition count.  The\ndocuments advertise that {3^B} should move BACK three screen-fulls, but I\ncan't get it to work.\n\nPosition the cursor on some text and try {3r.}.  This replaces three characters\nwith '...'.  However, {3s.....^[} is the same as {3xi.....^[}.\n\nTry {10a+----^[}.\n\nA very useful instance of a repetition count is one given to the '.' command,\nwhich repeats the last 'change' command.  If you {dw} and then {3.}, you will\ndelete first one and then three words.  You can then delete two more words with\n{2.}.  If you {3dw}, you will delete three words.  A subsequent {.} will delete\nthree more words.  But a subsequent {2.} will delete only two words, not three\ntimes two words.\n\nCaveat: The author has noticed that any repetition count with {^B} will NOT\nwork: indeed, if you are at the end of your file and try {3^B} sufficiently\noften, the editor will hang you in an infinite loop.  Please don't try it:\ntake my word for it.\n\nSection 28: {t} {T} {|}\n\nPosition the cursor on line 13 below:\n\nLine 13: Four score and seven years ago, our forefathers brought ...\n\nNote that {fv} moves the cursor on/over the 'v' in 'seven'.  Do a {0} to return\nto the beginning of the line and try a {tv}.  The cursor is now on/over the\nfirst 'e' in 'seven'.  The {f} command finds the next occurrence of the\nspecified letter and moves the cursor to it.  The {t} command finds the\nspecified letter and moves the cursor to the character immediately preceding\nit.  {T} searches backwards, as does {F}.\n\nNow try {60|}: the cursor is now on the 'o' in 'brought', which is the\nsixtieth character on the line.\n\nSection 29: {d} {c}\n\nDue to their complexity we have delayed discussion of two of the most powerful\noperators in vi until now.  Effective use of these operators requires more\nexplanation than was deemed appropriate for the first half of the tutorial.\n\n{d} and {c} are called operators instead of commands because they consist of\nthree parts: a count specification or a buffer specification (see section\n#BUFFERS), the {d} or {c}, and the object or range description.  We will not\ndiscuss buffers at this stage, but will limit ourselves to count\nspecifications.  Examples speak louder than words: position the cursor at the\nbeginning of line 14:\n\nLine 14: Euclid alone has looked on beauty bear.\n\nObviously, there is something wrong with this quotation.  Type {2fb} to\nposition the cursor on the 'b' of 'bear'.  Now, type {cwbare^[}\nand observe the results.  The {cw} specifies that the change command {c} is to\noperate on a word object.  More accurately, it specifies that the range of the\nchange command includes the next word.\n\nPosition the cursor on the period in Line 14. (one way is to use {f.})\nNow, type {cbbeast^[}.  This specifies the range of the change command to be the\nprevious word (the 'b' reminiscent of the {b} command).  If we had wished to\ndelete the word rather than change it, we would have used the {d} operator,\nrather than the {c} operator.\n\nPosition the cursor at the beginning of the line with {0}.  Type\n{d/look/^M}.  The search string specified the range of the delete.\nEverything UP TO the word 'looking' was deleted from the line.\n\nIn general, almost any command that would move the cursor will specify a range\nfor these commands.  The most confusing exception to this rule is when {dd} or\n{cc} is entered: they refer to the whole line.  Following is a summary of the\nsuffixes (suffices? suffici?) and the ranges they specify:\n\n    suffix        will delete{d}/change{c}\n    ------        ------------------------\n      ^[            cancels the command\n      w             the word to the right of the cursor\n      W             ditto, but ignoring punctuation\n      b             the word to the left of the cursor\n      B             ditto, but ignoring punctuation\n      e             see below.\n      E               ditto\n      (space)       a character\n      $             to the end of the line\n      ^             to the beginning of the line\n      / .. /        up to, but not including, the string\n      ? .. ?        back to and including the string\n      fc            up to and including the occurrence of c\n      Fc            back to and including the occurrence of c\n      tc            up to but not including the occurrence of c\n      Tc            back to but not including the occurrence of c\n      ^M            TWO lines (that's right: two)\n      (number)^M    that many lines plus one\n      (number)G     up to and including line (number)\n      (             the previous sentence if you are at the beginning of\n                    the current sentence, or the current sentence up to where\n                    you are if you are not at the beginning of the current\n                    sentence.  Here, 'sentence' refers to the intuitive\n                    notion of an English sentence, ending with '!', '?',\n                    or '.' and followed by an end of line or two spaces.\n      )             the rest of the current sentence\n      {             analogous to '(', but in reference to paragraphs:\n                    sections of text surrounded by blank lines\n      }             analogous to ')', but in reference to paragraphs\n      [[            analogous to '(', but in reference to sections\n      ]]            analogous to ')', but in reference to sections\n      H             the first line on the screen\n      M             the middle line on the screen\n      L             the last line on the screen\n      3L            through the third line from the bottom of the screen\n      ^F            forward a screenful\n      ^B            backward a screenful\n      :\n      :  etc. etc. etc.\n\nThis list is not exhaustive, but it should be sufficient to get the idea\nacross: after the {c} or {d} operator, you can specify a range with another\nmove-the-cursor command, and that is the region of text over which the command\nwill be effective.\n\nSection 30: updating the screen {^R}\n\nVi tries to be very intelligent about the type of terminal you are working on\nand tries to use the in-terminal computing power (if any) of your terminal.\nAlso if the terminal is running at a low baud rate (say 1200 or below), vi sets\nvarious parameters to make things easier for you.  For example, if you were\nrunning on a 300 baud terminal (that's 30 characters per second transmission\nrate) not all 24 lines of the screen would be used by vi.  In addition, there\nis a large portion of the editor keeping track of what your screen currently\nlooks like, and what it would look like after a command has been executed.  Vi\nthen compares the two, and updates only those portions of the screen that have\nchanged.\n\nFurthermore, some of you may have noticed (it depends on your terminal) that\ndeleting lines or changing large portions of text may leave some lines on the\nscreen looking like:\n@\nmeaning that this line of the screen does not correspond to any line in your\nfile. It would cost more to update the line than to leave it blank for the\nmoment.  If you would like to see your screen fully up-to-date with the\ncontents of your file, type {^R}.\n\nTo see it in action, delete several lines with {5dd}, type {^R}, and then type\n{u} to get the lines back.\n\nHere is as good a place as any to mention that if the editor is displaying the\nend of your file, there may be lines on the screen that look like:\n~\nindicating that that screen line would not be affected by {^R}.  These lines\nsimply indicate the end of the file.\n\nSection 31: text buffers {\"}\n\nVi gives you the ability to store text away in \"buffers\".  This feature is very\nconvenient for moving text around in your file.  There are a total of thirty-\nfive buffers available in vi.  There is the \"unnamed\" buffer that is used by all\ncommands that delete text, including the change operator {c}, the substitute\nand replace commands {s} and {r}, as well as the delete operator {d} and delete\ncommands {x} and {X}.  This buffer is filled each time any of these commands\nare used. However, the undo command {u} has no effect on the unnamed buffer.\n\nThere are twenty-six buffers named 'a' through 'z' which are available for the\nuser.  If the name of the buffer is capitalized, then the buffer is not\noverwritten but appended to.  For example, the command {\"qdd} will delete one\nline and store that line in the 'q' buffer, destroying the previous contents of\nthe buffer.  However, {\"Qdd} will delete one line of text and append that line\nto the current contents of the 'q' buffer.\n\nFinally, there are nine buffers named '1' through '9' in which the last nine\ndeletes are stored.  Buffer 1 is the default buffer for the modify commands and\nis sometimes called the unnamed buffer.\n\nTo reference a specific buffer, use the double-quote command {\"} followed by\nthe name of the buffer.  The next two sections show how buffers can be used to\nadvantage.\n\nSection 32: rearranging and duplicating text: {y} {Y} {p} {P}\n\nPosition yourself on line 15 below and {z^M}:\n\nLine 15: A tree as lovely as a poem ...\nLine 16: I think that I shall never see\n\nType {dd}.  Line 15 has disappeared and been replaced with the empty line (one\nwith the single character @ on it) or (again depending on your terminal) Line\n16 has moved up and taken its place.  We could recover Line 15 with an undo\n{u} but that would simply return it to its original location.  Obviously, the\ntwo lines are reversed, so we want to put line 15 AFTER line 16.  This is\nsimply done with the put command {p}, which you should type now.  What has\nhappened is that {dd} put Line 15 into the unnamed buffer, and the {p} command\nretrieved the line from the unnamed buffer.\n\nNow type {u} and observe that Line 15 disappears again (the put was undone\nwithout affecting the unnamed buffer).  Type {P} and see that the capital {P}\nputs the line BEFORE the cursor.\n\nTo get Line 15 where it belongs again type {dd}{p}.\n\nAlso in Line 15 note that the words 'tree' and 'poem' are reversed.  Using the\nunnamed buffer again: {ft}{dw}{ma}{fp}{P}{w}{dw}{`aP} will set things aright\n(note the use of the reverse quote).\n\nThe put commands {p} and {P} do not affect the contents of the buffer.\nTherefore, multiple {p} or {P} will put multiple copies of the unnamed buffer\ninto your file.\n\nExperiment with {d} and {p} on words, paragraphs, etc.  Whatever {d}\ndeletes, {p} can put.\n\nPosition the cursor on Line 17 and {z^M}:\n\nLine 17: interest apple cat elephant boy dog girl hay farmer\n\nOur task is to alphabetize the words on line 17.  With the named buffers (and a\ncontrived example) it is quite easy:\n\n{\"idw}{\"adw}{\"cdw}{\"edw}{\"bdw}{\"ddw}{\"gdw}{\"hdw}{\"fdw}\n\nstores each of the words in the named buffer corresponding to the first letter\nof each of the words ('interest' goes in buffer \"i, 'apple' goes in buffer \"a,\netc.).  Now to put the words in order type:\n\n{\"ap$}{\"bp$}{\"cp$}{\"dp$}{\"ep$}{\"fp$}{\"gp$}{\"hp$}{\"ip$}\n\nNotice that, because 'farmer' was at the end of the line, {dw} did not include\na space after it, and that, therefore, there is no space between 'farmer' and\n'girl'.  This is corrected with {Fg}{i ^[}.\n\nThis example could have been done just as easily with lines as with\nwords.\n\nYou do not have to delete the text in order to put it into a buffer.  If all\nyou wish to do is to copy the text somewhere else, don't use {d}, rather use\nthe yank commands {y} or {Y}.  {y} is like {d} and {c} - an operator rather\nthan a command.  It, too, takes a buffer specification and a range\nspecification.  Therefore, instead of {dw}{P} to load the unnamed buffer with a\nword without deleting the word, use {yw} (yank a word).\n\n{Y} is designed yank lines, and not arbitrary ranges.  That is, {Y} is\nequivalent to {yy} (remember that operators doubled means the current line),\nand {3Y} is equivalent to {3yy}.\n\nIf the text you yank or modify forms a part of a line, or is an object such as\na sentence which partially spans more than one line, then when you put the text\nback, it will be placed after the cursor (or before if you use {P}).  If the\nyanked text forms whole lines, they will be put back as whole lines, without\nchanging the current line.  In this case, the put acts much like the {o} or {O}\ncommand.\n\nThe named buffers \"a through \"z are not affected by changing edit files.\nHowever, the unnamed buffer is lost when you change files, so to move text from\none file to another you should use a named buffer.\n\nSection 33: recovering lost lines\n\nVi also keeps track of the last nine deletes, whether you ask for it or not.\nThis is very convenient if you would like to recover some text that was\naccidentally deleted or modified.  Position the cursor on line 18 following,\nand {z^M}.\n\n\nLine 18: line 1\nLine 19: line 2\nLine 20: line 3\nLine 21: line 4\nLine 22: line 5\nLine 23: line 6\nLine 24: line 7\nLine 25: line 8\nLine 26: line 9\nType {dd} nine times: now don't cheat with {9dd}!  That is totally different.\n\nThe command {\"1p} will retrieve the last delete.  Furthermore, when the\nnumbered buffers are used, the repeat-command command {.} will increment the\nbuffer numbers before executing, so that subsequent {.} will recover all nine\nof the deleted lines, albeit in reverse order.  If you would like to review the\nlast nine deletes without affecting the buffers or your file, do an undo {u}\nafter each put {p} and {.}:\n\n{\"1p}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}\n\nwill show you all the buffers and leave them and your file intact.\n\nIf you had cheated above and deleted the nine lines with {9dd}, all nine lines\nwould have been stored in both the unnamed buffer and in buffer number 1.\n(Obviously, buffer number 1 IS the unnamed buffer and is just the default\nbuffer for the modify commands.)\n\nSection 34: advanced file manipulation: {:r} {:e} {:n} {:w} {!} {:!}\n\nWe've already looked at writing out the file you are editing with the\n{:w} command.  Now let's look at some other vi commands to make editing\nmore efficient.\n\nSection 34.1: more than one file at a time {:n} {:args}\n\nMany times you will want to edit more than one file in an editing session.\nInstead of entering vi and editing the first file, exiting, entering vi and\nediting the second, etc., vi will allow you to specify ALL files that you wish\nto edit on the invocation line.  Therefore, if you wanted to edit file1 and\nfile2:\n\n% vi file1 file2\n\nwill set up file1 for editing.  When you are done editing file one, write it\nout {:w^M} and then type {:n^M} to get the next file on the list.  On large\nprogramming projects with many source files, it is often convenient just to\nspecify all source files with, say:\n\n% vi *.c\n\nIf {:n^M} brings in a file that does not need any editing, another {:n^M}\nwill bring in the next file.\n\nIf you have made changes to the first file, but decide to discard these changes\nand proceed to the next file, {:n!^M} forces the editor to discard the current\ncontents of the editor.\n\nYou can specify a new list of files after {:n}; e.g., {:n f1 f2 f3^M}.  This\nwill replace the current list of files (if any).\n\nYou can see the current list of files being edited with {:args^M}.\n\nSection 34.2: reading files and command output: {:r}\n\nTyping {:r fname^M} will read the contents of file fname into the editor and\nput the contents AFTER the cursor line.\n\nTyping {:r !cmd^M} will read the output of the command cmd and place that\noutput after the cursor line.\n\nSection 34.3: invoking vi from within vi: {:e} {:vi}\n\nTo edit another file not mentioned on the invocation line, type {:e filename^M}\nor {:vi filename^M}.  If you wish to discard the changes to the current file,\nuse the exclamation point after the command, e.g. {:e! filename^M}.\n\nSection 34.4: escaping to a shell: {:sh} {:!} {^Z}\n\nOccasionally, it is useful to interrupt the current editing session to perform\na UNIX task.  However, there is no need to write the current file out, exit\nthe editor, perform the task, and then re-invoke the editor on the same file.\nOne thing to do is to spin off another process.  If there are several UNIX\ncommands you will need to execute, simply create another shell with {:sh^M}.\nAt this point, the editor is put to sleep and will be reawakened when you log\nout of the shell.\n\nIf it is a single command that you want to execute, type {:!cmd^M}, where cmd\nis the command that you wish to run.  The output of the command will come to\nthe terminal as normal, and will not be made part of your file.  The message\n\"[Hit return to continue]\" will be displayed by vi after the command is\nfinished.  Hitting return will then repaint the screen.  Typing another\n{:!cmd^M} at this point is also acceptable.\n\nHowever, there is a quicker, easier way: type {^Z}.  Now this is a little\ntricky, but hang in there.  When you logged into UNIX, the first program you\nbegan communicating with was a program that is called a \"shell\" (i.e. it 'lays\nover' the operating system protecting you from it, sort of like a considerate\nporcupine).  When you got your first prompt on the terminal (probably a '%'\ncharacter) this was the shell telling you to type your first command.  When\nyou typed {vi filename} for some file, the shell did not go away, it just went\nto sleep.  The shell is now the parent of vi.  When you type {^Z} the editor\ngoes to sleep, the shell wakes up and says \"you rang?\" in the form of another\nprompt (probably '%').  At this point you are talking to the shell again and\nyou can do anything that you could before including edit another file!  (The\nonly thing you can't do is log out: you will get the message \"There are\nstopped jobs.\")\n\nWhen your business with the shell is done, type {fg} for 'foreground' and the\nlast process which you ^Z'd out of will be reawakened and the shell will go\nback to sleep.  I will refer you to the documentation for the Berkeley shell\n'csh' for more information on this useful capability.\n\nSection 34.5: writing parts of a file: {:w}\n\nThe {:w} command will accept a range specifier that will then write only a\nselected range of lines to a file.  To write this section to a file, position\nthe cursor on the section line (e.g. {/^Section 34.5:/^M}) and {z^M}.  Now type\n{^G} to find out the line number (it will be something like \"line 513\").  Now\n{/^Section 34.6:/-1^M} to find the last line of this section, and {^G} to find\nits line number (it will be something like 542).  To write out this section of\ntext by itself to a separate file which we will call \"sepfile\", type\n{:510,542w sepfile^M}.  If sepfile already exists, you will have to use the\nexclamation point: {:1147,1168w! sepfile^M} or write to a different, non-\nexistent file.\n\n{:!cat sepfile^M} will display the file just written, and it should be the\ncontents of this section.\n\nThere is an alternate method of determining the line numbers for the write.\n{:set number^M} will repaint the screen with each line numbered.  When the file\nis written and the numbers no longer needed, {:set nonumber^M} will remove the\nnumbers, and {^R} will adjust the screen.\n\nOr, if you remember your earlier lessons about marking lines of text,\nmark the beginning and ending lines.  Suppose we had used {ma} to mark the\nfirst line of the section and {mb} to mark the last.  Then the command\n{:'a,'bw sepfile^M} will write the section into \"sepfile\".  In general,\nyou can replace a line number with the 'name' of a marked line (a single-quote\nfollowed by the letter used to mark the line)\n\n\nSection 34.6: filtering portions of text: {!}\n\n{!} is an operator like {c} and {d}.  That is, it consists of a repetition\ncount, {!}, and a range specifier.  Once the {!} operator is entered in its\nentirety, a prompt will be given at the bottom of the screen for a UNIX\ncommand.  The text specified by the {!} operator is then deleted and\npassed/filtered/piped to the UNIX command you type.  The output of the UNIX\ncommand is then placed in your file.  For example, place the cursor at the\nbeginning of the following line and {z^M}:\n\nls -l vi.tutorial\n********* marks the bottom of the output from the ls command **********\n\nNow type {!!csh^M}.  The line will be replaced with the output from the ls\ncommand.  The {u} command works on {!}, also.\n\nHere is an extended exercise to display some of these capabilities.  When this\ntutorial was prepared, certain auxiliary programs were created to aid in its\ndevelopment.  Of major concern was the formatting of sections of the tutorial\nto fit on a single screen, particularly the first few sections.  What was\nneeded was a vi command that would 'format' a paragraph; that is, fill out\nlines with as many words as would fit in eighty columns.  There is no such vi\ncommand.  Therefore, another method had to be found.\n\nOf course, nroff was designed to do text formatting.  However, it produces a\n'page'; meaning that there may be many blank lines at the end of a formatted\nparagraph from nroff.  The awk program was used to strip these blank lines from\nthe output from nroff.  Below are the two files used for this purpose: I refer\nyou to documentation on nroff and awk for a full explanation of their function.\nPosition the cursor on the next line and {z^M}.\n\n******** contents of file f **********\n#\nnroff -i form.mac | awk \"length != 0 { print }\"\n***** contents of file form.mac ******\n.na\n.nh\n.ll 79\n.ec \u0007\n.c2 \u0006\n.cc \u0002\n**************************************\n\nDetermine the line numbers of the two lines of file f.  They should be\nsomething like 574 and 575, although you better double check: this file is\nunder constant revision and the line numbers may change inadvertently.  Then\n{:574,575w f^M}.  Do the same for the lines of file form.mac.  They will be\napproximately 577 and 582.  Then {:577,582w form.mac^M}.  File f must have\nexecute privileges as a shell file: {:!chmod 744 f^M}.\n\nObserve that this paragraph is\nrather ratty in appearance.  With our newly created files we can\nclean it up dramatically.  Position the cursor at the beginning\nof this paragraph and type the following sequence of\ncharacters\n(note that we must abandon temporarily our convention\nof curly braces since the command itself contains a curly brace - we\nwill use square brackets for the nonce): [!}f^M].\n\nHere is a brief explanation of what has happened.  By typing [!}f^M] we\nspecified that the paragraph (all text between the cursor and the first blank\nline) will be removed from the edit file and piped to a UNIX program called\n\"f\".  This is a shell command file that we have created.  This shell file runs\nnroff, pipes its output to awk to remove blank lines, and the output from awk\nis then read back into our file in the place of the old, ratty paragraph.  The\nfile form.mac is a list of commands to nroff to get it to produce paragraphs\nto our taste (the right margin is not justified, the line is 79 characters\nlong, words are not hyphenated, and three nroff characters are renamed to\navoid conflict: note that in this file, the {^G} you see there is vi's display\nof the control-G character, and not the two separate characters ^ up-arrow and\nG upper-case g).\n\nThis example was created before the existence of the fmt program.  I now type\n[!}fmt^M] to get the same effect much faster.  Actually, I don't type those\nsix keys each time: I have an abbreviation (which see).\n\nSection 35: searching with magic patterns\n\nThe documentation available for \"magic patterns\" (i.e. regular expressions) is\nvery scanty.  The following should explain this possibly very confusing feature\nof the editor.  This section assumes that the magic option is on.  To make\nsure, you might want to type {:set magic^M}.\n\nBy \"magic pattern\" we mean a general description of a piece of text that the\neditor attempts to find during a search.  Most search patterns consist of\nstrings of characters that must be matched exactly, e.g.  {/card/^M} searches\nfor a specific string of four characters.  Let us suppose that you have\ndiscovered that you consistently have mistyped this simple word as either ccrd\nor czrd (this is not so far-fetched for touch typists).  You could {/ccrd/^M}\nand {n} until there are no more of this spelling, followed by {/czrd/^M} and\n{n} until there are no more of these.  Or you could {/c.rd/^M} and catch all of\nthem on the first pass.  Try typing {/c.rd/^M} followed by several {n} and\nobserve the effect.\n\nLine 27: card cord curd ceard\n\nWhen '.' is used in a search string, it has the effect of matching any single\ncharacter.\n\nThe character '^' (up-arrow) used at the beginning of a search string means\nthe beginning of the line.  {/^Line 27/^M} will find the example line above,\nwhile {/Line 27/^M} will find an occurrence of this string anywhere in the\nline.\n\nSimilarly, {/ the$/^M} will find all occurrences of the word 'the' occurring\nat the end of a line.  There are several of them in this file.\n\nNote that {:set nomagic^M} will turn off the special meaning of these magic\ncharacters EXCEPT for '^' and '$' which retain their special meanings at the\nbeginning and end of a search string.  Within the search string they hold no\nspecial meaning.  Try {/\\/ the$\\//^M} and note that the dollar-sign is not the\nlast character in the search string.  Let the dollar-sign be the last\ncharacter in the search string, as in {/\\/ the$/^M} and observe the result.\n\nObserve the result of {/back.*file/^M}.  This command, followed by sufficient\n{n}, will show you all lines in the file that contain both the words 'back'\nand 'file' on the same line.  The '*' magic character specifies that the\nprevious regular expression (the '.' in our example) is to be repeatedly\nmatched zero or more times.  In our example we specified that the words 'back'\nand 'file' must appear on the same line (they may be parts of words such as\n'backwards' or 'workfile') separated by any number (including zero) of\ncharacters.\n\nWe could have specified that 'back' and 'file' are to be words by themselves by\nusing the magic sequences '\\<' or '\\>'.  E.g.  {/\\<back\\>.*\\<file\\>/^M}.  The\nsequence '\\<' specifies that this point of the search string must match the\nbeginning of a word, while '\\>' specifies a match at the end of a word.  By\nsurrounding a string with these characters we have specified that they must be\nwords.\n\nTo find all words that begin with an 'l' or a 'w', followed by an 'a' or an\n'e', and ending in 'ing', try {/\\<[lw][ea][a-z]*ing\\>/^M}.  This will match\nwords like 'learning', 'warning', and 'leading'.  The '[..]' notation matches\nexactly ONE character.  The character matched will be one of the characters\nenclosed in the square brackets.  The characters may be specified individually\nas in [abcd] or a '-' may be used to specify a range of characters as in [a-d].\nThat is, [az] will match the letter 'a' OR the letter 'z', while [a-z] will\nmatch any of the lower case letters from 'a' through 'z'.  If you would like to\nmatch either an 'a', a '-', or a 'z', then the '-' must be escaped: [a\\-z] will\nmatch ONE of the three characters 'a', '-', or 'z'.\n\nIf you wish to find all Capitalized words, try {/\\<[A-Z][a-z]*\\>/^M}.  The\nfollowing will find all character sequences that do NOT begin with an\nun-capitalized letter by applying a special meaning to the '^' character in\nsquare brackets: {/\\<[^a-z][a-z]*\\>/^M}.  When '^' is the first character of a\nsquare-bracket expression, it specifies \"all but these characters\".  (No\none claimed vi was consistent.)\n\nTo find all variable names (the first character is alphabetic, the remaining\ncharacters are alphanumeric):  try {/\\<[A-Za-z][A-Za-z0-9]*\\>/^M}.\n\nIn summary, here are the primitives for building regular expressions:\n\n     ^      at beginning of pattern, matches beginning of line\n     $      at end of pattern, matches end of line\n     .      matches any single character\n     \\<     matches the beginning of a word\n     \\>     matches the end of a word\n     [str]  matches any single character in str\n     [^str] matches any single character NOT in str\n     [x-y]  matches any character in the ASCII range between x and y\n     *      matches any number (including zero) of the preceding pattern\n\nSection 36: advanced substitution: {:s}\n\nThe straightforward colon-substitute command looks like the substitute\ncommand of most line-oriented editors.  Indeed, vi is nothing more than a\nsuperstructure on the line-oriented editor ex and the colon commands are\nsimply a way of accessing commands within ex (see section #EX).  This gives us\na lot of global file processing not usually found in visual oriented editors.\n\nThe colon-substitute command looks like: {:s/ .. / .. /^M} and will find the\npattern specified after the first slash (this is called the search pattern),\nand replace it with the pattern specified after the second slash (called,\nobviously enough, the replacement pattern).  E.g. position the cursor on line\n28 below and {:s/esample/example/^M}:\n\nLine 28: This is an esample.\n\nThe {u} and {U} commands work for {:s}.  The first pattern (the search pattern)\nmay be a regular expression just as for the search command (after all, it IS a\nsearch, albeit limited to the current line).  Do an {u} on the above line, and\ntry the following substitute, which will do almost the same thing:\n{:s/s[^ ]/x/^M}.\nBetter undo it with {u}.  The first pattern {s[^ ]} matches an 's'\nNOT followed by a blank: the search therefore ignores the 's'es in 'This' and\n'is'.  However, the character matched by {[^ ]} must appear in the replacement\npattern.  But, in general, we do not know what that character is!  (In this\nparticular example we obviously do, but more complicated examples will follow.)\nTherefore, vi (really ex) has a duplication mechanism to copy patterns matched\nin the search string into the replacement string.  Line 29 below is a copy of\nline 28 above so you can adjust your screen.\n\nLine 29: This is an esample.\n\nIn general, you can nest parts of the search pattern in \\( .. \\) and refer to\nit in the replacement pattern as \\n, where n is a digit.  The problem outlined\nin the previous paragraph is solved with {:s/s\\([^ ]\\)/x\\1/^M}: try it.  Here\n\\1 refers to the first pattern grouping \\( .. \\) in the search string.\n\nObviously, for a single line, this is rather tedious.  Where it becomes\npowerful, if not necessary, is in colon-substitutes that cover a range of\nlines.  (See the next section for a particularly comprehensive example.)\n\nIf the entire character sequence matched by the search pattern is needed in\nthe replacement pattern, then the un-escaped character '&' can be used.  On\nLine 29 above, try {:s/an e.ample/not &/^M}.  If another line is to have the\nword 'not' prepended to a pattern, then '~' can save you from re-typing the\nreplacement pattern.  E.g. {:s/some pattern/~/^M} after the previous example\nwould be equivalent to {:s/some pattern/not &/^M}.\n\nOne other useful replacement pattern allows you to change the case of\nindividual letters.  The sequences {\\u} and {\\l} cause the immediately\nfollowing character in the replacement to be converted to upper- or lower-case,\nrespectively, if this character is a letter.  The sequences {\\U} and {\\L} turn\nsuch conversion on, either until {\\E} or {\\e} is encountered, or until the end\nof the replacement pattern.\n\nFor example, position the cursor on a line: pick a line, any line.  Type\n{:s/.*/\\U&/^M} and observe the result.  You can undo it with {u}.\n\nThe search pattern may actually match more than once on a single line.\nHowever, only the first pattern is substituted.  If you would like ALL\npatterns matched on the line to be substituted, append a 'g' after the\nreplacement pattern: {:s/123/456/g^M} will substitute EVERY occurrence\non the line of 123 with 456.\n\nSection 37: advanced line addressing: {:p} {:g} {:v}\n\nEx (available through the colon command in vi) offers several methods for\nspecifying the lines on which a set of commands will act.  For example, if you\nwould like to see lines 50 through 100 of your file: {:50,100p^M} will display\nthem, wait for you to [Hit return to continue], and leave you on line 100.\nObviously, it would be easier just to do {100G} from within vi.  But\nwhat if you would like to make changes to just those lines?  Then the\naddressing is important and powerful.\n\nLine 30: This is a text.\nLine 31: Here is another text.\nLine 32: One more text line.\n\nThe lines above contain a typing error that the author of this tutorial tends\nto make every time he attempts to type the word 'test'.  To change all of these\n'text's into 'test's, try the following:\n{:/^Line 30/,/^Line 32/s/text/test/^M}.  This finds the beginning and end of\nthe portion of text to be changed, and limits the substitution to each of the\nlines in that range.  The {u} command applies to ALL of the substitutions as\na group.\n\nThis provides a mechanism for powerful text manipulations.\nAnd very complicated examples.\n\nLine 33: This test is a.\nLine 34: Here test is another.\nLine 35: One line more test.\n\nThe above three lines have the second word out of order.  The following command\nstring will put things right.  Be very careful when typing this: it is very\nlong, full of special characters, and easy to mess up.  You may want to\nconsider reading the following section to understand it before trying the\nexperiment.  Don't worry about messing up the rest of the file, though: the\naddress range is specified.\n\n{:/^Line 33/,/^Line 35/s/\\([^:]*\\): \\([^ ]*\\) \\([^ ]*\\) \\([^.]*\\)/\\1: \\2 \\4 \\3/^M}\n\nThere are several things to note about this command string.  First of all, the\nrange of the substitute was limited by the address specification {/^Line\n33/,/^Line 35/^M}.  It might have been simpler to do {:set number^M} to see the\nline numbers directly, and then, in place of the two searches, typed\nthe line numbers, e.g. {1396,1398}.  Or to mark the lines with {ma} and {mb}\nand use {'a,'b}.\n\nThen follows the substitute pattern itself.  To make it easier to understand\nwhat the substitute is doing, the command is duplicated below with the various\npatterns named for easier reference:\n\n     s/\\([^:]*\\): \\([^ ]*\\) \\([^ ]*\\) \\([^.]*\\)/\\1: \\2 \\4 \\3/\n       |--\\1---|  |--\\2---| |--\\3---| |--\\4---|\n      |--------search pattern------------------|-replacement|\n                                               |--pattern---|\n\nIn overview, the substitute looks for a particular pattern made up of\nsub-patterns, which are named \\1, \\2, \\3, and \\4.  These patterns are specified\nby stating what they are NOT.  Pattern \\1 is the sequence of characters that\nare NOT colons: in the search string, {[^:]} will match exactly one character\nthat is not a colon, while appending the asterisk {[^:]*} specifies that the\n'not a colon' pattern is to be repeated until no longer satisfied, and\n{\\([^:]*\\)} then gives the pattern its name, in this case \\1.  Outside of the\nspecification of \\1 comes {: }, specifying that the next two characters must be\na colon followed by a blank.\n\nPatterns \\2 and \\3 are similar, specifying character sequences that are\nnot blanks.  Pattern \\4 matches up to the period at the end of the line.\n\nThe replacement pattern then consists of specifying the new order of the\npatterns.\n\nThis is a particularly complicated example, perhaps the most complicated\nin this tutorial/reference.  For our small examples, it is obviously\ntedious and error prone.  For large files, however, it may be the most\nefficient way to make the desired modifications.\n\n(The reader is advised to look at the documentation for awk.  This tool is very\npowerful and slightly simpler to use than vi for this kind of file\nmanipulation.  But, it is another command language to learn.)\n\nMany times, you will not want to operate on every line in a certain\nrange.  Rather you will want to make changes on lines that satisfy\ncertain patterns; e.g. for every line that has the string 'NPS' on it,\nchange 'NPS' to 'Naval Postgraduate School'.  The {:g} addressing\ncommand was designed for this purpose.  The example of this paragraph\ncould be typed as {:g/NPS/s//Naval Postgraduate School/^M}.\n\nThe general format of the command is {:g/(pattern)/cmds^M} and it\nworks in the following way: all lines that match the pattern\nfollowing the {:g} are 'tagged' in a special way.  Then each of these\nlines have the commands following the pattern executed over them.\n\nLine 36: ABC rhino george farmer Dick jester lest\nLine 37: george farmer rhino lest jester ABC\nLine 38: rhino lest george Dick farmer ABC jester\n\nType:\n\n{:g/^Line.*ABC/s/Dick/Harry Binswanger/|s/george farmer/gentleman george/p^M}\n\nThere are several things of note here.  First, lines 36, 37, and 38 above are\ntagged by the {:g}.  Type {:g/^Line.*ABC/p^M} to verify this.  Second, there\nare two substitutes on the same line separated by '|'.  In general, any colon\ncommands can be strung together with '|'.  Third, both substitutes operate on\nall three lines, even though the first substitution works on only two of the\nlines (36 and 38).  Fourth, the second substitute works on only two lines (36\nand 37) and those are the two lines printed by the trailing 'p'.\n\nThe {:v} command works similarly to the {:g} command, except that the sense of\nthe test for 'tagging' the lines is reversed: all lines NOT matching the search\npattern are tagged and operated on by the commands.\n\nUsing {^V} to quote carriage return (see section 39) can be used in global\nsubstitutions to split two lines.  For example, the command\n{:g/\\.  /s//.^V^M/g^M} will change your file so that each sentence is on a\nseparate line.  (Note that we have to 'escape' the '.', because '.' by itself\nmatches any character.  Our command says to find any line which contains a\nperiod followed by 2 spaces, and inserts a carriage return after the period.)\n\nCaveat:  In some of the documentation for ex and vi you may find the\ncomment to the effect that {\\^M} can be used between commands following\n{:g}.  The author of this tutorial has never gotten this to work and has\ncrashed the editor trying.\n\nSection 38: higher level text objects and nroff: {(} {)} [{] [}] {[[} {]]}\n\n(Note: this section may be a little confusing because of our command\nnotation.  Using curly braces to surround command strings works fine as\nlong as the command string does not contain any curly braces itself.\nHowever, the curly braces are legitimate commands in vi.  Therefore, for\nany command sequence that contains curly braces, we will surround that\nsequence with SQUARE braces, as on the previous Section line.)\n\nIn working with a document, particularly if using the text formatting\nprograms nroff or troff, it is often advantageous to work in terms of\nsentences, paragraphs, and sections.  The operations {(} and {)} move to\nthe beginning of the previous and next sentences, respectively.  Thus\nthe command {d)} will delete the rest of the current sentence; likewise\n{d(} will delete the previous sentence if you are at the beginning of\nthe current sentence, or, if you are not at the beginning of a sentence,\nit will delete the current sentence from the beginning\nup to where you are.\n\nA sentence is defined to end at a '.', '!', or '?' which is followed\nby either the end of a line, or by two spaces.  Any number of closing\n')', ']', '\"', and ''' characters may appear after the '.', '!', or '?'\nbefore the spaces or end of line.  Therefore, the {(} and {)} commands\nwould recognize only one sentence in the following line, but two\nsentences on the second following line.\n\nLine 39: This is one sentence. Even though it looks like two.\nLine 40: This is two sentences.  Because it has two spaces after the '.'.\n\nThe operations [{] and [}] move over paragraphs and the operations {[[}\nand {]]} move over sections.\n\nA paragraph begins after each empty line, and also at each of a set of nroff\nparagraph macros.  A section begins after each line with a form-feed ^L in the\nfirst column, and at each of a set of nroff section macros.  When preparing a\ntext file as input to nroff, you will probably be using a set of nroff macros\nto make the formatting specifications easier, or more to your taste.  These\nmacros are invoked by beginning a line with a period followed by the one or two\nletter macro name. Vi has been programmed to recognize these nroff macros, and\nif it doesn't recognize your particular macro you can use the {:set paragraphs}\nor {:set sections} commands so that it will.\n\nSection 39: more about inserting text\n\nThere are a number of characters which you can use to make corrections\nduring input mode.  These are summarized in the following table.\n\n    ^H      deletes the last input character\n    ^W      deletes the last input word\n    (erase) same as ^H; each terminal can define its own erase character;\n            for some it is ^H, for others it is the DELETE key, and for\n            others it is '@'.\n    (kill)  deletes the input on this line; each terminal can define its\n            own line-kill character; for some it is ^U, for others it is\n            '@'; you will need to experiment on your terminal to find\n            out what your line-kill and erase characters are.\n    \\       escapes a following ^H, (kill), and (erase) characters: i.e.\n            this is how to put these characters in your file.\n    ^[      escape key; ends insertion mode\n    ^?      the delete key; interrupts an insertion, terminating it\n            abnormally.\n    ^M      the return key; starts a new line.\n    ^D      back-tabs over the indentation set by the autoindent option\n    0^D     back-tabs over all indentation back to the beginning of the line\n    ^^D     (up-arrow followed by control-d)same as 0^D, except the indentation\n            will be restored at the beginning of the next line.\n    ^V      quotes the next non-printing character into the file\n\nIf you wish to type in your erase or kill character (say # or @ or ^U) then you\nmust precede it with a \\, just as you would do at the normal system command\nlevel.  A more general way of typing non-printing characters into the file is\nto precede them with a ^V.  The ^V echoes as a ^ character on which the cursor\nrests.  This indicates that the editor expects you to type a control character\nand it will be inserted into the file at that point.  There are a few\nexceptions to note.  The implementation of the editor does not allow the null\ncharacter ^@ to appear in files.  Also the linefeed character ^J is used by the\neditor to separate lines in the file, so it cannot appear in the middle of a\nline.  (Trying to insert a ^M into a file, or putting it in the replacement\npart of a substitution string will result in the matched line being split in\ntwo.  This, in effect, is how to split lines by using a substitution.)  You can\ninsert any other character, however, if you wait for the editor to echo the ^\nbefore you type the character.  In fact, the editor will treat a following\nletter as a request for the corresponding control character.  This is the only\nway to type ^S or ^Q, since the system normally uses them to suspend and resume\noutput and never gives them to the editor to process.\n\nIf you are using the autoindent option you can back-tab over the indent which it\nsupplies by typing a ^D.  This backs up to the boundary specified by the\nshiftwidth option.  This only works immediately after the supplied autoindent.\n\nWhen you are using the autoindent option you may wish to place a label at the\nleft margin of a line.  The way to do this easily is to type ^ (up-arrow) and\nthen ^D.  The editor will move the cursor to the left margin for one line, and\nrestore the previous indent on the next.  You can also type a 0 followed\nimmediately by a ^D if you wish to kill all indentation and not have it resume\non the next line.\n\nSection 40: more on operators: {d} {c} {<} {>} {!} {=} {y}\n\nBelow is a non-exhaustive list of commands that can follow the operators\nto affect the range over which the operators will work.  However, note\nthat the operators {<}, {>}, {!}, and {=} do not operate on any object\nless than a line.  Try {!w} and you will get a beep.  To get the\noperator to work on just the current line, double it.  E.g. {<<}.\n\n    suffix        will operate on\n    ------        ------------------------\n      ^[            cancels the command\n      w             the word to the right of the cursor\n      W             ditto, but ignoring punctuation\n      b             the word to the left of the cursor\n      B             ditto, but ignoring punctuation\n      e             see below.\n      E               ditto\n      (space)       a character\n      $             to the end of the line\n      ^             to the beginning of the line\n      / .. /        up to, but not including, the string\n      ? .. ?        back to and including the string\n      fc            up to and including the occurrence of c\n      Fc            back to and including the occurrence of c\n      tc            up to but not including the occurrence of c\n      Tc            back to but not including the occurrence of c\n      ^M            TWO lines (that's right: two)\n      (number)^M    that many lines plus one\n      (number)G     up to and including line (number)\n      (             the previous sentence if you are at the beginning of\n                    the current sentence, or the current sentence up to where\n                    you are if you are not at the beginning of the current\n                    sentence.  Here, 'sentence' refers to the intuitive\n                    notion of an English sentence, ending with '!', '?',\n                    or '.' and followed by an end of line or two spaces.\n      )             the rest of the current sentence\n      {             analogous to '(', but in reference to paragraphs:\n                    sections of text surrounded by blank lines\n      }             analogous to ')', but in reference to paragraphs\n      [[            analogous to '(', but in reference to sections\n      ]]            analogous to ')', but in reference to sections\n      H             the first line on the screen\n      M             the middle line on the screen\n      L             the last line on the screen\n      3L            through the third line from the bottom of the screen\n      ^F            forward a screenful\n      ^B            backward a screenful\n      :\n      :  etc. etc. etc.\n\nThis list is not exhaustive, but it should be sufficient to get the idea\nacross: after the operator, you can specify a range with a move-the-cursor\ncommand, and that is the region of text over which the operator will be\neffective.\n\nSection 41: abbreviations: {:ab}\n\nWhen typing large documents you may find yourself typing a large phrase\nover and over.  Vi gives you the ability to specify an abbreviation for\na long string such that typing the abbreviation will automatically\nexpand into the longer phrase.\n\nType {:ab nps Naval Postgraduate School^M}.  Now type:\n\n{iThis is to show off the nps's UNIX editor.^M^[}\n\nSection 42: vi's relationship with the ex editor: {:}\n\nVi is actually one mode of editing within the editor ex.  When you are\nrunning vi you can escape to the line oriented editor of ex by giving\nthe command {Q}.  All of the colon-commands which were introduced above\nare available in ex.  Likewise, most ex commands can be invoked from vi\nusing {:}.\n\nIn rare instances, an internal error may occur in vi.  In this case you\nwill get a diagnostic and will be left in the command mode of ex.  You can\nthen save your work and quit if you wish by giving the command {x} after\nthe colon prompt of ex.  Or you can reenter vi (if you are brave) by\ngiving ex the command {vi}.\n\nSection 43: vi on hard-copy terminals and dumb terminals: open mode\n\n(The author has not checked the following documentation for accuracy.  It is\nabstracted from the Introduction to Vi Editing document.)\n\nIf you are on a hard-copy terminal or a terminal which does not have a cursor\nwhich can move off the bottom line, you can still use the command set of vi,\nbut in a different mode.  When you give the vi command to UNIX, the editor will\ntell you that it is using open mode.  This name comes from the open command in\nex, which is used to get into the same mode.\n\nThe only difference between visual mode (normal vi) and open mode is the way in\nwhich the text is displayed.\n\nIn open mode the editor uses a single line window into the file, and moving\nbackward and forward in the file causes new lines to be displayed, always below\nthe current line.  Two commands of vi work differently in open: {z} and {^R}.\nThe {z} command does not take parameters, but rather draws a window of context\naround the current line and then returns you to the current line.\n\nIf you are on a hard-copy terminal, the {^R} command will retype the current\nline.  On such terminals, the editor normally uses two lines to represent the\ncurrent line.  The first line is a copy of the line as you started to edit it,\nand you work on the line below this line.  When you delete characters, the\neditor types a number of \\'s to show you the characters which are deleted.  The\neditor also reprints the current line soon after such changes so that you can\nsee what the line looks like again.\n\nIt is sometimes useful to use this mode on very slow terminals which can\nsupport vi in the full screen mode.  You can do this by entering ex and using\nan {open} command.\n\n*********************************************************************\nSection 44: options: {:set} {setenv EXINIT}\n\nYou will discover options as you need them.  Do not worry about them very much\non the first pass through this document.  My advice is to glance through them,\nnoting the ones that look interesting, ignoring the ones you don't understand,\nand try re-scanning them in a couple of weeks.\n\nIf you decide that you have a favorite set of options and would like to change\nthe default values for the editor, place a {setenv EXINIT} command in your\n.login file.  When you are given an account under UNIX your directory has\nplaced in it a file that is executed each time you log in.  If one of the\ncommands in this file sets the environment variable EXINIT to a string of vi\ncommands, you can have many things done for you each time you invoke vi.  For\nexample, if you decide that you don't like tabstops placed every eight columns\nbut prefer every four columns, and that you wish the editor to insert linefeeds\nfor you when your typing gets you close to column 72, and you want\nauto-indentation, then include the following line in your .login file:\n\nsetenv EXINIT='set tabstop=4 wrapmargin=8 autoindent'\n\nor equivalently\n\nsetenv EXINIT='se ts=4 wm=8 ai'\n\nEach time you bring up vi, this command will be executed and the options set.\n\nThere are forty options in the vi/ex editor that the user can set for his/her\nown convenience.  They are described in more detail in individual sections\nbelow.  The section line will show the full spelling of the option name, the\nabbreviation, and the default value of the option.  The text itself\ncomes from the ex reference manual and is not the epitome of clarity.\n\nSection 44.1: {autoindent}, {ai} default: noai\n\nCan be used to ease the preparation of structured program text.  At the\nbeginning of each append, change or insert command or when a new line is opened\nor created by an append, change, insert, or substitute operation within open or\nvisual mode, ex looks at the line being appended after, the first line changed\nor the line inserted before and calculates the amount of white space at the\nstart of the line.  It then aligns the cursor at the level of indentation so\ndetermined.\n\nIf the user then types lines of text in, they will continue to be justified at\nthe displayed indenting level.  If more white space is typed at the beginning\nof a line, the following line will start aligned with the first non-white\ncharacter of the previous line.  To back the cursor up to the preceding tab\nstop one can hit {^D}.  The tab stops going backwards are defined at multiples\nof the shiftwidth option.  You cannot backspace over the indent, except by\nsending an end-of-file with a {^D}.  A line with no characters added to it\nturns into a completely blank line (the white space provided for the autoindent\nis discarded). Also specially processed in this mode are lines beginning with\nan up-arrow `^' and immediately followed by a {^D}.  This causes the input to\nbe repositioned at the beginning of the line, but retaining the previous indent\nfor the next line.  Similarly, a `0' followed by a {^D} repositions at the\nbeginning but without retaining the previous indent.  Autoindent doesn't happen\nin global commands or when the input is not a terminal.\n\nSection 44.2: {autoprint}, {ap} default: ap\n\nCauses the current line to be printed after each delete, copy, join, move,\nsubstitute, t, undo or shift command.  This has the same effect as supplying a\ntrailing `p' to each such command.  Autoprint is suppressed in globals, and\nonly applies to the last of many commands on a line.\n\nSection 44.3: {autowrite}, {aw} default: noaw\n\nCauses the contents of the buffer to be written to the current file if you have\nmodified it and give a next, rewind, stop, tag, or {!} command, or a control-\nup-arrow {^^} (switch files) or {^]} (tag goto) command in visual.  Note, that\nthe edit and ex commands do not autowrite.  In each case, there is an\nequivalent way of switching when autowrite is set to avoid the autowrite\n({edit} for next, rewind!  for rewind, stop!  for stop, tag!  for tag, shell\nfor {!}, and {:e #} and a {:ta!} command from within visual).\n\nSection 44.4: {beautify}, {bf} default: nobeautify\n\nCauses all control characters except tab ^I, newline ^M and form-feed ^L to be\ndiscarded from the input.  A complaint is registered the first time a backspace\ncharacter is discarded.  Beautify does not apply to command input.\n\nSection 44.5: {directory}, {dir} default: dir=/tmp\n\nSpecifies the directory in which ex places its buffer file.  If this directory\nin not writable, then the editor will exit abruptly when it fails to be able to\ncreate its buffer there.\n\nSection 44.6: {edcompatible} default: noedcompatible\n\nCauses the presence or absence of g and c suffixes on substitute commands to be\nremembered, and to be toggled by repeating the suffices.  The suffix r makes\nthe substitution be as in the {~} command, instead of like {&}.\n\n[Author's note: this should not concern users of vi.]\n\nSection 44.7: {errorbells}, {eb} default: noeb\n\nError messages are preceded by a bell.  However, bell ringing in open and\nvisual modes on errors is not suppressed by setting noeb.  If possible the\neditor always places the error message in a standout mode of the terminal (such\nas inverse video) instead of ringing the bell.\n\nSection 44.8: {hardtabs}, {ht} default: ht=8\n\nGives the boundaries on which terminal hardware tabs are set (or on which the\nsystem expands tabs).\n\nSection 44.9: {ignorecase}, {ic} default: noic\n\nAll upper case characters in the text are mapped to lower case in regular\nexpression matching.  In addition, all upper case characters in regular\nexpressions are mapped to lower case except in character class specifications\n(that is, character in square brackets).\n\nSection 44.10: {lisp} default: nolisp\n\nAutoindent indents appropriately for lisp code, and the {(}, {)}, [{], [}],\n{[[}, and {]]} commands in open and visual modes are modified in a\nstraightforward, intuitive fashion to have meaning for lisp.\n\n[Author's note: but don't ask me to define them precisely.]\n\nSection 44.11: {list} default: nolist\n\nAll printed lines will be displayed (more) unambiguously, showing tabs as ^I\nand end-of-lines with `$'.  This is the same as in the ex command {list}.\n\nSection 44.12: {magic} default: magic for {ex} and {vi}, nomagic for edit.\n\nIf nomagic is set, the number of regular expression meta-characters is greatly\nreduced, with only up-arrow `^' and `$' having special effects.  In addition\nthe meta-characters `~' and `&' of the replacement pattern are treated as normal\ncharacters.  All the normal meta-characters may be made magic when nomagic is\nset by preceding them with a `\\'.\n\n[Author's note: In other words, if magic is set a back-slant turns the magic\noff for the following character, and if nomagic is set a back-slant turns the\nmagic ON for the following character.  And, no, we are not playing Dungeons and\nDragons, although I think the writers of these option notes must have played it\nall the time.]\n\nSection 44.13: {mesg} default: mesg\n\nCauses write permission to be turned off to the terminal while you are in\nvisual mode, if nomesg is set.\n\n[Author's note: I don't know if anyone could have made any one sentence\nparagraph more confusing than this one.  What it says is: mesg allows people to\nwrite to you even if you are in visual or open mode; nomesg locks your terminal\nso they can't write to you and mess up your screen.]\n\nSection 44.14: {number, nu} default: nonumber\n\nCauses all output lines to be printed with their line numbers.  In addition\neach input line will be prompted with its line number.\n\nSection 44.15: {open} default: open\n\nIf {noopen}, the commands open and visual are not permitted.  This is set for\nedit to prevent confusion resulting from accidental entry to open or visual\nmode.\n\n[Author's note: As you may have guessed by now, there are actually three\neditors available under Berkeley UNIX that are in reality the same\nprogram, ex, with different options set: ex itself, vi, and edit.]\n\nSection 44.16: {optimize, opt} default: optimize\n\nThroughput of text is expedited by setting the terminal to not do automatic\ncarriage returns when printing more than one (logical) line of output, greatly\nspeeding output on terminals without addressable cursors when text with leading\nwhite space is printed.\n\n[Author's note: I still don't know what this option does.]\n\nSection 44.17: {paragraphs, para} default: para=IPLPPPQPP LIbp\n\nSpecifies the paragraphs for the [{] and [}] operations in open and visual.\nThe pairs of characters in the option's value are the names of the nroff macros\nwhich start paragraphs.\n\nSection 44.18: {prompt} default: prompt\n\nCommand mode input is prompted for with a `:'.\n\n[Author's note: Doesn't seem to have any effect on vi.]\n\nSection 44.19: {readonly}, {ro} default: noro, unless invoked with -R\n                                         or insufficient privileges on file\n\nThis option allows you to guarantee that you won't clobber your file by\naccident.  You can set the option and writes will fail unless you use an `!'\nafter the write.  Commands such as {x}, {ZZ}, the autowrite option, and in\ngeneral anything that writes is affected.  This option is turned on if you\ninvoke the editor with the -R flag.\n\nSection 44.20: {redraw} default: noredraw\n\nThe editor simulates (using great amounts of output), an intelligent terminal\non a dumb terminal (e.g. during insertions in visual the characters to the\nright of the cursor position are refreshed as each input character is typed).\nUseful only at very high baud rates, and should be used only if the system is\nnot heavily loaded: you will notice the performance degradation yourself.\n\nSection 44.21: {remap} default: remap\n\nIf on, macros are repeatedly tried until they are unchanged.  For example, if o\nis mapped to O, and O is mapped to I, then if remap is set, o will map to I,\nbut if noremap is set, it will map to O .\n\nSection 44.22: {report} default: report=5 for ex and vi, 2 for edit\n\nSpecifies a threshold for feedback from commands.  Any command which modifies\nmore than the specified number of lines will provide feedback as to the scope\nof its changes.  For commands such as global, open, undo, and visual which have\npotentially more far reaching scope, the net change in the number of lines in\nthe buffer is presented at the end of the command, subject to this same\nthreshold.  Thus notification is suppressed during a global command on the\nindividual commands performed.\n\nSection 44.23: {scroll} default: scroll=1/2 window\n\nDetermines the number of logical lines scrolled when a {^D} is received from a\nterminal in command mode, and determines the number of lines printed by a\ncommand mode z command (double the value of scroll).\n\n[Author's note: Doesn't seem to affect {^D} and {z} in visual (vi) mode.]\n\nSection 44.24: sections {sections} default: sections=SHNHH HU\n\nSpecifies the section macros from nroff for the {[[} and {]]} operations in\nopen and visual.  The pairs of characters in the options's value are the names\nof the macros which start paragraphs.\n\nSection 44.25: {shell}, {sh} default: sh=/bin/sh\n\nGives the path name of the shell forked for the shell escape command `!', and\nby the shell command.  The default is taken from SHELL in the environment, if\npresent.\n\n[Editor's note: I would suggest that you place the following line in\nyour .login file:\nsetenv SHELL '/bin/csh'\n]\n\nSection 44.26: {shiftwidth}, {sw} default: sw=8\n\nUsed in reverse tabbing with {^D} when using autoindent to append text, and\nused by the shift commands.  Should probably be the same value as the tabstop\noption.\n\nSection 44.27: {showmatch}, {sm} default: nosm\n\nIn open and visual mode, when a `)' or `}' is typed, if the matching `(' or `{'\nis on the screen, move the cursor to it for one second.  Extremely useful with\ncomplicated nested expressions, or with lisp.\n\nSection 44.28: {slowopen}, {slow} default: terminal dependent\n\nAffects the display algorithm used in visual mode, holding off display updating\nduring input of new text to improve throughput when the terminal in use is both\nslow and unintelligent.  See \"An Introduction to Display Editing with Vi\" for\nmore details.\n\nSection 44.29: {tabstop}, {ts} default: ts=8\n\nThe editor expands tabs ^I to tabstop boundaries in the display.\n\nSection 44.30: {taglength}, {tl} default: tl=0\n\nTags are not significant beyond this many characters.\nA value of zero (the default) means that all characters are significant.\n\nSection 44.31: {tags} default: tags=tags /usr/lib/tags\n\nA path of files to be used as tag files for the tag command.  A requested tag\nis searched for in the specified files, sequentially.  By default files called\ntags are searched for in the current directory and in /usr/lib (a master file\nfor the entire system).\n\n[Author's note: The author of this tutorial has never used this option, nor\nseen it used.  I'm not even sure I know what they are talking about.]\n\nSection 44.32: {term} default: from environment variable TERM\n\nThe terminal type of the output device.\n\nSection 44.33: {terse} default: noterse\n\nShorter error diagnostics are produced for the experienced user.\n\nSection 44.34: {timeout} default: timeout\n\nCauses macros to time out after one second.  Turn it off and they will\nwait forever.  This is useful if you want multi-character macros, but if\nyour terminal sends escape sequences for arrow keys, it will be\nnecessary to hit escape twice to get a beep.\n\n[Editor's note: Another paragraph which requires a cryptographer.]\n\nSection 44.35: ttytype\n\n[Editor's note: I have found no documentation for this option at all.]\n\nSection 44.36: {warn} default: warn\n\nWarn if there has been `[No write since last change]' before a `!' command\nescape.\n\nSection 44.37: {window} default: window=speed dependent\n\nThe number of lines in a text window in the visual command.  The default is 8\nat slow speeds (600 baud or less), 16 at medium speed (1200 baud), and the full\nscreen (minus one line) at higher speeds.\n\nSection 44.38: {wrapscan}, {ws} default: ws\n\nSearches using the regular expressions in addressing will wrap around past the\nend of the file.\n\nSection 44.39: {wrapmargin}, {wm} default: wm=0\n\nDefines a margin for automatic wrapover of text during input in open and visual\nmodes.  The numeric value is the number of columns from the right edge of the\nscreen around which vi looks for a convenient place to insert a new-line\ncharacter (wm=0 is OFF).  This is very convenient for touch typists.\nWrapmargin behaves much like fill/nojustify mode does in nroff.\n\nSection 44.40: {writeany}, {wa} default: nowa\n\nInhibit the checks normally made before write commands, allowing a write to any\nfile which the system protection mechanism will allow.\n\nSection 44.41: {w300}, {w1200}, {w9600} defaults: w300=8\n                                                 w1200=16\n                                                 w9600=full screen minus one\n\nThese are not true options but set the default size of the window for when the\nspeed is slow (300), medium (1200), or high (9600), respectively.  They are\nsuitable for an EXINIT and make it easy to change the 8/16/full screen rule.\n\nSection 45: Limitations\n\nHere are some editor limits that the user is likely to encounter:\n       1024   characters per line\n       256    characters per global command list\n       128    characters per file name\n       128    characters in the previous inserted and deleted text in open or\n              visual\n       100    characters in a shell escape command\n       63     characters in a string valued option\n       30     characters in a tag name\n       250000 lines in the file (this is silently enforced).\n\nThe visual implementation limits the number of macros defined with map to 32,\nand the total number of characters in macros to be less than 512.\n\n[Editor's note: These limits no longer apply to versions after 4.1BSD.]\n"
  },
  {
    "path": "docs/tutorial/vi.advanced.license",
    "content": "SPDX-License-Identifier: BSD-3-Clause\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/tutorial/vi.beginner",
    "content": "Section 1: {^F} {ZZ}\n\nTo get out of this tutorial, type: ZZ (two capital Z's).\n\nLearning a new computer system implies learning a new text editor.  These\ntutorial lessons were created by Dain Samples to help you come to grips with\nUC Berkeley's screen oriented editor called vi (for VIsual). This tutorial\nuses the vi editor itself as the means of presentation.\n\nFor best use of this tutorial, read all of a screen before performing any of\nthe indicated actions.  This tutorial (or, at least, the first half of it) has\nbeen designed to systematically present the vi commands IF THE INSTRUCTIONS\nARE FOLLOWED!  If you are too adventuresome, you may find yourself lost.  If\nyou ever find yourself stuck, remember the first line of this section.\n\nOK, now find the control key on your keyboard; it usually has CTL or CTRL\nwritten on its upper surface.  Your first assignment is to hold the control\nkey down while you press the 'F' key on your keyboard.  Please do so now.\n\n\n\nSection 2: {^F} {^B}\nMany of vi's commands use the control key and some other key in combination,\nas with the control and the 'F' key above.  This is abbreviated CTL-F, or ^F.\n\nAs you have probably guessed by now, ^F (CTL-F) moves you forward a fixed\nnumber of lines in the file.  Throughout the remainder of the tutorial when\nyou are ready to advance to the next section of text, hit ^F.\n\nThe opposite command is ^B.  Just for fun, you might want to try a ^B to see\nthe previous section again.  Be sure to do a ^F to return you here.\n\nDetermine what the cursor looks like on your screen.  Whatever it is (a box,\nan underscore, blinking, flashing, inverse, etc.) it should now be positioned\nin the upper left-hand corner of your screen under or on the S of Section.\nBecome familiar with your cursor: to use vi correctly it is important to\nalways know where the cursor is.\n\nDid you notice that when you do a ^F the cursor is left at the top of the\nscreen, and a ^B leaves the cursor near the bottom of the screen?  Try the two\ncommands ^B^F again.  And now do another ^F to see the next section.\n\nSection 3: {^F} {^B}\nYou now have two basic commands for examining a file, both forwards (^F) and\nbackwards (^B).\n\nNote that these are vi text editing commands: they are not commands for the\ntutorial.  Indeed, this tutorial is nothing but a text file which you are now\nediting.  Everything you do and learn in this tutorial will be applicable to\nediting text files.\n\nTherefore, when you are editing a file and are ready to see more of the text,\nentering ^F will get you to the next section of the file.  Entering ^B will\nshow you the previous section.\n\nTime for you to do another ^F.\n\n\n\n\n\n\n\nSection 4: {^F} {^B} {^M} (return key)\nWe will adopt the notation of putting commands in curly braces so we can write\nthem unambiguously.  For example, if you are to type the command sequence\n\"control B control F\" (as we asked you to do above) it would appear as {^B^F}.\nThis allows clear delineation of the command strings from the text. Remember\nthat the curly braces are NOT part of the command string you are to type. Do\nNOT type the curly braces.\n\nSometimes, the command string in the curly braces will be rather long, and may\nbe such that the first couple of characters of the command will erase from\nthe screen the string you are trying to read and type.  It is suggested that\nyou write down the longer commands BEFORE you type them so you won't forget\nthem once they disappear.\n\nNow locate the return key on your keyboard: it is usually marked 'RETURN',\nindicate hitting the return key.  In fact, the control-M key sequence is\nexactly the same as if you hit the return key, and vice versa.\n\nNow type {^F}.\n\n\nSection 5: {:q!} {ZZ} {^M} (return key)\nRecognize that this tutorial is nothing more than a text file that you\nare editing.  This means that if you do something wrong, it is possible\nfor you to destroy the information in this file.  Don't worry.  If this\nhappens, type {ZZ} (two capital Z's) or {:q!^M} to leave the tutorial.\nRestart the tutorial.  Once in the tutorial, you can then page forward\nwith {^F} until you are back to where you want to be.  (There are\neasier ways to do this, some of which will be discussed later, but this\nis the most straightforward.)\n\nYou may want to write these commands down in a convenient place for quick\nreference: {:q!^M} and {ZZ}\n\nWe will assume that you now know to do a {^F} to advance the file\n\n\n\n\n\n\n\nSection 6: {m} {G} {'} {z}\nNow that you know how to get around in the file via ^F and ^B let's look at\nother ways of examining a text file.  Sometimes it is necessary, in the midst\nof editing a file, to examine another part of the file.  You are then faced\nwith the problem of remembering your place in the file, looking at the other\ntext, and then getting back to your original location.  Vi has a 'mark'\ncommand, m. Type {mp}.  You have just 'marked' your current location in the\nfile and given it the name 'p'.  The command string below will do three\nthings: position you at the beginning of the file (line 1), then return you to\nthe location 'p' that you just marked with the 'm' command, and, since the\nscreen will not look exactly the same as it does right now, the 'z' command\nwill reposition the screen. (You may want to write the string down before\ntyping it: once you type {1G} it will no longer be on the screen.)\n\nSo now type {1G'pz^M} - a one followed by a capital G, followed by the quote\nmark, followed by a lower case 'p', then a lower case 'z', then a return\n(which is the same as a ^M).  The {1G} moves you to line 1, i.e. the beginning\nof the file.  The {'p} moves you to the location you marked with {mp}.  The\n{z^M} command will repaint the screen putting the cursor at the top of the\nscreen. (Now {^F}.)\n\nSection 7: {m} {G} {'} {z}\nLet's look at some variations on those commands.  If you wanted to look at\nline 22 in the file and return to this location you could type {mp22G'p}.  Do\nso now, observing that {22G} puts your cursor at the beginning of section 2 in\nthe middle of the screen.\n\nAlso note that, without the {z^M} command, the line with 'Section 7' on it is\nnow in the MIDDLE of the screen, and not at the top.  Our cursor is on the\ncorrect line (where we did the {mp} command) but the line is not where we\nmight like it to be on the screen.  That is the function of the {z^M} command.\n(Remember, ^M is the same as the 'return' key on your keyboard.)  Type {z^M}\nnow and observe the effect.\n\nAs you can see, the 'Section 7' line is now at the top of the screen with the\ncursor happily under the capital S.  If you would like the cursor line (i.e.\nthe line which the cursor is on) in the middle of the screen again, you would\ntype {z.}.  If you wanted the cursor line to be at the BOTTOM of the screen,\ntype {z-}.  Try typing {z-z.z^M} and watch what happens.\n\n{^F}\n\nSection 8: {z} {m} {'}\n\nNote that the z command does not change the position of our cursor in the file\nitself, it simply moves the cursor around on the screen by moving the contents\nof the file around on the screen.  The cursor stays on the same line of the\nfile when using the z command.\n\nThis brings up an important point.  There are two questions that the users of\nvi continually need to know the answer to: \"Where am I in the file?\" and\n\"Where am I on the screen?\"  The cursor on your terminal shows the answer to\nboth questions.  Some commands will move you around in the file, usually\nchanging the location of the cursor on the screen as well.  Other commands\nmove the cursor around on the screen without changing your location in the\nfile.\n\nNow type {ma}.  Your location in the file has been given the name 'a'. If you\ntype {'p'a} you will see the previous location we marked in section 7, and\nthen will be returned to the current location.  (You will want to do a {z^M}\nto repaint the screen afterwards.)  Try it.\n{^F}\n\nSection 9: {m} {''}\nNow we can move about in our file pretty freely.  By using the {m} command we\ncan give the current cursor position a lower-case-character name, like 'p',\n'a', 'e', 'm', or 'b'.  Using the {G} command preceded by a line number we can\nlook at any line in the file we like.  Using the single quote command {'}\nfollowed by a character used in an {m} command, we can return to any location\nin the file we have marked.\n\nTry {m3}, {mM}, or {m$}.  Not only lower-case letters are acceptable to the\n{m} and {'} commands: numbers, upper-case letters, and special characters are\nalso acceptable.\n\nIf you type the {'} command with a character that that has not been used in an\n{m} command, or for which the 'marked' text has been deleted, you will get a\nbeep.  Try {'i}.  You should get a beep because the command {mi} has never\nbeen issued.  (Unless you've been experimenting.)\n\nThe command {''} attempts to return you to the location at which you last\nmodified some part of your file.  However, my experience has been that it is\ndifficult to predict exactly where you will end up.\n\nSection 10: {^M} {-}\nNow do {ma}, marking your position at the top of the screen.  Now hit {^M} (or\nreturn) until the cursor is right ...\n* <- here, over/under the asterisk.  Now\ntype {mb'a'b} and watch the cursor move from the asterisk to the top of the\nscreen and back again.\n\nThe {^M} command moves the cursor to the beginning of the next line.  Now type\n{^M} until the cursor is right ...\n* <- here.  The command to move the cursor to the beginning of the\nprevious line is {-}.  Practice moving the cursor around on the screen by using\n{^M} and {-}.  BE CAREFUL to not move the cursor OFF the screen just yet.  If\nyou do, type {'az^M}.\n\nNow we can move to any line within the screen.  Practice moving around in the\nfile using the {^F}, {^B}, {-}, {^M}, {z}, and {'} commands.  When you are\nfairly confident that you can get to where you need to be in the file, and\nposition the cursor on the screen where you want it type {'az^M^F} (which, of\ncourse, moves you back to the beginning of this section, repositions the\ncursor at the top of the screen, and advances you to the next section).\n\nSection 11: scrolling: {^M}\nThe cursor should now be on the S of 'Section 11', and this should be on the\nfirst line of the screen.  If it is not, do {^M} or {-} as appropriate to put\nthe cursor on the section line, and type {z^M}.\n\nType {mc} to mark your place.\n\nNow type {^M} until the cursor is on the last line of this screen.  Now do one\nmore {^M} and observe the result.  This is called scrolling.  When you\nattempted to move to a line not displayed on the screen, the line at the top of\nthe screen was 'scrolled off', and a line at the bottom of the screen was\n'scrolled on'.  The top line with 'Section 11' should no longer be visible.\n\nNow type {'cz^M} to reset the screen and type {^F} for the next section.\n\n\n\n\n\n\n\nSection 12: {-} {z}\n\nThe {-} command moves the cursor to the previous line in the file.  Now type\n{-}, which attempts to move the cursor to the previous line in this file.\nHowever, that line is not on the screen.  The resulting action will depend on\nyour terminal.  (Do a {^Mz^M} to reposition the file).  On intelligent\nterminals (e.g. VT100s, xterm, most modern terminals), a top line is 'scrolled\non' and the bottom line is 'scrolled off'.  Some very old terminals, however,\nmay not have this 'reverse scrolling' feature.  They will simply repaint the\nscreen with the cursor line in the middle of the screen.  On such terminals it\nis necessary to type {z^M} to get the cursor line back to the top of the\nscreen.\n\n\n\n\n\n\n\n\n\nSection 13:\nUp until this point, the tutorial has always tried to make sure that the first\nline of each screen has on it the section number and a list of the commands\ncovered in that section.  This will no longer be strictly maintained.  If you\nwant the section line at the top of the screen, you now know enough commands to\ndo it easily: do {^M} or {-} until the cursor is on the section line and\nthen {z^M}.  Also, from this point on, it may not be the case that a {^F} will\nput you at the beginning of the next section.  Therefore, be aware of where you\nare in the file as we look at other commands.  You may have to find your way\nback to a particular section without any help from the tutorial.  If you do not\nfeel comfortable with this, then it is suggested that you practice moving from\nsection 1 to section 13, back and forth, using {^M}, {-}, {^F}, and {^B}\ncommands for a while.\n\nAlso make liberal use of the mark command {m}: if, for example, you make a\nhabit of using {mz} to mark your current location in the file, then you will\nalways be able to return to that location with {'z} if the editor does\nsomething strange and you have no idea where you are or what happened.\n\nAnd finally, the proscription against experimentation is hereby lifted: play\nwith the editor.  Feel free to try out variations on the commands and move\naround in the file.  By this time you should be able to recover from any gross\nerrors.\n\nSection 14: {^E} {^Y} {^D} {^U}\nLet us now look at a few other commands for moving around in the file, and\nmoving the file around on the screen.  Note that the commands we have already\nlooked at are sufficient: you really don't need any more commands for looking\nin a file.  The following commands are not absolutely necessary.  However,\nthey can make editing more convenient, and you should take note of their\nexistence.  But it would be perfectly valid to decide to ignore them on this\nfirst pass: you can learn them later when you see a need for them, if you ever\ndo.\n\nFirst, let's clear up some potentially confusing language.  In at least one\nplace in the official document ('An Introduction to Display Editing with Vi'\nby William Joy, and Mark Horton, September 1980), the expression \"to scroll\ndown text\" means that the cursor is moved down in your file.  However, note\nthat this may result in the text on the screen moving UP.  This use of the\nword 'scroll' refers to the action of the cursor within the file.  However,\nanother legitimate use of the word refers to the action of the text on the\nscreen.  That is, if the lines on your screen move up toward the top of the\nscreen, this would be 'scrolling the screen up'.  If the lines move down\ntoward the bottom of the screen, this would be referred to as scrolling down.\n\nI have tried to maintain the following jargon: 'scrolling' refers to what the\ntext does on the screen, not to what the cursor does within the file.  For the\nlatter I will refer to the cursor 'moving', or to 'moving the cursor'.  I\nrealize that this is not necessarily consistent with Joy and Horton, but they\nwere wrong.\n\n{^E} scrolls the whole screen up one line, keeping the cursor on the same line,\nif possible.  However, if the cursor line is the first line on the screen, then\nthe cursor is moved to the next line in the file.  Try typing {^E}.\n\n{^Y} scrolls the screen down one line, keeping the cursor on the same line, if\npossible.  However, if the cursor line is the last line on the screen, then the\ncursor is moved to the previous line in the file.  Try it.\n\n{^D} moves the cursor down into the file, scrolling the screen up.\n\n{^U} moves the cursor up into the file, also scrolling the screen if the\nterminal you are on has the reverse scroll capability.  Otherwise the\nscreen is repainted.\n\nNote that {^E} and {^Y} move the cursor on the screen while trying to keep the\ncursor at the same place in the file (if possible: however, the cursor can\nnever move off screen), while {^D} and {^U} keep the cursor at the same place\non the screen while moving the cursor within the file.\n\nSection 15: {/ .. /^M}\n\nAnother way to position yourself in the file is by giving the editor a string\nto search for.  Type the following: {/Here 1/^M} and the cursor should end up\nright ...........................here ^.  Now type {/Section 15:/^M} and the\ncursor will end up over/on .....................here ^.  Now type {//^M} and\nobserve that the cursor is now over the capital S five lines above this line.\nTyping {//^M} several more times will bounce the cursor back and forth between\nthe two occurrences of the string.  In other words, when you type a string\nbetween the two slashes, it is searched for.  Typing the slashes with nothing\nbetween them acts as if you had typed the previous string again.\n\nObserve that the string you type between the two slashes is entered on the\nbottom line of the screen.  Now type {/Search for x /^M} except replace the 'x'\nin the string with some other character, say 'b'.  The message \"Pattern not\nfound\" should appear on the bottom of the screen.  If you hadn't replaced the\n'x', then you would have found the string.  Try it.\n\nSection 16: {? .. ?^M} {n} (search strings: ^ $)\n\nWhen you surround the sought-for string with slashes as in {/Search/}, the\nfile is searched beginning from your current position in the file.  If the\nstring is not found by the end of the file, searching is restarted at the\nbeginning of the file.  However, if you do want the search to find the\nPREVIOUS rather than the NEXT occurrence of the string, surround the string\nwith question marks instead of slash marks.\n\nBelow are several occurrences of the same string.\nHere 2            Here 2 Here 2\n Here 2             Here 2.\nObserve the effect of the following search commands (try them in the\nsequence shown):\n{/Here 2/^M}  {//^M}  {??^M}\n{/^Here 2/^M}  {//^M}  {??^M}\n{/Here 2$/^M}  {//^M}  {??^M}\n\nThe first command looks for the next occurrence of the string 'Here 2'.\nHowever the second line of commands looks for an occurrence of 'Here 2' that\nis at the beginning of the line.  When the caret (circumflex, up-arrow) is the\nfirst character of a search string it stands for the beginning of the line.\nWhen the dollar-sign is the last character of the search string it stands for\nthe end of the line.  Therefore, the third line of commands searches for the\nstring only when it is at the end of the line.  Since there is only one place\nthe string begins a line, and only one place the string ends the line,\nsubsequent {//^M} and {??^M} will find those same strings over and over.\n\nThe {n} command will find the next occurrence of the / or ? search\nstring.  Try {/Here 2/^M} followed by several {n} and observe the\neffect.  Then try {??^M} followed by several {n}.  The {n} command\nremembers the direction of the last search.  It is just a way to save a\nfew keystrokes.\n\nSection 17: \\ and magic-characters in search strings\n\nNow type {/Here 3$/^M}.  You might expect the cursor to end up\nright......^ here.  However, you will get \"Pattern not found\" at the bottom of\nthe screen.  Remember that the dollar-sign stands for the end of the line.\nSomehow, you must tell vi that you do not want the end of the line, but a\ndollar-sign.  In other words, you must take away the special meaning that the\ndollar-sign has for the search mechanism.  You do this (for any special\ncharacter, including the caret ^) by putting a back-slash ('\\', not '/') in\nfront of the character.\n\nNow try {/Here 3\\$/^M} and you should end up nine lines above this one.  Try\n{//^M} and note that it returns you to the same place, and not to the first\nline of this paragraph: the back-slash character is not part of the search\nstring and will not be found.  To find the string in the first line of this\nparagraph, type {/Here 3\\\\\\$/^M}.  There are three back-slashes: the first takes\naway the special meaning from the second, and the third takes away the special\nmeaning from the dollar-sign.\n\nFollowing is a list of the characters that have special meanings in search\nstrings.  If you wish to find a string containing one of these characters, you\nwill have to precede the character with a backslash.  These characters are\ncalled magic characters because of the fun and games you can have with them\nand they can have with you, if you aren't aware of what they do.\n\n  ^ - (caret)          beginning of a line\n  $ - (dollar-sign)    end of a line\n  . - (period)         matches any character\n  \\ - (backslash)      the escape character itself\n  [ - (square bracket) for finding patterns (see section #SEARCH)\n  ] - (square bracket) ditto\n  * - (asterisk)       ditto\n\nWithout trying to explain it here, note that {:set nomagic^M} turns off the\nspecial meanings of all but the ^ caret, $ dollar-sign, and backslash\ncharacters.\n\nSection 18: {: (colon commands)} {ZZ}\n\nIn this section we will discuss getting into and out of the editor in more\ndetail.  If you are editing a file and wish to save the results the command\nsequence {:w^M} writes the current contents of the file out to disk, using the\nfile name you used when you invoked the editor.  That is, if you are at the\ncommand level in Unix, and you invoke vi with {vi foo} where foo is the name\nof the file you wish to edit, then foo is the name of the file used by the\n{:w^M} command.\n\nIf you are done, the write and quit commands can be combined into a single\ncommand {:wq^M}.  An even simpler way is the command {ZZ} (two capital Z's).\n\nIf, for some reason, you wish to exit without saving any changes you have made,\n{:q!^M} does the trick.  If you have not made any changes, the exclamation\npoint is not necessary: {:q^M}.  Vi is pretty good about not letting you\nget out without warning you that you haven't saved your file.\n\nWe have mentioned before that you are currently in the vi editor, editing a\nfile.  If you wish to start the tutorial over from the very beginning, you\ncould {:q!^M}, and then type {vi.tut beginner} or {vi vi.beginner} in response\nto the Unix prompt.  This will provide an unmodified copy of this file for you,\nwhich might be necessary if you accidentally destroyed the copy you were\nworking with.  Just do a search for the last section you were in: e.g.\n{/Section 18:/^Mz^M}.\n\nSection 19: {H} {M} {L}\n\nHere are a few more commands that will move you around on the screen.  Again,\nthey are not absolutely necessary, but they can make screen positioning easier:\n\n{H} - puts the cursor at the top of the screen (the 'home' position)\n\n{M} - puts the cursor in the middle of the screen\n\n{L} - puts the cursor at the bottom of the screen.\n\nTry typing {HML} and watch the cursor.\n\nTry typing {5HM5L} and note that 5H puts you five lines from the top of the\nscreen, and 5L puts you five lines from the bottom of the screen.\n\nSection 20: {w} {b} {0} {W} {B} {e} {E} {'} {`}\n\nUp to this point we have concentrated on positioning in the file, and\npositioning on the screen.  Now let's look at positioning in a line.  Put the\ncursor at the beginning of the following line and type {z^M}:\n\nThis is a test line: your cursor should initially be at its beginning.\n\nThe test line should now be at the top of your screen. Type {w} several times.\nNote that it moves you forward to the beginning of the next word.  Now type\n{b} (back to the beginning of the word) several times till you are at the\nbeginning of the line.  (If you accidentally type too many {b}, type {w} until\nyou are on the beginning of the line again.) Type {wwwww} (five w's) and note\nthat the cursor is now on the colon in the sentence.  The lower-case w command\nmoves you forward one word, paying attention to certain characters such as\ncolon and period as delimiters and counting them as words themselves.  Now\ntype {0} (zero, not o 'oh'): this moves you to the beginning of the current\nline.  Now type {5w} and notice that this has the effect of repeating {w} five\ntimes and that you are now back on the colon.  Type {0} (zero) again.  To\nignore the delimiters and to move to the beginning of the next word using only\nblanks, tabs and carriage-returns (these are called white-space characters) to\ndelimit the words, use the {W} command: upper-case W.  {B} takes you back a\nword using white-space characters as word delimiters.\n\nNote that the commands {wbWB} do not stop at the beginning or end of a line:\nthey will continue to the next word on the next line in the direction specified\n(a blank line counts as a word).\n\nIf you are interested in the END of the word, and not the BEGINNING, then use\nthe {e} and {E} commands.  These commands only move forward and there are no\ncorresponding 'reverse search' commands for the end of a word.\n\nAlso, we have been using the {'} command to move the cursor to a position that\nwe have previously marked with the {m} command.  However, position the cursor\nin the middle of a line (any line, just pick one) and type {mk}, marking that\nposition with the letter k.  Now type a few returns {^M} and type {'k}.\nObserve that the cursor is now at the beginning of the line that you marked.\nNow try {`k}: note that this is the reverse apostrophe, or back-quote, or grave\naccent, or whatever you want to call it.  Also note that it moves you to the\ncharacter that was marked, not just to the line that was marked.\n\nIn addition, the {``} command works just like the {''} command except that you\nare taken to the exact character, not just to the line.  (I'm still not\nsure which exact character, just as I'm still not sure which line.)\n\nSection 21: {l} {k} {j} {h}\n\nThere are several commands to move around on the screen on a character by\ncharacter basis:\n\nl - moves the cursor one character to the RIGHT\nk - moves the cursor UP one line\nj - moves the cursor DOWN one line\nh - moves the cursor one character to the LEFT\n\nSection 22: {i} {a} {I} {A} {o} {O} {^[} (escape key)\n\nFor this and following sections you will need to use the ESCAPE key on your\nterminal.  It is usually marked ESC.  Since the escape key is the same as\ntyping {^[} we will use ^[ for the escape key.\n\nProbably the most often used command in an editor is the insert command.  Below\nare two lines of text, the first correct, the second incorrect.  Position your\ncursor at the beginning of Line 1 and type {z^M}.\n\nLine 1: This is an example of the insert command.\nLine 2: This is an of the insert command.\n\nTo make line 2 look like line 1, we are going to insert the characters\n'example ' before the word 'of'.  So, now move the cursor so that it is\npositioned on the 'o' of 'of'.  (You can do this by typing {^M} to move\nto the beginning of line 2, followed by {6w} or {wwwwww} to position the cursor\non the word 'of'.)\n\nNow carefully type the following string and observe the effects:\n  {iexample ^[}  (remember: ^[ is the escape key)\nThe {i} begins the insert mode, and 'example ' is inserted into the line:\nbe sure to notice the blank in 'example '.  The {^[} ends insertion mode,\nand the line is updated to include the new string.  Line 1 should look exactly\nlike Line 2.\n\nMove the cursor to the beginning of Line 3 below and type {z^M}:\n\nLine 3: These lines are examples for the 'a' command.\nLine 4: These line are examples for the '\n\nWe will change line four to look like line three by using the append command.\nWe need to append an 's' to the word 'line'.  Position the cursor on the 'e'\nof 'line'.  You can do this in several ways, one way is the following:\nFirst, type {/line /^M}.  This puts us on the word 'line' in Line 4\n(the blank in the search string is important!).  Next, type {e}.  The 'e' puts\nus at the end of the word.  Now, type {as^[} (^[ is the escape character).\nThe 'a' puts us in insert mode, AFTER the current character.  We appended the\n's', and the escape '^[' ended the insert mode.\n\nThe difference between {i} (insert) and {a} (append) is that {i} begins\ninserting text BEFORE the cursor, and {a} begins inserting AFTER the cursor.\n\nNow type {Aa' command.^[}.  The cursor is moved to the end of the line and the\nstring following {A} is inserted into the text.  Line 4 should now look like\nline 3.\n\nJust as {A} moves you to the end of the line to begin inserting, {I} would\nbegin inserting at the FRONT of the line.\n\nTo begin the insertion of a line after the cursor line, type {o}.  To insert a\nline before the cursor line, type {O}.  In other words {o123^[} is equivalent\nto {A^M123^[}, and {O123^[} is equivalent to {I123^M^[}.  The text after the\n{o} or {O} is ended with an escape ^[.\n\nThis paragraph contains information that is terminal dependent: you will just\nhave to experiment to discover what your terminal does.  Once in the insert\nmode, if you make a mistake in the typing, ^H will delete the previous\ncharacter up to the beginning of the current insertion.  ^W will delete the\nprevious word, and one of ^U, @, or ^X will delete the current line (up to the\nbeginning of the current insertion).  You will need to experiment with ^U, @,\nand ^X to determine which works for your terminal.\n\nSection 23: {f} {x} {X} {w} {l} {r} {R} {s} {S} {J}\n\nPosition the cursor at the beginning of line 5 and {z^M}:\n\nLine 5: The line as it should be.\nLine 6: The line as it shouldn't be.\n\nTo make Line 6 like Line 5, we have to delete the 'n', the apostrophe, and the\n't'.  There are several ways to position ourselves at the 'n'.  Choose\nwhichever one suits your fancy:\n\n{/n't/^M}\n{^M7w6l}  or  {^M7w6 } (note the space)\n{^M3fn}  (finds the 3rd 'n' on the line)\n\nNow {xxx} will delete the three characters, as will {3x}.\n\nNote that {X} deletes the character just BEFORE the cursor, as opposed\nto the character AT the cursor.\n\nPosition the cursor at line 7 and {z^M}:\n\nLine 7: The line as it would be.\nLine 8: The line as it could be.\n\nTo change line 8 into line 7 we need to change the 'c' in 'could' into a 'w'.\nThe 'r' (replace) command was designed for this.  Typing {rc} is the same as\ntyping {xic^[} (i.e.  delete the 'bad' character and insert the correct\nnew character).  Therefore, assuming that you have positioned the cursor on the\n'c' of 'could', the easiest way to change 'could' into 'would' is {rw}.\n\nIf you would like to now change the 'would' into 'should', use the substitute\ncommand, 's': {ssh^[}.  The difference between 'r' and 's' is that 'r'\n(replace) replaces the current character with another character, while 's'\n(substitute) substitutes the current character with a string, ended with an\nescape.\n\nThe capital letter version of replace {R} replaces each character by a\ncharacter one at a time until you type an escape, ^[.  The 'S' command\nsubstitutes the whole line.\n\nPosition your cursor at the beginning of line 9 and {z^M}.\n\nLine  9: Love is a many splendored thing.\nLine 10: Love is a most splendored thing.\n\nTo change line 10 into line 9, position the cursor at the beginning of 'most',\nand type {Rmany^[}.\n\nYou may have noticed that, when inserting text, a new line is formed by typing\n{^M}.  When changing, replacing, or substituting text you can make a new line\nby typing {^M}.  However, neither {x} nor {X} will remove ^M to make two lines\ninto one line.  To do this, position the cursor on the first of the two lines\nyou wish to make into a single line and type {J} (uppercase J for 'Join').\n\nSection 24: {u} {U}\n\nFinally, before we review, let's look at the undo command.  Position\nyour cursor on line 11 below and {z^M}.\n\nLine 11: The quick brown fox jumped over the lazy hound dog.\nLine 12: the qwick black dog dumped over the laxy poune fox.\n\nType the following set of commands, and observe carefully the effect of each\nof the commands:\n\n{/^Line 12:/^M} {ft} {rT} {fw} {ru} {w} {Rbrown fox^[} {w} {rj}\n{fx} {rz} {w} {Rhound dog^[}\n\nLine 12 now matches line 11.  Now type {U} - capital 'U'.  And line 12 now\nlooks like it did before you typed in the command strings.  Now type:\n\n{ft} {rT} {fw} {ru} {^M} {^M}\n\nand then type {u}:  the cursor jumps back to the line containing the second\nchange you made and 'undoes' it.  That is, {U} 'undoes' all the changes on the\nline, and {u} 'undoes' only the last change.  Type {u} several times and\nobserve what happens: {u} can undo a previous {u}!\n\nCaveat: {U} only works as long as the cursor is still on the line.  Move the\ncursor off the line and {U} will have no effect, except to possibly beep at\nyou.  However, {u} will undo the last change, no matter where it occurred.\n\nSection 25: review\n\nAt this point, you have all the commands you need in order to make use of vi.\nThe remainder of this tutorial will discuss variations on these commands as\nwell as introduce new commands that make the job of editing more efficient.\nHere is a brief review of the basic commands we have covered.  They are listed\nin the order of increasing complexity and/or decreasing necessity (to say that\na command is less necessary is not to say that it is less useful!).  These\ncommands allow you to comfortably edit any text file.  There are other\ncommands that will make life easier but will require extra time to learn,\nobviously.  You may want to consider setting this tutorial aside for several\nweeks and returning to it later after gaining experience with vi and getting\ncomfortable with it.  The convenience of some of the more exotic commands may\nthen be apparent and worth the extra investment of time and effort\nrequired to master them.\n\nto get into the editor from Unix:           {vi filename}\nto exit the editor\n      saving all changes                    {ZZ} or {:wq^M}\n      throwing away all changes             {:q!^M}\n      when no changes have been made        {:q^M}\nsave a file without exiting the editor      {:w^M}\nwrite the file into another file            {:w filename^M}\ninsert text\n      before the cursor                     {i ...text... ^[}\n      at the beginning of the line          {I ...text... ^[}\n      after the cursor (append)             {a ...text... ^[}\n      at the end of the line                {A ...text... ^[}\n      after the current line                {o ...text... ^[}\n      before the current line               {O ...text... ^[}\ndelete the character  ...\n      under the cursor                      {x}\n      to the left of the cursor             {X}\ndelete n characters                         {nx} or {nX}  (for n a number)\nmake two lines into one line (Join)         {J}\nfind a string in the file ...\n      searching forward                     {/ ...string... /^M}\n      searching backwards                   {? ...string... ?^M}\nrepeat the last search command              {n}\nrepeat the last search command in the\n  opposite direction                        {N}\nfind the character c on this line ...\n      searching forward                     {fc}\n      searching backward                    {Fc}\nrepeat the last 'find character' command    {;}\nreplace a character with character x        {rx}\nsubstitute a single character with text     {s ...text... ^[}\nsubstitute n characters with text           {ns ...text... ^[}\nreplace characters one-by-one with text     {R ...text... ^[}\nundo all changes to the current line        {U}\nundo the last single change                 {u}\nmove forward in the file a \"screenful\"      {^F}\nmove back in the file a \"screenful\"         {^B}\nmove forward in the file one line           {^M} or {+}\nmove backward in the file one line          {-}\nmove to the beginning of the line           {0}\nmove to the end of the line                 {$}\nmove forward one word                       {w}\nmove forward one word, ignoring punctuation {W}\nmove forward to the end of the next word    {e}\nto the end of the word, ignoring punctuation{E}\nmove backward one word                      {b}\nmove back one word, ignoring punctuation    {B}\nreturn to the last line modified            {''}\nscroll a line onto the top of the screen    {^Y}\nscroll a line onto the bottom of the screen {^E}\nmove \"up\" in the file a half-screen         {^U}\nmove \"down\" in the file a half-screen       {^D}\nmove the cursor to the top screen line      {H}\nmove the cursor to the bottom screen line   {L}\nmove the cursor to the middle line          {M}\nmove LEFT one character position            {h} or {^H}\nmove RIGHT one character position           {l} or { }\nmove UP in the same column                  {k} or {^P}\nmove DOWN in the same column                {j} or {^N}\nmark the current position, name it x        {mx}\nmove to the line marked/named x             {'x}\nmove to the character position named x      {`x}\nmove to the beginning of the file           {1G}\nmove to the end of the file                 {G}\nmove to line 23 in the file                 {23G}\nrepaint the screen with the cursor line\n       at the top of the screen             {z^M}\n       in the middle of the screen          {z.}\n       at the bottom of the screen          {z-}\n\nMore information on vi can be found in the file vi.advanced, which you can\nperuse at your leisure.  From UNIX, type {vi.tut advanced^M} or\n{vi vi.advanced^M}.\n"
  },
  {
    "path": "docs/tutorial/vi.beginner.license",
    "content": "SPDX-License-Identifier: BSD-3-Clause\nCopyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n        The Regents of the University of California\nCopyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n        Keith Bostic\nCopyright (c) 2021-2024 Jeffrey H. Johnson\n"
  },
  {
    "path": "docs/tutorial/vi.tut.csh",
    "content": "#!/usr/bin/env csh\n# $OpenBSD: vi.tut.csh,v 1.2 2001/01/29 01:58:40 niklas Exp $\n# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n#         The Regents of the University of California\n# Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n#         Keith Bostic\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n\n# XXX I don't know what happens if there is .exrc file!\n# XXX Make sure that user is using a 24 line window!!!\n\nif ($1 != \"beginner\" && $1 != \"advanced\") then\n        echo Usage: $0 beginner or $0 advanced\n        exit\nendif # ($1 != \"beginner\" && $1 != \"advanced\")\n\nif ($?EXINIT) then\n        set oexinit=\"$EXINIT\"\n        setenv EXINIT 'se ts=4 wm=8 sw=4'\nendif # $?EXINIT\n\nvi vi.{$1}\n\nonintr:\n\nif ($?oexinit) then\n                setenv EXINIT \"$oexinit\"\nendif # $?oexinit\n"
  },
  {
    "path": "ex/ex.awk",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n# Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n#         The Regents of the University of California\n# Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000\n#         Keith Bostic\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n\nBEGIN {\n        printf(\"enum {\");\n        first = 1;\n}\n/^\\/\\* C_[0-9A-Z_]* \\*\\// {\n        printf(\"%s\\n\\t%s%s\", first ? \"\" : \",\", $2, first ? \" = 0\" : \"\");\n        first = 0;\n        next;\n}\nEND {\n        printf(\"\\n};\\n\");\n}\n"
  },
  {
    "path": "ex/ex.c",
    "content": "/*      $OpenBSD: ex.c,v 1.23 2023/06/23 15:06:45 millert Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n#if defined(DEBUG) && defined(COMLOG)\nstatic void     ex_comlog(SCR *, EXCMD *);\n#endif /* if defined(DEBUG) && defined(COMLOG) */\nstatic EXCMDLIST const *\n                ex_comm_search(char *, size_t);\nstatic int      ex_discard(SCR *);\nstatic int      ex_line(SCR *, EXCMD *, MARK *, int *, int *);\nstatic int      ex_load(SCR *);\nstatic void     ex_unknown(SCR *, char *, size_t);\n\n/*\n * ex --\n *      Main ex loop.\n *\n * PUBLIC: int ex(SCR **);\n */\n\nint\nex(SCR **spp)\n{\n        GS *gp;\n        MSGS *mp;\n        SCR *sp;\n        TEXT *tp;\n        u_int32_t flags;\n\n        sp = *spp;\n        gp = sp->gp;\n\n        /* Start the ex screen. */\n        if (ex_init(sp))\n                return (1);\n\n        /* Flush any saved messages. */\n        while ((mp = LIST_FIRST(&gp->msgq)) != NULL) {\n                gp->scr_msg(sp, mp->mtype, mp->buf, mp->len);\n                LIST_REMOVE(mp, q);\n                free(mp->buf);\n                free(mp);\n        }\n\n        /* If reading from a file, errors should have name and line info. */\n        if (F_ISSET(gp, G_SCRIPTED)) {\n                gp->excmd.if_lno = 1;\n                gp->excmd.if_name = \"script\";\n        }\n\n        /*\n         * !!!\n         * Initialize the text flags.  The beautify edit option historically\n         * applied to ex command input read from a file.  In addition, the\n         * first time a ^H was discarded from the input, there was a message,\n         * \"^H discarded\", that was displayed.  We don't bother.\n         */\n\n        LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR);\n        for (;; ++gp->excmd.if_lno) {\n                /* Display status line and flush. */\n                if (F_ISSET(sp, SC_STATUS)) {\n                        if (!F_ISSET(sp, SC_EX_SILENT))\n                                msgq_status(sp, sp->lno, 0);\n                        F_CLR(sp, SC_STATUS);\n                }\n                (void)ex_fflush(sp);\n\n                /* Set the flags the user can reset. */\n                if (O_ISSET(sp, O_BEAUTIFY))\n                        LF_SET(TXT_BEAUTIFY);\n                if (O_ISSET(sp, O_PROMPT))\n                        LF_SET(TXT_PROMPT);\n\n                /* Clear any current interrupts, and get a command. */\n                CLR_INTERRUPT(sp);\n                if (ex_txt(sp, &sp->tiq, ':', flags))\n                        return (1);\n                if (INTERRUPTED(sp)) {\n                        (void)ex_puts(sp, \"\\n\");\n                        (void)ex_fflush(sp);\n                        continue;\n                }\n\n                /* Initialize the command structure. */\n                CLEAR_EX_PARSER(&gp->excmd);\n\n                /*\n                 * If the user entered a single carriage return, send\n                 * ex_cmd() a separator -- it discards single newlines.\n                 */\n\n                tp = TAILQ_FIRST(&sp->tiq);\n                if (tp->len == 0) {\n                        gp->excmd.cp = \" \";     /* __TK__ why not |? */\n                        gp->excmd.clen = 1;\n                } else {\n                        gp->excmd.cp = tp->lb;\n                        gp->excmd.clen = tp->len;\n                }\n                F_INIT(&gp->excmd, E_NRSEP);\n\n                if (ex_cmd(sp) && F_ISSET(gp, G_SCRIPTED))\n                        return (1);\n\n                if (INTERRUPTED(sp)) {\n                        CLR_INTERRUPT(sp);\n                        msgq(sp, M_ERR, \"Interrupted\");\n                }\n\n                /* Sync recovery if changes were made. */\n                if (F_ISSET(sp->ep, F_RCV_SYNC))\n                        rcv_sync(sp, 0);\n\n                /*\n                 * If the last command caused a restart, or switched screens\n                 * or into vi, return.\n                 */\n\n                if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_SSWITCH | SC_VI)) {\n                        *spp = sp;\n                        break;\n                }\n\n                /* If the last command switched files, we don't care. */\n                F_CLR(sp, SC_FSWITCH);\n\n                /*\n                 * If we're exiting this screen, move to the next one.  By\n                 * definition, this means returning into vi, so return to the\n                 * main editor loop.  The ordering is careful, don't discard\n                 * the contents of sp until the end.\n                 */\n\n                if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {\n                        if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE)))\n                                return (1);\n                        *spp = screen_next(sp);\n                        if (*spp) {\n                                F_CLR(*spp, SC_SCR_VI);\n                                F_SET(*spp, SC_SCR_EX);\n                        }\n                        return (screen_end(sp));\n                }\n        }\n        return (0);\n}\n\n/*\n * ex_cmd --\n *      The guts of the ex parser: parse and execute a string containing\n *      ex commands.\n *\n * !!!\n * This code MODIFIES the string that gets passed in, to delete quoting\n * characters, etc.  The string cannot be readonly/text space, nor should\n * you expect to use it again after ex_cmd() returns.\n *\n * !!!\n * For the fun of it, if you want to see if a vi clone got the ex argument\n * parsing right, try:\n *\n *      echo 'foo|bar' > file1; echo 'foo/bar' > file2;\n *      vi\n *      :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\\//SLASH/|wq\n *\n * or:  vi\n *      :set|file|append|set|file\n *\n * For extra credit, try them in a startup .exrc file.\n *\n * PUBLIC: int ex_cmd(SCR *);\n */\n\nint\nex_cmd(SCR *sp)\n{\n        enum nresult nret;\n        EX_PRIVATE *exp;\n        EXCMD *ecp;\n        GS *gp;\n        MARK cur;\n        recno_t lno;\n        size_t arg1_len, discard, len;\n        u_int32_t flags;\n        long ltmp;\n        int at_found, gv_found;\n        int ch, cnt, delim, isaddr, namelen;\n        int newscreen, notempty, tmp, vi_address;\n        char *arg1, *p, *s, *t;\n\n        gp = sp->gp;\n        exp = EXP(sp);\n\n        /*\n         * We always start running the command on the top of the stack.\n         * This means that *everything* must be resolved when we leave\n         * this function for any reason.\n         */\nloop:   ecp = LIST_FIRST(&gp->ecq);\n\n        /* If we're reading a command from a file, set up error information. */\n        if (ecp->if_name != NULL) {\n                gp->if_lno = ecp->if_lno;\n                gp->if_name = ecp->if_name;\n        }\n\n        /*\n         * If a move to the end of the file is scheduled for this command,\n         * do it now.\n         */\n        if (F_ISSET(ecp, E_MOVETOEND)) {\n                if (db_last(sp, &sp->lno))\n                        goto rfail;\n                sp->cno = 0;\n                F_CLR(ecp, E_MOVETOEND);\n        }\n\n        /* If we found a newline, increment the count now. */\n        if (F_ISSET(ecp, E_NEWLINE)) {\n                ++gp->if_lno;\n                ++ecp->if_lno;\n                F_CLR(ecp, E_NEWLINE);\n        }\n\n        /* (Re)initialize the EXCMD structure, preserving some flags. */\n        CLEAR_EX_CMD(ecp);\n\n        /* Initialize the argument structures. */\n        if (argv_init(sp, ecp))\n                goto err;\n\n        /* Initialize +cmd, saved command information. */\n        arg1 = NULL;\n        ecp->save_cmdlen = 0;\n\n        /* Skip <blank>s, empty lines.  */\n        for (notempty = 0; ecp->clen > 0; ++ecp->cp, --ecp->clen)\n                if ((ch = *ecp->cp) == '\\n') {\n                        ++gp->if_lno;\n                        ++ecp->if_lno;\n                } else if (isblank(ch))\n                        notempty = 1;\n                else\n                        break;\n\n        /*\n         * !!!\n         * Permit extra colons at the start of the line.  Historically,\n         * ex/vi allowed a single extra one.  It's simpler not to count.\n         * The stripping is done here because, historically, any command\n         * could have preceding colons, e.g. \":g/pattern/:p\" worked.\n         */\n        if (ecp->clen != 0 && ch == ':') {\n                notempty = 1;\n                while (--ecp->clen > 0 && (ch = *++ecp->cp) == ':');\n        }\n\n        /*\n         * Command lines that start with a double-quote are comments.\n         *\n         * !!!\n         * Historically, there was no escape or delimiter for a comment, e.g.\n         * :\"foo|set was a single comment and nothing was output.  Since nvi\n         * permits users to escape <newline> characters into command lines, we\n         * have to check for that case.\n         */\n        if (ecp->clen != 0 && ch == '\"') {\n                while (--ecp->clen > 0 && *++ecp->cp != '\\n');\n                if (*ecp->cp == '\\n') {\n                        F_SET(ecp, E_NEWLINE);\n                        ++ecp->cp;\n                        --ecp->clen;\n                }\n                goto loop;\n        }\n\n        /* Skip whitespace. */\n        for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) {\n                ch = *ecp->cp;\n                if (!isblank(ch))\n                        break;\n        }\n\n        /*\n         * The last point at which an empty line can mean do nothing.\n         *\n         * !!!\n         * Historically, in ex mode, lines containing only <blank> characters\n         * were the same as a single <carriage-return>, i.e. a default command.\n         * In vi mode, they were ignored.  In .exrc files this was a serious\n         * annoyance, as vi kept trying to treat them as print commands.  We\n         * ignore backward compatibility in this case, discarding lines that\n         * contain only <blank> characters from .exrc files.\n         *\n         * !!!\n         * This is where you end up when you're done a command, i.e. clen has\n         * gone to zero.  Continue if there are more commands to run.\n         */\n        if (ecp->clen == 0 &&\n            (!notempty || F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_BLIGNORE))) {\n                if (ex_load(sp))\n                        goto rfail;\n                ecp = LIST_FIRST(&gp->ecq);\n                if (ecp->clen == 0)\n                        goto rsuccess;\n                goto loop;\n        }\n\n        /*\n         * Check to see if this is a command for which we may want to move\n         * the cursor back up to the previous line.  (The command :1<CR>\n         * wants a <newline> separator, but the command :<CR> wants to erase\n         * the command line.)  If the line is empty except for <blank>s,\n         * <carriage-return> or <eof>, we'll probably want to move up.  I\n         * don't think there's any way to get <blank> characters *after* the\n         * command character, but this is the ex parser, and I've been wrong\n         * before.\n         */\n        if (F_ISSET(ecp, E_NRSEP) &&\n            ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\\004'))\n                F_CLR(ecp, E_NRSEP);\n\n        /* Parse command addresses. */\n        if (ex_range(sp, ecp, &tmp))\n                goto rfail;\n        if (tmp)\n                goto err;\n\n        /*\n         * Skip <blank>s and any more colons (the command :3,5:print\n         * worked, historically).\n         */\n        for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) {\n                ch = *ecp->cp;\n                if (!isblank(ch) && ch != ':')\n                        break;\n        }\n\n        /*\n         * If no command, ex does the last specified of p, l, or #, and vi\n         * moves to the line.  Otherwise, determine the length of the command\n         * name by looking for the first non-alphabetic character.  (There\n         * are a few non-alphabetic characters in command names, but they're\n         * all single character commands.)  This isn't a great test, because\n         * it means that, for the command \":e +cut.c file\", we'll report that\n         * the command \"cut\" wasn't known.  However, it makes \":e+35 file\" work\n         * correctly.\n         *\n         * !!!\n         * Historically, lines with multiple adjacent (or <blank> separated)\n         * command separators were very strange.  For example, the command\n         * |||<carriage-return>, when the cursor was on line 1, displayed\n         * lines 2, 3 and 5 of the file.  In addition, the command \"   |  \"\n         * would only display the line after the next line, instead of the\n         * next two lines.  No ideas why.  It worked reasonably when executed\n         * from vi mode, and displayed lines 2, 3, and 4, so we do a default\n         * command for each separator.\n         */\n#define SINGLE_CHAR_COMMANDS    \"\\004!#&*<=>@~\"\n        newscreen = 0;\n        if (ecp->clen != 0 && ecp->cp[0] != '|' && ecp->cp[0] != '\\n') {\n                if (strchr(SINGLE_CHAR_COMMANDS, *ecp->cp)) {\n                        p = ecp->cp;\n                        ++ecp->cp;\n                        --ecp->clen;\n                        namelen = 1;\n                } else {\n                        for (p = ecp->cp;\n                            ecp->clen > 0; --ecp->clen, ++ecp->cp)\n                                if (!isalpha(*ecp->cp))\n                                        break;\n                        if ((namelen = ecp->cp - p) == 0) {\n                                msgq(sp, M_ERR, \"Unknown command name\");\n                                goto err;\n                        }\n                }\n\n                /*\n                 * !!!\n                 * Historic vi permitted flags to immediately follow any\n                 * subset of the 'delete' command, but then did not permit\n                 * further arguments (flag, buffer, count).  Make it work.\n                 * Permit further arguments for the few shreds of dignity\n                 * it offers.\n                 *\n                 * Adding commands that start with 'd', and match \"delete\"\n                 * up to a l, p, +, - or # character can break this code.\n                 *\n                 * !!!\n                 * Capital letters beginning the command names ex, edit,\n                 * next, previous, tag and visual (in vi mode) indicate the\n                 * command should happen in a new screen.\n                 */\n                switch (p[0]) {\n                case 'd':\n                        for (s = p,\n                            t = cmds[C_DELETE].name; *s == *t; ++s, ++t);\n                        if (s[0] == 'l' || s[0] == 'p' || s[0] == '+' ||\n                            s[0] == '-' || s[0] == '^' || s[0] == '#') {\n                                len = (ecp->cp - p) - (s - p);\n                                ecp->cp -= len;\n                                ecp->clen += len;\n                                ecp->rcmd = cmds[C_DELETE];\n                                ecp->rcmd.syntax = \"1bca1\";\n                                ecp->cmd = &ecp->rcmd;\n                                goto skip_srch;\n                        }\n                        break;\n                case 'E': case 'F': case 'N': case 'P': case 'T': case 'V':\n                        newscreen = 1;\n                        p[0] = tolower(p[0]);\n                        break;\n                }\n\n                /*\n                 * Search the table for the command.\n                 *\n                 * !!!\n                 * Historic vi permitted the mark to immediately follow the\n                 * 'k' in the 'k' command.  Make it work.\n                 *\n                 * !!!\n                 * Historic vi permitted any flag to follow the s command, e.g.\n                 * \"s/e/E/|s|sgc3p\" was legal.  Make the command \"sgc\" work.\n                 * Since the following characters all have to be flags, i.e.\n                 * alphabetics, we can let the s command routine return errors\n                 * if it was some illegal command string.  This code will break\n                 * if an \"sg\" or similar command is ever added.  The substitute\n                 * code doesn't care if it's a \"cgr\" flag or a \"#lp\" flag that\n                 * follows the 's', but we limit the choices here to \"cgr\" so\n                 * that we get unknown command messages for wrong combinations.\n                 */\n                if ((ecp->cmd = ex_comm_search(p, namelen)) == NULL)\n                        switch (p[0]) {\n                        case 'k':\n                                if (namelen == 2) {\n                                        ecp->cp -= namelen - 1;\n                                        ecp->clen += namelen - 1;\n                                        ecp->cmd = &cmds[C_K];\n                                        break;\n                                }\n                                goto unknown;\n                        case 's':\n                                for (s = p + 1, cnt = namelen; --cnt; ++s)\n                                        if (s[0] != 'c' &&\n                                            s[0] != 'g' && s[0] != 'r')\n                                                break;\n                                if (cnt == 0) {\n                                        ecp->cp -= namelen - 1;\n                                        ecp->clen += namelen - 1;\n                                        ecp->rcmd = cmds[C_SUBSTITUTE];\n                                        ecp->rcmd.fn = ex_subagain;\n                                        ecp->cmd = &ecp->rcmd;\n                                        break;\n                                }\n                                /* FALLTHROUGH */\n                        default:\nunknown:                        if (newscreen)\n                                        p[0] = toupper(p[0]);\n                                if (sp != NULL && p != NULL && namelen > 1)\n                                        ex_unknown(sp, p, namelen);\n                                goto err;\n                        }\n\n                /*\n                 * The visual command has a different syntax when called\n                 * from ex than when called from a vi colon command.  FMH.\n                 * Make the change now, before we test for the newscreen\n                 * semantic, so that we're testing the right one.\n                 */\nskip_srch:      if (ecp->cmd == &cmds[C_VISUAL_EX] && F_ISSET(sp, SC_VI))\n                        ecp->cmd = &cmds[C_VISUAL_VI];\n\n                /*\n                 * !!!\n                 * Historic vi permitted a capital 'P' at the beginning of\n                 * any command that started with 'p'.  Probably wanted the\n                 * P[rint] command for backward compatibility, and the code\n                 * just made Preserve and Put work by accident.  Nvi uses\n                 * Previous to mean previous-in-a-new-screen, so be careful.\n                 */\n                if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN) &&\n                    (ecp->cmd == &cmds[C_PRINT] ||\n                    ecp->cmd == &cmds[C_PRESERVE]))\n                        newscreen = 0;\n\n                /* Test for a newscreen associated with this command. */\n                if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN))\n                        goto unknown;\n\n                /* Secure means no shell access. */\n                if (F_ISSET(ecp->cmd, E_SECURE) && O_ISSET(sp, O_SECURE)) {\n                        ex_emsg(sp, ecp->cmd->name, EXM_SECURE);\n                        goto err;\n                }\n\n                /*\n                 * Multiple < and > characters; another \"feature\".  Note,\n                 * The string passed to the underlying function may not be\n                 * NULL terminated in this case.\n                 */\n                if ((ecp->cmd == &cmds[C_SHIFTL] && *p == '<') ||\n                    (ecp->cmd == &cmds[C_SHIFTR] && *p == '>')) {\n                        for (ch = *p;\n                            ecp->clen > 0; --ecp->clen, ++ecp->cp)\n                                if (*ecp->cp != ch)\n                                        break;\n                        if (argv_exp0(sp, ecp, p, ecp->cp - p))\n                                goto err;\n                }\n\n                /* Set the format style flags for the next command. */\n                if (ecp->cmd == &cmds[C_HASH])\n                        exp->fdef = E_C_HASH;\n                else if (ecp->cmd == &cmds[C_LIST])\n                        exp->fdef = E_C_LIST;\n                else if (ecp->cmd == &cmds[C_PRINT])\n                        exp->fdef = E_C_PRINT;\n                F_CLR(ecp, E_USELASTCMD);\n        } else {\n                /* Print is the default command. */\n                ecp->cmd = &cmds[C_PRINT];\n\n                /* Set the saved format flags. */\n                F_SET(ecp, exp->fdef);\n\n                /*\n                 * !!!\n                 * If no address was specified, and it's not a global command,\n                 * we up the address by one.  (I have no idea why globals are\n                 * exempted, but it's (ahem) historic practice.)\n                 */\n                if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) {\n                        ecp->addrcnt = 1;\n                        ecp->addr1.lno = sp->lno + 1;\n                        ecp->addr1.cno = sp->cno;\n                }\n\n                F_SET(ecp, E_USELASTCMD);\n        }\n\n        /*\n         * !!!\n         * Historically, the number option applied to both ex and vi.  One\n         * strangeness was that ex didn't switch display formats until a\n         * command was entered, e.g. <CR>'s after the set didn't change to\n         * the new format, but :1p would.\n         */\n        if (O_ISSET(sp, O_NUMBER)) {\n                F_SET(ecp, E_OPTNUM);\n                FL_SET(ecp->iflags, E_C_HASH);\n        } else\n                F_CLR(ecp, E_OPTNUM);\n\n        /* Check for ex mode legality. */\n        if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) {\n                msgq(sp, M_ERR,\n                    \"%s: command not available in ex mode\", ecp->cmd->name);\n                goto err;\n        }\n\n        /* Add standard command flags. */\n        F_SET(ecp, ecp->cmd->flags);\n        if (!newscreen)\n                F_CLR(ecp, E_NEWSCREEN);\n\n        /*\n         * There are three normal termination cases for an ex command.  They\n         * are the end of the string (ecp->clen), or unescaped (by <literal\n         * next> characters) <newline> or '|' characters.  As we're now past\n         * possible addresses, we can determine how long the command is, so we\n         * don't have to look for all the possible terminations.  Naturally,\n         * there are some exciting special cases:\n         *\n         * 1: The bang, global, v and the filter versions of the read and\n         *    write commands are delimited by <newline>s (they can contain\n         *    shell pipes).\n         * 2: The ex, edit, next and visual in vi mode commands all take ex\n         *    commands as their first arguments.\n         * 3: The s command takes an RE as its first argument, and wants it\n         *    to be specially delimited.\n         *\n         * Historically, '|' characters in the first argument of the ex, edit,\n         * next, vi visual, and s commands didn't delimit the command.  And,\n         * in the filter cases for read and write, and the bang, global and v\n         * commands, they did not delimit the command at all.\n         *\n         * For example, the following commands were legal:\n         *\n         *      :edit +25|s/abc/ABC/ file.c\n         *      :s/|/PIPE/\n         *      :read !spell % | columnate\n         *      :global/pattern/p|l\n         *\n         * It's not quite as simple as it sounds, however.  The command:\n         *\n         *      :s/a/b/|s/c/d|set\n         *\n         * was also legal, i.e. the historic ex parser (using the word loosely,\n         * since \"parser\" implies some regularity of syntax) delimited the RE's\n         * based on its delimiter and not anything so irretrievably vulgar as a\n         * command syntax.\n         *\n         * Anyhow, the following code makes this all work.  First, for the\n         * special cases we move past their special argument(s).  Then, we\n         * do normal command processing on whatever is left.  Barf-O-Rama.\n         */\n        discard = 0;            /* Characters discarded from the command. */\n        arg1_len = 0;\n        ecp->save_cmd = ecp->cp;\n        if (ecp->cmd == &cmds[C_EDIT] || ecp->cmd == &cmds[C_EX] ||\n            ecp->cmd == &cmds[C_NEXT] || ecp->cmd == &cmds[C_VISUAL_VI]) {\n                /*\n                 * Move to the next non-whitespace character.  A '!'\n                 * immediately following the command is eaten as a\n                 * force flag.\n                 */\n                if (ecp->clen > 0 && *ecp->cp == '!') {\n                        ++ecp->cp;\n                        --ecp->clen;\n                        FL_SET(ecp->iflags, E_C_FORCE);\n\n                        /* Reset, don't reparse. */\n                        ecp->save_cmd = ecp->cp;\n                }\n                for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)\n                        if (!isblank(*ecp->cp))\n                                break;\n                /*\n                 * QUOTING NOTE:\n                 *\n                 * The historic implementation ignored all escape characters\n                 * so there was no way to put a space or newline into the +cmd\n                 * field.  We do a simplistic job of fixing it by moving to the\n                 * first whitespace character that isn't escaped.  The escaping\n                 * characters are stripped as no longer useful.\n                 */\n                if (ecp->clen > 0 && *ecp->cp == '+') {\n                        ++ecp->cp;\n                        --ecp->clen;\n                        for (arg1 = p = ecp->cp;\n                            ecp->clen > 0; --ecp->clen, ++ecp->cp) {\n                                ch = *ecp->cp;\n                                if (IS_ESCAPE(sp, ecp, ch) &&\n                                    ecp->clen > 1) {\n                                        ++discard;\n                                        --ecp->clen;\n                                        ch = *++ecp->cp;\n                                } else if (isblank(ch))\n                                        break;\n                                *p++ = ch;\n                        }\n                        arg1_len = ecp->cp - arg1;\n\n                        /* Reset, so the first argument isn't reparsed. */\n                        ecp->save_cmd = ecp->cp;\n                }\n        } else if (ecp->cmd == &cmds[C_BANG] ||\n            ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) {\n                /*\n                 * QUOTING NOTE:\n                 *\n                 * We use backslashes to escape <newline> characters, although\n                 * this wasn't historic practice for the bang command.  It was\n                 * for the global and v commands, and it's common usage when\n                 * doing text insert during the command.  Escaping characters\n                 * are stripped as no longer useful.\n                 */\n                for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) {\n                        ch = *ecp->cp;\n                        if (ch == '\\\\' && ecp->clen > 1 && ecp->cp[1] == '\\n') {\n                                ++discard;\n                                --ecp->clen;\n                                ch = *++ecp->cp;\n\n                                ++gp->if_lno;\n                                ++ecp->if_lno;\n                        } else if (ch == '\\n')\n                                break;\n                        *p++ = ch;\n                }\n        } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &cmds[C_WRITE]) {\n                /*\n                 * For write commands, if the next character is a <blank>, and\n                 * the next non-blank character is a '!', it's a filter command\n                 * and we want to eat everything up to the <newline>.  For read\n                 * commands, if the next non-blank character is a '!', it's a\n                 * filter command and we want to eat everything up to the next\n                 * <newline>.  Otherwise, we're done.\n                 */\n                for (tmp = 0; ecp->clen > 0; --ecp->clen, ++ecp->cp) {\n                        ch = *ecp->cp;\n                        if (isblank(ch))\n                                tmp = 1;\n                        else\n                                break;\n                }\n                if (ecp->clen > 0 && ch == '!' &&\n                    (ecp->cmd == &cmds[C_READ] || tmp))\n                        for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)\n                                if (ecp->cp[0] == '\\n')\n                                        break;\n        } else if (ecp->cmd == &cmds[C_SUBSTITUTE]) {\n                /*\n                 * Move to the next non-whitespace character, we'll use it as\n                 * the delimiter.  If the character isn't an alphanumeric or\n                 * a '|', it's the delimiter, so parse it.  Otherwise, we're\n                 * into something like \":s g\", so use the special s command.\n                 */\n                for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)\n                        if (!isblank(ecp->cp[0]))\n                                break;\n\n                if (isalnum(ecp->cp[0]) || ecp->cp[0] == '|') {\n                        ecp->rcmd = cmds[C_SUBSTITUTE];\n                        ecp->rcmd.fn = ex_subagain;\n                        ecp->cmd = &ecp->rcmd;\n                } else if (ecp->clen > 0) {\n                        /*\n                         * QUOTING NOTE:\n                         *\n                         * Backslashes quote delimiter characters for RE's.\n                         * The backslashes are NOT removed since they'll be\n                         * used by the RE code.  Move to the third delimiter\n                         * that's not escaped (or the end of the command).\n                         */\n                        delim = *ecp->cp;\n                        ++ecp->cp;\n                        --ecp->clen;\n                        for (cnt = 2; ecp->clen > 0 &&\n                            cnt != 0; --ecp->clen, ++ecp->cp)\n                                if (ecp->cp[0] == '\\\\' &&\n                                    ecp->clen > 1) {\n                                        ++ecp->cp;\n                                        --ecp->clen;\n                                } else if (ecp->cp[0] == delim)\n                                        --cnt;\n                }\n        }\n\n        /*\n         * Use normal quoting and termination rules to find the end of this\n         * command.\n         *\n         * QUOTING NOTE:\n         *\n         * Historically, vi permitted ^V's to escape <newline>'s in the .exrc\n         * file.  It was almost certainly a bug, but that's what bug-for-bug\n         * compatibility means, Grasshopper.  Also, ^V's escape the command\n         * delimiters.  Literal next quote characters in front of the newlines,\n         * '|' characters or literal next characters are stripped as they're\n         * no longer useful.\n         */\n        vi_address = ecp->clen != 0 && ecp->cp[0] != '\\n';\n        for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) {\n                ch = ecp->cp[0];\n                if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) {\n                        tmp = ecp->cp[1];\n                        if (tmp == '\\n' || tmp == '|') {\n                                if (tmp == '\\n') {\n                                        ++gp->if_lno;\n                                        ++ecp->if_lno;\n                                }\n                                ++discard;\n                                --ecp->clen;\n                                ++ecp->cp;\n                                ch = tmp;\n                        }\n                } else if (ch == '\\n' || ch == '|') {\n                        if (ch == '\\n')\n                                F_SET(ecp, E_NEWLINE);\n                        --ecp->clen;\n                        break;\n                }\n                *p++ = ch;\n        }\n\n        /*\n         * Save off the next command information, go back to the\n         * original start of the command.\n         */\n        p = ecp->cp + 1;\n        ecp->cp = ecp->save_cmd;\n        ecp->save_cmd = p;\n        ecp->save_cmdlen = ecp->clen;\n        ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard;\n\n        /*\n         * QUOTING NOTE:\n         *\n         * The \"set tags\" command historically used a backslash, not the\n         * user's literal next character, to escape whitespace.  Handle\n         * it here instead of complicating the argv_exp3() code.  Note,\n         * this isn't a particularly complex trap, and if backslashes were\n         * legal in set commands, this would have to be much more complicated.\n         */\n        if (ecp->cmd == &cmds[C_SET])\n                for (p = ecp->cp, len = ecp->clen; len > 0; --len, ++p)\n                        if (*p == '\\\\')\n                                *p = CH_LITERAL;\n\n        /*\n         * Set the default addresses.  It's an error to specify an address for\n         * a command that doesn't take them.  If two addresses are specified\n         * for a command that only takes one, lose the first one.  Two special\n         * cases here, some commands take 0 or 2 addresses.  For most of them\n         * (the E_ADDR2_ALL flag), 0 defaults to the entire file.  For one\n         * (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines.\n         *\n         * Also, if the file is empty, some commands want to use an address of\n         * 0, i.e. the entire file is 0 to 0, and the default first address is\n         * 0.  Otherwise, an entire file is 1 to N and the default line is 1.\n         * Note, we also add the E_ADDR_ZERO flag to the command flags, for the\n         * case where the 0 address is only valid if it's a default address.\n         *\n         * Also, set a flag if we set the default addresses.  Some commands\n         * (ex: z) care if the user specified an address or if we just used\n         * the current cursor.\n         */\n        switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) {\n        case E_ADDR1:                           /* One address: */\n                switch (ecp->addrcnt) {\n                case 0:                         /* Default cursor/empty file. */\n                        ecp->addrcnt = 1;\n                        F_SET(ecp, E_ADDR_DEF);\n                        if (F_ISSET(ecp, E_ADDR_ZERODEF)) {\n                                if (db_last(sp, &lno))\n                                        goto err;\n                                if (lno == 0) {\n                                        ecp->addr1.lno = 0;\n                                        F_SET(ecp, E_ADDR_ZERO);\n                                } else\n                                        ecp->addr1.lno = sp->lno;\n                        } else\n                                ecp->addr1.lno = sp->lno;\n                        ecp->addr1.cno = sp->cno;\n                        break;\n                case 1:\n                        break;\n                case 2:                         /* Lose the first address. */\n                        ecp->addrcnt = 1;\n                        ecp->addr1 = ecp->addr2;\n                }\n                break;\n        case E_ADDR2_NONE:                      /* Zero/two addresses: */\n                if (ecp->addrcnt == 0)          /* Default to nothing. */\n                        break;\n                goto two_addr;\n        case E_ADDR2_ALL:                       /* Zero/two addresses: */\n                if (ecp->addrcnt == 0) {        /* Default entire/empty file. */\n                        F_SET(ecp, E_ADDR_DEF);\n                        ecp->addrcnt = 2;\n                        if (sp->ep == NULL)\n                                ecp->addr2.lno = 0;\n                        else if (db_last(sp, &ecp->addr2.lno))\n                                goto err;\n                        if (F_ISSET(ecp, E_ADDR_ZERODEF) &&\n                            ecp->addr2.lno == 0) {\n                                ecp->addr1.lno = 0;\n                                F_SET(ecp, E_ADDR_ZERO);\n                        } else\n                                ecp->addr1.lno = 1;\n                        ecp->addr1.cno = ecp->addr2.cno = 0;\n                        F_SET(ecp, E_ADDR2_ALL);\n                        break;\n                }\n                /* FALLTHROUGH */\n        case E_ADDR2:                           /* Two addresses: */\ntwo_addr:       switch (ecp->addrcnt) {\n                case 0:                         /* Default cursor/empty file. */\n                        ecp->addrcnt = 2;\n                        F_SET(ecp, E_ADDR_DEF);\n                        if (sp->lno == 1 &&\n                            F_ISSET(ecp, E_ADDR_ZERODEF)) {\n                                if (db_last(sp, &lno))\n                                        goto err;\n                                if (lno == 0) {\n                                        ecp->addr1.lno = ecp->addr2.lno = 0;\n                                        F_SET(ecp, E_ADDR_ZERO);\n                                } else\n                                        ecp->addr1.lno =\n                                            ecp->addr2.lno = sp->lno;\n                        } else\n                                ecp->addr1.lno = ecp->addr2.lno = sp->lno;\n                        ecp->addr1.cno = ecp->addr2.cno = sp->cno;\n                        break;\n                case 1:                         /* Default to first address. */\n                        ecp->addr2 = ecp->addr1;\n                        break;\n                case 2:\n                        break;\n                }\n                break;\n        default:\n                if (ecp->addrcnt)               /* Error. */\n                        goto usage;\n        }\n\n        /*\n         * !!!\n         * The ^D scroll command historically scrolled the value of the scroll\n         * option or to EOF.  It was an error if the cursor was already at EOF.\n         * (Leading addresses were permitted, but were then ignored.)\n         */\n        if (ecp->cmd == &cmds[C_SCROLL]) {\n                ecp->addrcnt = 2;\n                ecp->addr1.lno = sp->lno + 1;\n                ecp->addr2.lno = sp->lno + O_VAL(sp, O_SCROLL);\n                ecp->addr1.cno = ecp->addr2.cno = sp->cno;\n                if (db_last(sp, &lno))\n                        goto err;\n                if (lno != 0 && lno > sp->lno && ecp->addr2.lno > lno)\n                        ecp->addr2.lno = lno;\n        }\n\n        ecp->flagoff = 0;\n        for (p = ecp->cmd->syntax; *p != '\\0'; ++p) {\n                /*\n                 * The force flag is sensitive to leading whitespace, i.e.\n                 * \"next !\" is different from \"next!\".  Handle it before\n                 * skipping leading <blank>s.\n                 */\n                if (*p == '!') {\n                        if (ecp->clen > 0 && *ecp->cp == '!') {\n                                ++ecp->cp;\n                                --ecp->clen;\n                                FL_SET(ecp->iflags, E_C_FORCE);\n                        }\n                        continue;\n                }\n\n                /* Skip leading <blank>s. */\n                for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)\n                        if (!isblank(*ecp->cp))\n                                break;\n                if (ecp->clen == 0)\n                        break;\n\n                switch (*p) {\n                case '1':                               /* +, -, #, l, p */\n                        /*\n                         * !!!\n                         * Historically, some flags were ignored depending\n                         * on where they occurred in the command line.  For\n                         * example, in the command, \":3+++p--#\", historic vi\n                         * acted on the '#' flag, but ignored the '-' flags.\n                         * It's unambiguous what the flags mean, so we just\n                         * handle them regardless of the stupidity of their\n                         * location.\n                         */\n                        for (; ecp->clen; --ecp->clen, ++ecp->cp)\n                                switch (*ecp->cp) {\n                                case '+':\n                                        ++ecp->flagoff;\n                                        break;\n                                case '-':\n                                case '^':\n                                        --ecp->flagoff;\n                                        break;\n                                case '#':\n                                        F_CLR(ecp, E_OPTNUM);\n                                        FL_SET(ecp->iflags, E_C_HASH);\n                                        exp->fdef |= E_C_HASH;\n                                        break;\n                                case 'l':\n                                        FL_SET(ecp->iflags, E_C_LIST);\n                                        exp->fdef |= E_C_LIST;\n                                        break;\n                                case 'p':\n                                        FL_SET(ecp->iflags, E_C_PRINT);\n                                        exp->fdef |= E_C_PRINT;\n                                        break;\n                                default:\n                                        goto end_case1;\n                                }\nend_case1:              break;\n                case '2':                               /* -, ., +, ^ */\n                case '3':                               /* -, ., +, ^, = */\n                        for (; ecp->clen; --ecp->clen, ++ecp->cp)\n                                switch (*ecp->cp) {\n                                case '-':\n                                        FL_SET(ecp->iflags, E_C_DASH);\n                                        break;\n                                case '.':\n                                        FL_SET(ecp->iflags, E_C_DOT);\n                                        break;\n                                case '+':\n                                        FL_SET(ecp->iflags, E_C_PLUS);\n                                        break;\n                                case '^':\n                                        FL_SET(ecp->iflags, E_C_CARAT);\n                                        break;\n                                case '=':\n                                        if (*p == '3') {\n                                                FL_SET(ecp->iflags, E_C_EQUAL);\n                                                break;\n                                        }\n                                        /* FALLTHROUGH */\n                                default:\n                                        goto end_case23;\n                                }\nend_case23:             break;\n                case 'b':                               /* buffer */\n                        /*\n                         * !!!\n                         * Historically, \"d #\" was a delete with a flag, not a\n                         * delete into the '#' buffer.  If the current command\n                         * permits a flag, don't use one as a buffer.  However,\n                         * the 'l' and 'p' flags were legal buffer names in the\n                         * historic ex, and were used as buffers, not flags.\n                         */\n                        if ((ecp->cp[0] == '+' || ecp->cp[0] == '-' ||\n                            ecp->cp[0] == '^' || ecp->cp[0] == '#') &&\n                            strchr(p, '1') != NULL)\n                                break;\n                        /*\n                         * !!!\n                         * Digits can't be buffer names in ex commands, or the\n                         * command \"d2\" would be a delete into buffer '2', and\n                         * not a two-line deletion.\n                         */\n                        if (!isdigit(ecp->cp[0])) {\n                                ecp->buffer = *ecp->cp;\n                                ++ecp->cp;\n                                --ecp->clen;\n                                FL_SET(ecp->iflags, E_C_BUFFER);\n                        }\n                        break;\n                case 'c':                               /* count [01+a] */\n                        ++p;\n                        /* Validate any signed value. */\n                        if (!isdigit(*ecp->cp) && (*p != '+' ||\n                            (*ecp->cp != '+' && *ecp->cp != '-')))\n                                break;\n                        /* If a signed value, set appropriate flags. */\n                        if (*ecp->cp == '-')\n                                FL_SET(ecp->iflags, E_C_COUNT_NEG);\n                        else if (*ecp->cp == '+')\n                                FL_SET(ecp->iflags, E_C_COUNT_POS);\n                        if ((nret =\n                            nget_slong(&ltmp, ecp->cp, &t, 10)) != NUM_OK) {\n                                ex_badaddr(sp, NULL, A_NOTSET, nret);\n                                goto err;\n                        }\n                        if (ltmp == 0 && *p != '0') {\n                                msgq(sp, M_ERR, \"Count may not be zero\");\n                                goto err;\n                        }\n                        ecp->clen -= (t - ecp->cp);\n                        ecp->cp = t;\n\n                        /*\n                         * Counts as address offsets occur in commands taking\n                         * two addresses.  Historic vi practice was to use\n                         * the count as an offset from the *second* address.\n                         *\n                         * Set a count flag; some underlying commands (see\n                         * join) do different things with counts than with\n                         * line addresses.\n                         */\n                        if (*p == 'a') {\n                                ecp->addr1 = ecp->addr2;\n                                ecp->addr2.lno = ecp->addr1.lno + ltmp - 1;\n                        } else\n                                ecp->count = ltmp;\n                        FL_SET(ecp->iflags, E_C_COUNT);\n                        break;\n                case 'f':                               /* file */\n                        if (argv_exp2(sp, ecp, ecp->cp, ecp->clen))\n                                goto err;\n                        goto arg_cnt_chk;\n                case 'l':                               /* line */\n                        /*\n                         * Get a line specification.\n                         *\n                         * If the line was a search expression, we may have\n                         * changed state during the call, and we're now\n                         * searching the file.  Push ourselves onto the state\n                         * stack.\n                         */\n                        if (ex_line(sp, ecp, &cur, &isaddr, &tmp))\n                                goto rfail;\n                        if (tmp)\n                                goto err;\n\n                        /* Line specifications are always required. */\n                        if (!isaddr) {\n                                msgq_str(sp, M_ERR, ecp->cp,\n                                     \"%s: bad line specification\");\n                                goto err;\n                        }\n                        /*\n                         * The target line should exist for these commands,\n                         * but 0 is legal for them as well.\n                         */\n                        if (cur.lno != 0 && !db_exist(sp, cur.lno)) {\n                                ex_badaddr(sp, NULL, A_EOF, NUM_OK);\n                                goto err;\n                        }\n                        ecp->lineno = cur.lno;\n                        break;\n                case 'S':                               /* string, file exp. */\n                        if (ecp->clen != 0) {\n                                if (argv_exp1(sp, ecp, ecp->cp,\n                                    ecp->clen, ecp->cmd == &cmds[C_BANG]))\n                                        goto err;\n                                goto addr_verify;\n                        }\n                        /* FALLTHROUGH */\n                case 's':                               /* string */\n                        if (argv_exp0(sp, ecp, ecp->cp, ecp->clen))\n                                goto err;\n                        goto addr_verify;\n                case 'W':                               /* word string */\n                        /*\n                         * QUOTING NOTE:\n                         *\n                         * Literal next characters escape the following\n                         * character.  Quoting characters are stripped here\n                         * since they are no longer useful.\n                         *\n                         * First there was the word.\n                         */\n                        for (p = t = ecp->cp;\n                            ecp->clen > 0; --ecp->clen, ++ecp->cp) {\n                                ch = *ecp->cp;\n                                if (IS_ESCAPE(sp,\n                                    ecp, ch) && ecp->clen > 1) {\n                                        --ecp->clen;\n                                        *p++ = *++ecp->cp;\n                                } else if (isblank(ch)) {\n                                        ++ecp->cp;\n                                        --ecp->clen;\n                                        break;\n                                } else\n                                        *p++ = ch;\n                        }\n                        if (argv_exp0(sp, ecp, t, p - t))\n                                goto err;\n\n                        /* Delete intervening whitespace. */\n                        for (; ecp->clen > 0;\n                            --ecp->clen, ++ecp->cp) {\n                                ch = *ecp->cp;\n                                if (!isblank(ch))\n                                        break;\n                        }\n                        if (ecp->clen == 0)\n                                goto usage;\n\n                        /* Followed by the string. */\n                        for (p = t = ecp->cp; ecp->clen > 0;\n                            --ecp->clen, ++ecp->cp, ++p) {\n                                ch = *ecp->cp;\n                                if (IS_ESCAPE(sp,\n                                    ecp, ch) && ecp->clen > 1) {\n                                        --ecp->clen;\n                                        *p = *++ecp->cp;\n                                } else\n                                        *p = ch;\n                        }\n                        if (argv_exp0(sp, ecp, t, p - t))\n                                goto err;\n                        goto addr_verify;\n                case 'w':                               /* word */\n                        if (argv_exp3(sp, ecp, ecp->cp, ecp->clen))\n                                goto err;\narg_cnt_chk:            if (*++p != 'N') {              /* N */\n                                /*\n                                 * If a number is specified, must either be\n                                 * 0 or that number, if optional, and that\n                                 * number, if required.\n                                 */\n                                tmp = *p - '0';\n                                if ((*++p != 'o' || exp->argsoff != 0) &&\n                                    exp->argsoff != tmp)\n                                        goto usage;\n                        }\n                        goto addr_verify;\n                default:\n                        msgq(sp, M_ERR,\n                            \"Internal syntax table error (%s: %s)\",\n                            ecp->cmd->name, KEY_NAME(sp, *p));\n                }\n        }\n\n        /* Skip trailing whitespace. */\n        for (; ecp->clen > 0; --ecp->clen) {\n                ch = *ecp->cp++;\n                if (!isblank(ch))\n                        break;\n        }\n\n        /*\n         * There shouldn't be anything left, and no more required fields,\n         * i.e neither 'l' or 'r' in the syntax string.\n         */\n        if (ecp->clen != 0 || strpbrk(p, \"lr\")) {\nusage:          msgq(sp, M_ERR, \"Usage: %s\", ecp->cmd->usage);\n                goto err;\n        }\n\n        /*\n         * Verify that the addresses are legal.  Check the addresses here,\n         * because this is a place where all ex addresses pass through.\n         * (They don't all pass through ex_line(), for instance.)  We're\n         * assuming that any non-existent line doesn't exist because it's\n         * past the end-of-file.  That's a pretty good guess.\n         *\n         * If it's a \"default vi command\", an address of zero is okay.\n         */\naddr_verify:\n        switch (ecp->addrcnt) {\n        case 2:\n                /*\n                 * Historic ex/vi permitted commands with counts to go past\n                 * EOF.  So, for example, if the file only had 5 lines, the\n                 * ex command \"1,6>\" would fail, but the command \">300\"\n                 * would succeed.  Since we don't want to have to make all\n                 * of the underlying commands handle random line numbers,\n                 * fix it here.\n                 */\n                if (ecp->addr2.lno == 0) {\n                        if (!F_ISSET(ecp, E_ADDR_ZERO) &&\n                            (F_ISSET(sp, SC_EX) ||\n                            !F_ISSET(ecp, E_USELASTCMD))) {\n                                ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK);\n                                goto err;\n                        }\n                } else if (!db_exist(sp, ecp->addr2.lno)) {\n                        if (FL_ISSET(ecp->iflags, E_C_COUNT)) {\n                                if (db_last(sp, &lno))\n                                        goto err;\n                                ecp->addr2.lno = lno;\n                        } else {\n                                ex_badaddr(sp, NULL, A_EOF, NUM_OK);\n                                goto err;\n                        }\n                }\n                /* FALLTHROUGH */\n        case 1:\n                if (ecp->addr1.lno == 0) {\n                        if (!F_ISSET(ecp, E_ADDR_ZERO) &&\n                            (F_ISSET(sp, SC_EX) ||\n                            !F_ISSET(ecp, E_USELASTCMD))) {\n                                ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK);\n                                goto err;\n                        }\n                } else if (!db_exist(sp, ecp->addr1.lno)) {\n                        ex_badaddr(sp, NULL, A_EOF, NUM_OK);\n                        goto err;\n                }\n                break;\n        }\n\n        /*\n         * If doing a default command and there's nothing left on the line,\n         * vi just moves to the line.  For example, \":3\" and \":'a,'b\" just\n         * move to line 3 and line 'b, respectively, but \":3|\" prints line 3.\n         *\n         * !!!\n         * In addition, IF THE LINE CHANGES, move to the first nonblank of\n         * the line.\n         *\n         * !!!\n         * This is done before the absolute mark gets set; historically,\n         * \"/a/,/b/\" did NOT set vi's absolute mark, but \"/a/,/b/d\" did.\n         */\n        if ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) &&\n            F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) {\n                switch (ecp->addrcnt) {\n                case 2:\n                        if (sp->lno !=\n                            (ecp->addr2.lno ? ecp->addr2.lno : 1)) {\n                                sp->lno =\n                                    ecp->addr2.lno ? ecp->addr2.lno : 1;\n                                sp->cno = 0;\n                                (void)nonblank(sp, sp->lno, &sp->cno);\n                        }\n                        break;\n                case 1:\n                        if (sp->lno !=\n                            (ecp->addr1.lno ? ecp->addr1.lno : 1)) {\n                                sp->lno =\n                                    ecp->addr1.lno ? ecp->addr1.lno : 1;\n                                sp->cno = 0;\n                                (void)nonblank(sp, sp->lno, &sp->cno);\n                        }\n                        break;\n                }\n                ecp->cp = ecp->save_cmd;\n                ecp->clen = ecp->save_cmdlen;\n                goto loop;\n        }\n\n        /*\n         * Set the absolute mark -- we have to set it for vi here, in case\n         * it's a compound command, e.g. \":5p|6\" should set the absolute\n         * mark for vi.\n         */\n        if (F_ISSET(ecp, E_ABSMARK)) {\n                cur.lno = sp->lno;\n                cur.cno = sp->cno;\n                F_CLR(ecp, E_ABSMARK);\n                if (mark_set(sp, ABSMARK1, &cur, 1))\n                        goto err;\n        }\n\n#if defined(DEBUG) && defined(COMLOG)\n        ex_comlog(sp, ecp);\n#endif /* if defined(DEBUG) && defined(COMLOG) */\n        /* Increment the command count if not called from vi. */\n        if (F_ISSET(sp, SC_EX))\n                ++sp->ccnt;\n\n        /*\n         * If file state available, and not doing a global command,\n         * log the start of an action.\n         */\n        if (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL))\n                (void)log_cursor(sp);\n\n        /*\n         * !!!\n         * There are two special commands for the purposes of this code: the\n         * default command (<carriage-return>) or the scrolling commands (^D\n         * and <EOF>) as the first non-<blank> characters  in the line.\n         *\n         * If this is the first command in the command line, we received the\n         * command from the ex command loop and we're talking to a tty, and\n         * and there's nothing else on the command line, and it's one of the\n         * special commands, we move back up to the previous line, and erase\n         * the prompt character with the output.  Since ex runs in canonical\n         * mode, we don't have to do anything else, a <newline> has already\n         * been echoed by the tty driver.  It's OK if vi calls us -- we won't\n         * be in ex mode so we'll do nothing.\n         */\n        if (F_ISSET(ecp, E_NRSEP)) {\n                if (sp->ep != NULL &&\n                    F_ISSET(sp, SC_EX) && !F_ISSET(gp, G_SCRIPTED) &&\n                    (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL]))\n                        gp->scr_ex_adjust(sp, EX_TERM_SCROLL);\n                F_CLR(ecp, E_NRSEP);\n        }\n\n        /*\n         * Call the underlying function for the ex command.\n         *\n         * XXX\n         * Interrupts behave like errors, for now.\n         */\n        if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) {\n                if (F_ISSET(gp, G_SCRIPTED))\n                        F_SET(sp, SC_EXIT_FORCE);\n                goto err;\n        }\n\n#ifdef DEBUG\n        /* Make sure no function left global temporary space locked. */\n        if (F_ISSET(gp, G_TMP_INUSE)) {\n                F_CLR(gp, G_TMP_INUSE);\n                msgq(sp, M_ERR, \"%s: temporary buffer not released\",\n                    ecp->cmd->name);\n        }\n#endif /* ifdef DEBUG */\n        /*\n         * Ex displayed the number of lines modified immediately after each\n         * command, so the command \"1,10d|1,10d\" would display:\n         *\n         *      10 lines deleted\n         *      10 lines deleted\n         *      <autoprint line>\n         *\n         * Executing ex commands from vi only reported the final modified\n         * lines message -- that's wrong enough that we don't match it.\n         */\n        if (F_ISSET(sp, SC_EX))\n                mod_rpt(sp);\n\n        /*\n         * Integrate any offset parsed by the underlying command, and make\n         * sure the referenced line exists.\n         *\n         * XXX\n         * May not match historic practice (which I've never been able to\n         * completely figure out.)  For example, the '=' command from vi\n         * mode often got the offset wrong, and complained it was too large,\n         * but didn't seem to have a problem with the cursor.  If anyone\n         * complains, ask them how it's supposed to work, they might know.\n         */\n        if (sp->ep != NULL && ecp->flagoff) {\n                if (ecp->flagoff < 0) {\n                        if (sp->lno <= -ecp->flagoff) {\n                                msgq(sp, M_ERR,\n                                    \"Flag offset to before line 1\");\n                                goto err;\n                        }\n                } else {\n                        if (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) {\n                                ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER);\n                                goto err;\n                        }\n                        if (!db_exist(sp, sp->lno + ecp->flagoff)) {\n                                msgq(sp, M_ERR,\n                                    \"Flag offset past end-of-file\");\n                                goto err;\n                        }\n                }\n                sp->lno += ecp->flagoff;\n        }\n\n        /*\n         * If the command executed successfully, we may want to display a line\n         * based on the autoprint option or an explicit print flag.  (Make sure\n         * that there's a line to display.)  Also, the autoprint edit option is\n         * turned off for the duration of global commands.\n         */\n        if (F_ISSET(sp, SC_EX) && sp->ep != NULL && sp->lno != 0) {\n                /*\n                 * The print commands have already handled the `print' flags.\n                 * If so, clear them.\n                 */\n                if (FL_ISSET(ecp->iflags, E_CLRFLAG))\n                        FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT);\n\n                /* If hash set only because of the number option, discard it. */\n                if (F_ISSET(ecp, E_OPTNUM))\n                        FL_CLR(ecp->iflags, E_C_HASH);\n\n                /*\n                 * If there was an explicit flag to display the new cursor line,\n                 * or autoprint is set and a change was made, display the line.\n                 * If any print flags were set use them, else default to print.\n                 */\n                LF_INIT(FL_ISSET(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT));\n                if (!LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT | E_NOAUTO) &&\n                    !F_ISSET(sp, SC_EX_GLOBAL) &&\n                    O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) {\n\n                        /* Honor the number option if autoprint is set. */\n                        if (F_ISSET(ecp, E_OPTNUM))\n                                LF_INIT(E_C_HASH);\n                        else\n                                LF_INIT(E_C_PRINT);\n                }\n\n                if (LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT)) {\n                        cur.lno = sp->lno;\n                        cur.cno = 0;\n                        (void)ex_print(sp, ecp, &cur, &cur, flags);\n                }\n        }\n\n        /*\n         * If the command had an associated \"+cmd\", it has to be executed\n         * before we finish executing any more of this ex command.  For\n         * example, consider a .exrc file that contains the following lines:\n         *\n         *      :set all\n         *      :edit +25 file.c|s/abc/ABC/|1\n         *      :3,5 print\n         *\n         * This can happen more than once -- the historic vi simply hung or\n         * dropped core, of course.  Prepend the + command back into the\n         * current command and continue.  We may have to add an additional\n         * <literal next> character.  We know that it will fit because we\n         * discarded at least one space and the + character.\n         */\n        if (arg1_len != 0) {\n                /*\n                 * If the last character of the + command was a <literal next>\n                 * character, it would be treated differently because of the\n                 * append.  Quote it, if necessary.\n                 */\n                if (IS_ESCAPE(sp, ecp, arg1[arg1_len - 1])) {\n                        *--ecp->save_cmd = CH_LITERAL;\n                        ++ecp->save_cmdlen;\n                }\n\n                ecp->save_cmd -= arg1_len;\n                ecp->save_cmdlen += arg1_len;\n                memmove(ecp->save_cmd, arg1, arg1_len);\n\n                /*\n                 * Any commands executed from a +cmd are executed starting at\n                 * the first column of the last line of the file -- NOT the\n                 * first nonblank.)  The main file startup code doesn't know\n                 * that a +cmd was set, however, so it may have put us at the\n                 * top of the file.  (Note, this is safe because we must have\n                 * switched files to get here.)\n                 */\n                F_SET(ecp, E_MOVETOEND);\n        }\n\n        /* Update the current command. */\n        ecp->cp = ecp->save_cmd;\n        ecp->clen = ecp->save_cmdlen;\n\n        /*\n         * !!!\n         * If we've changed screens or underlying files, any pending global or\n         * v command, or @ buffer that has associated addresses, has to be\n         * discarded.  This is historic practice for globals, and necessary for\n         * @ buffers that had associated addresses.\n         *\n         * Otherwise, if we've changed underlying files, it's not a problem,\n         * we continue with the rest of the ex command(s), operating on the\n         * new file.  However, if we switch screens (either by exiting or by\n         * an explicit command), we have no way of knowing where to put output\n         * messages, and, since we don't control screens here, we could screw\n         * up the upper layers, (e.g. we could exit/reenter a screen multiple\n         * times).  So, return and continue after we've got a new screen.\n         */\n        if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH)) {\n                at_found = gv_found = 0;\n                LIST_FOREACH(ecp, &sp->gp->ecq, q)\n                        switch (ecp->agv_flags) {\n                        case 0:\n                        case AGV_AT_NORANGE:\n                                break;\n                        case AGV_AT:\n                                if (!at_found) {\n                                        at_found = 1;\n                                        msgq(sp, M_ERR,\n                \"@ with range running when the file/screen changed\");\n                                }\n                                break;\n                        case AGV_GLOBAL:\n                        case AGV_V:\n                                if (!gv_found) {\n                                        gv_found = 1;\n                                        msgq(sp, M_ERR,\n                \"Global/v command running when the file/screen changed\");\n                                }\n                                break;\n                        default:\n                                abort();\n                        }\n                if (at_found || gv_found)\n                        goto discard;\n                if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_SSWITCH))\n                        goto rsuccess;\n        }\n\n        goto loop;\n        /* NOTREACHED */\n\nerr:    /*\n         * On command failure, we discard keys and pending commands remaining,\n         * as well as any keys that were mapped and waiting.  The save_cmdlen\n         * test is not necessarily correct.  If we fail early enough we don't\n         * know if the entire string was a single command or not.  Guess, as\n         * it's useful to know if commands other than the current one are being\n         * discarded.\n         */\n        if (ecp->save_cmdlen == 0)\n                for (; ecp->clen; --ecp->clen) {\n                        ch = *ecp->cp++;\n                        if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) {\n                                --ecp->clen;\n                                ++ecp->cp;\n                        } else if (ch == '\\n' || ch == '|') {\n                                if (ecp->clen > 1)\n                                        ecp->save_cmdlen = 1;\n                                break;\n                        }\n                }\n        if (ecp->save_cmdlen != 0 || LIST_FIRST(&gp->ecq) != &gp->excmd) {\ndiscard:        msgq(sp, M_BERR,\n                    \"Ex command failed: pending commands discarded\");\n                ex_discard(sp);\n        }\n        if (v_event_flush(sp, CH_MAPPED))\n                msgq(sp, M_BERR,\n                    \"Ex command failed: mapped keys discarded\");\n\nrfail:  tmp = 1;\n        if (0)\nrsuccess:       tmp = 0;\n\n        /* Turn off any file name error information. */\n        gp->if_name = NULL;\n\n        /* Turn off the global bit. */\n        F_CLR(sp, SC_EX_GLOBAL);\n\n        return (tmp);\n}\n\n/*\n * ex_range --\n *      Get a line range for ex commands, or perform a vi ex address search.\n *\n * PUBLIC: int ex_range(SCR *, EXCMD *, int *);\n */\nint\nex_range(SCR *sp, EXCMD *ecp, int *errp)\n{\n        enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr;\n        MARK m;\n        int isaddr;\n\n        *errp = 0;\n\n        /*\n         * Parse comma or semi-colon delimited line specs.\n         *\n         * Semi-colon delimiters update the current address to be the last\n         * address.  For example, the command\n         *\n         *      :3;/pattern/ecp->cp\n         *\n         * will search for pattern from line 3.  In addition, if ecp->cp\n         * is not a valid command, the current line will be left at 3, not\n         * at the original address.\n         *\n         * Extra addresses are discarded, starting with the first.\n         *\n         * !!!\n         * If any addresses are missing, they default to the current line.\n         * This was historically true for both leading and trailing comma\n         * delimited addresses as well as for trailing semicolon delimited\n         * addresses.  For consistency, we make it true for leading semicolon\n         * addresses as well.\n         */\n        for (addr = ADDR_NONE, ecp->addrcnt = 0; ecp->clen > 0;)\n                switch (*ecp->cp) {\n                case '%':               /* Entire file. */\n                        /* Vi ex address searches didn't permit % signs. */\n                        if (F_ISSET(ecp, E_VISEARCH))\n                                goto ret;\n\n                        /* It's an error if the file is empty. */\n                        if (sp->ep == NULL) {\n                                ex_badaddr(sp, NULL, A_EMPTY, NUM_OK);\n                                *errp = 1;\n                                return (0);\n                        }\n                        /*\n                         * !!!\n                         * A percent character addresses all of the lines in\n                         * the file.  Historically, it couldn't be followed by\n                         * any other address.  We do it as a text substitution\n                         * for simplicity.  POSIX 1003.2 is expected to follow\n                         * this practice.\n                         *\n                         * If it's an empty file, the first line is 0, not 1.\n                         */\n                        if (addr == ADDR_FOUND) {\n                                ex_badaddr(sp, NULL, A_COMBO, NUM_OK);\n                                *errp = 1;\n                                return (0);\n                        }\n                        if (db_last(sp, &ecp->addr2.lno))\n                                return (1);\n                        ecp->addr1.lno = ecp->addr2.lno == 0 ? 0 : 1;\n                        ecp->addr1.cno = ecp->addr2.cno = 0;\n                        ecp->addrcnt = 2;\n                        addr = ADDR_FOUND;\n                        ++ecp->cp;\n                        --ecp->clen;\n                        break;\n                case ',':               /* Comma delimiter. */\n                        /* Vi ex address searches didn't permit commas. */\n                        if (F_ISSET(ecp, E_VISEARCH))\n                                goto ret;\n                        /* FALLTHROUGH */\n                case ';':               /* Semi-colon delimiter. */\n                        if (sp->ep == NULL) {\n                                ex_badaddr(sp, NULL, A_EMPTY, NUM_OK);\n                                *errp = 1;\n                                return (0);\n                        }\n                        if (addr != ADDR_FOUND)\n                                switch (ecp->addrcnt) {\n                                case 0:\n                                        ecp->addr1.lno = sp->lno;\n                                        ecp->addr1.cno = sp->cno;\n                                        ecp->addrcnt = 1;\n                                        break;\n                                case 2:\n                                        ecp->addr1 = ecp->addr2;\n                                        /* FALLTHROUGH */\n                                case 1:\n                                        ecp->addr2.lno = sp->lno;\n                                        ecp->addr2.cno = sp->cno;\n                                        ecp->addrcnt = 2;\n                                        break;\n                                }\n                        if (*ecp->cp == ';')\n                                switch (ecp->addrcnt) {\n                                case 0:\n                                        abort();\n                                        /* NOTREACHED */\n                                case 1:\n                                        sp->lno = ecp->addr1.lno;\n                                        sp->cno = ecp->addr1.cno;\n                                        break;\n                                case 2:\n                                        sp->lno = ecp->addr2.lno;\n                                        sp->cno = ecp->addr2.cno;\n                                        break;\n                                }\n                        addr = ADDR_NEED;\n                        /* FALLTHROUGH */\n                case ' ':               /* Whitespace. */\n                case '\\t':              /* Whitespace. */\n                        ++ecp->cp;\n                        --ecp->clen;\n                        break;\n                default:\n                        /* Get a line specification. */\n                        if (ex_line(sp, ecp, &m, &isaddr, errp))\n                                return (1);\n                        if (*errp)\n                                return (0);\n                        if (!isaddr)\n                                goto ret;\n                        if (addr == ADDR_FOUND) {\n                                ex_badaddr(sp, NULL, A_COMBO, NUM_OK);\n                                *errp = 1;\n                                return (0);\n                        }\n                        switch (ecp->addrcnt) {\n                        case 0:\n                                ecp->addr1 = m;\n                                ecp->addrcnt = 1;\n                                break;\n                        case 1:\n                                ecp->addr2 = m;\n                                ecp->addrcnt = 2;\n                                break;\n                        case 2:\n                                ecp->addr1 = ecp->addr2;\n                                ecp->addr2 = m;\n                                break;\n                        }\n                        addr = ADDR_FOUND;\n                        break;\n                }\n\n        /*\n         * !!!\n         * Vi ex address searches are indifferent to order or trailing\n         * semi-colons.\n         */\nret:    if (F_ISSET(ecp, E_VISEARCH))\n                return (0);\n\n        if (addr == ADDR_NEED)\n                switch (ecp->addrcnt) {\n                case 0:\n                        ecp->addr1.lno = sp->lno;\n                        ecp->addr1.cno = sp->cno;\n                        ecp->addrcnt = 1;\n                        break;\n                case 2:\n                        ecp->addr1 = ecp->addr2;\n                        /* FALLTHROUGH */\n                case 1:\n                        ecp->addr2.lno = sp->lno;\n                        ecp->addr2.cno = sp->cno;\n                        ecp->addrcnt = 2;\n                        break;\n                }\n\n        if (ecp->addrcnt == 2 && ecp->addr2.lno < ecp->addr1.lno) {\n                msgq(sp, M_ERR,\n                    \"The second address is smaller than the first\");\n                *errp = 1;\n        }\n        return (0);\n}\n\n/*\n * ex_line --\n *      Get a single line address specifier.\n *\n * The way the \"previous context\" mark worked was that any \"non-relative\"\n * motion set it.  While ex/vi wasn't totally consistent about this, ANY\n * numeric address, search pattern, '$', or mark reference in an address\n * was considered non-relative, and set the value.  Which should explain\n * why we're hacking marks down here.  The problem was that the mark was\n * only set if the command was called, i.e. we have to set a flag and test\n * it later.\n *\n * XXX\n * This is probably still not exactly historic practice, although I think\n * it's fairly close.\n */\nstatic int\nex_line(SCR *sp, EXCMD *ecp, MARK *mp, int *isaddrp, int *errp)\n{\n        enum nresult nret;\n        long total, val;\n        int isneg;\n        int (*sf)(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int);\n        char *endp;\n\n        *isaddrp = *errp = 0;\n        F_CLR(ecp, E_DELTA);\n\n        /* No addresses permitted until a file has been read in. */\n        if (sp->ep == NULL && strchr(\"$0123456789'\\\\/?.+-^\", *ecp->cp)) {\n                ex_badaddr(sp, NULL, A_EMPTY, NUM_OK);\n                *errp = 1;\n                return (0);\n        }\n\n        switch (*ecp->cp) {\n        case '$':                               /* Last line in the file. */\n                *isaddrp = 1;\n                F_SET(ecp, E_ABSMARK);\n\n                mp->cno = 0;\n                if (db_last(sp, &mp->lno))\n                        return (1);\n                ++ecp->cp;\n                --ecp->clen;\n                break;                          /* Absolute line number. */\n        case '0': case '1': case '2': case '3': case '4':\n        case '5': case '6': case '7': case '8': case '9':\n                *isaddrp = 1;\n                F_SET(ecp, E_ABSMARK);\n\n                if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK) {\n                        ex_badaddr(sp, NULL, A_NOTSET, nret);\n                        *errp = 1;\n                        return (0);\n                }\n                if (!NPFITS(MAX_REC_NUMBER, 0, val)) {\n                        ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER);\n                        *errp = 1;\n                        return (0);\n                }\n                mp->lno = val;\n                mp->cno = 0;\n                ecp->clen -= (endp - ecp->cp);\n                ecp->cp = endp;\n                break;\n        case '\\'':                              /* Use a mark. */\n                *isaddrp = 1;\n                F_SET(ecp, E_ABSMARK);\n\n                if (ecp->clen == 1) {\n                        msgq(sp, M_ERR, \"No mark name supplied\");\n                        *errp = 1;\n                        return (0);\n                }\n                if (mark_get(sp, ecp->cp[1], mp, M_ERR)) {\n                        *errp = 1;\n                        return (0);\n                }\n                ecp->cp += 2;\n                ecp->clen -= 2;\n                break;\n        case '\\\\':                              /* Search: forward/backward. */\n                /*\n                 * !!!\n                 * I can't find any difference between // and \\/ or between\n                 * ?? and \\?.  Mark Horton doesn't remember there being any\n                 * difference.  C'est la vie.\n                 */\n                if (ecp->clen < 2 ||\n                    (ecp->cp[1] != '/' && ecp->cp[1] != '?')) {\n                        msgq(sp, M_ERR, \"\\\\ not followed by / or ?\");\n                        *errp = 1;\n                        return (0);\n                }\n                ++ecp->cp;\n                --ecp->clen;\n                sf = ecp->cp[0] == '/' ? f_search : b_search;\n                goto search;\n        case '/':                               /* Search forward. */\n                sf = f_search;\n                goto search;\n        case '?':                               /* Search backward. */\n                sf = b_search;\n\nsearch:         mp->lno = sp->lno;\n                mp->cno = sp->cno;\n                if (sf(sp, mp, mp, ecp->cp, ecp->clen, &endp,\n                    SEARCH_MSG | SEARCH_PARSE | SEARCH_SET |\n                    (F_ISSET(ecp, E_SEARCH_WMSG) ? SEARCH_WMSG : 0))) {\n                        *errp = 1;\n                        return (0);\n                }\n\n                /* Fix up the command pointers. */\n                ecp->clen -= (endp - ecp->cp);\n                ecp->cp = endp;\n\n                *isaddrp = 1;\n                F_SET(ecp, E_ABSMARK);\n                break;\n        case '.':                               /* Current position. */\n                *isaddrp = 1;\n                mp->cno = sp->cno;\n\n                /* If an empty file, then '.' is 0, not 1. */\n                if (sp->lno == 1) {\n                        if (db_last(sp, &mp->lno))\n                                return (1);\n                        if (mp->lno != 0)\n                                mp->lno = 1;\n                } else\n                        mp->lno = sp->lno;\n\n                /*\n                 * !!!\n                 * Historically, .<number> was the same as .+<number>, i.e.\n                 * the '+' could be omitted.  (This feature is found in ed\n                 * as well.)\n                 */\n                if (ecp->clen > 1 && isdigit(ecp->cp[1]))\n                        *ecp->cp = '+';\n                else {\n                        ++ecp->cp;\n                        --ecp->clen;\n                }\n                break;\n        }\n\n        /* Skip trailing <blank>s. */\n        for (; ecp->clen > 0 &&\n            isblank(ecp->cp[0]); ++ecp->cp, --ecp->clen);\n\n        /*\n         * Evaluate any offset.  If no address yet found, the offset\n         * is relative to \".\".\n         */\n        total = 0;\n        if (ecp->clen != 0 && (isdigit(ecp->cp[0]) ||\n            ecp->cp[0] == '+' || ecp->cp[0] == '-' ||\n            ecp->cp[0] == '^')) {\n                if (!*isaddrp) {\n                        *isaddrp = 1;\n                        mp->lno = sp->lno;\n                        mp->cno = sp->cno;\n                }\n                /*\n                 * Evaluate an offset, defined as:\n                 *\n                 *              [+-^<blank>]*[<blank>]*[0-9]*\n                 *\n                 * The rough translation is any number of signs, optionally\n                 * followed by numbers, or a number by itself, all <blank>\n                 * separated.\n                 *\n                 * !!!\n                 * All address offsets were additive, e.g. \"2 2 3p\" was the\n                 * same as \"7p\", or, \"/ZZZ/ 2\" was the same as \"/ZZZ/+2\".\n                 * Note, however, \"2 /ZZZ/\" was an error.  It was also legal\n                 * to insert signs without numbers, so \"3 - 2\" was legal, and\n                 * equal to 4.\n                 *\n                 * !!!\n                 * Offsets were historically permitted for any line address,\n                 * e.g. the command \"1,2 copy 2 2 2 2\" copied lines 1,2 after\n                 * line 8.\n                 *\n                 * !!!\n                 * Offsets were historically permitted for search commands,\n                 * and handled as addresses: \"/pattern/2 2 2\" was legal, and\n                 * referenced the 6th line after pattern.\n                 */\n                F_SET(ecp, E_DELTA);\n                for (;;) {\n                        for (; ecp->clen > 0 && isblank(ecp->cp[0]);\n                            ++ecp->cp, --ecp->clen);\n                        if (ecp->clen == 0 || (!isdigit(ecp->cp[0]) &&\n                            ecp->cp[0] != '+' && ecp->cp[0] != '-' &&\n                            ecp->cp[0] != '^'))\n                                break;\n                        if (!isdigit(ecp->cp[0]) &&\n                            !isdigit(ecp->cp[1])) {\n                                total += ecp->cp[0] == '+' ? 1 : -1;\n                                --ecp->clen;\n                                ++ecp->cp;\n                        } else {\n                                if (ecp->cp[0] == '-' ||\n                                    ecp->cp[0] == '^') {\n                                        ++ecp->cp;\n                                        --ecp->clen;\n                                        isneg = 1;\n                                } else\n                                        isneg = 0;\n\n                                /* Get a signed long, add it to the total. */\n                                if ((nret = nget_slong(&val,\n                                    ecp->cp, &endp, 10)) != NUM_OK ||\n                                    (nret = NADD_SLONG(total, val)) != NUM_OK) {\n                                        ex_badaddr(sp, NULL, A_NOTSET, nret);\n                                        *errp = 1;\n                                        return (0);\n                                }\n                                total += isneg ? -val : val;\n                                ecp->clen -= (endp - ecp->cp);\n                                ecp->cp = endp;\n                        }\n                }\n        }\n\n        /*\n         * Any value less than 0 is an error.  Make sure that the new value\n         * will fit into a recno_t.\n         */\n        if (*isaddrp && total != 0) {\n                if (total < 0) {\n                        if (-total > mp->lno) {\n                                msgq(sp, M_ERR,\n                            \"Reference to a line number less than 0\");\n                                *errp = 1;\n                                return (0);\n                        }\n                } else\n                        if (!NPFITS(MAX_REC_NUMBER, mp->lno, total)) {\n                                ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER);\n                                *errp = 1;\n                                return (0);\n                        }\n                mp->lno += total;\n        }\n        return (0);\n}\n\n/*\n * ex_load --\n *      Load up the next command, which may be an @ buffer or global command.\n */\nstatic int\nex_load(SCR *sp)\n{\n        GS *gp;\n        EXCMD *ecp;\n        RANGE *rp;\n\n        F_CLR(sp, SC_EX_GLOBAL);\n\n        /*\n         * Lose any exhausted commands.  We know that the first command\n         * can't be an AGV command, which makes things a bit easier.\n         */\n        for (gp = sp->gp;;) {\n                /*\n                 * If we're back to the original structure, leave it around,\n                 * but discard any allocated source name, we've returned to\n                 * the beginning of the command stack.\n                 */\n                if ((ecp = LIST_FIRST(&gp->ecq)) == &gp->excmd) {\n                        if (F_ISSET(ecp, E_NAMEDISCARD)) {\n                                free(ecp->if_name);\n                                ecp->if_name = NULL;\n                        }\n                        return (0);\n                }\n\n                /*\n                 * ecp->clen will be 0 for the first discarded command, but\n                 * may not be 0 for subsequent ones, e.g. if the original\n                 * command was \":g/xx/@a|s/b/c/\", then when we discard the\n                 * command pushed on the stack by the @a, we have to resume\n                 * the global command which included the substitute command.\n                 */\n                if (ecp->clen != 0)\n                        return (0);\n\n                /*\n                 * If it's an @, global or v command, we may need to continue\n                 * the command on a different line.\n                 */\n                if (FL_ISSET(ecp->agv_flags, AGV_ALL)) {\n                        /* Discard any exhausted ranges. */\n                        while ((rp = TAILQ_FIRST(&ecp->rq))) {\n                                if (rp->start > rp->stop) {\n                                        TAILQ_REMOVE(&ecp->rq, rp, q);\n                                        free(rp);\n                                } else\n                                        break;\n                        }\n\n                        /* If there's another range, continue with it. */\n                        if (rp)\n                                break;\n\n                        /* If it's a global/v command, fix up the last line. */\n                        if (FL_ISSET(ecp->agv_flags,\n                            AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) {\n                                if (db_exist(sp, ecp->range_lno))\n                                        sp->lno = ecp->range_lno;\n                                else {\n                                        if (db_last(sp, &sp->lno))\n                                                return (1);\n                                        if (sp->lno == 0)\n                                                sp->lno = 1;\n                                }\n                        }\n                        free(ecp->o_cp);\n                }\n\n                /* Discard the EXCMD. */\n                LIST_REMOVE(ecp, q);\n                free(ecp);\n        }\n\n        /*\n         * We only get here if it's an active @, global or v command.  Set\n         * the current line number, and get a new copy of the command for\n         * the parser.  Note, the original pointer almost certainly moved,\n         * so we have play games.\n         */\n        ecp->cp = ecp->o_cp;\n        memcpy(ecp->cp, ecp->cp + ecp->o_clen, ecp->o_clen);\n        ecp->clen = ecp->o_clen;\n        ecp->range_lno = sp->lno = rp->start++;\n\n        if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V))\n                F_SET(sp, SC_EX_GLOBAL);\n        return (0);\n}\n\n/*\n * ex_discard --\n *      Discard any pending ex commands.\n */\nstatic int\nex_discard(SCR *sp)\n{\n        GS *gp;\n        EXCMD *ecp;\n        RANGE *rp;\n\n        /*\n         * We know the first command can't be an AGV command, so we don't\n         * process it specially.  We do, however, nail the command itself.\n         */\n        for (gp = sp->gp; (ecp = LIST_FIRST(&gp->ecq)) != &gp->excmd;) {\n                if (FL_ISSET(ecp->agv_flags, AGV_ALL)) {\n                        while ((rp = TAILQ_FIRST(&ecp->rq))) {\n                                TAILQ_REMOVE(&ecp->rq, rp, q);\n                                free(rp);\n                        }\n                        free(ecp->o_cp);\n                }\n                LIST_REMOVE(ecp, q);\n                free(ecp);\n        }\n        LIST_FIRST(&gp->ecq)->clen = 0;\n        return (0);\n}\n\n/*\n * ex_unknown --\n *      Display an unknown command name.\n */\nstatic void\nex_unknown(SCR *sp, char *cmd, size_t len)\n{\n        size_t blen;\n        char *bp;\n\n        GET_SPACE_GOTO(sp, bp, blen, len + 1);\n        bp[len] = '\\0';\n        memcpy(bp, cmd, len);\n        msgq_str(sp, M_ERR, bp, \"The %s command is unknown\");\n        FREE_SPACE(sp, bp, blen);\n\nalloc_err:\n        return;\n}\n\n/*\n * ex_is_abbrev -\n *      The vi text input routine needs to know if ex thinks this is an\n *      [un]abbreviate command, so it can turn off abbreviations.  See\n *      the usual ranting in the vi/v_txt_ev.c:txt_abbrev() routine.\n *\n * PUBLIC: int ex_is_abbrev(char *, size_t);\n */\nint\nex_is_abbrev(char *name, size_t len)\n{\n        EXCMDLIST const *cp;\n\n        return ((cp = ex_comm_search(name, len)) != NULL &&\n            (cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE]));\n}\n\n/*\n * ex_is_unmap -\n *      The vi text input routine needs to know if ex thinks this is an\n *      unmap command, so it can turn off input mapping.  See the usual\n *      ranting in the vi/v_txt_ev.c:txt_unmap() routine.\n *\n * PUBLIC: int ex_is_unmap(char *, size_t);\n */\nint\nex_is_unmap(char *name, size_t len)\n{\n        EXCMDLIST const *cp;\n\n        /*\n         * The command the vi input routines are really interested in\n         * is \"unmap!\", not just unmap.\n         */\n        if (len > 0 && name[len - 1] != '!')\n                return (0);\n        --len;\n        return ((cp = ex_comm_search(name, len)) != NULL &&\n            cp == &cmds[C_UNMAP]);\n}\n\n/*\n * ex_comm_search --\n *      Search for a command name.\n */\nstatic EXCMDLIST const *\nex_comm_search(char *name, size_t len)\n{\n        EXCMDLIST const *cp;\n\n        for (cp = cmds; cp->name != NULL; ++cp) {\n                if (cp->name[0] > name[0])\n                        return (NULL);\n                if (cp->name[0] != name[0])\n                        continue;\n                if (strlen(cp->name) >= len && !memcmp(name, cp->name, len))\n                        return (cp);\n        }\n        return (NULL);\n}\n\n/*\n * ex_badaddr --\n *      Display a bad address message.\n *\n * PUBLIC: void ex_badaddr\n * PUBLIC:(SCR *, EXCMDLIST const *, enum badaddr, enum nresult);\n */\nvoid\nex_badaddr(SCR *sp, EXCMDLIST const *cp, enum badaddr ba, enum nresult nret)\n{\n        recno_t lno;\n\n        switch (nret) {\n        case NUM_OK:\n                break;\n        case NUM_ERR:\n                msgq(sp, M_SYSERR, NULL);\n                return;\n        case NUM_OVER:\n                msgq(sp, M_ERR, \"Address value overflow\");\n                return;\n        case NUM_UNDER:\n                msgq(sp, M_ERR, \"Address value underflow\");\n                return;\n        }\n\n        /*\n         * When encountering an address error, tell the user if there's no\n         * underlying file, that's the real problem.\n         */\n        if (sp->ep == NULL) {\n                ex_emsg(sp, cp != NULL ? cp->name : NULL, EXM_NOFILEYET);\n                return;\n        }\n\n        switch (ba) {\n        case A_COMBO:\n                msgq(sp, M_ERR, \"Illegal address combination\");\n                break;\n        case A_EOF:\n                if (db_last(sp, &lno))\n                        return;\n                if (lno != 0) {\n                        msgq(sp, M_ERR,\n                            \"Illegal address: only %'lu lines in the file\",\n                            lno);\n                        break;\n                }\n                /* FALLTHROUGH */\n        case A_EMPTY:\n                msgq(sp, M_ERR, \"Illegal address: the file is empty\");\n                break;\n        case A_NOTSET:\n                abort();\n                /* NOTREACHED */\n        case A_ZERO:\n                msgq(sp, M_ERR,\n                    \"The %s command doesn't permit an address of 0\",\n                    cp->name);\n                break;\n        }\n        return;\n}\n\n#if defined(DEBUG) && defined(COMLOG)\n/*\n * ex_comlog --\n *      Log ex commands.\n */\nstatic void\nex_comlog(SCR *sp, EXCMD *ecp)\n{\n        TRACE(sp, \"ecmd: %s\", ecp->cmd->name);\n        if (ecp->addrcnt > 0) {\n                TRACE(sp, \" a1 %d\", ecp->addr1.lno);\n                if (ecp->addrcnt > 1)\n                        TRACE(sp, \" a2: %d\", ecp->addr2.lno);\n        }\n        if (ecp->lineno)\n                TRACE(sp, \" line %d\", ecp->lineno);\n        if (ecp->flags)\n                TRACE(sp, \" flags 0x%x\", ecp->flags);\n        if (F_ISSET(&exc, E_BUFFER))\n                TRACE(sp, \" buffer %c\", ecp->buffer);\n        if (ecp->argc)\n                for (cnt = 0; cnt < ecp->argc; ++cnt)\n                        TRACE(sp, \" arg %d: {%s}\", cnt, ecp->argv[cnt]->bp);\n        TRACE(sp, \"\\n\");\n}\n#endif /* if defined(DEBUG) && defined(COMLOG) */\n"
  },
  {
    "path": "ex/ex.h",
    "content": "/*      $OpenBSD: ex.h,v 1.11 2016/05/27 09:18:12 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)ex.h        10.24 (Berkeley) 8/12/96\n */\n\n#include \"../include/compat.h\"\n\n#define PROMPTCHAR      ':'             /* Prompt using a colon. */\n\ntypedef struct _excmdlist {             /* Ex command table structure.       */\n        char *name;                     /* Command name, underlying function */\n        int (*fn)(SCR *, EXCMD *);\n\n#define E_ADDR1         0x00000001      /* One address.                      */\n#define E_ADDR2         0x00000002      /* Two addresses.                    */\n#define E_ADDR2_ALL     0x00000004      /* Zero/two addresses; zero == all.  */\n#define E_ADDR2_NONE    0x00000008      /* Zero/two addresses; zero == none. */\n#define E_ADDR_ZERO     0x00000010      /* 0 is a legal addr1.               */\n#define E_ADDR_ZERODEF  0x00000020      /* 0 is default addr1 of empty files */\n#define E_AUTOPRINT     0x00000040      /* Command always sets autoprint.    */\n#define E_CLRFLAG       0x00000080      /* Clear the print (#, l, p) flags.  */\n#define E_NEWSCREEN     0x00000100      /* Create a new screen.              */\n#define E_SECURE        0x00000200      /* Permission denied if O_SECURE set */\n#define E_VIONLY        0x00000400      /* Meaningful only in vi.            */\n#define __INUSE1        0xfffff800      /* Same name space as EX_PRIVATE.    */\n        u_int16_t flags;\n\n        char *syntax;                   /* Syntax script. */\n        char *usage;                    /* Usage line.    */\n        char *help;                     /* Help line.     */\n} EXCMDLIST;\n\n#define MAXCMDNAMELEN   12              /* Longest command name. */\nextern EXCMDLIST const cmds[];          /* Table of ex commands. */\n\n/*\n * !!!\n * QUOTING NOTE:\n *\n * Historically, .exrc files and EXINIT variables could only use ^V as an\n * escape character, neither ^Q or a user specified character worked.  We\n * enforce that here, just in case someone depends on it.\n */\n\n#define IS_ESCAPE(sp, cmdp, ch)                                         \\\n        (F_ISSET((cmdp), E_VLITONLY) ?                                  \\\n            (ch) == CH_LITERAL : KEY_VAL((sp), (ch)) == K_VLNEXT)\n\n/*\n * File state must be checked for each command -- any ex command may be entered\n * at any time, and most of them won't work well if a file hasn't yet been read\n * in.  Historic vi generally took the easy way out and dropped core.\n */\n\n#define NEEDFILE(sp, cmdp) {                                            \\\n        if ((sp)->ep == NULL) {                                         \\\n                ex_emsg((sp), (cmdp)->cmd->name, EXM_NOFILEYET);        \\\n                return (1);                                             \\\n        }                                                               \\\n}\n\n/* Range structures for global and @ commands. */\ntypedef struct _range RANGE;\nstruct _range {                         /* Global command range.    */\n        TAILQ_ENTRY(_range) q;          /* Linked list of ranges.   */\n        recno_t start, stop;            /* Start/stop of the range. */\n};\n\n/* Ex command structure. */\nstruct _excmd {\n        LIST_ENTRY(_excmd) q;           /* Linked list of commands */\n\n        char     *if_name;              /* Associated file.        */\n        recno_t   if_lno;               /* Associated line number. */\n\n        /* Clear the structure for the ex parser. */\n#define CLEAR_EX_PARSER(cmdp)                                           \\\n        memset(&((cmdp)->cp), 0, ((char *)&(cmdp)->flags -              \\\n            (char *)&((cmdp)->cp)) + sizeof((cmdp)->flags))\n\n        char     *cp;                   /* Current command text.  */\n        size_t    clen;                 /* Current command length */\n\n        char     *save_cmd;             /* Remaining command.       */\n        size_t    save_cmdlen;          /* Remaining command length */\n\n        EXCMDLIST const *cmd;           /* Command: entry in command table.  */\n        EXCMDLIST rcmd;                 /* Command: table entry/replacement. */\n\n        TAILQ_HEAD(_rh, _range) rq;     /* @/global range: linked list.      */\n        recno_t   range_lno;            /* @/global range: set line number.  */\n        char     *o_cp;                 /* Original @/global command.        */\n        size_t    o_clen;               /* Original @/global command length. */\n#define AGV_AT          0x01            /* @ buffer execution.               */\n#define AGV_AT_NORANGE  0x02            /* @ buffer execution without range. */\n#define AGV_GLOBAL      0x04            /* global command.                   */\n#define AGV_V           0x08            /* v command.                        */\n#define AGV_ALL         (AGV_AT | AGV_AT_NORANGE | AGV_GLOBAL | AGV_V)\n        u_int8_t  agv_flags;\n\n        /* Clear the structure before each ex command. */\n#define CLEAR_EX_CMD(cmdp) {                                            \\\n        u_int32_t L__f = F_ISSET((cmdp), E_PRESERVE);                   \\\n        memset(&((cmdp)->buffer), 0, ((char *)&(cmdp)->flags -          \\\n            (char *)&((cmdp)->buffer)) + sizeof((cmdp)->flags));        \\\n        F_SET((cmdp), L__f);                                            \\\n}\n\n        CHAR_T    buffer;               /* Command: named buffer.          */\n        recno_t   lineno;               /* Command: line number.           */\n        long      count;                /* Command: signed count.          */\n        long      flagoff;              /* Command: signed flag offset.    */\n        int       addrcnt;              /* Command: addresses (0, 1 or 2). */\n        MARK      addr1;                /* Command: 1st address.           */\n        MARK      addr2;                /* Command: 2nd address.           */\n        ARGS    **argv;                 /* Command: array of arguments.    */\n        int       argc;                 /* Command: count of arguments.    */\n\n#define E_C_BUFFER      0x00001         /* Buffer name specified.     */\n#define E_C_CARAT       0x00002         /*    ^ flag.                 */\n#define E_C_COUNT       0x00004         /* Count specified.           */\n#define E_C_COUNT_NEG   0x00008         /* Count was signed negative. */\n#define E_C_COUNT_POS   0x00010         /* Count was signed positive. */\n#define E_C_DASH        0x00020         /*    - flag.                 */\n#define E_C_DOT         0x00040         /*    . flag.                 */\n#define E_C_EQUAL       0x00080         /*    = flag.                 */\n#define E_C_FORCE       0x00100         /*    ! flag.                 */\n#define E_C_HASH        0x00200         /*    # flag.                 */\n#define E_C_LIST        0x00400         /*    l flag.                 */\n#define E_C_PLUS        0x00800         /*    + flag.                 */\n#define E_C_PRINT       0x01000         /*    p flag.                 */\n        u_int16_t iflags;               /* User input information.    */\n\n#define __INUSE2        0x000007ff      /* Same name space as EXCMDLIST.     */\n#define E_BLIGNORE      0x00000800      /* Ignore blank lines.               */\n#define E_NAMEDISCARD   0x00001000      /* Free/discard the name.            */\n#define E_NOAUTO        0x00002000      /* Don't do autoprint output.        */\n#define E_NOPRDEF       0x00004000      /* Don't print as default.           */\n#define E_NRSEP         0x00008000      /* Need to line adjust ex output.    */\n#define E_OPTNUM        0x00010000      /* Number edit option affected.      */\n#define E_VLITONLY      0x00020000      /* Use ^V quoting only.              */\n#define E_PRESERVE      0x0003f800      /* Bits to preserve across commands. */\n\n#define E_ABSMARK       0x00040000      /* Set the absolute mark.            */\n#define E_ADDR_DEF      0x00080000      /* Default addresses used.           */\n#define E_DELTA         0x00100000      /* Search address with delta.        */\n#define E_MODIFY        0x00200000      /* File name expansion modified arg. */\n#define E_MOVETOEND     0x00400000      /* Move to the end of the file first */\n#define E_NEWLINE       0x00800000      /* Found ending <newline>.           */\n#define E_SEARCH_WMSG   0x01000000      /* Display search-wrapped message.   */\n#define E_USELASTCMD    0x02000000      /* Use the last command.             */\n#define E_VISEARCH      0x04000000      /* It's really a vi search command.  */\n        u_int32_t flags;                /* Current flags.                    */\n};\n\n/* Ex private, per-screen memory. */\ntypedef struct _ex_private {\n        TAILQ_HEAD(_tqh, _tagq) tq;     /* Tag queue.             */\n        TAILQ_HEAD(_tagfh, _tagf) tagfq;/* Tag file list.         */\n        char    *tag_last;              /* Saved last tag string. */\n\n        CHAR_T  *lastbcomm;             /* Last bang command. */\n\n        ARGS   **args;                  /* Command: argument list.         */\n        int      argscnt;               /* Command: argument list count.   */\n        int      argsoff;               /* Command: offset into arguments. */\n\n        u_int32_t fdef;                 /* Saved E_C_* default command flags */\n\n        char    *ibp;                   /* File line input buffer.        */\n        size_t   ibp_len;               /* File line input buffer length. */\n\n        /*\n         * Buffers for the ex output.  The screen/vi support doesn't do any\n         * character buffering of any kind.  We do it here so that we're not\n         * calling the screen output routines on every character.\n         *\n         * XXX\n         * Should change to grow dynamically.\n         */\n\n        char     obp[1024];             /* Ex output buffer.        */\n        size_t   obp_len;               /* Ex output buffer length. */\n\n        u_int8_t flags;\n} EX_PRIVATE;\n#define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private))\n\n/*\n * Filter actions:\n *\n *      FILTER_BANG     !:      filter text through the utility.\n *      FILTER_RBANG    !:      read from the utility (without stdin).\n *      FILTER_READ     read:   read from the utility (with stdin).\n *      FILTER_WRITE    write:  write to the utility, display its output.\n */\n\nenum filtertype { FILTER_BANG, FILTER_RBANG, FILTER_READ, FILTER_WRITE };\n\n/* Ex common error messages. */\ntypedef enum {\n        EXM_EMPTYBUF,                   /* Empty buffer.                     */\n        EXM_FILECOUNT,                  /* Too many file names.              */\n        EXM_NOCANON,                    /* No terminal interface.            */\n        EXM_NOCANON_F,                  /* EXM_NOCANO: filter version.       */\n        EXM_NOFILEYET,                  /* Illegal until a file read in.     */\n        EXM_NOPREVBUF,                  /* No previous buffer specified.     */\n        EXM_NOPREVRE,                   /* No previous RE specified.         */\n        EXM_NOSUSPEND,                  /* No suspension.                    */\n        EXM_SECURE,                     /* Illegal if secure edit option set */\n        EXM_SECURE_F,                   /* EXM_SECURE: filter version        */\n        EXM_USAGE                       /* Standard usage message.           */\n} exm_t;\n\n/* Ex address error types. */\nenum badaddr { A_COMBO, A_EMPTY, A_EOF, A_NOTSET, A_ZERO };\n\n/* Ex common tag error messages. */\ntypedef enum {\n        TAG_BADLNO,             /* Tag line doesn't exist.           */\n        TAG_EMPTY,              /* Tags stack is empty.              */\n        TAG_SEARCH              /* Tags search pattern wasn't found. */\n} tagmsg_t;\n\n#include \"ex_def.h\"\n#include \"ex_extern.h\"\n"
  },
  {
    "path": "ex/ex_abbrev.c",
    "content": "/*      $OpenBSD: ex_abbrev.c,v 1.9 2016/05/27 09:18:12 martijn Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * ex_abbr -- :abbreviate [key replacement]\n *      Create an abbreviation or display abbreviations.\n *\n * PUBLIC: int ex_abbr(SCR *, EXCMD *);\n */\nint\nex_abbr(SCR *sp, EXCMD *cmdp)\n{\n        CHAR_T *p;\n        size_t len;\n\n        switch (cmdp->argc) {\n        case 0:\n                if (seq_dump(sp, SEQ_ABBREV, 0) == 0)\n                        msgq(sp, M_INFO, \"No abbreviations to display\");\n                return (0);\n        case 2:\n                break;\n        default:\n                abort();\n        }\n\n        /*\n         * Check for illegal characters.\n         *\n         * !!!\n         * Another fun one, historically.  See vi/v_ntext.c:txt_abbrev() for\n         * details.  The bottom line is that all abbreviations have to end\n         * with a \"word\" character, because it's the transition from word to\n         * non-word characters that triggers the test for an abbreviation.  In\n         * addition, because of the way the test is done, there can't be any\n         * transitions from word to non-word character (or vice-versa) other\n         * than between the next-to-last and last characters of the string,\n         * and there can't be any <blank> characters.  Warn the user.\n         */\n        if (!inword(cmdp->argv[0]->bp[cmdp->argv[0]->len - 1])) {\n                msgq(sp, M_ERR,\n                    \"Abbreviations must end with a \\\"word\\\" character\");\n                        return (1);\n        }\n        for (p = cmdp->argv[0]->bp; *p != '\\0'; ++p)\n                if (isblank(p[0])) {\n                        msgq(sp, M_ERR,\n                            \"Abbreviations may not contain tabs or spaces\");\n                        return (1);\n                }\n        if (cmdp->argv[0]->len > 2)\n                for (p = cmdp->argv[0]->bp,\n                    len = cmdp->argv[0]->len - 2; len; --len, ++p)\n                        if (inword(p[0]) != inword(p[1])) {\n                                msgq(sp, M_ERR,\n\"Abbreviations may not mix word/non-word characters, except at the end\");\n                                return (1);\n                        }\n\n        if (seq_set(sp, NULL, 0, cmdp->argv[0]->bp, cmdp->argv[0]->len,\n            cmdp->argv[1]->bp, cmdp->argv[1]->len, SEQ_ABBREV, SEQ_USERDEF))\n                return (1);\n\n        F_SET(sp->gp, G_ABBREV);\n        return (0);\n}\n\n/*\n * ex_unabbr -- :unabbreviate key\n *      Delete an abbreviation.\n *\n * PUBLIC: int ex_unabbr(SCR *, EXCMD *);\n */\nint\nex_unabbr(SCR *sp, EXCMD *cmdp)\n{\n        ARGS *ap;\n\n        ap = cmdp->argv[0];\n        if (!F_ISSET(sp->gp, G_ABBREV) ||\n            seq_delete(sp, ap->bp, ap->len, SEQ_ABBREV)) {\n                msgq_str(sp, M_ERR, ap->bp,\n                    \"\\\"%s\\\" is not an abbreviation\");\n                return (1);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_append.c",
    "content": "/*      $OpenBSD: ex_append.c,v 1.14 2016/05/27 09:18:12 martijn Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\nenum which {APPEND, CHANGE, INSERT};\n\nstatic int ex_aci(SCR *, EXCMD *, enum which);\n\n/*\n * ex_append -- :[line] a[ppend][!]\n *      Append one or more lines of new text after the specified line,\n *      or the current line if no address is specified.\n *\n * PUBLIC: int ex_append(SCR *, EXCMD *);\n */\nint\nex_append(SCR *sp, EXCMD *cmdp)\n{\n        return (ex_aci(sp, cmdp, APPEND));\n}\n\n/*\n * ex_change -- :[line[,line]] c[hange][!] [count]\n *      Change one or more lines to the input text.\n *\n * PUBLIC: int ex_change(SCR *, EXCMD *);\n */\nint\nex_change(SCR *sp, EXCMD *cmdp)\n{\n        return (ex_aci(sp, cmdp, CHANGE));\n}\n\n/*\n * ex_insert -- :[line] i[nsert][!]\n *      Insert one or more lines of new text before the specified line,\n *      or the current line if no address is specified.\n *\n * PUBLIC: int ex_insert(SCR *, EXCMD *);\n */\nint\nex_insert(SCR *sp, EXCMD *cmdp)\n{\n        return (ex_aci(sp, cmdp, INSERT));\n}\n\n/*\n * ex_aci --\n *      Append, change, insert in ex.\n */\nstatic int\nex_aci(SCR *sp, EXCMD *cmdp, enum which cmd)\n{\n        CHAR_T *p, *t;\n        GS *gp;\n        TEXT *tp;\n        TEXTH tiq;\n        recno_t cnt, lno;\n        size_t len;\n        u_int32_t flags;\n        int need_newline;\n\n        (void)cnt;\n        gp = sp->gp;\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * If doing a change, replace lines for as long as possible.  Then,\n         * append more lines or delete remaining lines.  Changes to an empty\n         * file are appends, inserts are the same as appends to the previous\n         * line.\n         *\n         * !!!\n         * Set the address to which we'll append.  We set sp->lno to this\n         * address as well so that autoindent works correctly when get text\n         * from the user.\n         */\n        lno = cmdp->addr1.lno;\n        sp->lno = lno;\n        if ((cmd == CHANGE || cmd == INSERT) && lno != 0)\n                --lno;\n\n        /*\n         * !!!\n         * If the file isn't empty, cut changes into the unnamed buffer.\n         */\n        if (cmd == CHANGE && cmdp->addr1.lno != 0 &&\n            (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE) ||\n            del(sp, &cmdp->addr1, &cmdp->addr2, 1)))\n                return (1);\n\n        /*\n         * !!!\n         * Anything that was left after the command separator becomes part\n         * of the inserted text.  Apparently, it was common usage to enter:\n         *\n         *      :g/pattern/append|stuff1\n         *\n         * and append the line of text \"stuff1\" to the lines containing the\n         * pattern.  It was also historically legal to enter:\n         *\n         *      :append|stuff1\n         *      stuff2\n         *      .\n         *\n         * and the text on the ex command line would be appended as well as\n         * the text inserted after it.  There was an historic bug however,\n         * that the user had to enter *two* terminating lines (the '.' lines)\n         * to terminate text input mode, in this case.  This whole thing\n         * could be taken too far, however.  Entering:\n         *\n         *      :append|stuff1\\\n         *      stuff2\n         *      stuff3\n         *      .\n         *\n         * i.e. mixing and matching the forms confused the historic vi, and,\n         * not only did it take two terminating lines to terminate text input\n         * mode, but the trailing backslashes were retained on the input.  We\n         * match historic practice except that we discard the backslashes.\n         *\n         * Input lines specified on the ex command line lines are separated by\n         * <newline>s.  If there is a trailing delimiter an empty line was\n         * inserted.  There may also be a leading delimiter, which is ignored\n         * unless it's also a trailing delimiter.  It is possible to encounter\n         * a termination line, i.e. a single '.', in a global command, but not\n         * necessary if the text insert command was the last of the global\n         * commands.\n         */\n        if (cmdp->save_cmdlen != 0) {\n                for (p = cmdp->save_cmd,\n                    len = cmdp->save_cmdlen; len > 0; p = t) {\n                        for (t = p; len > 0 && t[0] != '\\n'; ++t, --len);\n                        if (t != p || len == 0) {\n                                if (F_ISSET(sp, SC_EX_GLOBAL) &&\n                                    t - p == 1 && p[0] == '.') {\n                                        ++t;\n                                        if (len > 0)\n                                                --len;\n                                        break;\n                                }\n                                if (db_append(sp, 1, lno++, p, t - p))\n                                        return (1);\n                        }\n                        if (len != 0) {\n                                ++t;\n                                if (--len == 0 &&\n                                    db_append(sp, 1, lno++, \"\", 0))\n                                        return (1);\n                        }\n                }\n                /*\n                 * If there's any remaining text, we're in a global, and\n                 * there's more command to parse.\n                 *\n                 * !!!\n                 * We depend on the fact that non-global commands will eat the\n                 * rest of the command line as text input, and before getting\n                 * any text input from the user.  Otherwise, we'd have to save\n                 * off the command text before or during the call to the text\n                 * input function below.\n                 */\n                if (len != 0)\n                        cmdp->save_cmd = t;\n                cmdp->save_cmdlen = len;\n        }\n\n        if (F_ISSET(sp, SC_EX_GLOBAL)) {\n                if ((sp->lno = lno) == 0 && db_exist(sp, 1))\n                        sp->lno = 1;\n                return (0);\n        }\n\n        /*\n         * If not in a global command, read from the terminal.\n         *\n         * If this code is called by vi, we want to reset the terminal and use\n         * ex's line get routine.  It actually works fine if we use vi's get\n         * routine, but it doesn't look as nice.  Maybe if we had a separate\n         * window or something, but getting a line at a time looks awkward.\n         * However, depending on the screen that we're using, that may not\n         * be possible.\n         */\n        if (F_ISSET(sp, SC_VI)) {\n                if (gp->scr_screen(sp, SC_EX)) {\n                        ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON);\n                        return (1);\n                }\n\n                /* If we're still in the vi screen, move out explicitly. */\n                need_newline = !F_ISSET(sp, SC_SCR_EXWROTE);\n                F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);\n                if (need_newline)\n                        (void)ex_puts(sp, \"\\n\");\n\n                /*\n                 * !!!\n                 * Users of historical versions of vi sometimes get confused\n                 * when they enter append mode, and can't seem to get out of\n                 * it.  Give them an informational message.\n                 */\n                (void)ex_puts(sp, \"Entering ex input mode.\\n\");\n                (void)ex_fflush(sp);\n        }\n\n        /*\n         * Set input flags; the ! flag turns off autoindent for append,\n         * change and insert.\n         */\n        LF_INIT(TXT_DOTTERM | TXT_NUMBER);\n        if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && O_ISSET(sp, O_AUTOINDENT))\n                LF_SET(TXT_AUTOINDENT);\n        if (O_ISSET(sp, O_BEAUTIFY))\n                LF_SET(TXT_BEAUTIFY);\n\n        /*\n         * This code can't use the common screen TEXTH structure (sp->tiq),\n         * as it may already be in use, e.g. \":append|s/abc/ABC/\" would fail\n         * as we are only halfway through the text when the append code fires.\n         * Use a local structure instead.  (The ex code would have to use a\n         * local structure except that we're guaranteed to finish remaining\n         * characters in the common TEXTH structure when they were inserted\n         * into the file, above.)\n         */\n        memset(&tiq, 0, sizeof(TEXTH));\n        TAILQ_INIT(&tiq);\n\n        if (ex_txt(sp, &tiq, 0, flags))\n                return (1);\n\n        cnt = 0;\n        TAILQ_FOREACH(tp, &tiq, q) {\n                if (db_append(sp, 1, lno++, tp->lb, tp->len))\n                        return (1);\n                cnt++;\n        }\n\n        /*\n         * Set sp->lno to the final line number value (correcting for a\n         * possible 0 value) as that's historically correct for the final\n         * line value, whether or not the user entered any text.\n         */\n        if ((sp->lno = lno) == 0 && db_exist(sp, 1))\n                sp->lno = 1;\n\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_args.c",
    "content": "/*      $OpenBSD: ex_args.c,v 1.12 2016/01/06 22:28:52 millert Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\nstatic int ex_N_next(SCR *, EXCMD *);\n\n/*\n * ex_next -- :next [+cmd] [files]\n *      Edit the next file, optionally setting the list of files.\n *\n * !!!\n * The :next command behaved differently from the :rewind command in\n * historic vi.  See nvi/docs/autowrite for details, but the basic\n * idea was that it ignored the force flag if the autowrite flag was\n * set.  This implementation handles them all identically.\n *\n * PUBLIC: int ex_next(SCR *, EXCMD *);\n */\nint\nex_next(SCR *sp, EXCMD *cmdp)\n{\n        ARGS **argv;\n        FREF *frp;\n        int noargs;\n        char **ap;\n\n        /* Check for file to move to. */\n        if (cmdp->argc == 0 && (sp->cargv == NULL || sp->cargv[1] == NULL)) {\n                msgq(sp, M_ERR, \"No more files to edit\");\n                return (1);\n        }\n\n        if (F_ISSET(cmdp, E_NEWSCREEN)) {\n                /* By default, edit the next file in the old argument list. */\n                if (cmdp->argc == 0) {\n                        if (argv_exp0(sp,\n                            cmdp, sp->cargv[1], strlen(sp->cargv[1])))\n                                return (1);\n                        return (ex_edit(sp, cmdp));\n                }\n                return (ex_N_next(sp, cmdp));\n        }\n\n        /* Check modification. */\n        if (file_m1(sp,\n            FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE))\n                return (1);\n\n        /* Any arguments are a replacement file list. */\n        if (cmdp->argc) {\n                /* Free the current list. */\n                if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) {\n                        for (ap = sp->argv; *ap != NULL; ++ap)\n                                free(*ap);\n                        free(sp->argv);\n                }\n                F_CLR(sp, SC_ARGNOFREE | SC_ARGRECOVER);\n                sp->cargv = NULL;\n\n                /* Create a new list. */\n                CALLOC_RET(sp,\n                    sp->argv, cmdp->argc + 1, sizeof(char *));\n                for (ap = sp->argv,\n                    argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv)\n                        if ((*ap =\n                            v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL)\n                                return (1);\n                *ap = NULL;\n\n                /* Switch to the first file. */\n                sp->cargv = sp->argv;\n                if ((frp = file_add(sp, *sp->cargv)) == NULL)\n                        return (1);\n                noargs = 0;\n\n                /* Display a file count with the welcome message. */\n                F_SET(sp, SC_STATUS_CNT);\n        } else {\n                if ((frp = file_add(sp, sp->cargv[1])) == NULL)\n                        return (1);\n                if (F_ISSET(sp, SC_ARGRECOVER))\n                        F_SET(frp, FR_RECOVER);\n                noargs = 1;\n        }\n\n        if (file_init(sp, frp, NULL, FS_SETALT |\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))\n                return (1);\n        if (noargs)\n                ++sp->cargv;\n\n        F_SET(sp, SC_FSWITCH);\n        return (0);\n}\n\n/*\n * ex_N_next --\n *      New screen version of ex_next.\n */\nstatic int\nex_N_next(SCR *sp, EXCMD *cmdp)\n{\n        SCR *new;\n        FREF *frp;\n\n        /* Get a new screen. */\n        if (screen_init(sp->gp, sp, &new))\n                return (1);\n        if (vs_split(sp, new, 0)) {\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* Get a backing file. */\n        if ((frp = file_add(new, cmdp->argv[0]->bp)) == NULL ||\n            file_init(new, frp, NULL,\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) {\n                (void)vs_discard(new, NULL);\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* The arguments are a replacement file list. */\n        new->cargv = new->argv = ex_buildargv(sp, cmdp, NULL);\n\n        /* Display a file count with the welcome message. */\n        F_SET(new, SC_STATUS_CNT);\n\n        /* Set up the switch. */\n        sp->nextdisp = new;\n        F_SET(sp, SC_SSWITCH);\n\n        return (0);\n}\n\n/*\n * ex_prev -- :prev\n *      Edit the previous file.\n *\n * PUBLIC: int ex_prev(SCR *, EXCMD *);\n */\nint\nex_prev(SCR *sp, EXCMD *cmdp)\n{\n        FREF *frp;\n\n        if (sp->cargv == sp->argv) {\n                msgq(sp, M_ERR, \"No previous files to edit\");\n                return (1);\n        }\n\n        if (F_ISSET(cmdp, E_NEWSCREEN)) {\n                if (argv_exp0(sp, cmdp, sp->cargv[-1], strlen(sp->cargv[-1])))\n                        return (1);\n                return (ex_edit(sp, cmdp));\n        }\n\n        if (file_m1(sp,\n            FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE))\n                return (1);\n\n        if ((frp = file_add(sp, sp->cargv[-1])) == NULL)\n                return (1);\n\n        if (file_init(sp, frp, NULL, FS_SETALT |\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))\n                return (1);\n        --sp->cargv;\n\n        F_SET(sp, SC_FSWITCH);\n        return (0);\n}\n\n/*\n * ex_rew -- :rew\n *      Re-edit the list of files.\n *\n * !!!\n * Historic practice was that all files would start editing at the beginning\n * of the file.  We don't get this right because we may have multiple screens\n * and we can't clear the FR_CURSORSET bit for a single screen.  I don't see\n * anyone noticing, but if they do, we'll have to put information into the SCR\n * structure so we can keep track of it.\n *\n * PUBLIC: int ex_rew(SCR *, EXCMD *);\n */\nint\nex_rew(SCR *sp, EXCMD *cmdp)\n{\n        FREF *frp;\n\n        /*\n         * !!!\n         * Historic practice -- you can rewind to the current file.\n         */\n        if (sp->argv == NULL) {\n                msgq(sp, M_ERR, \"No previous files to rewind\");\n                return (1);\n        }\n\n        if (file_m1(sp,\n            FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE))\n                return (1);\n\n        /* Switch to the first one. */\n        sp->cargv = sp->argv;\n        if ((frp = file_add(sp, *sp->cargv)) == NULL)\n                return (1);\n        if (file_init(sp, frp, NULL, FS_SETALT |\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))\n                return (1);\n\n        /* Switch and display a file count with the welcome message. */\n        F_SET(sp, SC_FSWITCH | SC_STATUS_CNT);\n\n        return (0);\n}\n\n/*\n * ex_args -- :args\n *      Display the list of files.\n *\n * PUBLIC: int ex_args(SCR *, EXCMD *);\n */\nint\nex_args(SCR *sp, EXCMD *cmdp)\n{\n        int cnt, col, len, sep;\n        char **ap;\n\n        if (sp->argv == NULL) {\n                (void)msgq(sp, M_ERR, \"No file list to display\");\n                return (0);\n        }\n\n        col = len = sep = 0;\n        for (cnt = 1, ap = sp->argv; *ap != NULL; ++ap) {\n                col += len = strlen(*ap) + sep + (ap == sp->cargv ? 2 : 0);\n                if (col >= sp->cols - 1) {\n                        col = len;\n                        sep = 0;\n                        (void)ex_puts(sp, \"\\n\");\n                } else if (cnt != 1) {\n                        sep = 1;\n                        (void)ex_puts(sp, \" \");\n                }\n                ++cnt;\n\n                (void)ex_printf(sp, \"%s%s%s\", ap == sp->cargv ? \"[\" : \"\",\n                    *ap, ap == sp->cargv ? \"]\" : \"\");\n                if (INTERRUPTED(sp))\n                        break;\n        }\n        (void)ex_puts(sp, \"\\n\");\n        return (0);\n}\n\n/*\n * ex_buildargv --\n *      Build a new file argument list.\n *\n * PUBLIC: char **ex_buildargv(SCR *, EXCMD *, char *);\n */\nchar **\nex_buildargv(SCR *sp, EXCMD *cmdp, char *name)\n{\n        ARGS **argv;\n        int argc;\n        char **ap, **s_argv;\n\n        argc = cmdp == NULL ? 1 : cmdp->argc;\n        CALLOC(sp, s_argv, argc + 1, sizeof(char *));\n        if ((ap = s_argv) == NULL)\n                return (NULL);\n\n        if (cmdp == NULL) {\n                if ((*ap = v_strdup(sp, name, strlen(name))) == NULL) {\n                        free(s_argv);\n                        return (NULL);\n                }\n                ++ap;\n        } else\n                for (argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv)\n                        if ((*ap =\n                            v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL) {\n                                while (--ap >= s_argv)\n                                        free(*ap);\n                                free(s_argv);\n                                return (NULL);\n                        }\n        *ap = NULL;\n        return (s_argv);\n}\n"
  },
  {
    "path": "ex/ex_argv.c",
    "content": "/*      $OpenBSD: ex_argv.c,v 1.20 2016/05/27 09:18:12 martijn Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n#undef open\n\nstatic int argv_alloc(SCR *, size_t);\nstatic int argv_comp(const void *, const void *);\nstatic int argv_fexp(SCR *, EXCMD *,\n        char *, size_t, char *, size_t *, char **, size_t *, int);\nstatic int argv_lexp(SCR *, EXCMD *, char *);\nstatic int argv_sexp(SCR *, char **, size_t *, size_t *);\n\n/*\n * argv_init --\n *      Build a prototype arguments list.\n *\n * PUBLIC: int argv_init(SCR *, EXCMD *);\n */\nint\nargv_init(SCR *sp, EXCMD *excp)\n{\n        EX_PRIVATE *exp;\n\n        exp = EXP(sp);\n        exp->argsoff = 0;\n        argv_alloc(sp, 1);\n\n        excp->argv = exp->args;\n        excp->argc = exp->argsoff;\n        return (0);\n}\n\n/*\n * argv_exp0 --\n *      Append a string to the argument list.\n *\n * PUBLIC: int argv_exp0(SCR *, EXCMD *, char *, size_t);\n */\nint\nargv_exp0(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen)\n{\n        EX_PRIVATE *exp;\n\n        exp = EXP(sp);\n        argv_alloc(sp, cmdlen);\n        memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen);\n        exp->args[exp->argsoff]->bp[cmdlen] = '\\0';\n        exp->args[exp->argsoff]->len = cmdlen;\n        ++exp->argsoff;\n        excp->argv = exp->args;\n        excp->argc = exp->argsoff;\n        return (0);\n}\n\n/*\n * argv_exp1 --\n *      Do file name expansion on a string, and append it to the\n *      argument list.\n *\n * PUBLIC: int argv_exp1(SCR *, EXCMD *, char *, size_t, int);\n */\nint\nargv_exp1(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, int is_bang)\n{\n        size_t blen, len;\n        char *bp, *p, *t;\n\n        GET_SPACE_RET(sp, bp, blen, 512);\n\n        len = 0;\n        if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {\n                FREE_SPACE(sp, bp, blen);\n                return (1);\n        }\n\n        /* If it's empty, we're done. */\n        if (len != 0) {\n                for (p = bp, t = bp + len; p < t; ++p)\n                        if (!isblank(*p))\n                                break;\n                if (p == t)\n                        goto ret;\n        } else\n                goto ret;\n\n        (void)argv_exp0(sp, excp, bp, len);\n\nret:    FREE_SPACE(sp, bp, blen);\n        return (0);\n}\n\n/*\n * argv_exp2 --\n *      Do file name and shell expansion on a string, and append it to\n *      the argument list.\n *\n * PUBLIC: int argv_exp2(SCR *, EXCMD *, char *, size_t);\n */\nint\nargv_exp2(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen)\n{\n        size_t blen, len, n;\n        int rval;\n        char *bp, *mp, *p;\n\n        GET_SPACE_RET(sp, bp, blen, 512);\n\n#define SHELLECHO       \"echo \"\n#define SHELLOFFSET     (sizeof(SHELLECHO) - 1)\n        memcpy(bp, SHELLECHO, SHELLOFFSET);\n        p = bp + SHELLOFFSET;\n        len = SHELLOFFSET;\n\n        if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {\n                rval = 1;\n                goto err;\n        }\n\n        /*\n         * Do shell word expansion -- it's very, very hard to figure out what\n         * magic characters the user's shell expects.  Historically, it was a\n         * union of v7 shell and csh meta characters.  We match that practice\n         * by default, so \":read \\%\" tries to read a file named '%'.  It would\n         * make more sense to pass any special characters through the shell,\n         * but then, if your shell was csh, the above example will behave\n         * differently in nvi than in vi.  If you want to get other characters\n         * passed through to your shell, change the \"meta\" option.\n         *\n         * To avoid a function call per character, we do a first pass through\n         * the meta characters looking for characters that aren't expected\n         * to be there, and then we can ignore them in the user's argument.\n         */\n        if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))\n                n = 0;\n        else {\n                for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\\0'; ++p)\n                        if (isblank(*p) || isalnum(*p))\n                                break;\n                p = bp + SHELLOFFSET;\n                n = len - SHELLOFFSET;\n                if (*p != '\\0') {\n                        for (; n > 0; --n, ++p)\n                                if (strchr(mp, *p) != NULL)\n                                        break;\n                } else\n                        for (; n > 0; --n, ++p)\n                                if (!isblank(*p) &&\n                                    !isalnum(*p) && strchr(mp, *p) != NULL)\n                                        break;\n        }\n\n        /*\n         * If we found a meta character in the string, fork a shell to expand\n         * it.  Unfortunately, this is comparatively slow.  Historically, it\n         * didn't matter much, since users don't enter meta characters as part\n         * of pathnames that frequently.  The addition of filename completion\n         * broke that assumption because it's easy to use.  As a result, lots\n         * folks have complained that the expansion code is too slow.  So, we\n         * detect filename completion as a special case, and do it internally.\n         * Note that this code assumes that the <asterisk> character is the\n         * match-anything meta character.  That feels safe -- if anyone writes\n         * a shell that doesn't follow that convention, I'd suggest giving them\n         * a festive hot-lead enema.\n         */\n        switch (n) {\n        case 0:\n                p = bp + SHELLOFFSET;\n                len -= SHELLOFFSET;\n                rval = argv_exp3(sp, excp, p, len);\n                break;\n        case 1:\n                if (*p == '*') {\n                        *p = '\\0';\n                        rval = argv_lexp(sp, excp, bp + SHELLOFFSET);\n                        break;\n                }\n                /* FALLTHROUGH */\n        default:\n                if (argv_sexp(sp, &bp, &blen, &len)) {\n                        rval = 1;\n                        goto err;\n                }\n                p = bp;\n                rval = argv_exp3(sp, excp, p, len);\n                break;\n        }\n\nerr:    FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n\n/*\n * argv_exp3 --\n *      Take a string and break it up into an argv, which is appended\n *      to the argument list.\n *\n * PUBLIC: int argv_exp3(SCR *, EXCMD *, char *, size_t);\n */\nint\nargv_exp3(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen)\n{\n        EX_PRIVATE *exp;\n        size_t len;\n        int ch, off;\n        char *ap, *p;\n\n        for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {\n                /* Skip any leading whitespace. */\n                for (; cmdlen > 0; --cmdlen, ++cmd) {\n                        ch = *cmd;\n                        if (!isblank(ch))\n                                break;\n                }\n                if (cmdlen == 0)\n                        break;\n\n                /*\n                 * Determine the length of this whitespace delimited\n                 * argument.\n                 *\n                 * QUOTING NOTE:\n                 *\n                 * Skip any character preceded by the user's quoting\n                 * character.\n                 */\n                for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {\n                        ch = *cmd;\n                        if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {\n                                ++cmd;\n                                --cmdlen;\n                        } else if (isblank(ch))\n                                break;\n                }\n\n                /*\n                 * Copy the argument into place.\n                 *\n                 * QUOTING NOTE:\n                 *\n                 * Lose quote chars.\n                 */\n                argv_alloc(sp, len);\n                off = exp->argsoff;\n                exp->args[off]->len = len;\n                for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)\n                        if (IS_ESCAPE(sp, excp, *ap))\n                                ++ap;\n                *p = '\\0';\n        }\n        excp->argv = exp->args;\n        excp->argc = exp->argsoff;\n\n        return (0);\n}\n\n/*\n * argv_fexp --\n *      Do file name and bang command expansion.\n */\nstatic int\nargv_fexp(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, char *p,\n    size_t *lenp, char **bpp, size_t *blenp, int is_bang)\n{\n        EX_PRIVATE *exp;\n        char *bp, *t;\n        size_t blen, len, off, tlen;\n\n        /* Replace file name characters. */\n        for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)\n                switch (*cmd) {\n                case '!':\n                        if (!is_bang)\n                                goto ins_ch;\n                        exp = EXP(sp);\n                        if (exp->lastbcomm == NULL) {\n                                msgq(sp, M_ERR,\n                                    \"No previous command to replace \\\"!\\\"\");\n                                return (1);\n                        }\n                        len += tlen = strlen(exp->lastbcomm);\n                        off = p - bp;\n                        ADD_SPACE_RET(sp, bp, blen, len);\n                        p = bp + off;\n                        memcpy(p, exp->lastbcomm, tlen);\n                        p += tlen;\n                        F_SET(excp, E_MODIFY);\n                        break;\n                case '%':\n                        if ((t = sp->frp->name) == NULL) {\n                                msgq(sp, M_ERR,\n                                    \"No filename to substitute for %%\");\n                                return (1);\n                        }\n                        tlen = strlen(t);\n                        len += tlen;\n                        off = p - bp;\n                        ADD_SPACE_RET(sp, bp, blen, len);\n                        p = bp + off;\n                        memcpy(p, t, tlen);\n                        p += tlen;\n                        F_SET(excp, E_MODIFY);\n                        break;\n                case '#':\n                        if ((t = sp->alt_name) == NULL) {\n                                msgq(sp, M_ERR,\n                                    \"No filename to substitute for #\");\n                                return (1);\n                        }\n                        len += tlen = strlen(t);\n                        off = p - bp;\n                        ADD_SPACE_RET(sp, bp, blen, len);\n                        p = bp + off;\n                        memcpy(p, t, tlen);\n                        p += tlen;\n                        F_SET(excp, E_MODIFY);\n                        break;\n                case '\\\\':\n                        /*\n                         * QUOTING NOTE:\n                         *\n                         * Strip any backslashes that protected the file\n                         * expansion characters.\n                         */\n                        if (cmdlen > 1 &&\n                            (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {\n                                ++cmd;\n                                --cmdlen;\n                        }\n                        /* FALLTHROUGH */\n                default:\nins_ch:                 ++len;\n                        off = p - bp;\n                        ADD_SPACE_RET(sp, bp, blen, len);\n                        p = bp + off;\n                        *p++ = *cmd;\n                }\n\n        /* NULL termination. */\n        ++len;\n        off = p - bp;\n        ADD_SPACE_RET(sp, bp, blen, len);\n        p = bp + off;\n        *p = '\\0';\n\n        /* Return the new string length, buffer, buffer length. */\n        *lenp = len - 1;\n        *bpp = bp;\n        *blenp = blen;\n        return (0);\n}\n\n/*\n * argv_alloc --\n *      Make more space for arguments.\n */\nstatic int\nargv_alloc(SCR *sp, size_t len)\n{\n        ARGS *ap;\n        EX_PRIVATE *exp;\n        int cnt, off;\n\n        /*\n         * Allocate room for another argument, always leaving\n         * enough room for an ARGS structure with a length of 0.\n         */\n#define INCREMENT       20\n        exp = EXP(sp);\n        off = exp->argsoff;\n        if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {\n                cnt = exp->argscnt + INCREMENT;\n                REALLOCARRAY(sp, exp->args, cnt, sizeof(ARGS *));\n                if (exp->args == NULL) {\n                        (void)argv_free(sp);\n                        goto mem;\n                }\n                memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));\n                exp->argscnt = cnt;\n        }\n\n        /* First argument. */\n        if (exp->args[off] == NULL) {\n                CALLOC(sp, exp->args[off], 1, sizeof(ARGS));\n                if (exp->args[off] == NULL)\n                        goto mem;\n        }\n\n        /* First argument buffer. */\n        ap = exp->args[off];\n        ap->len = 0;\n        if (ap->blen < len + 1) {\n                ap->blen = len + 1;\n                REALLOCARRAY(sp, ap->bp, ap->blen, sizeof(CHAR_T));\n                if (ap->bp == NULL) {\n                        ap->bp = NULL;\n                        ap->blen = 0;\n                        F_CLR(ap, A_ALLOCATED);\nmem:                    msgq(sp, M_SYSERR, NULL);\n                        return (1);\n                }\n                F_SET(ap, A_ALLOCATED);\n        }\n\n        /* Second argument. */\n        if (exp->args[++off] == NULL) {\n                CALLOC(sp, exp->args[off], 1, sizeof(ARGS));\n                if (exp->args[off] == NULL)\n                        goto mem;\n        }\n        /* 0 length serves as end-of-argument marker. */\n        exp->args[off]->len = 0;\n        return (0);\n}\n\n/*\n * argv_free --\n *      Free up argument structures.\n *\n * PUBLIC: int argv_free(SCR *);\n */\nint\nargv_free(SCR *sp)\n{\n        EX_PRIVATE *exp;\n        int off;\n\n        exp = EXP(sp);\n        if (exp->args != NULL) {\n                for (off = 0; off < exp->argscnt; ++off) {\n                        if (exp->args[off] == NULL)\n                                continue;\n                        if (F_ISSET(exp->args[off], A_ALLOCATED))\n                                free(exp->args[off]->bp);\n                        free(exp->args[off]);\n                }\n                free(exp->args);\n        }\n        exp->args = NULL;\n        exp->argscnt = 0;\n        exp->argsoff = 0;\n        return (0);\n}\n\n/*\n * argv_lexp --\n *      Find all file names matching the prefix and append them to the\n *      buffer.\n */\nstatic int\nargv_lexp(SCR *sp, EXCMD *excp, char *path)\n{\n        struct dirent *dp;\n        DIR *dirp;\n        EX_PRIVATE *exp;\n        int off;\n        size_t dlen, nlen;\n        char *dname, *name, *p;\n\n        exp = EXP(sp);\n\n        /* Set up the name and length for comparison. */\n        if ((p = strrchr(path, '/')) == NULL) {\n                dname = \".\";\n                dlen = 0;\n                name = path;\n        } else {\n                if (p == path) {\n                        dname = \"/\";\n                        dlen = 1;\n                } else {\n                        *p = '\\0';\n                        dname = path;\n                        dlen = strlen(path);\n                }\n                name = p + 1;\n        }\n        nlen = strlen(name);\n\n        if ((dirp = opendir(dname)) == NULL) {\n                msgq_str(sp, M_SYSERR, dname, \"%s\");\n                return (1);\n        }\n        for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {\n                if (nlen == 0) {\n                        if (dp->d_name[0] == '.')\n                                continue;\n                } else {\n                        if (D_NAMLEN(dp) < nlen ||\n                            memcmp(dp->d_name, name, nlen))\n                                continue;\n                }\n\n                /* Directory + name + slash + NULL. */\n                argv_alloc(sp, dlen + D_NAMLEN(dp) + 2);\n                p = exp->args[exp->argsoff]->bp;\n                if (dlen != 0) {\n                        memcpy(p, dname, dlen);\n                        p += dlen;\n                        if (dlen > 1 || dname[0] != '/')\n                                *p++ = '/';\n                }\n                memcpy(p, dp->d_name, D_NAMLEN(dp) + 1);\n                exp->args[exp->argsoff]->len = dlen + D_NAMLEN(dp) + 1;\n                ++exp->argsoff;\n                excp->argv = exp->args;\n                excp->argc = exp->argsoff;\n        }\n        closedir(dirp);\n\n        if (off == exp->argsoff) {\n                /*\n                 * If we didn't find a match, complain that the expansion\n                 * failed.  We can't know for certain that's the error, but\n                 * it's a good guess, and it matches historic practice.\n                 */\n                msgq(sp, M_ERR, \"Shell expansion failed\");\n                return (1);\n        }\n        qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);\n        return (0);\n}\n\n/*\n * argv_comp --\n *      Alphabetic comparison.\n */\nstatic int\nargv_comp(const void *a, const void *b)\n{\n        return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));\n}\n\n/*\n * argv_sexp --\n *      Fork a shell, pipe a command through it, and read the output into\n *      a buffer.\n */\nstatic int\nargv_sexp(SCR *sp, char **bpp, size_t *blenp, size_t *lenp)\n{\n        enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;\n        FILE *ifp;\n        pid_t pid;\n        size_t blen, len;\n        int ch, std_output[2];\n        char *bp, *p, *sh, *sh_path;\n\n        /* Secure means no shell access. */\n        if (O_ISSET(sp, O_SECURE)) {\n                msgq(sp, M_ERR,\n         \"Shell expansions not supported when the secure edit option is set\");\n                return (1);\n        }\n\n        sh_path = O_STR(sp, O_SHELL);\n        if ((sh = strrchr(sh_path, '/')) == NULL)\n                sh = sh_path;\n        else\n                ++sh;\n\n        /* Local copies of the buffer variables. */\n        bp = *bpp;\n        blen = *blenp;\n\n        /*\n         * There are two different processes running through this code, named\n         * the utility (the shell) and the parent. The utility reads standard\n         * input and writes standard output and standard error output.  The\n         * parent writes to the utility, reads its standard output and ignores\n         * its standard error output.  Historically, the standard error output\n         * was discarded by vi, as it produces a lot of noise when file patterns\n         * don't match.\n         *\n         * The parent reads std_output[0], and the utility writes std_output[1].\n         */\n        ifp = NULL;\n        std_output[0] = std_output[1] = -1;\n        if (pipe(std_output) < 0) {\n                msgq(sp, M_SYSERR, \"pipe\");\n                return (1);\n        }\n        if ((ifp = fdopen(std_output[0], \"r\")) == NULL) {\n                msgq(sp, M_SYSERR, \"fdopen\");\n                goto err;\n        }\n\n        /*\n         * Do the minimal amount of work possible, the shell is going to run\n         * briefly and then exit.  We sincerely hope.\n         */\n        switch (pid = fork()) {\n        case -1:                        /* Error. */\n                msgq(sp, M_SYSERR, \"fork\");\nerr:            if (ifp != NULL)\n                        (void)fclose(ifp);\n                else if (std_output[0] != -1)\n                        close(std_output[0]);\n                if (std_output[1] != -1)\n                        close(std_output[0]);\n                return (1);\n        case 0:                         /* Utility. */\n                /* Redirect stdout to the write end of the pipe. */\n                (void)dup2(std_output[1], STDOUT_FILENO);\n\n                /* Close the utility's file descriptors. */\n                (void)close(std_output[0]);\n                (void)close(std_output[1]);\n                (void)close(STDERR_FILENO);\n\n                /*\n                 * XXX\n                 * Assume that all shells have -c.\n                 */\n                execl(sh_path, sh, \"-c\", bp, (char *)NULL);\n                msgq_str(sp, M_SYSERR, sh_path, \"Error: execl: %s\");\n                _exit(127);\n        default:                        /* Parent. */\n                /* Close the pipe ends the parent won't use. */\n                (void)close(std_output[1]);\n                break;\n        }\n\n        /*\n         * Copy process standard output into a buffer.\n         *\n         * !!!\n         * Historic vi apparently discarded leading \\n and \\r's from\n         * the shell output stream.  We don't on the grounds that any\n         * shell that does that is broken.\n         */\n        for (p = bp, len = 0, ch = EOF;\n            (ch = getc(ifp)) != EOF; *p++ = ch, blen -= sizeof(CHAR_T), ++len)\n                if (blen < 5) {\n                        ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);\n                        p = bp + len;\n                        blen = *blenp - len * sizeof(CHAR_T);\n                }\n\n        /* Delete the final newline, NULL terminate the string. */\n        if (p > bp && (p[-1] == '\\n' || p[-1] == '\\r')) {\n                --p;\n                --len;\n        }\n        *p = '\\0';\n        *lenp = len;\n        *bpp = bp;              /* *blenp is already updated. */\n\n        if (ferror(ifp))\n                goto ioerr;\n        if (fclose(ifp)) {\nioerr:          msgq_str(sp, M_ERR, sh, \"I/O error: %s\");\nalloc_err:      rval = SEXP_ERR;\n        } else\n                rval = SEXP_OK;\n\n        /*\n         * Wait for the process.  If the shell process fails (e.g., \"echo $q\"\n         * where q wasn't a defined variable) or if the returned string has\n         * no characters or only blank characters, (e.g., \"echo $5\"), complain\n         * that the shell expansion failed.  We can't know for certain that's\n         * the error, but it's a good guess, and it matches historic practice.\n         * This won't catch \"echo foo_$5\", but that's not a common error and\n         * historic vi didn't catch it either.\n         */\n        if (proc_wait(sp, pid, sh, 1, 0))\n                rval = SEXP_EXPANSION_ERR;\n\n        for (p = bp; len; ++p, --len)\n                if (!isblank(*p))\n                        break;\n        if (len == 0)\n                rval = SEXP_EXPANSION_ERR;\n\n        if (rval == SEXP_EXPANSION_ERR)\n                msgq(sp, M_ERR, \"Shell expansion failed\");\n\n        return (rval == SEXP_OK ? 0 : 1);\n}\n"
  },
  {
    "path": "ex/ex_at.c",
    "content": "/*      $OpenBSD: ex_at.c,v 1.14 2016/05/27 09:18:12 martijn Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_at -- :@[@ | buffer]\n *          :*[* | buffer]\n *\n *      Execute the contents of the buffer.\n *\n * PUBLIC: int ex_at(SCR *, EXCMD *);\n */\nint\nex_at(SCR *sp, EXCMD *cmdp)\n{\n        CB *cbp;\n        CHAR_T name;\n        EXCMD *ecp;\n        RANGE *rp;\n        TEXT *tp;\n        size_t len;\n        char *p;\n\n        /*\n         * !!!\n         * Historically, [@*]<carriage-return> and [@*][@*] executed the most\n         * recently executed buffer in ex mode.\n         */\n        name = FL_ISSET(cmdp->iflags, E_C_BUFFER) ? cmdp->buffer : '@';\n        if (name == '@' || name == '*') {\n                if (!F_ISSET(sp, SC_AT_SET)) {\n                        ex_emsg(sp, NULL, EXM_NOPREVBUF);\n                        return (1);\n                }\n                name = sp->at_lbuf;\n        }\n        sp->at_lbuf = name;\n        F_SET(sp, SC_AT_SET);\n\n        CBNAME(sp, cbp, name);\n        if (cbp == NULL) {\n                ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF);\n                return (1);\n        }\n\n        /*\n         * !!!\n         * Historically the @ command took a range of lines, and the @ buffer\n         * was executed once per line.  The historic vi could be trashed by\n         * this because it didn't notice if the underlying file changed, or,\n         * for that matter, if there were no more lines on which to operate.\n         * For example, take a 10 line file, load \"%delete\" into a buffer,\n         * and enter :8,10@<buffer>.\n         *\n         * The solution is a bit tricky.  If the user specifies a range, take\n         * the same approach as for global commands, and discard the command\n         * if exit or switch to a new file/screen.  If the user doesn't specify\n         * the  range, continue to execute after a file/screen switch, which\n         * means @ buffers are still useful in a multi-screen environment.\n         */\n        CALLOC_RET(sp, ecp, 1, sizeof(EXCMD));\n        TAILQ_INIT(&ecp->rq);\n        CALLOC_RET(sp, rp, 1, sizeof(RANGE));\n        rp->start = cmdp->addr1.lno;\n        if (F_ISSET(cmdp, E_ADDR_DEF)) {\n                rp->stop = rp->start;\n                FL_SET(ecp->agv_flags, AGV_AT_NORANGE);\n        } else {\n                rp->stop = cmdp->addr2.lno;\n                FL_SET(ecp->agv_flags, AGV_AT);\n        }\n        TAILQ_INSERT_HEAD(&ecp->rq, rp, q);\n\n        /*\n         * Buffers executed in ex mode or from the colon command line in vi\n         * were ex commands.  We can't push it on the terminal queue, since\n         * it has to be executed immediately, and we may be in the middle of\n         * an ex command already.  Push the command on the ex command stack.\n         * Build two copies of the command.  We need two copies because the\n         * ex parser may step on the command string when it's parsing it.\n         */\n        len = 0;\n        TAILQ_FOREACH_REVERSE(tp, &cbp->textq, _texth, q) {\n                len += tp->len + 1;\n        }\n\n        MALLOC_RET(sp, ecp->cp, len * 2);\n        ecp->o_cp = ecp->cp;\n        ecp->o_clen = len;\n        ecp->cp[len] = '\\0';\n\n        /* Copy the buffer into the command space. */\n        p = ecp->cp + len;\n        TAILQ_FOREACH_REVERSE(tp, &cbp->textq, _texth, q) {\n                memcpy(p, tp->lb, tp->len);\n                p += tp->len;\n                *p++ = '\\n';\n        }\n\n        LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_bang.c",
    "content": "/*      $OpenBSD: ex_bang.c,v 1.13 2025/07/30 22:19:13 millert Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_err.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n#undef open\n\n/*\n * ex_bang -- :[line [,line]] ! command\n *\n * Pass the rest of the line after the ! character to the program named by\n * the O_SHELL option.\n *\n * Historical vi did NOT do shell expansion on the arguments before passing\n * them, only file name expansion.  This means that the O_SHELL program got\n * \"$t\" as an argument if that is what the user entered.  Also, there's a\n * special expansion done for the bang command.  Any exclamation points in\n * the user's argument are replaced by the last, expanded ! command.\n *\n * There's some fairly amazing slop in this routine to make the different\n * ways of getting here display the right things.  It took a long time to\n * get it right (wrong?), so be careful.\n *\n * PUBLIC: int ex_bang(SCR *, EXCMD *);\n */\nint\nex_bang(SCR *sp, EXCMD *cmdp)\n{\n        enum filtertype ftype;\n        ARGS *ap;\n        EX_PRIVATE *exp;\n        MARK rm;\n        recno_t lno;\n        int rval;\n        const char *msg;\n\n        ap = cmdp->argv[0];\n        if (ap->len == 0) {\n                ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                return (1);\n        }\n\n        /* Set the \"last bang command\" remembered value. */\n        exp = EXP(sp);\n        free(exp->lastbcomm);\n        if ((exp->lastbcomm = strdup(ap->bp)) == NULL) {\n                msgq(sp, M_SYSERR, NULL);\n                return (1);\n        }\n\n        /*\n         * If the command was modified by the expansion, it was historically\n         * redisplayed.\n         */\n        if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) {\n                /*\n                 * Display the command if modified.  Historic ex/vi displayed\n                 * the command if it was modified due to file name and/or bang\n                 * expansion.  If piping lines in vi, it would be immediately\n                 * overwritten by any error or line change reporting.\n                 */\n                if (F_ISSET(sp, SC_VI))\n                        vs_update(sp, \"!\", ap->bp);\n                else {\n                        (void)ex_printf(sp, \"!%s\\n\", ap->bp);\n                        (void)ex_fflush(sp);\n                }\n        }\n\n        /*\n         * If no addresses were specified, run the command.  If there's an\n         * underlying file, it's been modified and autowrite is set, write\n         * the file back.  If the file has been modified, autowrite is not\n         * set and the warn option is set, tell the user about the file.\n         */\n        if (cmdp->addrcnt == 0) {\n                msg = NULL;\n                if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) {\n                        if (O_ISSET(sp, O_AUTOWRITE)) {\n                                if (file_aw(sp, FS_ALL))\n                                        return (0);\n                        } else if (O_ISSET(sp, O_WARN) &&\n                            !F_ISSET(sp, SC_EX_SILENT))\n                                msg = \"File may be modified since last write.\";\n                }\n\n                /* If we're still in a vi screen, move out explicitly. */\n                (void)ex_exec_proc(sp,\n                    cmdp, ap->bp, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE));\n        }\n\n        /*\n         * If addresses were specified, pipe lines from the file through the\n         * command.\n         *\n         * Historically, vi lines were replaced by both the stdout and stderr\n         * lines of the command, but ex lines by only the stdout lines.  This\n         * makes no sense to me, so nvi makes it consistent for both, and\n         * matches vi's historic behavior.\n         */\n        else {\n                NEEDFILE(sp, cmdp);\n\n                /* Autoprint is set historically, even if the command fails. */\n                F_SET(cmdp, E_AUTOPRINT);\n\n                /*\n                 * !!!\n                 * Historical vi permitted \"!!\" in an empty file.  When this\n                 * happens, we arrive here with two addresses of 1,1 and a\n                 * bad attitude.  The simple solution is to turn it into a\n                 * FILTER_READ operation, with the exception that stdin isn't\n                 * opened for the utility, and the cursor position isn't the\n                 * same.  The only historic glitch (I think) is that we don't\n                 * put an empty line into the default cut buffer, as historic\n                 * vi did.  Imagine, if you can, my disappointment.\n                 */\n                ftype = FILTER_BANG;\n                if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) {\n                        if (db_last(sp, &lno))\n                                return (1);\n                        if (lno == 0) {\n                                cmdp->addr1.lno = cmdp->addr2.lno = 0;\n                                ftype = FILTER_RBANG;\n                        }\n                }\n                rval = ex_filter(sp, cmdp,\n                    &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype);\n                (void)rval;\n\n                /*\n                 * If in vi mode, move to the first nonblank.\n                 *\n                 * !!!\n                 * Historic vi wasn't consistent in this area -- if you used\n                 * a forward motion it moved to the first nonblank, but if you\n                 * did a backward motion it didn't.  And, if you followed a\n                 * backward motion with a forward motion, it wouldn't move to\n                 * the nonblank for either.  Going to the nonblank generally\n                 * seems more useful and consistent, so we do it.\n                 */\n                sp->lno = rm.lno;\n                if (F_ISSET(sp, SC_VI)) {\n                        sp->cno = 0;\n                        (void)nonblank(sp, sp->lno, &sp->cno);\n                } else\n                        sp->cno = rm.cno;\n        }\n\n        /* Ex terminates with a bang, even if the command fails. */\n        if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT))\n                (void)ex_puts(sp, \"!\\n\");\n\n        /* If addresses were specified, apply expandtab to the new text. */\n        if (cmdp->addrcnt != 0 && O_ISSET(sp, O_EXPANDTAB))\n                ex_retab(sp, cmdp);\n\n        /*\n         * XXX\n         * The ! commands never return an error, so that autoprint always\n         * happens in the ex parser.\n         */\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_cd.c",
    "content": "/*      $OpenBSD: ex_cd.c,v 1.15 2016/05/27 09:18:12 martijn Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <pwd.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_cd -- :cd[!] [directory]\n *      Change directories.\n *\n * PUBLIC: int ex_cd(SCR *, EXCMD *);\n */\nint\nex_cd(SCR *sp, EXCMD *cmdp)\n{\n        struct passwd *pw;\n        ARGS *ap;\n        CHAR_T savech;\n        char *dir, *p, *t;\n        char buf[PATH_MAX * 2];\n\n        /*\n         * !!!\n         * Historic practice is that the cd isn't attempted if the file has\n         * been modified, unless its name begins with a leading '/' or the\n         * force flag is set.\n         */\n        if (F_ISSET(sp->ep, F_MODIFIED) &&\n            !FL_ISSET(cmdp->iflags, E_C_FORCE) && sp->frp->name[0] != '/') {\n                msgq(sp, M_ERR,\n \"File may be modified since last complete write; write or use ! to override\");\n                return (1);\n        }\n\n        switch (cmdp->argc) {\n        case 0:\n                /* If no argument, change to the user's home directory. */\n                if ((dir = getenv(\"HOME\")) == NULL || *dir == '\\0') {\n                        if ((pw = getpwuid(getuid())) == NULL ||\n                            pw->pw_dir == NULL || pw->pw_dir[0] == '\\0') {\n                                msgq(sp, M_ERR,\n                           \"Unable to find $HOME directory location\");\n                                return (1);\n                        }\n                        dir = pw->pw_dir;\n                }\n                break;\n        case 1:\n                dir = cmdp->argv[0]->bp;\n                break;\n        default:\n                abort();\n        }\n\n        /*\n         * Try the current directory first.  If this succeeds, don't display\n         * a message, vi didn't historically, and it should be obvious to the\n         * user where they are.\n         */\n        if (!chdir(dir))\n                return (0);\n\n        /*\n         * If moving to the user's home directory, or, the path begins with\n         * \"/\", \"./\" or \"../\", it's the only place we try.\n         */\n        if (cmdp->argc == 0 ||\n            (ap = cmdp->argv[0])->bp[0] == '/' ||\n            (ap->len == 1 && ap->bp[0] == '.') ||\n            (ap->len >= 2 && ap->bp[0] == '.' && ap->bp[1] == '.' &&\n            (ap->bp[2] == '/' || ap->bp[2] == '\\0')))\n                goto err;\n\n        /* Try the O_CDPATH option values. */\n        for (p = t = O_STR(sp, O_CDPATH);; ++p)\n                if (*p == '\\0' || *p == ':') {\n                        /*\n                         * Empty strings specify \".\".  The only way to get an\n                         * empty string is a leading colon, colons in a row,\n                         * or a trailing colon.  Or, to put it the other way,\n                         * if the length is 1 or less, then we're dealing with\n                         * \":XXX\", \"XXX::XXXX\" , \"XXX:\", or \"\".  Since we've\n                         * already tried dot, we ignore them all.\n                         */\n                        if (t < p - 1) {\n                                savech = *p;\n                                *p = '\\0';\n                                (void)snprintf(buf,\n                                    sizeof(buf), \"%s/%s\", t, dir);\n                                *p = savech;\n                                if (!chdir(buf)) {\n                                        if (getcwd(buf, sizeof(buf)) != NULL)\n                msgq_str(sp, M_INFO, buf, \"New current directory: %s\");\n                                        return (0);\n                                }\n                        }\n                        t = p + 1;\n                        if (*p == '\\0')\n                                break;\n                }\n\nerr:    msgq_str(sp, M_SYSERR, dir, \"%s\");\n        return (1);\n}\n"
  },
  {
    "path": "ex/ex_cmd.c",
    "content": "/*      $OpenBSD: ex_cmd.c,v 1.12 2018/07/13 20:06:10 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * This array maps ex command names to command functions.\n *\n * The order in which command names are listed below is important --\n * ambiguous abbreviations are resolved to be the first possible match,\n * e.g. \"r\" means \"read\", not \"rewind\", because \"read\" is listed before\n * \"rewind\".\n *\n * The syntax of the ex commands is unbelievably irregular, and a special\n * case from beginning to end.  Each command has an associated \"syntax\n * script\" which describes the \"arguments\" that are possible.  The script\n * syntax is as follows:\n *\n *      !               -- ! flag\n *      1               -- flags: [+-]*[pl#][+-]*\n *      2               -- flags: [-.+^]\n *      3               -- flags: [-.+^=]\n *      b               -- buffer\n *      c[01+a]         -- count (0-N, 1-N, signed 1-N, address offset)\n *      f[N#][or]       -- file (a number or N, optional or required)\n *      l               -- line\n *      S               -- string with file name expansion\n *      s               -- string\n *      W               -- word string\n *      w[N#][or]       -- word (a number or N, optional or required)\n */\nEXCMDLIST const cmds[] = {\n/* C_SCROLL */\n        {\"\\004\",        ex_pr,          E_ADDR2,\n            \"\",\n            \"^D\",\n            \"scroll lines\"},\n/* C_BANG */\n        {\"!\",           ex_bang,        E_ADDR2_NONE | E_SECURE,\n            \"S\",\n            \"[line [,line]] ! command\",\n            \"filter lines through commands or run commands\"},\n/* C_HASH */\n        {\"#\",           ex_number,      E_ADDR2|E_CLRFLAG,\n            \"ca1\",\n            \"[line [,line]] # [count] [l]\",\n            \"display numbered lines\"},\n/* C_SUBAGAIN */\n        {\"&\",           ex_subagain,    E_ADDR2,\n            \"s\",\n            \"[line [,line]] & [cgr] [count] [#lp]\",\n            \"repeat the last substitution\"},\n/* C_STAR */\n        {\"*\",           ex_at,          0,\n            \"b\",\n            \"* [buffer]\",\n            \"execute a buffer\"},\n/* C_SHIFTL */\n        {\"<\",           ex_shiftl,      E_ADDR2|E_AUTOPRINT,\n            \"ca1\",\n            \"[line [,line]] <[<...] [count] [flags]\",\n            \"shift lines left\"},\n/* C_EQUAL */\n        {\"=\",           ex_equal,       E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,\n            \"1\",\n            \"[line] = [flags]\",\n            \"display line number\"},\n/* C_SHIFTR */\n        {\">\",           ex_shiftr,      E_ADDR2|E_AUTOPRINT,\n            \"ca1\",\n            \"[line [,line]] >[>...] [count] [flags]\",\n            \"shift lines right\"},\n/* C_AT */\n        {\"@\",           ex_at,          E_ADDR2,\n            \"b\",\n            \"@ [buffer]\",\n            \"execute a buffer\"},\n/* C_APPEND */\n        {\"append\",      ex_append,      E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,\n            \"!\",\n            \"[line] a[ppend][!]\",\n            \"append input to a line\"},\n/* C_ABBR */\n        {\"abbreviate\",  ex_abbr,        0,\n            \"W\",\n            \"ab[brev] [word replace]\",\n            \"specify an input abbreviation\"},\n/* C_ARGS */\n        {\"args\",        ex_args,        0,\n            \"\",\n            \"ar[gs]\",\n            \"display file argument list\"},\n/* C_BG */\n        {\"bg\",          ex_bg,          E_VIONLY,\n            \"\",\n            \"bg\",\n            \"put the current screen into the background\"},\n/* C_CHANGE */\n        {\"change\",      ex_change,      E_ADDR2|E_ADDR_ZERODEF,\n            \"!ca\",\n            \"[line [,line]] c[hange][!] [count]\",\n            \"change lines to input\"},\n/* C_CD */\n        {\"cd\",          ex_cd,          0,\n            \"!f1o\",\n            \"cd[!] [directory]\",\n            \"change the current directory\"},\n/* C_CHDIR */\n        {\"chdir\",       ex_cd,          0,\n            \"!f1o\",\n            \"chd[ir][!] [directory]\",\n            \"change the current directory\"},\n/* C_COPY */\n        {\"copy\",        ex_copy,        E_ADDR2|E_AUTOPRINT,\n            \"l1\",\n            \"[line [,line]] co[py] line [flags]\",\n            \"copy lines elsewhere in the file\"},\n/*\n * !!!\n * Adding new commands starting with 'd' may break the delete command code\n * in ex_cmd() (the ex parser).  Read through the comments there, first.\n */\n/* C_DELETE */\n        {\"delete\",      ex_delete,      E_ADDR2|E_AUTOPRINT,\n            \"bca1\",\n            \"[line [,line]] d[elete][flags] [buffer] [count] [flags]\",\n            \"delete lines from the file\"},\n/* C_DISPLAY */\n        {\"display\",     ex_display,     0,\n            \"w1r\",\n            \"display b[uffers] | s[creens] | t[ags]\",\n            \"display buffers, screens or tags\"},\n/* C_EDIT */\n        {\"edit\",        ex_edit,        E_NEWSCREEN,\n            \"f1o\",\n            \"e[dit][!] [+cmd] [file]\",\n            \"begin editing another file\"},\n/* C_EX */\n        {\"ex\",          ex_edit,        E_NEWSCREEN,\n            \"f1o\",\n            \"ex[!] [+cmd] [file]\",\n            \"begin editing another file\"},\n/* C_EXUSAGE */\n        {\"exusage\",     ex_usage,       0,\n            \"w1o\",\n            \"[exu]sage [command]\",\n            \"display ex command usage statement\"},\n/* C_FILE */\n        {\"file\",        ex_file,        0,\n            \"f1o\",\n            \"f[ile] [name]\",\n            \"display (and optionally set) file name\"},\n/* C_FG */\n        {\"fg\",          ex_fg,          E_NEWSCREEN|E_VIONLY,\n            \"f1o\",\n            \"fg [file]\",\n            \"bring a backgrounded screen into the foreground\"},\n/* C_GLOBAL */\n        {\"global\",      ex_global,      E_ADDR2_ALL,\n            \"!s\",\n            \"[line [,line]] g[lobal][!] [;/]RE[;/] [commands]\",\n            \"execute a global command on lines matching an RE\"},\n/* C_HELP */\n        {\"help\",        ex_help,        0,\n            \"\",\n            \"he[lp]\",\n            \"display help statement\"},\n/* C_INSERT */\n        {\"insert\",      ex_insert,      E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,\n            \"!\",\n            \"[line] i[nsert][!]\",\n            \"insert input before a line\"},\n/* C_JOIN */\n        {\"join\",        ex_join,        E_ADDR2|E_AUTOPRINT,\n            \"!ca1\",\n            \"[line [,line]] j[oin][!] [count] [flags]\",\n            \"join lines into a single line\"},\n/* C_K */\n        {\"k\",           ex_mark,        E_ADDR1,\n            \"w1r\",\n            \"[line] k key\",\n            \"mark a line position\"},\n/* C_LIST */\n        {\"list\",        ex_list,        E_ADDR2|E_CLRFLAG,\n            \"ca1\",\n            \"[line [,line]] l[ist] [count] [#]\",\n            \"display lines in an unambiguous form\"},\n/* C_MOVE */\n        {\"move\",        ex_move,        E_ADDR2|E_AUTOPRINT,\n            \"l\",\n            \"[line [,line]] m[ove] line\",\n            \"move lines elsewhere in the file\"},\n/* C_MARK */\n        {\"mark\",        ex_mark,        E_ADDR1,\n            \"w1r\",\n            \"[line] ma[rk] key\",\n            \"mark a line position\"},\n/* C_MAP */\n        {\"map\",         ex_map,         0,\n            \"!W\",\n            \"map[!] [keys replace]\",\n            \"map input or commands to one or more keys\"},\n/* C_MKEXRC */\n        {\"mkexrc\",      ex_mkexrc,      0,\n            \"!f1r\",\n            \"mkexrc[!] file\",\n            \"write a .exrc file\"},\n/* C_NEXT */\n        {\"next\",        ex_next,        E_NEWSCREEN,\n            \"!fN\",\n            \"n[ext][!] [+cmd] [file ...]\",\n            \"edit (and optionally specify) the next file\"},\n/* C_NUMBER */\n        {\"number\",      ex_number,      E_ADDR2|E_CLRFLAG,\n            \"ca1\",\n            \"[line [,line]] nu[mber] [count] [l]\",\n            \"change display to number lines\"},\n/* C_OPEN */\n        {\"open\",        ex_open,        E_ADDR1,\n            \"s\",\n            \"[line] o[pen] [/RE/] [flags]\",\n            \"enter \\\"open\\\" mode (not implemented)\"},\n/* C_PRINT */\n        {\"print\",       ex_pr,          E_ADDR2|E_CLRFLAG,\n            \"ca1\",\n            \"[line [,line]] p[rint] [count] [#l]\",\n            \"display lines\"},\n/* C_PRESERVE */\n        {\"preserve\",    ex_preserve,    0,\n            \"\",\n            \"pre[serve]\",\n            \"preserve an edit session for recovery\"},\n/* C_PREVIOUS */\n        {\"previous\",    ex_prev,        E_NEWSCREEN,\n            \"!\",\n            \"prev[ious][!]\",\n            \"edit the previous file in the file argument list\"},\n/* C_PUT */\n        {\"put\",         ex_put,\n            E_ADDR1|E_AUTOPRINT|E_ADDR_ZERO|E_ADDR_ZERODEF,\n            \"b\",\n            \"[line] pu[t] [buffer]\",\n            \"append a cut buffer to the line\"},\n/* C_QUIT */\n        {\"quit\",        ex_quit,        0,\n            \"!\",\n            \"q[uit][!]\",\n            \"exit ex/vi or close the current screen\"},\n/* C_READ */\n        {\"read\",        ex_read,        E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,\n            \"s\",\n            \"[line] r[ead] [!cmd | [file]]\",\n            \"append input from a command or file to the line\"},\n/* C_RECOVER */\n        {\"recover\",     ex_recover,     0,\n            \"!f1r\",\n            \"recover[!] file\",\n            \"recover a saved file\"},\n/* C_RESIZE */\n        {\"resize\",      ex_resize,      E_VIONLY,\n            \"c+\",\n            \"resize [+-]rows\",\n            \"grow or shrink the current screen\"},\n/* C_REWIND */\n        {\"rewind\",      ex_rew,         0,\n            \"!\",\n            \"rew[ind][!]\",\n            \"re-edit all the files in the file argument list\"},\n/*\n * !!!\n * Adding new commands starting with 's' may break the substitute command code\n * in ex_cmd() (the ex parser).  Read through the comments there, first.\n */\n/* C_SUBSTITUTE */\n        {\"s\",           ex_s,           E_ADDR2,\n            \"s\",\n            \"[line [,line]] s [[/;]RE[/;]repl[/;] [cgr] [count] [#lp]]\",\n            \"substitute on lines matching an RE\"},\n/* C_SCRIPT */\n        {\"script\",      ex_script,      E_SECURE,\n            \"!f1o\",\n            \"sc[ript][!] [file]\",\n            \"run a shell in a screen\"},\n/* C_SET */\n        {\"set\",         ex_set,         0,\n            \"wN\",\n            \"se[t] [option[=[value]]...] [nooption ...] [option? ...] [all]\",\n            \"set options (use \\\":set all\\\" to see all options)\"},\n/* C_SHELL */\n        {\"shell\",       ex_shell,       E_SECURE,\n            \"\",\n            \"sh[ell]\",\n            \"suspend editing and run a shell\"},\n/* C_SOURCE */\n        {\"source\",      ex_source,      0,\n            \"f1r\",\n            \"so[urce] file\",\n            \"read a file of ex commands\"},\n/* C_STOP */\n        {\"stop\",        ex_stop,        E_SECURE,\n            \"!\",\n            \"st[op][!]\",\n            \"suspend the edit session\"},\n/* C_SUSPEND */\n        {\"suspend\",     ex_stop,        E_SECURE,\n            \"!\",\n            \"su[spend][!]\",\n            \"suspend the edit session\"},\n/* C_T */\n        {\"t\",           ex_copy,        E_ADDR2|E_AUTOPRINT,\n            \"l1\",\n            \"[line [,line]] t line [flags]\",\n            \"copy lines elsewhere in the file\"},\n/* C_TAG */\n        {\"tag\",         ex_tag_push,    E_NEWSCREEN,\n            \"!w1o\",\n            \"ta[g][!] [string]\",\n            \"edit the file containing the tag\"},\n/* C_TAGNEXT */\n        {\"tagnext\",     ex_tag_next,    0,\n            \"!\",\n            \"tagn[ext][!]\",\n            \"move to the next tag\"},\n/* C_TAGPOP */\n        {\"tagpop\",      ex_tag_pop,     0,\n            \"!w1o\",\n            \"tagp[op][!] [number | file]\",\n            \"return to the previous group of tags\"},\n/* C_TAGPREV */\n        {\"tagprev\",     ex_tag_prev,    0,\n            \"!\",\n            \"tagpr[ev][!]\",\n            \"move to the previous tag\"},\n/* C_TAGTOP */\n        {\"tagtop\",      ex_tag_top,     0,\n            \"!\",\n            \"tagt[op][!]\",\n            \"discard all tags\"},\n/* C_UNDO */\n        {\"undo\",        ex_undo,        E_AUTOPRINT,\n            \"\",\n            \"u[ndo]\",\n            \"undo the most recent change\"},\n/* C_UNABBREVIATE */\n        {\"unabbreviate\",ex_unabbr,      0,\n            \"w1r\",\n            \"una[bbrev] word\",\n            \"delete an abbreviation\"},\n/* C_UNMAP */\n        {\"unmap\",       ex_unmap,       0,\n            \"!w1r\",\n            \"unm[ap][!] word\",\n            \"delete an input or command map\"},\n/* C_V */\n        {\"v\",           ex_v,           E_ADDR2_ALL,\n            \"s\",\n            \"[line [,line]] v [;/]RE[;/] [commands]\",\n            \"execute a global command on lines NOT matching an RE\"},\n/* C_VERSION */\n        {\"version\",     ex_version,     0,\n            \"\",\n            \"version\",\n            \"display the program version information\"},\n/* C_VISUAL_EX */\n        {\"visual\",      ex_visual,      E_ADDR1|E_ADDR_ZERODEF,\n            \"2c11\",\n            \"[line] vi[sual] [-|.|+|^] [window_size] [flags]\",\n            \"enter visual (vi) mode from ex mode\"},\n/* C_VISUAL_VI */\n        {\"visual\",      ex_edit,        E_NEWSCREEN,\n            \"f1o\",\n            \"vi[sual][!] [+cmd] [file]\",\n            \"edit another file (from vi mode only)\"},\n/* C_VIUSAGE */\n        {\"viusage\",     ex_viusage,     0,\n            \"w1o\",\n            \"[viu]sage [key]\",\n            \"display vi key usage statement\"},\n/* C_WRITE */\n        {\"write\",       ex_write,       E_ADDR2_ALL|E_ADDR_ZERODEF,\n            \"!s\",\n            \"[line [,line]] w[rite][!] [ !cmd | [>>] [file]]\",\n            \"write the file\"},\n/* C_WN */\n        {\"wn\",          ex_wn,          E_ADDR2_ALL|E_ADDR_ZERODEF,\n            \"!s\",\n            \"[line [,line]] wn[!] [>>] [file]\",\n            \"write the file and switch to the next file\"},\n/* C_WQ */\n        {\"wq\",          ex_wq,          E_ADDR2_ALL|E_ADDR_ZERODEF,\n            \"!s\",\n            \"[line [,line]] wq[!] [>>] [file]\",\n            \"write the file and exit\"},\n/* C_XIT */\n        {\"xit\",         ex_xit,         E_ADDR2_ALL|E_ADDR_ZERODEF,\n            \"!f1o\",\n            \"[line [,line]] x[it][!] [file]\",\n            \"write if modified and exit\"},\n/* C_YANK */\n        {\"yank\",        ex_yank,        E_ADDR2,\n            \"bca\",\n            \"[line [,line]] ya[nk] [buffer] [count]\",\n            \"copy lines to a cut buffer\"},\n/* C_Z */\n        {\"z\",           ex_z,           E_ADDR1,\n            \"3c01\",\n            \"[line] z [-|.|+|^|=] [count] [flags]\",\n            \"display different screens of the file\"},\n/* C_SUBTILDE */\n        {\"~\",           ex_subtilde,    E_ADDR2,\n            \"s\",\n            \"[line [,line]] ~ [cgr] [count] [#lp]\",\n            \"replace previous RE with previous replacement string\"},\n        {NULL, NULL, 0, NULL, NULL, NULL},\n};\n"
  },
  {
    "path": "ex/ex_delete.c",
    "content": "/*      $OpenBSD: ex_delete.c,v 1.7 2014/11/12 04:28:41 bentley Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_delete: [line [,line]] d[elete] [buffer] [count] [flags]\n *\n *      Delete lines from the file.\n *\n * PUBLIC: int ex_delete(SCR *, EXCMD *);\n */\nint\nex_delete(SCR *sp, EXCMD *cmdp)\n{\n        recno_t lno;\n\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * !!!\n         * Historically, lines deleted in ex were not placed in the numeric\n         * buffers.  We follow historic practice so that we don't overwrite\n         * vi buffers accidentally.\n         */\n        if (cut(sp,\n            FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL,\n            &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE))\n                return (1);\n\n        /* Delete the lines. */\n        if (del(sp, &cmdp->addr1, &cmdp->addr2, 1))\n                return (1);\n\n        /* Set the cursor to the line after the last line deleted. */\n        sp->lno = cmdp->addr1.lno;\n\n        /* Or the last line in the file if deleted to the end of the file. */\n        if (db_last(sp, &lno))\n                return (1);\n        if (sp->lno > lno)\n                sp->lno = lno;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_display.c",
    "content": "/*      $OpenBSD: ex_display.c,v 1.13 2016/05/27 09:18:12 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"tag.h\"\n\nstatic int      bdisplay(SCR *);\nstatic void     db(SCR *, CB *, CHAR_T *);\n\n/*\n * ex_display -- :display b[uffers] | s[creens] | t[ags]\n *\n *      Display buffers, tags or screens.\n *\n * PUBLIC: int ex_display(SCR *, EXCMD *);\n */\nint\nex_display(SCR *sp, EXCMD *cmdp)\n{\n        switch (cmdp->argv[0]->bp[0]) {\n        case 'b':\n#undef  ARG\n#define ARG     \"buffers\"\n                if (cmdp->argv[0]->len >= sizeof(ARG) ||\n                    memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))\n                        break;\n                return (bdisplay(sp));\n        case 's':\n#undef  ARG\n#define ARG     \"screens\"\n                if (cmdp->argv[0]->len >= sizeof(ARG) ||\n                    memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))\n                        break;\n                return (ex_sdisplay(sp));\n        case 't':\n#undef  ARG\n#define ARG     \"tags\"\n                if (cmdp->argv[0]->len >= sizeof(ARG) ||\n                    memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))\n                        break;\n                return (ex_tag_display(sp));\n        }\n        ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n        return (1);\n}\n\n/*\n * bdisplay --\n *\n *      Display buffers.\n */\nstatic int\nbdisplay(SCR *sp)\n{\n        CB *cbp;\n\n        if (LIST_FIRST(&sp->gp->cutq) == NULL && sp->gp->dcbp == NULL) {\n                msgq(sp, M_INFO, \"No cut buffers to display\");\n                return (0);\n        }\n\n        /* Display regular cut buffers. */\n        LIST_FOREACH(cbp, &sp->gp->cutq, q) {\n                if (isdigit(cbp->name))\n                        continue;\n                if (!TAILQ_EMPTY(&cbp->textq))\n                        db(sp, cbp, NULL);\n                if (INTERRUPTED(sp))\n                        return (0);\n        }\n        /* Display numbered buffers. */\n        LIST_FOREACH(cbp, &sp->gp->cutq, q) {\n                if (!isdigit(cbp->name))\n                        continue;\n                if (!TAILQ_EMPTY(&cbp->textq))\n                        db(sp, cbp, NULL);\n                if (INTERRUPTED(sp))\n                        return (0);\n        }\n        /* Display default buffer. */\n        if ((cbp = sp->gp->dcbp) != NULL)\n                db(sp, cbp, \"default buffer\");\n        return (0);\n}\n\n/*\n * db --\n *      Display a buffer.\n */\nstatic void\ndb(SCR *sp, CB *cbp, CHAR_T *name)\n{\n        CHAR_T *p;\n        TEXT *tp;\n        size_t len;\n\n        (void)ex_printf(sp, \"********** %s%s\\n\",\n            name == NULL ? KEY_NAME(sp, cbp->name) : name,\n            F_ISSET(cbp, CB_LMODE) ? \" (line mode)\" : \" (character mode)\");\n        TAILQ_FOREACH(tp, &cbp->textq, q) {\n                for (len = tp->len, p = tp->lb; len--; ++p) {\n                        (void)ex_puts(sp, KEY_NAME(sp, *p));\n                        if (INTERRUPTED(sp))\n                                return;\n                }\n                (void)ex_puts(sp, \"\\n\");\n        }\n}\n"
  },
  {
    "path": "ex/ex_edit.c",
    "content": "/*      $OpenBSD: ex_edit.c,v 1.6 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\nstatic int ex_N_edit(SCR *, EXCMD *, FREF *, int);\n\n/*\n * ex_edit --   :e[dit][!] [+cmd] [file]\n *              :ex[!] [+cmd] [file]\n *              :vi[sual][!] [+cmd] [file]\n *\n * Edit a file; if none specified, re-edit the current file.  The third\n * form of the command can only be executed while in vi mode.  See the\n * hack in ex.c:ex_cmd().\n *\n * !!!\n * Historic vi didn't permit the '+' command form without specifying\n * a file name as well.  This seems unreasonable, so we support it\n * regardless.\n *\n * PUBLIC: int ex_edit(SCR *, EXCMD *);\n */\nint\nex_edit(SCR *sp, EXCMD *cmdp)\n{\n        FREF *frp;\n        int attach, setalt;\n\n        switch (cmdp->argc) {\n        case 0:\n                /*\n                 * If the name has been changed, we edit that file, not the\n                 * original name.  If the user was editing a temporary file\n                 * (or wasn't editing any file), create another one.  The\n                 * reason for not reusing temporary files is that there is\n                 * special exit processing of them, and reuse is tricky.\n                 */\n                frp = sp->frp;\n                if (sp->ep == NULL || F_ISSET(frp, FR_TMPFILE)) {\n                        if ((frp = file_add(sp, NULL)) == NULL)\n                                return (1);\n                        attach = 0;\n                } else\n                        attach = 1;\n                setalt = 0;\n                break;\n        case 1:\n                if ((frp = file_add(sp, cmdp->argv[0]->bp)) == NULL)\n                        return (1);\n                attach = 0;\n                setalt = 1;\n                set_alt_name(sp, cmdp->argv[0]->bp);\n                break;\n        default:\n                abort();\n        }\n\n        if (F_ISSET(cmdp, E_NEWSCREEN))\n                return (ex_N_edit(sp, cmdp, frp, attach));\n\n        /*\n         * Check for modifications.\n         *\n         * !!!\n         * Contrary to POSIX 1003.2-1992, autowrite did not affect :edit.\n         */\n        if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE)))\n                return (1);\n\n        /* Switch files. */\n        if (file_init(sp, frp, NULL, (setalt ? FS_SETALT : 0) |\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))\n                return (1);\n\n        F_SET(sp, SC_FSWITCH);\n        return (0);\n}\n\n/*\n * ex_N_edit --\n *      New screen version of ex_edit.\n */\nstatic int\nex_N_edit(SCR *sp, EXCMD *cmdp, FREF *frp, int attach)\n{\n        SCR *new;\n\n        /* Get a new screen. */\n        if (screen_init(sp->gp, sp, &new))\n                return (1);\n        if (vs_split(sp, new, 0)) {\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* Get a backing file. */\n        if (attach) {\n                /* Copy file state, keep the screen and cursor the same. */\n                new->ep = sp->ep;\n                ++new->ep->refcnt;\n\n                new->frp = frp;\n                new->frp->flags = sp->frp->flags;\n\n                new->lno = sp->lno;\n                new->cno = sp->cno;\n        } else if (file_init(new, frp, NULL,\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) {\n                (void)vs_discard(new, NULL);\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* Create the argument list. */\n        new->cargv = new->argv = ex_buildargv(sp, NULL, frp->name);\n\n        /* Set up the switch. */\n        sp->nextdisp = new;\n        F_SET(sp, SC_SSWITCH);\n\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_equal.c",
    "content": "/*      $OpenBSD: ex_equal.c,v 1.6 2014/11/12 04:28:41 bentley Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_equal -- :address =\n *\n * PUBLIC: int ex_equal(SCR *, EXCMD *);\n */\nint\nex_equal(SCR *sp, EXCMD *cmdp)\n{\n        recno_t lno;\n\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * Print out the line number matching the specified address,\n         * or the number of the last line in the file if no address\n         * specified.\n         *\n         * !!!\n         * Historically, \":0=\" displayed 0, and \":=\" or \":1=\" in an\n         * empty file displayed 1.  Until somebody complains loudly,\n         * we're going to do it right.  The tables in excmd.c permit\n         * lno to get away with any address from 0 to the end of the\n         * file, which, in an empty file, is 0.\n         */\n        if (F_ISSET(cmdp, E_ADDR_DEF)) {\n                if (db_last(sp, &lno))\n                        return (1);\n        } else\n                lno = cmdp->addr1.lno;\n\n        (void)ex_printf(sp, \"%ld\\n\", lno);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_file.c",
    "content": "/*      $OpenBSD: ex_file.c,v 1.9 2016/05/27 09:18:12 martijn Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_file -- :f[ile] [name]\n *      Change the file's name and display the status line.\n *\n * PUBLIC: int ex_file(SCR *, EXCMD *);\n */\nint\nex_file(SCR *sp, EXCMD *cmdp)\n{\n        CHAR_T *p;\n        FREF *frp;\n\n        NEEDFILE(sp, cmdp);\n\n        switch (cmdp->argc) {\n        case 0:\n                break;\n        case 1:\n                frp = sp->frp;\n\n                /* Make sure can allocate enough space. */\n                if ((p = v_strdup(sp,\n                    cmdp->argv[0]->bp, cmdp->argv[0]->len)) == NULL)\n                        return (1);\n\n                /* If already have a file name, it becomes the alternate. */\n                if (!F_ISSET(frp, FR_TMPFILE))\n                        set_alt_name(sp, frp->name);\n\n                /* Free the previous name. */\n                free(frp->name);\n                frp->name = p;\n\n                /*\n                 * The file has a real name, it's no longer a temporary,\n                 * clear the temporary file flags.\n                 */\n                F_CLR(frp, FR_TMPEXIT | FR_TMPFILE);\n\n                /* Have to force a write if the file exists, next time. */\n                F_SET(frp, FR_NAMECHANGE);\n\n                /* Notify the screen. */\n                (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n                break;\n        default:\n                abort();\n        }\n        msgq_status(sp, sp->lno, MSTAT_SHOWLAST);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_filter.c",
    "content": "/*      $OpenBSD: ex_filter.c,v 1.15 2016/08/01 18:27:35 bentley Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n#undef open\n\nstatic int filter_ldisplay(SCR *, FILE *);\n\n/*\n * ex_filter --\n *      Run a range of lines through a filter utility and optionally\n *      replace the original text with the stdout/stderr output of\n *      the utility.\n *\n * PUBLIC: int ex_filter(SCR *,\n * PUBLIC:    EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype);\n */\nint\nex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, char *cmd,\n    enum filtertype ftype)\n{\n        FILE *ifp, *ofp;\n        pid_t parent_writer_pid, utility_pid;\n        recno_t nread;\n        int input[2], output[2], fd, rval;\n        char *name, tname[] = \"/tmp/vi.XXXXXX\";\n\n        rval = 0;\n\n        /* Set return cursor position, which is never less than line 1. */\n        *rp = *fm;\n        if (rp->lno == 0)\n                rp->lno = 1;\n\n        /* We're going to need a shell. */\n        if (opts_empty(sp, O_SHELL, 0))\n                return (1);\n\n        /*\n         * There are three different processes running through this code.\n         * They are the utility, the parent-writer and the parent-reader.\n         * The parent-writer is the process that writes from the file to\n         * the utility, the parent reader is the process that reads from\n         * the utility.\n         *\n         * Input and output are named from the utility's point of view.\n         * The utility reads from input[0] and the parent(s) write to\n         * input[1].  The parent(s) read from output[0] and the utility\n         * writes to output[1].\n         *\n         * !!!\n         * Historically, in the FILTER_READ case, the utility reads from\n         * the terminal (e.g. :r! cat works).  Otherwise open up utility\n         * input pipe.\n         */\n        ofp = NULL;\n        input[0] = input[1] = output[0] = output[1] = -1;\n\n        if (ftype == FILTER_BANG) {\n                fd = mkstemp(tname);\n                if (fd == -1) {\n                        msgq(sp, M_SYSERR,\n                            \"Unable to create temporary file\");\n                        if (fd != -1) {\n                                (void)close(fd);\n                                (void)unlink(tname);\n                        }\n                        goto err;\n                }\n                if (unlink(tname) == -1)\n                        msgq(sp, M_SYSERR, \"unlink\");\n                if ((ifp = fdopen(fd, \"w\")) == NULL) {\n                        msgq(sp, M_SYSERR, \"fdopen\");\n                        (void)close(fd);\n                        goto err;\n                }\n                if ((input[0] = dup(fd)) == -1) {\n                        msgq(sp, M_SYSERR, \"dup\");\n                        (void)fclose(ifp);\n                        goto err;\n                }\n                /*\n                 * Write the selected lines into the temporary file.\n                 * This instance of ifp is closed by ex_writefp.\n                 */\n                if (ex_writefp(sp, \"filter\", ifp, fm, tm, NULL, NULL, 1))\n                        goto err;\n                if (lseek(input[0], 0, SEEK_SET) == -1) {\n                        msgq(sp, M_SYSERR, \"lseek\");\n                        goto err;\n                }\n        } else if (ftype != FILTER_READ && pipe(input) < 0) {\n                msgq(sp, M_SYSERR, \"pipe\");\n                goto err;\n        }\n\n        /* Open up utility output pipe. */\n        if (pipe(output) < 0) {\n                msgq(sp, M_SYSERR, \"pipe\");\n                goto err;\n        }\n        if ((ofp = fdopen(output[0], \"r\")) == NULL) {\n                msgq(sp, M_SYSERR, \"fdopen\");\n                goto err;\n        }\n\n        /* Fork off the utility process. */\n        switch (utility_pid = fork()) {\n        case -1:                        /* Error. */\n                msgq(sp, M_SYSERR, \"fork\");\nerr:            if (input[0] != -1)\n                        (void)close(input[0]);\n                if (input[1] != -1)\n                        (void)close(input[1]);\n                if (ofp != NULL)\n                        (void)fclose(ofp);\n                else if (output[0] != -1)\n                        (void)close(output[0]);\n                if (output[1] != -1)\n                        (void)close(output[1]);\n                return (1);\n        case 0:                         /* Utility. */\n                /*\n                 * Redirect stdin from the read end of the input pipe, and\n                 * redirect stdout/stderr to the write end of the output pipe.\n                 *\n                 * !!!\n                 * Historically, ex only directed stdout into the input pipe,\n                 * letting stderr come out on the terminal as usual.  Vi did\n                 * not, directing both stdout and stderr into the input pipe.\n                 * We match that practice in both ex and vi for consistency.\n                 */\n                if (input[0] != -1)\n                        (void)dup2(input[0], STDIN_FILENO);\n                (void)dup2(output[1], STDOUT_FILENO);\n                (void)dup2(output[1], STDERR_FILENO);\n\n                /* Close the utility's file descriptors. */\n                if (input[0] != -1)\n                        (void)close(input[0]);\n                if (input[1] != -1)\n                        (void)close(input[1]);\n                (void)close(output[0]);\n                (void)close(output[1]);\n\n                if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)\n                        name = O_STR(sp, O_SHELL);\n                else\n                        ++name;\n\n                execl(O_STR(sp, O_SHELL), name, \"-c\", cmd, (char *)NULL);\n                msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), \"execl: %s\");\n                _exit (127);\n                /* NOTREACHED */\n        default:                        /* Parent-reader, parent-writer. */\n                /* Close the pipe ends neither parent will use. */\n                if (input[0] != -1)\n                        (void)close(input[0]);\n                (void)close(output[1]);\n                break;\n        }\n\n        /*\n         * FILTER_RBANG, FILTER_READ:\n         *\n         * Reading is the simple case -- we don't need a parent writer,\n         * so the parent reads the output from the read end of the output\n         * pipe until it finishes, then waits for the child.  Ex_readfp\n         * appends to the MARK, and closes ofp.\n         *\n         * For FILTER_RBANG, there is nothing to write to the utility.\n         * Make sure it doesn't wait forever by closing its standard\n         * input.\n         *\n         * !!!\n         * Set the return cursor to the last line read in for FILTER_READ.\n         * Historically, this behaves differently from \":r file\" command,\n         * which leaves the cursor at the first line read in.  Check to\n         * make sure that it's not past EOF because we were reading into an\n         * empty file.\n         */\n        if (ftype == FILTER_RBANG || ftype == FILTER_READ) {\n                if (ftype == FILTER_RBANG)\n                        (void)close(input[1]);\n\n                if (ex_readfp(sp, \"filter\", ofp, fm, &nread, 1))\n                        rval = 1;\n                sp->rptlines[L_ADDED] += nread;\n                if (ftype == FILTER_READ) {\n                        if (fm->lno == 0)\n                                rp->lno = nread;\n                        else\n                                rp->lno += nread;\n                }\n        }\n\n        /*\n         * FILTER_WRITE\n         *\n         * Here we need both a reader and a writer.  Temporary files are\n         * expensive and we'd like to avoid disk I/O.  Using pipes has the\n         * obvious starvation conditions.  It's done as follows:\n         *\n         *      fork\n         *      child\n         *              write lines out\n         *              exit\n         *      parent\n         *              read and display lines\n         *              wait for child\n         *\n         * We get away without locking the underlying database because we know\n         * that filter_ldisplay() does not modify it.  When the DB code has\n         * locking, we should treat vi as if it were multiple applications\n         * sharing a database, and do the required locking.  If necessary a\n         * work-around would be to do explicit locking in the line.c:db_get()\n         * code, based on the flag set here.\n         */\n        if (ftype == FILTER_WRITE) {\n                F_SET(sp->ep, F_MULTILOCK);\n                switch (parent_writer_pid = fork()) {\n                case -1:                /* Error. */\n                        msgq(sp, M_SYSERR, \"fork\");\n                        (void)close(input[1]);\n                        (void)close(output[0]);\n                        rval = 1;\n                        break;\n                case 0:                 /* Parent-writer. */\n                        /*\n                         * Write the selected lines to the write end of the\n                         * input pipe.  This instance of ifp is closed by\n                         * ex_writefp.\n                         */\n                        (void)close(output[0]);\n                        if ((ifp = fdopen(input[1], \"w\")) == NULL)\n                                _exit (1);\n                        _exit(ex_writefp(sp, \"filter\",\n                            ifp, fm, tm, NULL, NULL, 1));\n                        /* NOTREACHED */\n                default:                /* Parent-reader. */\n                        (void)close(input[1]);\n                        /*\n                         * Read the output from the read end of the output\n                         * pipe and display it.  Filter_ldisplay closes ofp.\n                         */\n                        if (filter_ldisplay(sp, ofp))\n                                rval = 1;\n\n                        /* Wait for the parent-writer. */\n                        if (proc_wait(sp,\n                            parent_writer_pid, \"parent-writer\", 0, 1))\n                                rval = 1;\n                        break;\n                }\n                F_CLR(sp->ep, F_MULTILOCK);\n        }\n\n        /*\n         * FILTER_BANG\n         *\n         * Here we need a temporary file because our database lacks locking.\n         *\n         * XXX\n         * Temporary files are expensive and we'd like to avoid disk I/O.\n         * When the DB code has locking, we should treat vi as if it were\n         * multiple applications sharing a database, and do the required\n         * locking.  If necessary a work-around would be to do explicit\n         * locking in the line.c:db_get() code, based on F_MULTILOCK flag set\n         * here.\n         */\n        if (ftype == FILTER_BANG) {\n                /*\n                 * Read the output from the read end of the output\n                 * pipe.  Ex_readfp appends to the MARK and closes\n                 * ofp.\n                 */\n                if (ex_readfp(sp, \"filter\", ofp, tm, &nread, 1))\n                        rval = 1;\n                sp->rptlines[L_ADDED] += nread;\n\n                /* Delete any lines written to the utility. */\n                if (rval == 0 &&\n                    (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||\n                    del(sp, fm, tm, 1))) {\n                        rval = 1;\n                        goto uwait;\n                }\n\n                /*\n                 * If the filter had no output, we may have just deleted\n                 * the cursor.  Don't do any real error correction, we'll\n                 * try and recover later.\n                 */\n                 if (rp->lno > 1 && !db_exist(sp, rp->lno))\n                        --rp->lno;\n        }\n\n        /*\n         * !!!\n         * Ignore errors on vi file reads, to make reads prettier.  It's\n         * completely inconsistent, and historic practice.\n         */\nuwait:  return (proc_wait(sp, utility_pid, cmd,\n            ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);\n}\n\n/*\n * filter_ldisplay --\n *      Display output from a utility.\n *\n * !!!\n * Historically, the characters were passed unmodified to the terminal.\n * We use the ex print routines to make sure they're printable.\n */\nstatic int\nfilter_ldisplay(SCR *sp, FILE *fp)\n{\n        size_t len;\n\n        EX_PRIVATE *exp;\n\n        for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);)\n                if (ex_ldisplay(sp, exp->ibp, len, 0, 0))\n                        break;\n        if (ferror(fp))\n                msgq(sp, M_SYSERR, \"filter read\");\n        (void)fclose(fp);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_global.c",
    "content": "/*      $OpenBSD: ex_global.c,v 1.17 2016/05/27 09:18:12 martijn Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\nenum which {GLOBAL, V};\n\nstatic int ex_g_setup(SCR *, EXCMD *, enum which);\n\n/*\n * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands]\n *      Exec on lines matching a pattern.\n *\n * PUBLIC: int ex_global(SCR *, EXCMD *);\n */\nint\nex_global(SCR *sp, EXCMD *cmdp)\n{\n        return (ex_g_setup(sp,\n            cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL));\n}\n\n/*\n * ex_v -- [line [,line]] v /pattern/ [commands]\n *      Exec on lines not matching a pattern.\n *\n * PUBLIC: int ex_v(SCR *, EXCMD *);\n */\nint\nex_v(SCR *sp, EXCMD *cmdp)\n{\n        return (ex_g_setup(sp, cmdp, V));\n}\n\n/*\n * ex_g_setup --\n *      Ex global and v commands.\n */\nstatic int\nex_g_setup(SCR *sp, EXCMD *cmdp, enum which cmd)\n{\n        CHAR_T *ptrn, *p, *t;\n        EXCMD *ecp;\n        MARK abs_mark;\n        RANGE *rp;\n        busy_t btype;\n        recno_t start, end;\n        regex_t *re;\n        regmatch_t match[1];\n        size_t len;\n        int cnt, delim, eval;\n        char *dbp;\n\n        NEEDFILE(sp, cmdp);\n\n        if (F_ISSET(sp, SC_EX_GLOBAL)) {\n                msgq(sp, M_ERR,\n        \"The %s command can't be used as part of a global or v command\",\n                    cmdp->cmd->name);\n                return (1);\n        }\n\n        /*\n         * Skip leading white space.  Historic vi allowed any non-alphanumeric\n         * to serve as the global command delimiter.\n         */\n        if (cmdp->argc == 0)\n                goto usage;\n        for (p = cmdp->argv[0]->bp; isblank(*p); ++p);\n        if (*p == '\\0' || isalnum(*p) ||\n            *p == '\\\\' || *p == '|' || *p == '\\n') {\nusage:          ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                return (1);\n        }\n        delim = *p++;\n\n        /*\n         * Get the pattern string, toss escaped characters.\n         *\n         * QUOTING NOTE:\n         * Only toss an escaped character if it escapes a delimiter.\n         */\n        for (ptrn = t = p;;) {\n                if (p[0] == '\\0' || p[0] == delim) {\n                        if (p[0] == delim)\n                                ++p;\n                        /*\n                         * !!!\n                         * NULL terminate the pattern string -- it's passed\n                         * to regcomp which doesn't understand anything else.\n                         */\n                        *t = '\\0';\n                        break;\n                }\n                if (p[0] == '\\\\') {\n                        if (p[1] == delim)\n                                ++p;\n                        else if (p[1] == '\\\\')\n                                *t++ = *p++;\n                }\n                *t++ = *p++;\n        }\n\n        /* If the pattern string is empty, use the last one. */\n        if (*ptrn == '\\0') {\n                if (sp->re == NULL) {\n                        ex_emsg(sp, NULL, EXM_NOPREVRE);\n                        return (1);\n                }\n\n                /* Re-compile the RE if necessary. */\n                if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,\n                    sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))\n                        return (1);\n        } else {\n                /* Compile the RE. */\n                if (re_compile(sp, ptrn, t - ptrn,\n                    &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH))\n                        return (1);\n\n                /*\n                 * Set saved RE.  Historic practice is that globals set\n                 * direction as well as the RE.\n                 */\n                sp->searchdir = FORWARD;\n        }\n        re = &sp->re_c;\n        (void)re;\n\n        /* The global commands always set the previous context mark. */\n        abs_mark.lno = sp->lno;\n        abs_mark.cno = sp->cno;\n        if (mark_set(sp, ABSMARK1, &abs_mark, 1))\n                return (1);\n\n        /* Get an EXCMD structure. */\n        CALLOC_RET(sp, ecp, 1, sizeof(EXCMD));\n        TAILQ_INIT(&ecp->rq);\n\n        /*\n         * Get a copy of the command string; the default command is print.\n         * Don't worry about a set of <blank>s with no command, that will\n         * default to print in the ex parser.  We need to have two copies\n         * because the ex parser may step on the command string when it's\n         * parsing it.\n         */\n        if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) {\n                p = \"pp\";\n                len = 1;\n        }\n\n        MALLOC_RET(sp, ecp->cp, len * 2);\n        ecp->o_cp = ecp->cp;\n        ecp->o_clen = len;\n        memcpy(ecp->cp + len, p, len);\n        ecp->range_lno = OOBLNO;\n        FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V);\n        LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q);\n\n        /*\n         * For each line...  The semantics of global matching are that we first\n         * have to decide which lines are going to get passed to the command,\n         * and then pass them to the command, ignoring other changes.  There's\n         * really no way to do this in a single pass, since arbitrary line\n         * creation, deletion and movement can be done in the ex command.  For\n         * example, a good vi clone test is \":g/X/mo.-3\", or \"g/X/.,.+1d\".\n         * What we do is create linked list of lines that are tracked through\n         * each ex command.  There's a callback routine which the DB interface\n         * routines call when a line is created or deleted.  This doesn't help\n         * the layering much.\n         */\n        btype = BUSY_ON;\n        cnt = INTERRUPT_CHECK;\n        for (start = cmdp->addr1.lno,\n            end = cmdp->addr2.lno; start <= end; ++start) {\n                if (cnt-- == 0) {\n                        if (INTERRUPTED(sp)) {\n                                LIST_REMOVE(ecp, q);\n                                free(ecp->cp);\n                                free(ecp);\n                                break;\n                        }\n                        search_busy(sp, btype);\n                        btype = BUSY_UPDATE;\n                        cnt = INTERRUPT_CHECK;\n                }\n                if (db_get(sp, start, DBG_FATAL, &dbp, &len))\n                        return (1);\n                match[0].rm_so = 0;\n                match[0].rm_eo = len;\n                switch (eval =\n                    regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) {\n                case 0:\n                        if (cmd == V)\n                                continue;\n                        break;\n                case REG_NOMATCH:\n                        if (cmd == GLOBAL)\n                                continue;\n                        break;\n                default:\n                        re_error(sp, eval, &sp->re_c);\n                        break;\n                }\n\n                /* If follows the last entry, extend the last entry's range. */\n                if ((rp = TAILQ_LAST(&ecp->rq, _rh)) && rp->stop == start - 1) {\n                        ++rp->stop;\n                        continue;\n                }\n\n                /* Allocate a new range, and append it to the list. */\n                CALLOC(sp, rp, 1, sizeof(RANGE));\n                if (rp == NULL)\n                        return (1);\n                rp->start = rp->stop = start;\n                TAILQ_INSERT_TAIL(&ecp->rq, rp, q);\n        }\n        search_busy(sp, BUSY_OFF);\n        return (0);\n}\n\n/*\n * ex_g_insdel --\n *      Update the ranges based on an insertion or deletion.\n *\n * PUBLIC: int ex_g_insdel(SCR *, lnop_t, recno_t);\n */\nint\nex_g_insdel(SCR *sp, lnop_t op, recno_t lno)\n{\n        EXCMD *ecp;\n        RANGE *nrp, *rp;\n\n        /* All insert/append operations are done as inserts. */\n        if (op == LINE_APPEND)\n                abort();\n\n        if (op == LINE_RESET)\n                return (0);\n\n        LIST_FOREACH(ecp, &sp->gp->ecq, q) {\n                if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V))\n                        continue;\n                for (rp = TAILQ_FIRST(&ecp->rq); rp != NULL; rp = nrp) {\n                        nrp = TAILQ_NEXT(rp, q);\n\n                        /* If range less than the line, ignore it. */\n                        if (rp->stop < lno)\n                                continue;\n\n                        /*\n                         * If range greater than the line, decrement or\n                         * increment the range.\n                         */\n                        if (rp->start > lno) {\n                                if (op == LINE_DELETE) {\n                                        --rp->start;\n                                        --rp->stop;\n                                } else {\n                                        ++rp->start;\n                                        ++rp->stop;\n                                }\n                                continue;\n                        }\n\n                        /*\n                         * Lno is inside the range, decrement the end point\n                         * for deletion, and split the range for insertion.\n                         * In the latter case, since we're inserting a new\n                         * element, neither range can be exhausted.\n                         */\n                        if (op == LINE_DELETE) {\n                                if (rp->start > --rp->stop) {\n                                        TAILQ_REMOVE(&ecp->rq, rp, q);\n                                        free(rp);\n                                }\n                        } else {\n                                CALLOC_RET(sp, nrp, 1, sizeof(RANGE));\n                                nrp->start = lno + 1;\n                                nrp->stop = rp->stop + 1;\n                                rp->stop = lno - 1;\n                                TAILQ_INSERT_AFTER(&ecp->rq, rp, nrp, q);\n                                rp = nrp;\n                                (void)rp;\n                        }\n                }\n\n                /*\n                 * If the command deleted/inserted lines, the cursor moves to\n                 * the line after the deleted/inserted line.\n                 */\n                ecp->range_lno = lno;\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_init.c",
    "content": "/*      $OpenBSD: ex_init.c,v 1.19 2021/10/24 21:24:17 deraadt Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n#include <pwd.h>\n#include <grp.h>\n\n#include \"../common/common.h\"\n#include \"tag.h\"\n#include \"pathnames.h\"\n\n#undef open\n\nenum rc { NOEXIST, NOPERM, RCOK };\nstatic enum rc  exrc_isok(SCR *, struct stat *, int *, char *, int, int);\n\nstatic int ex_run_file(SCR *, int, char *);\n\n/*\n * ex_screen_copy --\n *      Copy ex screen.\n *\n * PUBLIC: int ex_screen_copy(SCR *, SCR *);\n */\nint\nex_screen_copy(SCR *orig, SCR *sp)\n{\n        EX_PRIVATE *oexp, *nexp;\n\n        /* Create the private ex structure. */\n        CALLOC_RET(orig, nexp, 1, sizeof(EX_PRIVATE));\n        sp->ex_private = nexp;\n\n        /* Initialize queues. */\n        TAILQ_INIT(&nexp->tq);\n        TAILQ_INIT(&nexp->tagfq);\n\n        if (orig == NULL) {\n        } else {\n                oexp = EXP(orig);\n\n                if (oexp->lastbcomm != NULL &&\n                    (nexp->lastbcomm = strdup(oexp->lastbcomm)) == NULL) {\n                        msgq(sp, M_SYSERR, NULL);\n                        return(1);\n                }\n                if (ex_tag_copy(orig, sp))\n                        return (1);\n        }\n        return (0);\n}\n\n/*\n * ex_screen_end --\n *      End a vi screen.\n *\n * PUBLIC: int ex_screen_end(SCR *);\n */\nint\nex_screen_end(SCR *sp)\n{\n        EX_PRIVATE *exp;\n        int rval;\n\n        if ((exp = EXP(sp)) == NULL)\n                return (0);\n\n        rval = 0;\n\n        /* Close down script connections. */\n        if (F_ISSET(sp, SC_SCRIPT) && sscr_end(sp))\n                rval = 1;\n\n        if (argv_free(sp))\n                rval = 1;\n\n        free(exp->ibp);\n        free(exp->lastbcomm);\n\n        if (ex_tag_free(sp))\n                rval = 1;\n\n        /* Free private memory. */\n        free(exp);\n        sp->ex_private = NULL;\n\n        return (rval);\n}\n\n/*\n * ex_optchange --\n *      Handle change of options for ex.\n *\n * PUBLIC: int ex_optchange(SCR *, int, char *, unsigned long *);\n */\nint\nex_optchange(SCR *sp, int offset, char *str, unsigned long *valp)\n{\n        switch (offset) {\n        case O_TAGS:\n                return (ex_tagf_alloc(sp, str));\n        }\n        return (0);\n}\n\n/*\n * ex_exrc --\n *      Read the EXINIT environment variable and the startup exrc files,\n *      and execute their commands.\n *\n * PUBLIC: int ex_exrc(SCR *);\n */\nint\nex_exrc(SCR *sp)\n{\n        struct stat hsb, lsb;\n        char *p, path[PATH_MAX];\n        int fd;\n\n        /*\n         * Source the system, environment, $HOME and local .exrc values.\n         * Vi historically didn't check $HOME/.exrc if the environment\n         * variable EXINIT was set.  This is all done before the file is\n         * read in, because things in the .exrc information can set, for\n         * example, the recovery directory.\n         *\n         * !!!\n         * While nvi can handle any of the options settings of historic vi,\n         * the converse is not true.  Since users are going to have to have\n         * files and environmental variables that work with both, we use nvi\n         * versions of both the $HOME and local startup files if they exist,\n         * otherwise the historic ones.\n         *\n         * !!!\n         * For a discussion of permissions and when what .exrc files are\n         * read, see the comment above the exrc_isok() function below.\n         *\n         * !!!\n         * If the user started the historic of vi in $HOME, vi read the user's\n         * .exrc file twice, as $HOME/.exrc and as ./.exrc.  We avoid this, as\n         * it's going to make some commands behave oddly, and I can't imagine\n         * anyone depending on it.\n         */\n        switch (exrc_isok(sp, &hsb, &fd, _PATH_SYSEXRC, 1, 0)) {\n        case NOEXIST:\n        case NOPERM:\n                break;\n        case RCOK:\n                if (ex_run_file(sp, fd, _PATH_SYSEXRC))\n                        return (1);\n                break;\n        }\n\n        /* Run the commands. */\n        if (EXCMD_RUNNING(sp->gp))\n                (void)ex_cmd(sp);\n        if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE))\n                return (0);\n\n        if ((p = getenv(\"NEXINIT\")) != NULL) {\n                if (ex_run_str(sp, \"NEXINIT\", p, strlen(p), 1, 0))\n                        return (1);\n        } else if ((p = getenv(\"EXINIT\")) != NULL) {\n                if (ex_run_str(sp, \"EXINIT\", p, strlen(p), 1, 0))\n                        return (1);\n        } else if ((p = getenv(\"HOME\")) != NULL && *p) {\n                (void)snprintf(path, sizeof(path), \"%s/%s\", p, _PATH_NEXRC);\n                switch (exrc_isok(sp, &hsb, &fd, path, 0, 1)) {\n                case NOEXIST:\n                        (void)snprintf(path,\n                            sizeof(path), \"%s/%s\", p, _PATH_EXRC);\n                        if (exrc_isok(sp, &hsb, &fd, path, 0, 1) == RCOK &&\n                            ex_run_file(sp, fd, path))\n                                return (1);\n                        break;\n                case NOPERM:\n                        break;\n                case RCOK:\n                        if (ex_run_file(sp, fd, path))\n                                return (1);\n                        break;\n                }\n        }\n\n        /* Run the commands. */\n        if (EXCMD_RUNNING(sp->gp))\n                (void)ex_cmd(sp);\n        if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE))\n                return (0);\n\n        /* Previous commands may have set the exrc option. */\n        if (O_ISSET(sp, O_EXRC)) {\n                switch (exrc_isok(sp, &lsb, &fd, _PATH_NEXRC, 0, 0)) {\n                case NOEXIST:\n                        if (exrc_isok(sp, &lsb, &fd, _PATH_EXRC, 0, 0)\n                            == RCOK) {\n                                if (lsb.st_dev != hsb.st_dev ||\n                                    lsb.st_ino != hsb.st_ino) {\n                                        if (ex_run_file(sp, fd, _PATH_EXRC))\n                                                return (1);\n                                } else\n                                        close(fd);\n                        }\n                        break;\n                case NOPERM:\n                        break;\n                case RCOK:\n                        if (lsb.st_dev != hsb.st_dev ||\n                            lsb.st_ino != hsb.st_ino) {\n                                if (ex_run_file(sp, fd, _PATH_NEXRC))\n                                        return (1);\n                        } else\n                                close(fd);\n                        break;\n                }\n                /* Run the commands. */\n                if (EXCMD_RUNNING(sp->gp))\n                        (void)ex_cmd(sp);\n                if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE))\n                        return (0);\n        }\n\n        return (0);\n}\n\n/*\n * ex_run_file --\n *      Set up a file of ex commands to run.\n */\nstatic int\nex_run_file(SCR *sp, int fd, char *name)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_SOURCE, 0, OOBLNO, OOBLNO, 0, ap);\n        ex_cadd(&cmd, &a, name, strlen(name));\n        return (ex_sourcefd(sp, &cmd, fd));\n}\n\n/*\n * ex_run_str --\n *      Set up a string of ex commands to run.\n *\n * PUBLIC: int ex_run_str(SCR *, char *, char *, size_t, int, int);\n */\nint\nex_run_str(SCR *sp, char *name, char *str, size_t len, int ex_flags,\n    int nocopy)\n{\n        GS *gp;\n        EXCMD *ecp;\n\n        gp = sp->gp;\n        if (EXCMD_RUNNING(gp)) {\n                CALLOC_RET(sp, ecp, 1, sizeof(EXCMD));\n                LIST_INSERT_HEAD(&gp->ecq, ecp, q);\n        } else\n                ecp = &gp->excmd;\n\n        F_INIT(ecp,\n            ex_flags ? E_BLIGNORE | E_NOAUTO | E_NOPRDEF | E_VLITONLY : 0);\n\n        if (nocopy)\n                ecp->cp = str;\n        else\n                if ((ecp->cp = v_strdup(sp, str, len)) == NULL)\n                        return (1);\n        ecp->clen = len;\n\n        if (name == NULL)\n                ecp->if_name = NULL;\n        else {\n                if ((ecp->if_name = v_strdup(sp, name, strlen(name))) == NULL)\n                        return (1);\n                ecp->if_lno = 1;\n                F_SET(ecp, E_NAMEDISCARD);\n        }\n\n        return (0);\n}\n\n/*\n * exrc_isok --\n *      Open and check a .exrc file for source-ability.\n *\n * !!!\n * Historically, vi read the $HOME and local .exrc files if they were owned\n * by the user's real ID, or the \"sourceany\" option was set, regardless of\n * any other considerations.  We no longer support the sourceany option as\n * it's a security problem of mammoth proportions.  We require the system\n * .exrc file to be owned by root, the $HOME .exrc file to be owned by the\n * user's effective ID (or that the user's effective ID be root) and the\n * local .exrc files to be owned by the user's effective ID.  In all cases,\n * the file cannot be writeable by anyone other than its owner.\n *\n * In O'Reilly (\"Learning the VI Editor\", Fifth Ed., May 1992, page 106),\n * it notes that System V release 3.2 and later has an option \"[no]exrc\".\n * The behavior is that local .exrc files are read only if the exrc option\n * is set.  The default for the exrc option was off, so, by default, local\n * .exrc files were not read.  The problem this was intended to solve was\n * that System V permitted users to give away files, so there's no possible\n * ownership or writeability test to ensure that the file is safe.\n *\n * POSIX 1003.2-1992 standardized exrc as an option.  It required the exrc\n * option to be off by default, thus local .exrc files are not to be read\n * by default.  The Rationale noted (incorrectly) that this was a change\n * to historic practice, but correctly noted that a default of off improves\n * system security.  POSIX also required that vi check the effective user\n * ID instead of the real user ID, which is why we've switched from historic\n * practice.\n *\n * We initialize the exrc variable to off.  If it's turned on by the system\n * or $HOME .exrc files, and the local .exrc file passes the ownership and\n * writeability tests, then we read it.  This breaks historic 4BSD practice,\n * but it gives us a measure of security on systems where users can give away\n * files.\n */\nstatic enum rc\nexrc_isok(SCR *sp, struct stat *sbp, int *fdp, char *path, int rootown,\n    int rootid)\n{\n        enum { ROOTOWN, OWN, WRITER } etype;\n        uid_t euid;\n        int nf1, nf2;\n        char *a, *b, buf[PATH_MAX];\n\n        if ((*fdp = open(path, O_RDONLY)) < 0) {\n                if (errno == ENOENT)\n                        /* This is the only case where ex_exrc()\n                         * should silently try the next file, for\n                         * example .exrc after .nexrc.\n                         */\n                        return (NOEXIST);\n\n                msgq_str(sp, M_SYSERR, path, \"%s\");\n                return (NOPERM);\n        }\n\n        if (fstat(*fdp, sbp)) {\n                msgq_str(sp, M_SYSERR, path, \"%s\");\n                close(*fdp);\n                return (NOPERM);\n        }\n\n        /* Check ownership permissions. */\n        euid = geteuid();\n        if (!(rootown && sbp->st_uid == 0) &&\n            !(rootid && euid == 0) && sbp->st_uid != euid) {\n                etype = rootown ? ROOTOWN : OWN;\n                goto denied;\n        }\n\n        /* Check writeability. */\n        if (sbp->st_mode & S_IWOTH) {\n                etype = WRITER;\n                goto denied;\n        }\n\n        struct group *grp_p;\n        struct passwd *pwd_p;\n\n        if (sbp->st_mode & S_IWGRP) {\n                /* on system error (getgrgid or getpwnam return NULL) set etype to WRITER\n                 * and continue execution */\n                if( (grp_p = getgrgid(sbp->st_gid)) == NULL) {\n                        etype = WRITER;\n                        goto denied;\n                }\n\n                /* lookup the group members' uids for an uid different from euid */\n                while( ( *(grp_p->gr_mem) ) != NULL) { /* gr_mem is a null-terminated array */\n                        if( (pwd_p = getpwnam(*(grp_p->gr_mem)++)) == NULL) {\n                                etype = WRITER;\n                                goto denied;\n                        }\n                        if(pwd_p->pw_uid != euid) {\n                                etype = WRITER;\n                                goto denied;\n                        }\n                }\n        }\n        return (RCOK);\n\ndenied: a = msg_print(sp, path, &nf1);\n        if (strchr(path, '/') == NULL && getcwd(buf, sizeof(buf)) != NULL) {\n                b = msg_print(sp, buf, &nf2);\n                switch (etype) {\n                case ROOTOWN:\n                        msgq(sp, M_ERR,\n                            \"%s/%s: not sourced: not owned by you or root\",\n                            b, a);\n                        break;\n                case OWN:\n                        msgq(sp, M_ERR,\n                            \"%s/%s: not sourced: not owned by you\", b, a);\n                        break;\n                case WRITER:\n                        msgq(sp, M_ERR,\n    \"%s/%s: not sourced: writable by a user other than the owner\", b, a);\n                        break;\n                }\n                if (nf2)\n                        FREE_SPACE(sp, b, 0);\n        } else\n                switch (etype) {\n                case ROOTOWN:\n                        msgq(sp, M_ERR,\n                            \"%s: not sourced: not owned by you or root\", a);\n                        break;\n                case OWN:\n                        msgq(sp, M_ERR,\n                            \"%s: not sourced: not owned by you\", a);\n                        break;\n                case WRITER:\n                        msgq(sp, M_ERR,\n            \"%s: not sourced: writable by a user other than the owner\", a);\n                        break;\n                }\n\n        if (nf1)\n                FREE_SPACE(sp, a, 0);\n        close(*fdp);\n        return (NOPERM);\n}\n"
  },
  {
    "path": "ex/ex_join.c",
    "content": "/*      $OpenBSD: ex_join.c,v 1.8 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_join -- :[line [,line]] j[oin][!] [count] [flags]\n *      Join lines.\n *\n * PUBLIC: int ex_join(SCR *, EXCMD *);\n */\nint\nex_join(SCR *sp, EXCMD *cmdp)\n{\n        recno_t from, to;\n        size_t blen, clen, len, tlen;\n        int echar, extra, first;\n        char *bp, *p, *tbp;\n\n        NEEDFILE(sp, cmdp);\n\n        from = cmdp->addr1.lno;\n        (void)from;\n        to = cmdp->addr2.lno;\n        (void)to;\n\n        /* Check for no lines to join. */\n        if (!db_exist(sp, from + 1)) {\n                msgq(sp, M_ERR, \"No following lines to join\");\n                return (1);\n        }\n\n        GET_SPACE_RET(sp, bp, blen, 256);\n\n        /*\n         * The count for the join command was off-by-one,\n         * historically, to other counts for other commands.\n         */\n        if (F_ISSET(cmdp, E_ADDR_DEF) || cmdp->addrcnt == 1)\n                ++cmdp->addr2.lno;\n\n        clen = tlen = 0;\n        for (first = 1,\n            from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) {\n                /*\n                 * Get next line.  Historic versions of vi allowed \"10J\" while\n                 * less than 10 lines from the end-of-file, so we do too.\n                 */\n                if (db_get(sp, from, 0, &p, &len)) {\n                        cmdp->addr2.lno = from - 1;\n                        break;\n                }\n\n                /* Empty lines just go away. */\n                if (len == 0)\n                        continue;\n\n                /*\n                 * Get more space if necessary.  Note, tlen isn't the length\n                 * of the new line, it's roughly the amount of space needed.\n                 * tbp - bp is the length of the new line.\n                 */\n                tlen += len + 2;\n                ADD_SPACE_RET(sp, bp, blen, tlen);\n                tbp = bp + clen;\n\n                /*\n                 * Historic practice:\n                 *\n                 * If force specified, join without modification.\n                 * If the current line ends with whitespace, strip leading\n                 *    whitespace from the joined line.\n                 * If the next line starts with a ), do nothing.\n                 * If the current line ends with ., insert two spaces.\n                 * Else, insert one space.\n                 *\n                 * One change -- add ? and ! to the list of characters for\n                 * which we insert two spaces.  I expect that POSIX 1003.2\n                 * will require this as well.\n                 *\n                 * Echar is the last character in the last line joined.\n                 */\n                extra = 0;\n                if (!first && !FL_ISSET(cmdp->iflags, E_C_FORCE)) {\n                        if (isblank(echar))\n                                for (; len && isblank(*p); --len, ++p);\n                        else if (p[0] != ')') {\n                                if (strchr(\".?!\", echar)) {\n                                        *tbp++ = ' ';\n                                        ++clen;\n                                        extra = 1;\n                                }\n                                *tbp++ = ' ';\n                                ++clen;\n                                for (; len && isblank(*p); --len, ++p);\n                        }\n                }\n\n                if (len != 0) {\n                        memcpy(tbp, p, len);\n                        tbp += len;\n                        clen += len;\n                        echar = p[len - 1];\n                } else\n                        echar = ' ';\n\n                /*\n                 * Historic practice for vi was to put the cursor at the first\n                 * inserted whitespace character, if there was one, or the\n                 * first character of the joined line, if there wasn't, or the\n                 * last character of the line if joined to an empty line.  If\n                 * a count was specified, the cursor was moved as described\n                 * for the first line joined, ignoring subsequent lines.  If\n                 * the join was a ':' command, the cursor was placed at the\n                 * first non-blank character of the line unless the cursor was\n                 * \"attracted\" to the end of line when the command was executed\n                 * in which case it moved to the new end of line.  There are\n                 * probably several more special cases, but frankly, my dear,\n                 * I don't give a damn.  This implementation puts the cursor\n                 * on the first inserted whitespace character, the first\n                 * character of the joined line, or the last character of the\n                 * line regardless.  Note, if the cursor isn't on the joined\n                 * line (possible with : commands), it is reset to the starting\n                 * line.\n                 */\n                if (first) {\n                        sp->cno = (tbp - bp) - (1 + extra);\n                        first = 0;\n                } else\n                        sp->cno = (tbp - bp) - len - (1 + extra);\n        }\n        sp->lno = cmdp->addr1.lno;\n\n        /* Delete the joined lines. */\n        for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; to > from; --to)\n                if (db_delete(sp, to))\n                        goto err;\n\n        /* If the original line changed, reset it. */\n        if (!first && db_set(sp, from, bp, tbp - bp)) {\nerr:            FREE_SPACE(sp, bp, blen);\n                return (1);\n        }\n        FREE_SPACE(sp, bp, blen);\n\n        sp->rptlines[L_JOINED] += (cmdp->addr2.lno - cmdp->addr1.lno) + 1;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_map.c",
    "content": "/*      $OpenBSD: ex_map.c,v 1.9 2016/05/27 09:18:12 martijn Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_map -- :map[!] [input] [replacement]\n *      Map a key/string or display mapped keys.\n *\n * Historical note:\n *      Historic vi maps were fairly bizarre, and likely to differ in\n *      very subtle and strange ways from this implementation.  Two\n *      things worth noting are that vi would often hang or drop core\n *      if the map was strange enough (ex: map X \"xy$@x^V), or, simply\n *      not work.  One trick worth remembering is that if you put a\n *      mark at the start of the map, e.g. map X mx\"xy ...), or if you\n *      put the map in a .exrc file, things would often work much better.\n *      No clue why.\n *\n * PUBLIC: int ex_map(SCR *, EXCMD *);\n */\nint\nex_map(SCR *sp, EXCMD *cmdp)\n{\n        seq_t stype;\n        CHAR_T *input, *p;\n\n        stype = FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND;\n\n        switch (cmdp->argc) {\n        case 0:\n                if (seq_dump(sp, stype, 1) == 0)\n                        msgq(sp, M_INFO, stype == SEQ_INPUT ?\n                            \"No input map entries\" :\n                            \"No command map entries\");\n                return (0);\n        case 2:\n                input = cmdp->argv[0]->bp;\n                break;\n        default:\n                abort();\n        }\n\n        /*\n         * If the mapped string is #[0-9]* (and wasn't quoted) then store the\n         * function key mapping.  If the screen specific routine has been set,\n         * call it as well.  Note, the SEQ_FUNCMAP type is persistent across\n         * screen types, maybe the next screen type will get it right.\n         */\n        if (input[0] == '#' && isdigit(input[1])) {\n                for (p = input + 2; isdigit(*p); ++p);\n                if (p[0] != '\\0')\n                        goto nofunc;\n\n                if (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len,\n                    cmdp->argv[1]->bp, cmdp->argv[1]->len, stype,\n                    SEQ_FUNCMAP | SEQ_USERDEF))\n                        return (1);\n                return (sp->gp->scr_fmap == NULL ? 0 :\n                    sp->gp->scr_fmap(sp, stype, input, cmdp->argv[0]->len,\n                    cmdp->argv[1]->bp, cmdp->argv[1]->len));\n        }\n\n        /* Some single keys may not be remapped in command mode. */\nnofunc: if (stype == SEQ_COMMAND && input[1] == '\\0')\n                switch (KEY_VAL(sp, input[0])) {\n                case K_COLON:\n                case K_ESCAPE:\n                case K_NL:\n                        msgq(sp, M_ERR,\n                            \"The %s character may not be remapped\",\n                            KEY_NAME(sp, input[0]));\n                        return (1);\n                }\n        return (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len,\n            cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_USERDEF));\n}\n\n/*\n * ex_unmap -- (:unmap[!] key)\n *      Unmap a key.\n *\n * PUBLIC: int ex_unmap(SCR *, EXCMD *);\n */\nint\nex_unmap(SCR *sp, EXCMD *cmdp)\n{\n        if (seq_delete(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len,\n            FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND)) {\n                msgq_str(sp, M_INFO,\n                    cmdp->argv[0]->bp, \"\\\"%s\\\" isn't currently mapped\");\n                return (1);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_mark.c",
    "content": "/*      $OpenBSD: ex_mark.c,v 1.7 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_mark -- :mark char\n *            :k char\n *      Mark lines.\n *\n *\n * PUBLIC: int ex_mark(SCR *, EXCMD *);\n */\nint\nex_mark(SCR *sp, EXCMD *cmdp)\n{\n        NEEDFILE(sp, cmdp);\n\n        if (cmdp->argv[0]->len != 1) {\n                msgq(sp, M_ERR, \"Mark names must be a single character\");\n                return (1);\n        }\n        return (mark_set(sp, cmdp->argv[0]->bp[0], &cmdp->addr1, 1));\n}\n"
  },
  {
    "path": "ex/ex_mkexrc.c",
    "content": "/*      $OpenBSD: ex_mkexrc.c,v 1.7 2016/01/06 22:28:52 millert Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"pathnames.h\"\n\n#undef open\n\n/*\n * ex_mkexrc -- :mkexrc[!] [file]\n *\n * Create (or overwrite) a .exrc file with the current info.\n *\n * PUBLIC: int ex_mkexrc(SCR *, EXCMD *);\n */\nint\nex_mkexrc(SCR *sp, EXCMD *cmdp)\n{\n        struct stat sb;\n        FILE *fp;\n        int fd, sverrno;\n        char *fname;\n\n        switch (cmdp->argc) {\n        case 0:\n                fname = _PATH_EXRC;\n                break;\n        case 1:\n                fname = cmdp->argv[0]->bp;\n                set_alt_name(sp, fname);\n                break;\n        default:\n                abort();\n        }\n\n        if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && !stat(fname, &sb)) {\n                msgq_str(sp, M_ERR, fname,\n                    \"%s exists, not written; use ! to override\");\n                return (1);\n        }\n\n        /* Create with max permissions of rw-r--r--. */\n        if ((fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY,\n            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {\n                msgq_str(sp, M_SYSERR, fname, \"%s\");\n                return (1);\n        }\n\n        if ((fp = fdopen(fd, \"w\")) == NULL) {\n                sverrno = errno;\n                (void)close(fd);\n                goto e2;\n        }\n\n        if (seq_save(sp, fp, \"abbreviate \", SEQ_ABBREV) || ferror(fp))\n                goto e1;\n        if (seq_save(sp, fp, \"map \", SEQ_COMMAND) || ferror(fp))\n                goto e1;\n        if (seq_save(sp, fp, \"map! \", SEQ_INPUT) || ferror(fp))\n                goto e1;\n        if (opts_save(sp, fp) || ferror(fp))\n                goto e1;\n        if (fclose(fp)) {\n                sverrno = errno;\n                goto e2;\n        }\n\n        msgq_str(sp, M_INFO, fname, \"New exrc file: %s\");\n        return (0);\n\ne1:     sverrno = errno;\n        (void)fclose(fp);\ne2:     errno = sverrno;\n        msgq_str(sp, M_SYSERR, fname, \"%s\");\n        return (1);\n}\n"
  },
  {
    "path": "ex/ex_move.c",
    "content": "/*      $OpenBSD: ex_move.c,v 1.12 2025/08/23 21:02:10 millert Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_copy -- :[line [,line]] co[py] line [flags]\n *      Copy selected lines.\n *\n * PUBLIC: int ex_copy(SCR *, EXCMD *);\n */\nint\nex_copy(SCR *sp, EXCMD *cmdp)\n{\n        CB cb;\n        MARK fm1, fm2, m, tm;\n        recno_t cnt;\n        int rval;\n\n        rval = 0;\n\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * It's possible to copy things into the area that's being\n         * copied, e.g. \"2,5copy3\" is legitimate.  Save the text to\n         * a cut buffer.\n         */\n        fm1 = cmdp->addr1;\n        fm2 = cmdp->addr2;\n        memset(&cb, 0, sizeof(cb));\n        TAILQ_INIT(&cb.textq);\n        for (cnt = fm1.lno; cnt <= fm2.lno; ++cnt)\n                if (cut_line(sp, cnt, 0, CUT_LINE_TO_EOL, &cb)) {\n                        rval = 1;\n                        goto err;\n                }\n        cb.flags |= CB_LMODE;\n\n        /* Put the text into place. */\n        tm.lno = cmdp->lineno;\n        tm.cno = 0;\n        if (put(sp, &cb, NULL, &tm, &m, 1, 1))\n                rval = 1;\n        else {\n                /*\n                 * Copy puts the cursor on the last line copied.  The cursor\n                 * returned by the put routine is the first line put, not the\n                 * last, because that's the historic semantic of vi.\n                 */\n                cnt = (fm2.lno - fm1.lno) + 1;\n                sp->lno = m.lno + (cnt - 1);\n                sp->cno = 0;\n        }\nerr:    text_lfree(&cb.textq);\n        return (rval);\n}\n\n/*\n * ex_move -- :[line [,line]] mo[ve] line\n *      Move selected lines.\n *\n * PUBLIC: int ex_move(SCR *, EXCMD *);\n */\nint\nex_move(SCR *sp, EXCMD *cmdp)\n{\n        LMARK *lmp;\n        MARK fm1, fm2;\n        recno_t cnt, diff, fl, tl, mfl, mtl;\n        size_t blen, len;\n        int mark_reset;\n        char *bp, *p;\n\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * It's not possible to move things into the area that's being\n         * moved.\n         */\n        fm1 = cmdp->addr1;\n        fm2 = cmdp->addr2;\n        if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) {\n                msgq(sp, M_ERR, \"Destination line is inside move range\");\n                return (1);\n        }\n\n        /*\n         * Log the positions of any marks in the to-be-deleted lines.  This\n         * has to work with the logging code.  What happens is that we log\n         * the old mark positions, make the changes, then log the new mark\n         * positions.  Then the marks end up in the right positions no matter\n         * which way the log is traversed.\n         *\n         * XXX\n         * Reset the MARK_USERSET flag so that the log can undo the mark.\n         * This isn't very clean, and should probably be fixed.\n         */\n        fl = fm1.lno;\n        tl = cmdp->lineno;\n\n        /* Log the old positions of the marks. */\n        mark_reset = 0;\n        LIST_FOREACH(lmp, &sp->ep->marks, q)\n                if (lmp->name != ABSMARK1 &&\n                    lmp->lno >= fl && lmp->lno <= tl) {\n                        mark_reset = 1;\n                        F_CLR(lmp, MARK_USERSET);\n                        (void)log_mark(sp, lmp);\n                }\n\n        /* Get memory for the copy. */\n        GET_SPACE_RET(sp, bp, blen, 256);\n\n        /* Move the lines. */\n        diff = (fm2.lno - fm1.lno) + 1;\n        if (tl > fl) {                          /* Destination > source. */\n                mfl = tl - diff;\n                mtl = tl;\n                for (cnt = diff; cnt--;) {\n                        if (db_get(sp, fl, DBG_FATAL, &p, &len))\n                                return (1);\n                        BINC_RET(sp, bp, blen, len);\n                        memcpy(bp, p, len);\n                        if (db_append(sp, 1, tl, bp, len))\n                                return (1);\n                        if (mark_reset)\n                                LIST_FOREACH(lmp, &sp->ep->marks, q)\n                                        if (lmp->name != ABSMARK1 &&\n                                            lmp->lno == fl)\n                                                lmp->lno = tl + 1;\n                        if (db_delete(sp, fl))\n                                return (1);\n                }\n        } else {                                /* Destination < source. */\n                mfl = tl;\n                mtl = tl + diff;\n                for (cnt = diff; cnt--;) {\n                        if (db_get(sp, fl, DBG_FATAL, &p, &len))\n                                return (1);\n                        BINC_RET(sp, bp, blen, len);\n                        memcpy(bp, p, len);\n                        if (db_append(sp, 1, tl++, bp, len))\n                                return (1);\n                        if (mark_reset)\n                                LIST_FOREACH(lmp, &sp->ep->marks, q)\n                                        if (lmp->name != ABSMARK1 &&\n                                            lmp->lno == fl)\n                                                lmp->lno = tl;\n                        ++fl;\n                        if (db_delete(sp, fl))\n                                return (1);\n                }\n        }\n        FREE_SPACE(sp, bp, blen);\n\n        sp->lno = tl;                           /* Last line moved. */\n        sp->cno = 0;\n\n        /* Log the new positions of the marks. */\n        if (mark_reset)\n                LIST_FOREACH(lmp, &sp->ep->marks, q)\n                        if (lmp->name != ABSMARK1 &&\n                            lmp->lno >= mfl && lmp->lno <= mtl)\n                                (void)log_mark(sp, lmp);\n\n        sp->rptlines[L_MOVED] += diff;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_open.c",
    "content": "/*      $OpenBSD: ex_open.c,v 1.7 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n#undef open\n\n/*\n * ex_open -- :[line] o[pen] [/pattern/] [flags]\n *\n *      Switch to single line \"open\" mode.\n *\n * PUBLIC: int ex_open(SCR *, EXCMD *);\n */\nint\nex_open(SCR *sp, EXCMD *cmdp)\n{\n        /* If open option off, disallow open command. */\n        if (!O_ISSET(sp, O_OPEN)) {\n                msgq(sp, M_ERR,\n            \"The open command requires that the open option be set\");\n                return (1);\n        }\n\n        msgq(sp, M_ERR, \"The open command is not yet implemented\");\n        return (1);\n}\n"
  },
  {
    "path": "ex/ex_preserve.c",
    "content": "/*      $OpenBSD: ex_preserve.c,v 1.7 2016/01/06 22:28:52 millert Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_preserve -- :pre[serve]\n *      Push the file to recovery.\n *\n * PUBLIC: int ex_preserve(SCR *, EXCMD *);\n */\nint\nex_preserve(SCR *sp, EXCMD *cmdp)\n{\n        recno_t lno;\n\n        NEEDFILE(sp, cmdp);\n\n        if (!F_ISSET(sp->ep, F_RCV_ON)) {\n                msgq(sp, M_ERR, \"Preservation of this file not possible\");\n                return (1);\n        }\n\n        /* If recovery not initialized, do so. */\n        if (F_ISSET(sp->ep, F_FIRSTMODIFY) && rcv_init(sp))\n                return (1);\n\n        /* Force the file to be read in, in case it hasn't yet. */\n        if (db_last(sp, &lno))\n                return (1);\n\n        /* Sync to disk. */\n        if (rcv_sync(sp, RCV_SNAPSHOT))\n                return (1);\n\n        msgq(sp, M_INFO, \"File preserved\");\n        return (0);\n}\n\n/*\n * ex_recover -- :rec[over][!] file\n *      Recover the file.\n *\n * PUBLIC: int ex_recover(SCR *, EXCMD *);\n */\nint\nex_recover(SCR *sp, EXCMD *cmdp)\n{\n        ARGS *ap;\n        FREF *frp;\n\n        ap = cmdp->argv[0];\n\n        /* Set the alternate file name. */\n        set_alt_name(sp, ap->bp);\n\n        /*\n         * Check for modifications.  Autowrite did not historically\n         * affect :recover.\n         */\n        if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE)))\n                return (1);\n\n        /* Get a file structure for the file. */\n        if ((frp = file_add(sp, ap->bp)) == NULL)\n                return (1);\n\n        /* Set the recover bit. */\n        F_SET(frp, FR_RECOVER);\n\n        /* Switch files. */\n        if (file_init(sp, frp, NULL, FS_SETALT |\n            (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))\n                return (1);\n\n        F_SET(sp, SC_FSWITCH);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_print.c",
    "content": "/*      $OpenBSD: ex_print.c,v 1.13 2016/05/27 09:18:12 martijn Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\nstatic int ex_prchars(SCR *, const char *, size_t *, size_t, unsigned int, int);\n\n/*\n * ex_list -- :[line [,line]] l[ist] [count] [flags]\n *\n *      Display the addressed lines such that the output is unambiguous.\n *\n * PUBLIC: int ex_list(SCR *, EXCMD *);\n */\nint\nex_list(SCR *sp, EXCMD *cmdp)\n{\n        if (ex_print(sp, cmdp,\n            &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_LIST))\n                return (1);\n        sp->lno = cmdp->addr2.lno;\n        sp->cno = cmdp->addr2.cno;\n        return (0);\n}\n\n/*\n * ex_number -- :[line [,line]] nu[mber] [count] [flags]\n *\n *      Display the addressed lines with a leading line number.\n *\n * PUBLIC: int ex_number(SCR *, EXCMD *);\n */\nint\nex_number(SCR *sp, EXCMD *cmdp)\n{\n        if (ex_print(sp, cmdp,\n            &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_HASH))\n                return (1);\n        sp->lno = cmdp->addr2.lno;\n        sp->cno = cmdp->addr2.cno;\n        return (0);\n}\n\n/*\n * ex_pr -- :[line [,line]] p[rint] [count] [flags]\n *\n *      Display the addressed lines.\n *\n * PUBLIC: int ex_pr(SCR *, EXCMD *);\n */\nint\nex_pr(SCR *sp, EXCMD *cmdp)\n{\n        if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags))\n                return (1);\n        sp->lno = cmdp->addr2.lno;\n        sp->cno = cmdp->addr2.cno;\n        return (0);\n}\n\n/*\n * ex_print --\n *      Print the selected lines.\n *\n * PUBLIC: int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t);\n */\nint\nex_print(SCR *sp, EXCMD *cmdp, MARK *fp, MARK *tp, u_int32_t flags)\n{\n        recno_t from, to;\n        size_t col, len;\n        char *p, buf[16];\n\n        NEEDFILE(sp, cmdp);\n\n        for (from = fp->lno, to = tp->lno; from <= to; ++from) {\n                col = 0;\n\n                /*\n                 * Display the line number.  The %6 format is specified\n                 * by POSIX 1003.2, and is almost certainly large enough.\n                 * Check, though, just in case.\n                 */\n                if (LF_ISSET(E_C_HASH)) {\n                        if (from <= 999999) {\n                                snprintf(buf, sizeof(buf), \"%6lu  \", (unsigned long)from);\n                                p = buf;\n                        } else\n                                p = \"TOOBIG  \";\n                        if (ex_prchars(sp, p, &col, 8, 0, 0))\n                                return (1);\n                }\n\n                /*\n                 * Display the line.  The format for E_C_PRINT isn't very good,\n                 * especially in handling end-of-line tabs, but they're almost\n                 * backward compatible.\n                 */\n                if (db_get(sp, from, DBG_FATAL, &p, &len))\n                        return (1);\n\n                if (len == 0 && !LF_ISSET(E_C_LIST))\n                        (void)ex_puts(sp, \"\\n\");\n                else if (ex_ldisplay(sp, p, len, col, flags))\n                        return (1);\n\n                if (INTERRUPTED(sp))\n                        break;\n        }\n        return (0);\n}\n\n/*\n * ex_ldisplay --\n *      Display a line without any preceding number.\n *\n * PUBLIC: int ex_ldisplay(SCR *, const char *, size_t, size_t, unsigned int);\n */\nint\nex_ldisplay(SCR *sp, const char *p, size_t len, size_t col, unsigned int flags)\n{\n        if (len > 0 && ex_prchars(sp, p, &col, len, LF_ISSET(E_C_LIST), 0))\n                return (1);\n        if (!INTERRUPTED(sp) && LF_ISSET(E_C_LIST)) {\n                p = \"$\";\n                if (ex_prchars(sp, p, &col, 1, LF_ISSET(E_C_LIST), 0))\n                        return (1);\n        }\n        if (!INTERRUPTED(sp))\n                (void)ex_puts(sp, \"\\n\");\n        return (0);\n}\n\n/*\n * ex_scprint --\n *      Display a line for the substitute with confirmation routine.\n *\n * PUBLIC: int ex_scprint(SCR *, MARK *, MARK *);\n */\nint\nex_scprint(SCR *sp, MARK *fp, MARK *tp)\n{\n        const char *p;\n        size_t col, len;\n\n        col = 0;\n        if (O_ISSET(sp, O_NUMBER)) {\n                p = \"        \";\n                if (ex_prchars(sp, p, &col, 8, 0, 0))\n                        return (1);\n        }\n\n        if (db_get(sp, fp->lno, DBG_FATAL, (char **)&p, &len))\n                return (1);\n\n        if (ex_prchars(sp, p, &col, fp->cno, 0, ' '))\n                return (1);\n        p += fp->cno;\n        if (ex_prchars(sp,\n            p, &col, tp->cno == fp->cno ? 1 : tp->cno - fp->cno, 0, '^'))\n                return (1);\n        if (INTERRUPTED(sp))\n                return (1);\n        p = \"[ynq]\";\n        if (ex_prchars(sp, p, &col, 5, 0, 0))\n                return (1);\n        (void)ex_fflush(sp);\n        return (0);\n}\n\n/*\n * ex_prchars --\n *      Local routine to dump characters to the screen.\n */\nstatic int\nex_prchars(SCR *sp, const char *p, size_t *colp, size_t len, unsigned int flags,\n    int repeatc)\n{\n        CHAR_T ch, *kp;\n        size_t col, tlen, ts;\n\n        if (O_ISSET(sp, O_LIST))\n                LF_SET(E_C_LIST);\n        ts = O_VAL(sp, O_TABSTOP);\n        for (col = *colp; len--;)\n                if ((ch = *p++) == '\\t' && !LF_ISSET(E_C_LIST))\n                        for (tlen = ts - col % ts;\n                            col < sp->cols && tlen--; ++col) {\n                                (void)ex_printf(sp,\n                                    \"%c\", repeatc ? repeatc : ' ');\n                                if (INTERRUPTED(sp))\n                                        goto intr;\n                        }\n                else {\n                        kp = KEY_NAME(sp, ch);\n                        tlen = KEY_LEN(sp, ch);\n                        if (!repeatc  && col + tlen < sp->cols) {\n                                (void)ex_puts(sp, kp);\n                                col += tlen;\n                        } else\n                                for (; tlen--; ++kp, ++col) {\n                                        if (col == sp->cols) {\n                                                col = 0;\n                                                (void)ex_puts(sp, \"\\n\");\n                                        }\n                                        (void)ex_printf(sp,\n                                            \"%c\", repeatc ? repeatc : *kp);\n                                        if (INTERRUPTED(sp))\n                                                goto intr;\n                                }\n                }\nintr:   *colp = col;\n        return (0);\n}\n\n/*\n * ex_printf --\n *      Ex's version of printf.\n *\n * PUBLIC: int ex_printf(SCR *, const char *, ...);\n */\nint\nex_printf(SCR *sp, const char *fmt, ...)\n{\n        EX_PRIVATE *exp;\n        va_list ap;\n        size_t n;\n\n        exp = EXP(sp);\n\n        va_start(ap, fmt);\n        n = vsnprintf(exp->obp + exp->obp_len,\n            sizeof(exp->obp) - exp->obp_len, fmt, ap);\n        va_end(ap);\n        if (n >= sizeof(exp->obp) - exp->obp_len)\n                n = sizeof(exp->obp) - exp->obp_len - 1;\n        exp->obp_len += n;\n\n        /* Flush when reach a <newline> or half the buffer. */\n        if (exp->obp[exp->obp_len - 1] == '\\n' ||\n            exp->obp_len > sizeof(exp->obp) / 2)\n                (void)ex_fflush(sp);\n        return (n);\n}\n\n/*\n * ex_puts --\n *      Ex's version of puts.\n *\n * PUBLIC: int ex_puts(SCR *, const char *);\n */\nint\nex_puts(SCR *sp, const char *str)\n{\n        EX_PRIVATE *exp;\n        int doflush, n;\n\n        exp = EXP(sp);\n\n        /* Flush when reach a <newline> or the end of the buffer. */\n        for (doflush = n = 0; *str != '\\0'; ++n) {\n                if (exp->obp_len > sizeof(exp->obp))\n                        (void)ex_fflush(sp);\n                if ((exp->obp[exp->obp_len++] = *str++) == '\\n')\n                        doflush = 1;\n        }\n        if (doflush)\n                (void)ex_fflush(sp);\n        return (n);\n}\n\n/*\n * ex_fflush --\n *      Ex's version of fflush.\n *\n * PUBLIC: int ex_fflush(SCR *sp);\n */\nint\nex_fflush(SCR *sp)\n{\n        EX_PRIVATE *exp;\n\n        exp = EXP(sp);\n\n        if (exp->obp_len != 0) {\n                sp->gp->scr_msg(sp, M_NONE, exp->obp, exp->obp_len);\n                exp->obp_len = 0;\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_put.c",
    "content": "/*      $OpenBSD: ex_put.c,v 1.7 2025/08/23 21:02:10 millert Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_put -- [line] pu[t] [buffer]\n *      Append a cut buffer into the file.\n *\n * PUBLIC: int ex_put(SCR *, EXCMD *);\n */\nint\nex_put(SCR *sp, EXCMD *cmdp)\n{\n        MARK m;\n\n        NEEDFILE(sp, cmdp);\n\n        m.lno = sp->lno;\n        m.cno = sp->cno;\n        if (put(sp, NULL,\n            FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL,\n            &cmdp->addr1, &m, 1, 1))\n                return (1);\n        sp->lno = m.lno;\n        sp->cno = m.cno;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_quit.c",
    "content": "/*      $OpenBSD: ex_quit.c,v 1.5 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_quit -- :quit[!]\n *      Quit.\n *\n * PUBLIC: int ex_quit(SCR *, EXCMD *);\n */\nint\nex_quit(SCR *sp, EXCMD *cmdp)\n{\n        int force;\n\n        force = FL_ISSET(cmdp->iflags, E_C_FORCE);\n\n        /* Check for file modifications, or more files to edit. */\n        if (file_m2(sp, force) || ex_ncheck(sp, force))\n                return (1);\n\n        F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_read.c",
    "content": "/*      $OpenBSD: ex_read.c,v 1.14 2017/04/18 01:45:35 deraadt Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n#undef open\n\n/*\n * ex_read --   :read [file]\n *              :read [!cmd]\n *      Read from a file or utility.\n *\n * !!!\n * Historical vi wouldn't undo a filter read, for no apparent reason.\n *\n * PUBLIC: int ex_read(SCR *, EXCMD *);\n */\nint\nex_read(SCR *sp, EXCMD *cmdp)\n{\n        enum { R_ARG, R_EXPANDARG, R_FILTER } which;\n        struct stat sb;\n        CHAR_T *arg, *name;\n        EX_PRIVATE *exp;\n        FILE *fp;\n        FREF *frp;\n        GS *gp;\n        MARK rm;\n        recno_t nlines = 0;\n        size_t arglen;\n        int argc, rval;\n        char *p;\n\n        gp = sp->gp;\n\n        /*\n         * 0 args: read the current pathname.\n         * 1 args: check for \"read !arg\".\n         */\n        switch (cmdp->argc) {\n        case 0:\n                which = R_ARG;\n                arg = NULL;     /* unused */\n                arglen = 0;     /* unused */\n                break;\n        case 1:\n                arg = cmdp->argv[0]->bp;\n                arglen = cmdp->argv[0]->len;\n                if (*arg == '!') {\n                        ++arg;\n                        --arglen;\n                        which = R_FILTER;\n\n                        /* Secure means no shell access. */\n                        if (O_ISSET(sp, O_SECURE)) {\n                                ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F);\n                                return (1);\n                        }\n                } else\n                        which = R_EXPANDARG;\n                break;\n        default:\n                abort();\n                /* NOTREACHED */\n        }\n\n        /* Load a temporary file if no file being edited. */\n        if (sp->ep == NULL) {\n                if ((frp = file_add(sp, NULL)) == NULL)\n                        return (1);\n                if (file_init(sp, frp, NULL, 0))\n                        return (1);\n        }\n\n        switch (which) {\n        case R_FILTER:\n                /*\n                 * File name and bang expand the user's argument.  If\n                 * we don't get an additional argument, it's illegal.\n                 */\n                argc = cmdp->argc;\n                if (argv_exp1(sp, cmdp, arg, arglen, 1))\n                        return (1);\n                if (argc == cmdp->argc) {\n                        ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                        return (1);\n                }\n                argc = cmdp->argc - 1;\n\n                /* Set the last bang command. */\n                exp = EXP(sp);\n                free(exp->lastbcomm);\n                if ((exp->lastbcomm =\n                    strdup(cmdp->argv[argc]->bp)) == NULL) {\n                        msgq(sp, M_SYSERR, NULL);\n                        return (1);\n                }\n\n                /*\n                 * Vi redisplayed the user's argument if it changed, ex\n                 * always displayed a !, plus the user's argument if it\n                 * changed.\n                 */\n                if (F_ISSET(sp, SC_VI)) {\n                        if (F_ISSET(cmdp, E_MODIFY))\n                                (void)vs_update(sp, \"!\", cmdp->argv[argc]->bp);\n                } else {\n                        if (F_ISSET(cmdp, E_MODIFY))\n                                (void)ex_printf(sp,\n                                    \"!%s\\n\", cmdp->argv[argc]->bp);\n                        else\n                                (void)ex_puts(sp, \"!\\n\");\n                        (void)ex_fflush(sp);\n                }\n\n                /*\n                 * Historically, filter reads as the first ex command didn't\n                 * wait for the user. If SC_SCR_EXWROTE not already set, set\n                 * the don't-wait flag.\n                 */\n                if (!F_ISSET(sp, SC_SCR_EXWROTE))\n                        F_SET(sp, SC_EX_WAIT_NO);\n\n                /*\n                 * Switch into ex canonical mode.  The reason to restore the\n                 * original terminal modes for read filters is so that users\n                 * can do things like \":r! cat /dev/tty\".\n                 *\n                 * !!!\n                 * We do not output an extra <newline>, so that we don't touch\n                 * the screen on a normal read.\n                 */\n                if (F_ISSET(sp, SC_VI)) {\n                        if (gp->scr_screen(sp, SC_EX)) {\n                                ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON_F);\n                                return (1);\n                        }\n                        /*\n                         * !!!\n                         * Historically, the read command doesn't switch to\n                         * the alternate X11 xterm screen, if doing a filter\n                         * read -- don't set SA_ALTERNATE.\n                         */\n                        F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);\n                }\n\n                if (ex_filter(sp, cmdp, &cmdp->addr1,\n                    NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ))\n                        return (1);\n\n                /* The filter version of read set the autoprint flag. */\n                F_SET(cmdp, E_AUTOPRINT);\n\n                /*\n                 * If in vi mode, move to the first nonblank.  Might have\n                 * switched into ex mode, so saved the original SC_VI value.\n                 */\n                sp->lno = rm.lno;\n                if (F_ISSET(sp, SC_VI)) {\n                        sp->cno = 0;\n                        (void)nonblank(sp, sp->lno, &sp->cno);\n                }\n                return (0);\n        case R_ARG:\n                name = sp->frp->name;\n                break;\n        case R_EXPANDARG:\n                if (argv_exp2(sp, cmdp, arg, arglen))\n                        return (1);\n                /*\n                 *  0 args: impossible.\n                 *  1 args: impossible (I hope).\n                 *  2 args: read it.\n                 * >2 args: object, too many args.\n                 *\n                 * The 1 args case depends on the argv_sexp() function refusing\n                 * to return success without at least one non-blank character.\n                 */\n                switch (cmdp->argc) {\n                case 0:\n                case 1:\n                        abort();\n                        /* NOTREACHED */\n                case 2:\n                        name = cmdp->argv[1]->bp;\n                        /*\n                         * !!!\n                         * Historically, the read and write commands renamed\n                         * \"unnamed\" files, or, if the file had a name, set\n                         * the alternate file name.\n                         */\n                        if (F_ISSET(sp->frp, FR_TMPFILE) &&\n                            !F_ISSET(sp->frp, FR_EXNAMED)) {\n                                if ((p = v_strdup(sp, cmdp->argv[1]->bp,\n                                    cmdp->argv[1]->len)) != NULL) {\n                                        free(sp->frp->name);\n                                        sp->frp->name = p;\n                                }\n                                /*\n                                 * The file has a real name, it's no longer a\n                                 * temporary, clear the temporary file flags.\n                                 */\n                                F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE);\n                                F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED);\n\n                                /* Notify the screen. */\n                                (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n                        } else\n                                set_alt_name(sp, name);\n                        break;\n                default:\n                        ex_emsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT);\n                        return (1);\n\n                }\n                break;\n        default:\n                abort();\n                /* NOTREACHED */\n        }\n\n        /*\n         * !!!\n         * Historically, vi did not permit reads from non-regular files, nor\n         * did it distinguish between \"read !\" and \"read!\", so there was no\n         * way to \"force\" it.  We permit reading from named pipes too, since\n         * they didn't exist when the original implementation of vi was done\n         * and they seem a reasonable addition.\n         */\n        if ((fp = fopen(name, \"r\")) == NULL || fstat(fileno(fp), &sb)) {\n                msgq_str(sp, M_SYSERR, name, \"%s\");\n                return (1);\n        }\n        if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) {\n                (void)fclose(fp);\n                msgq(sp, M_ERR,\n                    \"Only regular files and named pipes may be read\");\n                return (1);\n        }\n\n        /* Try and get a lock. */\n        if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL)\n                msgq(sp, M_ERR, \"%s: read lock was unavailable\", name);\n\n        rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0);\n\n        /*\n         * In vi, set the cursor to the first line read in, if anything read\n         * in, otherwise, the address.  (Historic vi set it to the line after\n         * the address regardless, but since that line may not exist we don't\n         * bother.)\n         *\n         * In ex, set the cursor to the last line read in, if anything read in,\n         * otherwise, the address.\n         */\n        if (F_ISSET(sp, SC_VI)) {\n                sp->lno = cmdp->addr1.lno;\n                if (nlines)\n                        ++sp->lno;\n        } else\n                sp->lno = cmdp->addr1.lno + nlines;\n        return (rval);\n}\n\n/*\n * ex_readfp --\n *      Read lines into the file.\n *\n * PUBLIC: int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int);\n */\nint\nex_readfp(SCR *sp, char *name, FILE *fp, MARK *fm, recno_t *nlinesp,\n    int silent)\n{\n        EX_PRIVATE *exp;\n        GS *gp;\n        recno_t lcnt, lno;\n        size_t len;\n        unsigned long ccnt;                    /* XXX: can't print off_t portably. */\n        int nf, rval;\n        char *p;\n\n        gp = sp->gp;\n        exp = EXP(sp);\n\n        /*\n         * Add in the lines from the output.  Insertion starts at the line\n         * following the address.\n         */\n        ccnt = 0;\n        lcnt = 0;\n        p = \"Reading...\";\n        for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) {\n                if ((lcnt + 1) % INTERRUPT_CHECK == 0) {\n                        if (INTERRUPTED(sp))\n                                break;\n                        if (!silent) {\n                                gp->scr_busy(sp, p,\n                                    p == NULL ? BUSY_UPDATE : BUSY_ON);\n                                p = NULL;\n                        }\n                }\n                if (db_append(sp, 1, lno, exp->ibp, len))\n                        goto err;\n                ccnt += len;\n        }\n\n        if (ferror(fp) || fclose(fp))\n                goto err;\n\n        /* Return the number of lines read in. */\n        if (nlinesp != NULL)\n                *nlinesp = lcnt;\n\n        if (!silent) {\n                p = msg_print(sp, name, &nf);\n                msgq(sp, M_INFO,\n                    \"%s: %'lu lines, %'lu characters\", p, lcnt, ccnt);\n                if (nf)\n                        FREE_SPACE(sp, p, 0);\n        }\n\n        rval = 0;\n        if (0) {\nerr:            msgq_str(sp, M_SYSERR, name, \"%s\");\n                (void)fclose(fp);\n                rval = 1;\n        }\n\n        if (!silent)\n                gp->scr_busy(sp, NULL, BUSY_OFF);\n        return (rval);\n}\n"
  },
  {
    "path": "ex/ex_screen.c",
    "content": "/*      $OpenBSD: ex_screen.c,v 1.10 2016/01/06 22:28:52 millert Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * ex_bg --     :bg\n *      Hide the screen.\n *\n * PUBLIC: int ex_bg(SCR *, EXCMD *);\n */\nint\nex_bg(SCR *sp, EXCMD *cmdp)\n{\n        return (vs_bg(sp));\n}\n\n/*\n * ex_fg --     :fg [file]\n *      Show the screen.\n *\n * PUBLIC: int ex_fg(SCR *, EXCMD *);\n */\nint\nex_fg(SCR *sp, EXCMD *cmdp)\n{\n        SCR *nsp;\n        int newscreen;\n\n        newscreen = F_ISSET(cmdp, E_NEWSCREEN);\n        if (vs_fg(sp, &nsp, cmdp->argc ? cmdp->argv[0]->bp : NULL, newscreen))\n                return (1);\n\n        /* Set up the switch. */\n        if (newscreen) {\n                sp->nextdisp = nsp;\n                F_SET(sp, SC_SSWITCH);\n        }\n        return (0);\n}\n\n/*\n * ex_resize -- :resize [+-]rows\n *      Change the screen size.\n *\n * PUBLIC: int ex_resize(SCR *, EXCMD *);\n */\nint\nex_resize(SCR *sp, EXCMD *cmdp)\n{\n        adj_t adj;\n\n        switch (FL_ISSET(cmdp->iflags,\n            E_C_COUNT | E_C_COUNT_NEG | E_C_COUNT_POS)) {\n        case E_C_COUNT:\n                adj = A_SET;\n                break;\n        case E_C_COUNT | E_C_COUNT_NEG:\n                adj = A_DECREASE;\n                break;\n        case E_C_COUNT | E_C_COUNT_POS:\n                adj = A_INCREASE;\n                break;\n        default:\n                ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                return (1);\n        }\n        return (vs_resize(sp, cmdp->count, adj));\n}\n\n/*\n * ex_sdisplay --\n *      Display the list of screens.\n *\n * PUBLIC: int ex_sdisplay(SCR *);\n */\nint\nex_sdisplay(SCR *sp)\n{\n        GS *gp;\n        SCR *tsp;\n        int cnt, col, len, sep;\n\n        gp = sp->gp;\n        if (TAILQ_EMPTY(&gp->hq)) {\n                msgq(sp, M_INFO, \"No background screens to display\");\n                return (0);\n        }\n\n        col = len = sep = 0;\n        cnt = 1;\n        TAILQ_FOREACH(tsp, &gp->hq, q) {\n                if (INTERRUPTED(sp))\n                        break;\n                col += len = strlen(tsp->frp->name) + sep;\n                if (col >= sp->cols - 1) {\n                        col = len;\n                        sep = 0;\n                        (void)ex_puts(sp, \"\\n\");\n                } else if (cnt != 1) {\n                        sep = 1;\n                        (void)ex_puts(sp, \" \");\n                }\n                (void)ex_puts(sp, tsp->frp->name);\n                ++cnt;\n        }\n        if (!INTERRUPTED(sp))\n                (void)ex_puts(sp, \"\\n\");\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_script.c",
    "content": "/*      $OpenBSD: ex_script.c,v 1.27 2017/04/18 01:45:35 deraadt Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * This code is derived from software contributed to Berkeley by\n * Brian Hirt.\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/ioctl.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/wait.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <stdio.h>\n#include <grp.h>\n#include <limits.h>\n#include <poll.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#if defined(__solaris__)\n# define __EXTENSIONS__\n# include <termios.h>\n# include <sys/termios.h>\n#endif /* if defined(__solaris__) */\n\n#if defined(__OpenBSD__) || defined(__NetBSD__)\n# include <unistd.h>\n# include <termios.h>\n# include <util.h>\n#else\n# include <bsd_termios.h>\n# include <bsd_unistd.h>\n# include <util.h>\n#endif /* if defined(__OpenBSD__) || defined(__NetBSD__) */\n\n#ifndef __FreeBSD__\n# ifdef _AIX\n#  include <sys/pty.h>\n# else\n#  ifndef __OpenBSD__\n#   if ( !defined(__APPLE__)  && !defined(__MACH__) && \\\n         !defined(__NetBSD__) )\n#    if !defined(__solaris__) && !defined(__illumos__)\n#     include <pty.h>\n#    endif /* if !defined(__solaris__) && !defined(__illumos__) */\n#   endif /* if ( !defined(__APPLE__)  && !defined(__MACH__) && \\\n                  !defined(__NetBSD__) ) */\n#  endif /* ifndef __OpenBSD__ */\n# endif /* ifdef _AIX */\n#else\n# include <libutil.h>\n#endif /* ifndef __FreeBSD__ */\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n#include \"script.h\"\n#include \"pathnames.h\"\n\n#if defined(__OpenBSD__) || defined(__NetBSD__) || \\\n  ( defined(__APPLE__) && defined(__MACH__) )\nint openpty(int *, int *, char *, struct termios *, struct winsize *);\n#endif /* defined(__OpenBSD__) || defined(__NetBSD__) ||\n        ( defined(__APPLE__) && defined(__MACH__) ) */\n\n#if defined(_AIX) || defined(__illumos__) || defined(__solaris__)\n# define HAVE_SYS5_PTY\n#endif /* if defined(_AIX) || defined(__illumos__) || defined(__solaris__) */\n\n#ifdef HAVE_SYS5_PTY\n# include <sys/stropts.h>\nstatic int ptys_open(int fdm, char *pts_name);\nstatic int ptym_open(char *pts_name);\nstatic int sscr_pty(int *amaster, int *aslave, char *name,\n                    struct termios *termp, void *winp);\n#endif /* ifdef HAVE_SYS5_PTY */\n\nstatic void     sscr_check(SCR *);\nstatic int      sscr_getprompt(SCR *);\nstatic int      sscr_init(SCR *);\nstatic int      sscr_insert(SCR *);\nstatic int      sscr_matchprompt(SCR *, char *, size_t, size_t *);\nstatic int      sscr_setprompt(SCR *, char *, size_t);\n\n/*\n * ex_script -- : sc[ript][!] [file]\n *      Switch to script mode.\n *\n * PUBLIC: int ex_script(SCR *, EXCMD *);\n */\nint\nex_script(SCR *sp, EXCMD *cmdp)\n{\n        /* Vi only command. */\n        if (!F_ISSET(sp, SC_VI)) {\n                msgq(sp, M_ERR,\n                    \"The script command is only available in vi mode\");\n                return (1);\n        }\n\n        /* Switch to the new file. */\n        if (cmdp->argc != 0 && ex_edit(sp, cmdp))\n                return (1);\n\n        /* Create the shell, figure out the prompt. */\n        if (sscr_init(sp))\n                return (1);\n\n        return (0);\n}\n\n/*\n * sscr_init --\n *      Create a pty setup for a shell.\n */\nstatic int\nsscr_init(SCR *sp)\n{\n        SCRIPT *sc;\n        char *sh, *sh_path;\n\n        /* We're going to need a shell. */\n        if (opts_empty(sp, O_SHELL, 0))\n                return (1);\n\n        MALLOC_RET(sp, sc, sizeof(SCRIPT));\n        sp->script = sc;\n        sc->sh_prompt = NULL;\n        sc->sh_prompt_len = 0;\n\n        /*\n         * There are two different processes running through this code.\n         * They are the shell and the parent.\n         */\n        sc->sh_master = sc->sh_slave = -1;\n\n        if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {\n                msgq(sp, M_SYSERR, \"tcgetattr\");\n                goto err;\n        }\n\n        /*\n         * Turn off output postprocessing and echo.\n         */\n        sc->sh_term.c_oflag &= ~OPOST;\n        sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);\n\n        if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {\n                msgq(sp, M_SYSERR, \"tcgetattr\");\n                goto err;\n        }\n\n#ifdef HAVE_SYS5_PTY\n# ifdef TIOCGWINSZ\n        if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {\n                msgq(sp, M_SYSERR, \"tcgetattr\");\n                goto err;\n        }\n\n        if (sscr_pty(&sc->sh_master,\n                &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {\n                msgq(sp, M_SYSERR, \"pty\");\n                goto err;\n        }\n# else\n        if (sscr_pty(&sc->sh_master,\n                &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) {\n                msgq(sp, M_SYSERR, \"pty\");\n                goto err;\n        }\n# endif /* ifdef TIOCGWINSZ */\n#else\n        if (openpty(&sc->sh_master,\n                &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {\n                    msgq(sp, M_SYSERR, \"pty\");\n                    goto err;\n        }\n#endif /* HAVE_SYS5_PTY */\n\n        switch (sc->sh_pid = fork()) {\n        case -1:                        /* Error. */\n                msgq(sp, M_SYSERR, \"fork\");\nerr:            if (sc->sh_master != -1)\n                        (void)close(sc->sh_master);\n                if (sc->sh_slave != -1)\n                        (void)close(sc->sh_slave);\n                return (1);\n        case 0:                         /* Utility. */\n                /*\n                 * XXX\n                 * So that shells that do command line editing turn it off.\n                 */\n                if (setenv(\"TERM\", \"emacs\", 1) == -1 ||\n                    setenv(\"TERMCAP\", \"emacs:\", 1) == -1 ||\n                    setenv(\"EMACS\", \"t\", 1) == -1)\n                        _exit(126);\n\n                (void)setsid();\n#ifdef TIOCSCTTY\n                /*\n                 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY\n                 * ioctl, not by opening a terminal device file.  POSIX 1003.1\n                 * doesn't define a portable way to do this.\n                 */\n                (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);\n#endif /* ifdef TIOCSCTTY */\n                (void)close(sc->sh_master);\n                (void)dup2(sc->sh_slave, STDIN_FILENO);\n                (void)dup2(sc->sh_slave, STDOUT_FILENO);\n                (void)dup2(sc->sh_slave, STDERR_FILENO);\n                (void)close(sc->sh_slave);\n\n                /* Assumes that all shells have -i. */\n                sh_path = O_STR(sp, O_SHELL);\n                if ((sh = strrchr(sh_path, '/')) == NULL)\n                        sh = sh_path;\n                else\n                        ++sh;\n                execl(sh_path, sh, \"-i\", (char *)NULL);\n                msgq_str(sp, M_SYSERR, sh_path, \"execl: %s\");\n                _exit(127);\n        default:                        /* Parent. */\n                break;\n        }\n\n        if (sscr_getprompt(sp))\n                return (1);\n\n        F_SET(sp, SC_SCRIPT);\n        F_SET(sp->gp, G_SCRWIN);\n        return (0);\n}\n\n/*\n * sscr_getprompt --\n *      Eat lines printed by the shell until a line with no trailing\n *      carriage return comes; set the prompt from that line.\n */\nstatic int\nsscr_getprompt(SCR *sp)\n{\n        CHAR_T *endp, *p, *t, buf[1024];\n        SCRIPT *sc;\n        struct pollfd pfd[1];\n        recno_t lline;\n        size_t llen, len;\n        unsigned int value;\n        int nr;\n\n        endp = buf;\n        len = sizeof(buf);\n        (void)len;\n\n        /* Wait up to a second for characters to read. */\n        sc = sp->script;\n        pfd[0].fd = sc->sh_master;\n        pfd[0].events = POLLIN;\n        switch (poll(pfd, 1, 5 * 1000)) {\n        case -1:                /* Error or interrupt. */\n                msgq(sp, M_SYSERR, \"poll\");\n                goto prompterr;\n        case  0:                /* Timeout */\n                msgq(sp, M_ERR, \"Error: timed out\");\n                goto prompterr;\n        default:                /* Characters to read. */\n                break;\n        }\n\n        /* Read the characters. */\nmore:   len = sizeof(buf) - (endp - buf);\n        switch (nr = read(sc->sh_master, endp, len)) {\n        case  0:                        /* EOF. */\n                msgq(sp, M_ERR, \"Error: shell: EOF\");\n                goto prompterr;\n        case -1:                        /* Error or interrupt. */\n                msgq(sp, M_SYSERR, \"shell\");\n                goto prompterr;\n        default:\n                endp += nr;\n                break;\n        }\n\n        /* If any complete lines, push them into the file. */\n        for (p = t = buf; p < endp; ++p) {\n                value = KEY_VAL(sp, *p);\n                if (value == K_CR || value == K_NL) {\n                        if (db_last(sp, &lline) ||\n                            db_append(sp, 0, lline, t, p - t))\n                                goto prompterr;\n                        t = p + 1;\n                }\n        }\n        if (p > buf) {\n                memmove(buf, t, endp - t);\n                endp = buf + (endp - t);\n        }\n        if (endp == buf)\n                goto more;\n\n        /* Wait up 1/10 of a second to make sure that we got it all. */\n        switch (poll(pfd, 1, 100)) {\n        case -1:                /* Error or interrupt. */\n                msgq(sp, M_SYSERR, \"poll\");\n                goto prompterr;\n        case  0:                /* Timeout */\n                break;\n        default:                /* Characters to read. */\n                goto more;\n        }\n\n        /* Timed out, so theoretically we have a prompt. */\n        llen = endp - buf;\n        (void)llen;\n        endp = buf;\n        (void)endp;\n\n        /* Append the line into the file. */\n        if (db_last(sp, &lline) || db_append(sp, 0, lline, buf, llen)) {\nprompterr:      sscr_end(sp);\n                return (1);\n        }\n\n        return (sscr_setprompt(sp, buf, llen));\n}\n\n/*\n * sscr_exec --\n *      Take a line and hand it off to the shell.\n *\n * PUBLIC: int sscr_exec(SCR *, recno_t);\n */\nint\nsscr_exec(SCR *sp, recno_t lno)\n{\n        SCRIPT *sc;\n        recno_t last_lno;\n        size_t blen, len, last_len, tlen;\n        int isempty, matchprompt, nw, rval;\n        char *bp, *p;\n\n        /* If there's a prompt on the last line, append the command. */\n        if (sp == NULL)\n                return (1);\n        if (db_last(sp, &last_lno))\n                return (1);\n        if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len))\n                return (1);\n        if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {\n                matchprompt = 1;\n                GET_SPACE_RET(sp, bp, blen, last_len + 128);\n                if (bp != NULL)\n                    memmove(bp, p, last_len);\n        } else\n                matchprompt = 0;\n\n        /* Get something to execute. */\n        if (db_eget(sp, lno, &p, &len, &isempty)) {\n                if (isempty)\n                        goto empty;\n                goto err1;\n        }\n\n        /* Empty lines aren't interesting. */\n        if (len == 0)\n                goto empty;\n\n        /* Delete any prompt. */\n        if (sscr_matchprompt(sp, p, len, &tlen)) {\n                if (tlen == len) {\nempty:                  msgq(sp, M_BERR, \"No command to execute\");\n                        goto err1;\n                }\n                p += (len - tlen);\n                len = tlen;\n        }\n\n        /* Push the line to the shell. */\n        sc = sp->script;\n        if ((nw = write(sc->sh_master, p, len)) != len)\n                goto err2;\n        rval = 0;\n        if (write(sc->sh_master, \"\\n\", 1) != 1) {\nerr2:           if (nw == 0)\n                        errno = EIO;\n                msgq(sp, M_SYSERR, \"shell\");\n                goto err1;\n        }\n\n        if (matchprompt) {\n                ADD_SPACE_RET(sp, bp, blen, last_len + len);\n                if (bp != NULL) {\n                    memmove(bp + last_len, p, len);\n                    if (db_set(sp, last_lno, bp, last_len + len))\nerr1:                       rval = 1;\n                }\n        }\n        if (matchprompt)\n                FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n\n/*\n * sscr_check_input -\n *      Check for input from command input or scripting windows.\n *\n * PUBLIC: int sscr_check_input(SCR *sp);\n */\nint\nsscr_check_input(SCR *sp)\n{\n        GS *gp;\n        SCR *tsp;\n        struct pollfd *pfd;\n        int nfds, rval;\n\n        gp = sp->gp;\n        rval = 0;\n\n        /* Allocate space for pfd. */\n        nfds = 1;\n        TAILQ_FOREACH(tsp, &gp->dq, q)\n                if (F_ISSET(sp, SC_SCRIPT))\n                        nfds++;\n        pfd = calloc(nfds, sizeof(struct pollfd));\n        if (pfd == NULL) {\n                msgq(sp, M_SYSERR, \"malloc\");\n                return (1);\n        }\n\n        /* Setup events bitmasks. */\n        pfd[0].fd = STDIN_FILENO;\n        pfd[0].events = POLLIN;\n        nfds = 1;\n        TAILQ_FOREACH(tsp, &gp->dq, q)\n                if (F_ISSET(sp, SC_SCRIPT)) {\n                        pfd[nfds].fd = sp->script->sh_master;\n                        pfd[nfds].events = POLLIN;\n                        nfds++;\n                }\n\nloop:\n        /* Check for input. */\n        switch (poll(pfd, nfds, INFTIM)) {\n        case -1:\n                msgq(sp, M_SYSERR, \"poll\");\n                rval = 1;\n                /* FALLTHROUGH */\n        case 0:\n                goto done;\n        default:\n                break;\n        }\n\n        /* Only insert from the scripting windows if no command input */\n        if (!(pfd[0].revents & POLLIN)) {\n                nfds = 1;\n                TAILQ_FOREACH(tsp, &gp->dq, q)\n                        if (F_ISSET(sp, SC_SCRIPT)) {\n                                if ((pfd[nfds].revents & POLLHUP) && sscr_end(sp))\n                                        goto done;\n                                if ((pfd[nfds].revents & POLLIN) && sscr_insert(sp))\n                                        goto done;\n                                nfds++;\n                        }\n                goto loop;\n        }\ndone:\n        free(pfd);\n        return (rval);\n}\n\n/*\n * sscr_input --\n *      Read any waiting shell input.\n *\n * PUBLIC: int sscr_input(SCR *);\n */\nint\nsscr_input(SCR *sp)\n{\n        GS *gp;\n        struct pollfd *pfd;\n        int nfds, rval;\n\n        gp = sp->gp;\n        rval = 0;\n\n        /* Allocate space for pfd. */\n        nfds = 0;\n        TAILQ_FOREACH(sp, &gp->dq, q)\n                if (F_ISSET(sp, SC_SCRIPT))\n                        nfds++;\n        if (nfds == 0)\n                return (0);\n        pfd = calloc(nfds, sizeof(struct pollfd));\n        if (pfd == NULL) {\n                msgq(sp, M_SYSERR, \"malloc\");\n                return (1);\n        }\n\n        /* Setup events bitmasks. */\n        nfds = 0;\n        TAILQ_FOREACH(sp, &gp->dq, q)\n                if (F_ISSET(sp, SC_SCRIPT)) {\n                        pfd[nfds].fd = sp->script->sh_master;\n                        pfd[nfds].events = POLLIN;\n                        nfds++;\n                }\n\nloop:\n        /* Check for input. */\n        switch (poll(pfd, nfds, 0)) {\n        case -1:\n                msgq(sp, M_SYSERR, \"poll\");\n                rval = 1;\n                /* FALLTHROUGH */\n        case 0:\n                goto done;\n        default:\n                break;\n        }\n\n        /* Read the input. */\n        nfds = 0;\n        TAILQ_FOREACH(sp, &gp->dq, q)\n                if (F_ISSET(sp, SC_SCRIPT)) {\n                        if ((pfd[nfds].revents & POLLHUP) && sscr_end(sp))\n                                goto done;\n                        if ((pfd[nfds].revents & POLLIN) && sscr_insert(sp))\n                                goto done;\n                        nfds++;\n                }\n        goto loop;\ndone:\n        free(pfd);\n        return (rval);\n}\n\n/*\n * sscr_insert --\n *      Take a line from the shell and insert it into the file.\n */\nstatic int\nsscr_insert(SCR *sp)\n{\n        CHAR_T *endp, *p, *t;\n        SCRIPT *sc;\n        struct pollfd pfd[1];\n        recno_t lno;\n        size_t blen, len, tlen;\n        unsigned int value;\n        int nr, rval;\n        char *bp;\n\n        /* Find out where the end of the file is. */\n        if (db_last(sp, &lno))\n                return (1);\n\n#define MINREAD 1024\n        GET_SPACE_RET(sp, bp, blen, MINREAD);\n        endp = bp;\n\n        /* Read the characters. */\n        rval = 1;\n        sc = sp->script;\nmore:   switch (nr = read(sc->sh_master, endp, MINREAD)) {\n        case  0:                        /* EOF; shell just exited. */\n                sscr_end(sp);\n                rval = 0;\n                goto ret;\n        case -1:                        /* Error or interrupt. */\n                msgq(sp, M_SYSERR, \"shell\");\n                goto ret;\n        default:\n                endp += nr;\n                break;\n        }\n\n        /* Append the lines into the file. */\n        for (p = t = bp; p < endp; ++p) {\n                value = KEY_VAL(sp, *p);\n                if (value == K_CR || value == K_NL) {\n                        len = p - t;\n                        if (db_append(sp, 1, lno++, t, len))\n                                goto ret;\n                        t = p + 1;\n                }\n        }\n        if (p > t) {\n                len = p - t;\n                /*\n                 * If the last thing from the shell isn't another prompt, wait\n                 * up to 1/10 of a second for more stuff to show up, so that\n                 * we don't break the output into two separate lines.  Don't\n                 * want to hang indefinitely because some program is hanging,\n                 * confused the shell, or whatever.\n                 */\n                if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {\n                        pfd[0].fd = sc->sh_master;\n                        pfd[0].events = POLLIN;\n                        if (poll(pfd, 1, 100) > 0) {\n                                memmove(bp, t, len);\n                                endp = bp + len;\n                                goto more;\n                        }\n                }\n                if (sscr_setprompt(sp, t, len))\n                        return (1);\n                if (db_append(sp, 1, lno++, t, len))\n                        goto ret;\n        }\n\n        /* The cursor moves to EOF. */\n        sp->lno = lno;\n        sp->cno = len ? len - 1 : 0;\n        rval = vs_refresh(sp, 1);\n\nret:    FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n\n/*\n * sscr_setprompt --\n *\n * Set the prompt to the last line we got from the shell.\n *\n */\nstatic int\nsscr_setprompt(SCR *sp, char *buf, size_t len)\n{\n        SCRIPT *sc;\n\n        sc = sp->script;\n        free(sc->sh_prompt);\n        MALLOC(sp, sc->sh_prompt, len + 1);\n        if (sc->sh_prompt == NULL) {\n                sscr_end(sp);\n                return (1);\n        }\n        memmove(sc->sh_prompt, buf, len);\n        sc->sh_prompt_len = len;\n        sc->sh_prompt[len] = '\\0';\n        return (0);\n}\n\n/*\n * sscr_matchprompt --\n *      Check to see if a line matches the prompt.  Nul's indicate\n *      parts that can change, in both content and size.\n */\nstatic int\nsscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp)\n{\n        SCRIPT *sc;\n        size_t prompt_len;\n        char *pp;\n\n        sc = sp->script;\n        if (line_len < (prompt_len = sc->sh_prompt_len))\n                return (0);\n\n        for (pp = sc->sh_prompt;\n            prompt_len && line_len; --prompt_len, --line_len) {\n                if (*pp == '\\0') {\n                        for (; prompt_len && *pp == '\\0'; --prompt_len, ++pp);\n                        if (!prompt_len)\n                                return (0);\n                        for (; line_len && *lp != *pp; --line_len, ++lp);\n                        if (!line_len)\n                                return (0);\n                }\n                if (*pp++ != *lp++)\n                        break;\n        }\n\n        if (prompt_len)\n                return (0);\n        if (lenp != NULL)\n                *lenp = line_len;\n        return (1);\n}\n\n/*\n * sscr_end --\n *      End the pipe to a shell.\n *\n * PUBLIC: int sscr_end(SCR *);\n */\nint\nsscr_end(SCR *sp)\n{\n        SCRIPT *sc;\n\n        if ((sc = sp->script) == NULL)\n                return (0);\n\n        /* Turn off the script flags. */\n        F_CLR(sp, SC_SCRIPT);\n        sscr_check(sp);\n\n        /* Close down the parent's file descriptors. */\n        if (sc->sh_master != -1)\n            (void)close(sc->sh_master);\n        if (sc->sh_slave != -1)\n            (void)close(sc->sh_slave);\n\n        /* This should have killed the child. */\n        (void)proc_wait(sp, sc->sh_pid, \"script-shell\", 0, 0);\n\n        /* Free memory. */\n        free(sc->sh_prompt);\n        free(sc);\n        sp->script = NULL;\n\n        return (0);\n}\n\n/*\n * sscr_check --\n *      Set/clear the global scripting bit.\n */\nstatic void\nsscr_check(SCR *sp)\n{\n        GS *gp;\n\n        gp = sp->gp;\n        TAILQ_FOREACH(sp, &gp->dq, q)\n                if (F_ISSET(sp, SC_SCRIPT)) {\n                        F_SET(gp, G_SCRWIN);\n                        return;\n                }\n        F_CLR(gp, G_SCRWIN);\n}\n\n#ifdef HAVE_SYS5_PTY\nstatic int\nsscr_pty(int *amaster, int *aslave, char *name,\n         struct termios *termp, void *winp)\n{\n    int master, slave;\n\n    /* open master terminal */\n    if ((master = ptym_open(name)) < 0)  {\n        errno = ENOENT;        /* out of ptys */\n        return (-1);\n    }\n\n    /* open slave terminal */\n    if ((slave = ptys_open(master, name)) >= 0) {\n        *amaster = master;\n        *aslave = slave;\n    } else {\n        errno = ENOENT;        /* out of ptys */\n        return (-1);\n    }\n\n    if (termp)\n        (void) tcsetattr(slave, TCSAFLUSH, termp);\n# ifdef TIOCSWINSZ\n    if (winp != NULL)\n        (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp);\n# endif /* ifdef TIOCSWINSZ */\n    return (0);\n}\n\n/*\n * ptym_open --\n *    This function opens a master pty and returns the file descriptor\n *    to it.  pts_name is also returned which is the name of the slave.\n */\nstatic int\nptym_open(char *pts_name)\n{\n    int fdm;\n    char *ptr, *ptsname(int fdm);\n\n    openbsd_strlcpy(pts_name, _PATH_SYSV_PTY, strlen(_PATH_SYSV_PTY));\n    if ((fdm = open(pts_name, O_RDWR)) < 0 )\n        return (-1);\n\n    if (grantpt(fdm) < 0) {\n        close(fdm);\n        return (-2);\n    }\n\n    if (unlockpt(fdm) < 0) {\n        close(fdm);\n        return (-3);\n    }\n\n    if (unlockpt(fdm) < 0) {\n        close(fdm);\n        return (-3);\n    }\n\n    /* get slave's name */\n    if ((ptr = ptsname(fdm)) == NULL) {\n        close(fdm);\n        return (-3);\n    }\n    openbsd_strlcpy(pts_name, ptr, strlen(_PATH_SYSV_PTY));\n    return (fdm);\n}\n\n/*\n * ptys_open --\n *     This function opens the slave pty.\n */\nstatic int\nptys_open(int fdm, char *pts_name)\n{\n    int fds;\n\n    if ((fds = open(pts_name, O_RDWR)) < 0) {\n        close(fdm);\n        return (-5);\n    }\n\n    if (ioctl(fds, I_PUSH, \"ptem\") < 0) {\n        close(fds);\n        close(fdm);\n        return (-6);\n    }\n\n    if (ioctl(fds, I_PUSH, \"ldterm\") < 0) {\n        close(fds);\n        close(fdm);\n        return (-7);\n    }\n\n    if (ioctl(fds, I_PUSH, \"ttcompat\") < 0) {\n        close(fds);\n        close(fdm);\n        return (-8);\n    }\n\n    return (fds);\n}\n#endif /* HAVE_SYS5_PTY */\n"
  },
  {
    "path": "ex/ex_set.c",
    "content": "/*      $OpenBSD: ex_set.c,v 1.6 2014/11/12 04:28:41 bentley Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_set -- :set\n *      Ex set option.\n *\n * PUBLIC: int ex_set(SCR *, EXCMD *);\n */\nint\nex_set(SCR *sp, EXCMD *cmdp)\n{\n        switch(cmdp->argc) {\n        case 0:\n                opts_dump(sp, CHANGED_DISPLAY);\n                break;\n        default:\n                if (opts_set(sp, cmdp->argv, cmdp->cmd->usage))\n                        return (1);\n                break;\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_shell.c",
    "content": "/*      $OpenBSD: ex_shell.c,v 1.15 2015/03/28 12:54:37 bcallah Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#include <sys/wait.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#ifdef __solaris__\n# define _XPG7\n#endif /* ifdef __solaris__ */\n#include <signal.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\n/*\n * ex_shell -- :sh[ell]\n *      Invoke the program named in the SHELL environment variable\n *      with the argument -i.\n *\n * PUBLIC: int ex_shell(SCR *, EXCMD *);\n */\nint\nex_shell(SCR *sp, EXCMD *cmdp)\n{\n        int rval;\n        char buf[PATH_MAX];\n\n        /* We'll need a shell. */\n        if (opts_empty(sp, O_SHELL, 0))\n                return (1);\n\n        /*\n         * XXX\n         * Assumes all shells use -i.\n         */\n        (void)snprintf(buf, sizeof(buf), \"%s -i\", O_STR(sp, O_SHELL));\n\n        /* Restore the window name. */\n        (void)sp->gp->scr_rename(sp, NULL, 0);\n\n        /* If we're still in a vi screen, move out explicitly. */\n        rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE));\n\n        /* Set the window name. */\n        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n\n        /*\n         * !!!\n         * Historically, vi didn't require a continue message after the\n         * return of the shell.  Match it.\n         */\n        F_SET(sp, SC_EX_WAIT_NO);\n\n        return (rval);\n}\n\n/*\n * ex_exec_proc --\n *      Run a separate process.\n *\n * PUBLIC: int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int);\n */\nint\nex_exec_proc(SCR *sp, EXCMD *cmdp, char *cmd, const char *msg,\n    int need_newline)\n{\n        GS *gp;\n        const char *name;\n        pid_t pid;\n\n        gp = sp->gp;\n\n        /* We'll need a shell. */\n        if (opts_empty(sp, O_SHELL, 0))\n                return (1);\n\n        /* Enter ex mode. */\n        if (F_ISSET(sp, SC_VI)) {\n                if (gp->scr_screen(sp, SC_EX)) {\n                        ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON);\n                        return (1);\n                }\n                (void)gp->scr_attr(sp, SA_ALTERNATE, 0);\n                F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);\n        }\n\n        /* Put out additional newline, message. */\n        if (need_newline)\n                (void)ex_puts(sp, \"\\n\");\n        if (msg != NULL) {\n                (void)ex_puts(sp, msg);\n                (void)ex_puts(sp, \"\\n\");\n        }\n        (void)ex_fflush(sp);\n\n        switch (pid = fork()) {\n        case -1:                        /* Error. */\n                msgq(sp, M_SYSERR, \"fork\");\n                return (1);\n        case 0:                         /* Utility. */\n                if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)\n                        name = O_STR(sp, O_SHELL);\n                else\n                        ++name;\n                execl(O_STR(sp, O_SHELL), name, \"-c\", cmd, (char *)NULL);\n                msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), \"execl: %s\");\n                _exit(127);\n                /* NOTREACHED */\n        default:                        /* Parent. */\n                return (proc_wait(sp, pid, cmd, 0, 0));\n        }\n        /* NOTREACHED */\n}\n\n/*\n * proc_wait --\n *      Wait for one of the processes.\n *\n * !!!\n * The pid_t type varies in size from a short to a long depending on the\n * system.  It has to be cast into something or the standard promotion\n * rules get you.  I'm using a long based on the belief that nobody is\n * going to make it unsigned and it's unlikely to be a quad.\n *\n * PUBLIC: int proc_wait(SCR *, pid_t, const char *, int, int);\n */\nint\nproc_wait(SCR *sp, pid_t pid, const char *cmd, int silent, int okpipe)\n{\n        size_t len;\n        int nf, pstat;\n        char *p;\n\n        /* Wait for the utility, ignoring interruptions. */\n        for (;;) {\n                errno = 0;\n                if (waitpid(pid, &pstat, 0) != -1)\n                        break;\n                if (errno != EINTR) {\n                        msgq(sp, M_SYSERR, \"waitpid\");\n                        return (1);\n                }\n        }\n\n        /*\n         * Display the utility's exit status.  Ignore SIGPIPE from the\n         * parent-writer, as that only means that the utility chose to\n         * exit before reading all of its input.\n         */\n        if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) {\n                for (; isblank(*cmd); ++cmd);\n                p = msg_print(sp, cmd, &nf);\n                len = strlen(p);\n                msgq(sp, M_ERR, \"%.*s%s: received signal: %s%s\",\n                     MINIMUM(len, 20),\n                     p,\n                     len > 20 ? \" ...\" : \"\",\n                     strsignal(WTERMSIG(pstat)),\n#ifdef WCOREDUMP\n                     WCOREDUMP(pstat) ? \"; core dumped\" : \"\");\n#else\n                     \"\");\n#endif /* ifdef WCOREDUMP */\n                if (nf)\n                        FREE_SPACE(sp, p, 0);\n                return (1);\n        }\n\n        if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) {\n                /*\n                 * Remain silent for \"normal\" errors when doing shell file\n                 * name expansions, they almost certainly indicate nothing\n                 * more than a failure to match.\n                 *\n                 * Remain silent for vi read filter errors.  It's historic\n                 * practice.\n                 */\n                if (!silent) {\n                        for (; isblank(*cmd); ++cmd);\n                        p = msg_print(sp, cmd, &nf);\n                        len = strlen(p);\n                        msgq(sp, M_ERR, \"%.*s%s: exited with status %d\",\n                            MINIMUM(len, 20), p, len > 20 ? \" ...\" : \"\",\n                            WEXITSTATUS(pstat));\n                        if (nf)\n                                FREE_SPACE(sp, p, 0);\n                }\n                return (1);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_shift.c",
    "content": "/*      $OpenBSD: ex_shift.c,v 1.11 2025/07/30 22:19:13 millert Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\nenum which {RETAB, LEFT, RIGHT};\nstatic int shift(SCR *, EXCMD *, enum which);\n\n/*\n * ex_shiftl -- :<[<...]\n *\n *\n * PUBLIC: int ex_shiftl(SCR *, EXCMD *);\n */\nint\nex_shiftl(SCR *sp, EXCMD *cmdp)\n{\n        return (shift(sp, cmdp, LEFT));\n}\n\n/*\n * ex_shiftr -- :>[>...]\n *\n * PUBLIC: int ex_shiftr(SCR *, EXCMD *);\n */\nint\nex_shiftr(SCR *sp, EXCMD *cmdp)\n{\n        return (shift(sp, cmdp, RIGHT));\n}\n\n/*\n * ex_retab --\n *      Re-expand tabs for expandtab\n *\n * PUBLIC: int ex_retab(SCR *sp, EXCMD *cmdp);\n */\nint\nex_retab(SCR *sp, EXCMD *cmdp)\n{\n        return (shift(sp, cmdp, RETAB));\n}\n\n/*\n * shift --\n *      Ex shift support.\n */\nstatic int\nshift(SCR *sp, EXCMD *cmdp, enum which rl)\n{\n        recno_t from, to;\n        size_t blen, len, newcol, newidx, oldcol, oldidx, sw;\n        int curset;\n        char *p, *bp, *tbp;\n\n        NEEDFILE(sp, cmdp);\n\n        if (O_VAL(sp, O_SHIFTWIDTH) == 0) {\n                msgq(sp, M_INFO, \"shiftwidth option set to 0\");\n                return (0);\n        }\n\n        /*\n         * When not doing re-expand tabs, copy the lines being shifted into\n         * the unnamed buffer.\n         */\n        if (rl != RETAB &&\n            cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE))\n                return (1);\n\n        /*\n         * The historic version of vi permitted the user to string any number\n         * of '>' or '<' characters together, resulting in an indent of the\n         * appropriate levels.  There's a special hack in ex_cmd() so that\n         * cmdp->argv[0] points to the string of '>' or '<' characters.\n         *\n         * Q: What's the difference between the people adding features\n         *    to vi and the Girl Scouts?\n         * A: The Girl Scouts have mint cookies and adult supervision.\n         */\n        for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p)\n                sw += O_VAL(sp, O_SHIFTWIDTH);\n\n        GET_SPACE_RET(sp, bp, blen, 256);\n\n        curset = 0;\n        for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) {\n                if (db_get(sp, from, DBG_FATAL, &p, &len))\n                        goto err;\n                if (!len) {\n                        if (sp->lno == from)\n                                curset = 1;\n                        continue;\n                }\n\n                /*\n                 * Calculate the old indent amount and the number of\n                 * characters it used.\n                 */\n                for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx)\n                        if (p[oldidx] == ' ')\n                                ++oldcol;\n                        else if (p[oldidx] == '\\t')\n                                oldcol += O_VAL(sp, O_TABSTOP) -\n                                    oldcol % O_VAL(sp, O_TABSTOP);\n                        else\n                                break;\n\n                /* Calculate the new indent amount. */\n                if (rl == RETAB)\n                        newcol = oldcol;\n                else if (rl == RIGHT)\n                        newcol = oldcol + sw;\n                else {\n                        newcol = oldcol < sw ? 0 : oldcol - sw;\n                        if (newcol == oldcol) {\n                                if (sp->lno == from)\n                                        curset = 1;\n                                continue;\n                        }\n                }\n\n                /* Get a buffer that will hold the new line. */\n                ADD_SPACE_RET(sp, bp, blen, newcol + len);\n\n                /*\n                 * Build a new indent string and count the number of\n                 * characters it uses.\n                 */\n                tbp = bp;\n                newidx = 0;\n                if (!O_ISSET(sp, O_EXPANDTAB)) {\n                        for (; newcol >= O_VAL(sp, O_TABSTOP); ++newidx) {\n                                *tbp++ = '\\t';\n                                newcol -= O_VAL(sp, O_TABSTOP);\n                        }\n                }\n                for (; newcol > 0; --newcol, ++newidx)\n                        *tbp++ = ' ';\n\n                /* Add the original line. */\n                memcpy(tbp, p + oldidx, len - oldidx);\n\n                /* Set the replacement line. */\n                if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) {\nerr:                    FREE_SPACE(sp, bp, blen);\n                        return (1);\n                }\n\n                /*\n                 * !!!\n                 * The shift command in historic vi had the usual bizarre\n                 * collection of cursor semantics.  If called from vi, the\n                 * cursor was repositioned to the first non-blank character\n                 * of the lowest numbered line shifted.  If called from ex,\n                 * the cursor was repositioned to the first non-blank of the\n                 * highest numbered line shifted.  Here, if the cursor isn't\n                 * part of the set of lines that are moved, move it to the\n                 * first non-blank of the last line shifted.  (This makes\n                 * \":3>>\" in vi work reasonably.)  If the cursor is part of\n                 * the shifted lines, it doesn't get moved at all.  This\n                 * permits shifting of marked areas, i.e. \">'a.\" shifts the\n                 * marked area twice, something that couldn't be done with\n                 * historic vi.\n                 */\n                if (sp->lno == from) {\n                        curset = 1;\n                        if (newidx > oldidx)\n                                sp->cno += newidx - oldidx;\n                        else if (sp->cno >= oldidx - newidx)\n                                sp->cno -= oldidx - newidx;\n                }\n        }\n        if (!curset) {\n                sp->lno = to;\n                sp->cno = 0;\n                (void)nonblank(sp, to, &sp->cno);\n        }\n\n        FREE_SPACE(sp, bp, blen);\n\n        sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_source.c",
    "content": "/*      $OpenBSD: ex_source.c,v 1.11 2021/10/24 21:24:17 deraadt Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n#undef open\n\n/*\n * ex_sourcefd -- :source already opened file\n *      Execute ex commands from the given file descriptor\n *\n * PUBLIC: int ex_sourcefd(SCR *, EXCMD *, int);\n */\nint\nex_sourcefd(SCR *sp, EXCMD *cmdp, int fd)\n{\n        struct stat sb;\n        int len;\n        char *bp, *name;\n\n        name = cmdp->argv[0]->bp;\n        if (fstat(fd, &sb))\n                goto err;\n\n        /*\n         * XXX\n         * I'd like to test to see if the file is too large to malloc.  Since\n         * we don't know what size or type off_t's or size_t's are, what the\n         * largest unsigned integral type is, or what random insanity the local\n         * C compiler will perpetrate, doing the comparison in a portable way\n         * is flatly impossible.  So, put an fairly unreasonable limit on it,\n         * I don't want to be dropping core here.\n         */\n#define MEGABYTE        1048576\n        if (sb.st_size > (8 * MEGABYTE)) {\n                errno = ENOMEM;\n                goto err;\n        }\n\n        MALLOC(sp, bp, (size_t)sb.st_size + 1);\n        if (bp == NULL) {\n                (void)close(fd);\n                return (1);\n        }\n        bp[sb.st_size] = '\\0';\n\n        /* Read the file into memory. */\n        len = read(fd, bp, (int)sb.st_size);\n        (void)close(fd);\n        if (len == -1 || len != sb.st_size) {\n                if (len != sb.st_size)\n                        errno = EIO;\n                free(bp);\nerr:            msgq_str(sp, M_SYSERR, name, \"%s\");\n                return (1);\n        }\n\n        /* Put it on the ex queue. */\n        return (ex_run_str(sp, name, bp, (size_t)sb.st_size, 1, 1));\n}\n\n/*\n * ex_source -- :source file\n *      Execute ex commands from a file.\n *\n * PUBLIC: int ex_source(SCR *, EXCMD *);\n */\nint\nex_source(SCR *sp, EXCMD *cmdp)\n{\n        char *name;\n        int fd;\n\n        name = cmdp->argv[0]->bp;\n        if ((fd = open(name, O_RDONLY)) >= 0)\n                return (ex_sourcefd(sp, cmdp, fd));\n\n        msgq_str(sp, M_SYSERR, name, \"%s\");\n        return (1);\n}\n"
  },
  {
    "path": "ex/ex_stop.c",
    "content": "/*      $OpenBSD: ex_stop.c,v 1.6 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_stop -- :stop[!]\n *            :suspend[!]\n *      Suspend execution.\n *\n * PUBLIC: int ex_stop(SCR *, EXCMD *);\n */\nint\nex_stop(SCR *sp, EXCMD *cmdp)\n{\n        int allowed;\n\n        /* For some strange reason, the force flag turns off autowrite. */\n        if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && file_aw(sp, FS_ALL))\n                return (1);\n\n        if (sp->gp->scr_suspend(sp, &allowed))\n                return (1);\n        if (!allowed)\n                ex_emsg(sp, NULL, EXM_NOSUSPEND);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_subst.c",
    "content": "/*      $OpenBSD: ex_subst.c,v 1.31 2023/06/23 15:06:45 millert Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n#define MAXIMUM(a, b)   (((a) > (b)) ? (a) : (b))\n\n#define SUB_FIRST       0x01            /* The 'r' flag isn't reasonable. */\n#define SUB_MUSTSETR    0x02            /* The 'r' flag is required.      */\n\nstatic int re_conv(SCR *, char **, size_t *, int *);\nstatic int re_sub(SCR *, char *, char **, size_t *, size_t *, regmatch_t [10]);\nstatic int re_tag_conv(SCR *, char **, size_t *, int *);\nstatic int s(SCR *, EXCMD *, char *, regex_t *, unsigned int);\n\n/*\n * ex_s --\n *      [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]]\n *\n *      Substitute on lines matching a pattern.\n *\n * PUBLIC: int ex_s(SCR *, EXCMD *);\n */\nint\nex_s(SCR *sp, EXCMD *cmdp)\n{\n        regex_t *re;\n        size_t blen, len;\n        unsigned int flags;\n        int delim;\n        char *bp, *ptrn, *rep, *p, *t;\n\n        /*\n         * Skip leading white space.\n         *\n         * !!!\n         * Historic vi allowed any non-alphanumeric to serve as the\n         * substitution command delimiter.\n         *\n         * !!!\n         * If the arguments are empty, it's the same as &, i.e. we\n         * repeat the last substitution.\n         */\n        if (cmdp->argc == 0)\n                goto subagain;\n        for (p = cmdp->argv[0]->bp,\n            len = cmdp->argv[0]->len; len > 0; --len, ++p) {\n                if (!isblank(*p))\n                        break;\n        }\n        if (len == 0)\nsubagain:       return (ex_subagain(sp, cmdp));\n\n        delim = *p++;\n        if (isalnum(delim) || delim == '\\\\')\n                return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR));\n\n        /*\n         * !!!\n         * The full-blown substitute command reset the remembered\n         * state of the 'c' and 'g' suffices.\n         */\n        sp->c_suffix = sp->g_suffix = 0;\n\n        /*\n         * Get the pattern string, toss escaping characters.\n         *\n         * !!!\n         * Historic vi accepted any of the following forms:\n         *\n         *      :s/abc/def/             change \"abc\" to \"def\"\n         *      :s/abc/def              change \"abc\" to \"def\"\n         *      :s/abc/                 delete \"abc\"\n         *      :s/abc                  delete \"abc\"\n         *\n         * QUOTING NOTE:\n         *\n         * Only toss an escaping character if it escapes a delimiter.\n         * This means that \"s/A/\\\\\\\\f\" replaces \"A\" with \"\\\\f\".  It\n         * would be nice to be more regular, i.e. for each layer of\n         * escaping a single escaping character is removed, but that's\n         * not how the historic vi worked.\n         */\n        for (ptrn = t = p;;) {\n                if (p[0] == '\\0' || p[0] == delim) {\n                        if (p[0] == delim)\n                                ++p;\n                        /*\n                         * !!!\n                         * NULL terminate the pattern string -- it's passed\n                         * to regcomp which doesn't understand anything else.\n                         */\n                        *t = '\\0';\n                        break;\n                }\n                if (p[0] == '\\\\') {\n                        if (p[1] == delim)\n                                ++p;\n                        else if (p[1] == '\\\\')\n                                *t++ = *p++;\n                }\n                *t++ = *p++;\n        }\n\n        /*\n         * If the pattern string is empty, use the last RE (not just the\n         * last substitution RE).\n         */\n        if (*ptrn == '\\0') {\n                if (sp->re == NULL) {\n                        ex_emsg(sp, NULL, EXM_NOPREVRE);\n                        return (1);\n                }\n\n                /* Re-compile the RE if necessary. */\n                if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,\n                    sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))\n                        return (1);\n                flags = 0;\n        } else {\n                /*\n                 * !!!\n                 * Compile the RE.  Historic practice is that substitutes set\n                 * the search direction as well as both substitute and search\n                 * RE's.  We compile the RE twice, as we don't want to bother\n                 * ref counting the pattern string and (opaque) structure.\n                 */\n                if (re_compile(sp, ptrn, t - ptrn,\n                    &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH))\n                        return (1);\n                if (re_compile(sp, ptrn, t - ptrn,\n                    &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST))\n                        return (1);\n\n                flags = SUB_FIRST;\n                sp->searchdir = FORWARD;\n        }\n        re = &sp->re_c;\n\n        /*\n         * Get the replacement string.\n         *\n         * The special character & (\\& if O_MAGIC not set) matches the\n         * entire RE.  No handling of & is required here, it's done by\n         * re_sub().\n         *\n         * The special character ~ (\\~ if O_MAGIC not set) inserts the\n         * previous replacement string into this replacement string.\n         * Count ~'s to figure out how much space we need.  We could\n         * special case nonexistent last patterns or whether or not\n         * O_MAGIC is set, but it's probably not worth the effort.\n         *\n         * QUOTING NOTE:\n         *\n         * Only toss an escaping character if it escapes a delimiter or\n         * if O_MAGIC is set and it escapes a tilde.\n         *\n         * !!!\n         * If the entire replacement pattern is \"%\", then use the last\n         * replacement pattern.  This semantic was added to vi in System\n         * V and then percolated elsewhere, presumably around the time\n         * that it was added to their version of ed(1).\n         */\n        if (p[0] == '\\0' || p[0] == delim) {\n                if (p[0] == delim)\n                        ++p;\n                free(sp->repl);\n                sp->repl = NULL;\n                sp->repl_len = 0;\n        } else if (p[0] == '%' && (p[1] == '\\0' || p[1] == delim))\n                p += p[1] == delim ? 2 : 1;\n        else {\n                for (rep = p, len = 0;\n                    p[0] != '\\0' && p[0] != delim; ++p, ++len)\n                        if (p[0] == '~')\n                                len += sp->repl_len;\n                GET_SPACE_RET(sp, bp, blen, len);\n                for (t = bp, len = 0, p = rep;;) {\n                        if (p[0] == '\\0' || p[0] == delim) {\n                                if (p[0] == delim)\n                                        ++p;\n                                break;\n                        }\n                        if (p[0] == '\\\\') {\n                                if (p[1] == delim)\n                                        ++p;\n                                else if (p[1] == '\\\\') {\n                                        *t++ = *p++;\n                                        ++len;\n                                } else if (p[1] == '~') {\n                                        ++p;\n                                        if (!O_ISSET(sp, O_MAGIC))\n                                                goto tilde;\n                                }\n                        } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) {\ntilde:                          ++p;\n                                memcpy(t, sp->repl, sp->repl_len);\n                                t += sp->repl_len;\n                                len += sp->repl_len;\n                                continue;\n                        }\n                        *t++ = *p++;\n                        ++len;\n                }\n                if ((sp->repl_len = len) != 0) {\n                        free(sp->repl);\n                        if ((sp->repl = malloc(len)) == NULL) {\n                                msgq(sp, M_SYSERR, NULL);\n                                FREE_SPACE(sp, bp, blen);\n                                return (1);\n                        }\n                        memcpy(sp->repl, bp, len);\n                }\n                FREE_SPACE(sp, bp, blen);\n        }\n        return (s(sp, cmdp, p, re, flags));\n}\n\n/*\n * ex_subagain --\n *      [line [,line]] & [cgr] [count] [#lp]]\n *\n *      Substitute using the last substitute RE and replacement pattern.\n *\n * PUBLIC: int ex_subagain(SCR *, EXCMD *);\n */\nint\nex_subagain(SCR *sp, EXCMD *cmdp)\n{\n        if (sp->subre == NULL) {\n                ex_emsg(sp, NULL, EXM_NOPREVRE);\n                return (1);\n        }\n        if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp,\n            sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST))\n                return (1);\n        return (s(sp,\n            cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0));\n}\n\n/*\n * ex_subtilde --\n *      [line [,line]] ~ [cgr] [count] [#lp]]\n *\n *      Substitute using the last RE and last substitute replacement pattern.\n *\n * PUBLIC: int ex_subtilde(SCR *, EXCMD *);\n */\nint\nex_subtilde(SCR *sp, EXCMD *cmdp)\n{\n        if (sp->re == NULL) {\n                ex_emsg(sp, NULL, EXM_NOPREVRE);\n                return (1);\n        }\n        if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,\n            sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))\n                return (1);\n        return (s(sp,\n            cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0));\n}\n\n/*\n * s --\n * Do the substitution.  This stuff is *really* tricky.  There are lots of\n * special cases, and general nastiness.  Don't mess with it unless you're\n * pretty confident.\n *\n * The nasty part of the substitution is what happens when the replacement\n * string contains newlines.  It's a bit tricky -- consider the information\n * that has to be retained for \"s/f\\(o\\)o/^M\\1^M\\1/\".  The solution here is\n * to build a set of newline offsets which we use to break the line up later,\n * when the replacement is done.  Don't change it unless you're *damned*\n * confident.\n */\n#define NEEDNEWLINE(sp) {                                               \\\n        if ((sp)->newl_len == (sp)->newl_cnt) {                         \\\n                (sp)->newl_len += 25;                                   \\\n                REALLOCARRAY((sp), (sp)->newl,                          \\\n                    (sp)->newl_len, sizeof(size_t));                    \\\n                if ((sp)->newl == NULL) {                               \\\n                        (sp)->newl_len = 0;                             \\\n                        return (1);                                     \\\n                }                                                       \\\n        }                                                               \\\n}\n\n#define BUILD(sp, l, len) {                                             \\\n        if (lbclen + (len) > lblen) {                                   \\\n                lblen += MAXIMUM(lbclen + (len), 256);                  \\\n                REALLOC((sp), lb, lblen);                               \\\n                if (lb == NULL) {                                       \\\n                        lbclen = 0;                                     \\\n                        return (1);                                     \\\n                }                                                       \\\n        }                                                               \\\n        memcpy(lb + lbclen, (l), (len));                                \\\n        lbclen += (len);                                                \\\n}\n\n#define NEEDSP(sp, len, pnt) {                                          \\\n        if (lbclen + (len) > lblen) {                                   \\\n                lblen += MAXIMUM(lbclen + (len), 256);                  \\\n                REALLOC((sp), lb, lblen);                               \\\n                if (lb == NULL) {                                       \\\n                        lbclen = 0;                                     \\\n                        return (1);                                     \\\n                }                                                       \\\n                (pnt) = lb + lbclen;                                    \\\n        }                                                               \\\n}\n\nstatic int\ns(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, unsigned int flags)\n{\n        EVENT ev;\n        MARK from, to;\n        TEXTH tiq;\n        recno_t elno, lno, slno;\n        regmatch_t match[10];\n        size_t blen, cnt, last, lbclen, lblen, len, llen;\n        size_t offset, saved_offset, scno;\n        int lflag, nflag, pflag, rflag;\n        int didsub, do_eol_match, eflags, nempty, eval;\n        int linechanged, matched, quit, rval;\n        unsigned long ul;\n        char *bp, *lb;\n\n        NEEDFILE(sp, cmdp);\n\n        slno = sp->lno;\n        scno = sp->cno;\n\n        /*\n         * !!!\n         * Historically, the 'g' and 'c' suffices were always toggled as flags,\n         * so \":s/A/B/\" was the same as \":s/A/B/ccgg\".  If O_EDCOMPATIBLE was\n         * not set, they were initialized to 0 for all substitute commands.  If\n         * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user\n         * specified substitute/replacement patterns (see ex_s()).\n         */\n        if (!O_ISSET(sp, O_EDCOMPATIBLE))\n                sp->c_suffix = sp->g_suffix = 0;\n\n        /*\n         * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but\n         * it only displayed the last change.  I'd disallow them, but they are\n         * useful in combination with the [v]global commands.  In the current\n         * model the problem is combining them with the 'c' flag -- the screen\n         * would have to flip back and forth between the confirm screen and the\n         * ex print screen, which would be pretty awful.  We do display all\n         * changes, though, for what that's worth.\n         *\n         * !!!\n         * Historic vi was fairly strict about the order of \"options\", the\n         * count, and \"flags\".  I'm somewhat fuzzy on the difference between\n         * options and flags, anyway, so this is a simpler approach, and we\n         * just take it them in whatever order the user gives them.  (The ex\n         * usage statement doesn't reflect this.)\n         */\n        lflag = nflag = pflag = rflag = 0;\n        if (s == NULL)\n                goto noargs;\n        for (lno = OOBLNO; *s != '\\0'; ++s)\n                switch (*s) {\n                case ' ':\n                case '\\t':\n                        continue;\n                case '+':\n                        ++cmdp->flagoff;\n                        break;\n                case '-':\n                        --cmdp->flagoff;\n                        break;\n                case '0': case '1': case '2': case '3': case '4':\n                case '5': case '6': case '7': case '8': case '9':\n                        if (lno != OOBLNO)\n                                goto usage;\n                        errno = 0;\n                        if ((ul = strtoul(s, &s, 10)) >= UINT_MAX)\n                                errno = ERANGE;\n                        if (*s == '\\0')         /* Loop increment correction. */\n                                --s;\n                        if (errno == ERANGE) {\n                                if (ul >= UINT_MAX)\n                                        msgq(sp, M_ERR, \"Count overflow\");\n                                else\n                                        msgq(sp, M_SYSERR, NULL);\n                                return (1);\n                        }\n                        lno = (recno_t)ul;\n                        /*\n                         * In historic vi, the count was inclusive from the\n                         * second address.\n                         */\n                        cmdp->addr1.lno = cmdp->addr2.lno;\n                        cmdp->addr2.lno += lno - 1;\n                        if (!db_exist(sp, cmdp->addr2.lno) &&\n                            db_last(sp, &cmdp->addr2.lno))\n                                return (1);\n                        break;\n                case '#':\n                        nflag = 1;\n                        break;\n                case 'c':\n                        sp->c_suffix = !sp->c_suffix;\n\n                        /* Ex text structure initialization. */\n                        if (F_ISSET(sp, SC_EX)) {\n                                memset(&tiq, 0, sizeof(TEXTH));\n                                TAILQ_INIT(&tiq);\n                        }\n                        break;\n                case 'g':\n                        sp->g_suffix = !sp->g_suffix;\n                        break;\n                case 'l':\n                        lflag = 1;\n                        break;\n                case 'p':\n                        pflag = 1;\n                        break;\n                case 'r':\n                        if (LF_ISSET(SUB_FIRST)) {\n                                msgq(sp, M_ERR,\n                    \"Regular expression specified; r flag meaningless\");\n                                return (1);\n                        }\n                        if (!F_ISSET(sp, SC_RE_SEARCH)) {\n                                ex_emsg(sp, NULL, EXM_NOPREVRE);\n                                return (1);\n                        }\n                        rflag = 1;\n                        re = &sp->re_c;\n                        break;\n                default:\n                        goto usage;\n                }\n\n        if (*s != '\\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) {\nusage:          ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                return (1);\n        }\n\nnoargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) {\n                msgq(sp, M_ERR,\n\"The #, l and p flags may not be combined with the c flag in vi mode\");\n                return (1);\n        }\n\n        /*\n         * bp:          if interactive, line cache\n         * blen:        if interactive, line cache length\n         * lb:          build buffer pointer.\n         * lbclen:      current length of built buffer.\n         * lblen;       length of build buffer.\n         */\n        bp = lb = NULL;\n        blen = lbclen = lblen = 0;\n\n        /* For each line... */\n        for (matched = quit = 0, lno = cmdp->addr1.lno,\n            elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) {\n\n                /* Someone's unhappy, time to stop. */\n                if (INTERRUPTED(sp))\n                        break;\n\n                /* Get the line. */\n                if (db_get(sp, lno, DBG_FATAL, &s, &llen))\n                        goto err;\n\n                /*\n                 * Make a local copy if doing confirmation -- when calling\n                 * the confirm routine we're likely to lose the cached copy.\n                 */\n                if (sp->c_suffix) {\n                        if (bp == NULL) {\n                                GET_SPACE_RET(sp, bp, blen, llen);\n                        } else\n                                ADD_SPACE_RET(sp, bp, blen, llen);\n                        memcpy(bp, s, llen);\n                        s = bp;\n                }\n\n                /* Start searching from the beginning. */\n                offset = 0;\n                len = llen;\n\n                /* Reset the build buffer offset. */\n                lbclen = 0;\n\n                /* Reset empty match test variable. */\n                nempty = -1;\n\n                /*\n                 * We don't want to have to do a setline if the line didn't\n                 * change -- keep track of whether or not this line changed.\n                 * If doing confirmations, don't want to keep setting the\n                 * line if change is refused -- keep track of substitutions.\n                 */\n                didsub = linechanged = 0;\n                (void)didsub;\n\n                /* New line, do an EOL match. */\n                do_eol_match = 1;\n\n                /* It's not NULL terminated, but we pretend it is. */\n                eflags = REG_STARTEND;\n\n                /* The search area is from s + offset to the EOL.  */\nnextmatch:      match[0].rm_so = offset;\n                match[0].rm_eo = llen;\n\n                /* Get the next match. */\n                eval = regexec(re, (char *)s, 10, match, eflags);\n\n                /*\n                 * There wasn't a match or if there was an error, deal with\n                 * it.  If there was a previous match in this line, resolve\n                 * the changes into the database.  Otherwise, just move on.\n                 */\n                if (eval == REG_NOMATCH)\n                        goto endmatch;\n                if (eval != 0) {\n                        re_error(sp, eval, re);\n                        goto err;\n                }\n                matched = 1;\n\n                /* Only the first search can match an anchored expression. */\n                eflags |= REG_NOTBOL;\n\n                /*\n                 * !!!\n                 * It's possible to match 0-length strings -- for example, the\n                 * command s;a*;X;, when matched against the string \"aabb\" will\n                 * result in \"XbXbX\", i.e. the matches are \"aa\", the space\n                 * between the b's and the space between the b's and the end of\n                 * the string.  There is a similar space between the beginning\n                 * of the string and the a's.  The rule that we use (because vi\n                 * historically used it) is that any 0-length match, occurring\n                 * immediately after a match, is ignored.  Otherwise, the above\n                 * example would have resulted in \"XXbXbX\".  Another example is\n                 * incorrectly using \" *\" to replace groups of spaces with one\n                 * space.\n                 *\n                 * If the match is empty and at the same place as the end of the\n                 * previous match, ignore the match and move forward.  If\n                 * there's no more characters in the string, we were\n                 * attempting to match after the last character, so quit.\n                 */\n                if (match[0].rm_so == nempty && match[0].rm_eo == nempty) {\n                        nempty = -1;\n                        if (len == 0)\n                                goto endmatch;\n                        BUILD(sp, s + offset, 1)\n                        ++offset;\n                        --len;\n                        goto nextmatch;\n                }\n\n                /* Confirm change. */\n                if (sp->c_suffix) {\n                        /*\n                         * Set the cursor position for confirmation.  Note,\n                         * if we matched on a '$', the cursor may be past\n                         * the end of line.\n                         */\n                        from.lno = to.lno = lno;\n                        from.cno = match[0].rm_so;\n                        to.cno = match[0].rm_eo;\n                        /*\n                         * Both ex and vi have to correct for a change before\n                         * the first character in the line.\n                         */\n                        if (llen == 0)\n                                from.cno = to.cno = 0;\n                        if (F_ISSET(sp, SC_VI)) {\n                                /*\n                                 * Only vi has to correct for a change after\n                                 * the last character in the line.\n                                 *\n                                 * XXX\n                                 * It would be nice to change the vi code so\n                                 * that we could display a cursor past EOL.\n                                 */\n                                if (to.cno >= llen)\n                                        to.cno = llen - 1;\n                                if (from.cno >= llen)\n                                        from.cno = llen - 1;\n\n                                sp->lno = from.lno;\n                                sp->cno = from.cno;\n                                if (vs_refresh(sp, 1))\n                                        goto err;\n\n                                vs_update(sp, \"Confirm change? [n]\", NULL);\n\n                                if (v_event_get(sp, &ev, 0, 0))\n                                        goto err;\n                                switch (ev.e_event) {\n                                case E_CHARACTER:\n                                        break;\n                                case E_EOF:\n                                case E_ERR:\n                                case E_INTERRUPT:\n                                        goto lquit;\n                                default:\n                                        v_event_err(sp, &ev);\n                                        goto lquit;\n                                }\n                        } else {\n                                const int flags =\n                                    O_ISSET(sp, O_NUMBER) ? E_C_HASH : 0;\n                                if (ex_print(sp, cmdp, &from, &to, flags) ||\n                                    ex_scprint(sp, &from, &to))\n                                        goto lquit;\n                                if (ex_txt(sp, &tiq, 0, TXT_CR))\n                                        goto err;\n                                ev.e_c = TAILQ_FIRST(&tiq)->lb[0];\n                        }\n\n                        switch (ev.e_c) {\n                        case CH_YES:\n                                break;\n                        default:\n                        case CH_NO:\n                                didsub = 0;\n                                BUILD(sp, s + offset, match[0].rm_eo - offset);\n                                goto skip;\n                        case CH_QUIT:\n                                /* Set the quit/interrupted flags. */\nlquit:                          quit = 1;\n                                F_SET(sp->gp, G_INTERRUPTED);\n\n                                /*\n                                 * Resolve any changes, then return to (and\n                                 * exit from) the main loop.\n                                 */\n                                goto endmatch;\n                        }\n                }\n\n                /*\n                 * Set the cursor to the last position changed, converting\n                 * from 1-based to 0-based.\n                 */\n                sp->lno = lno;\n                sp->cno = match[0].rm_so;\n\n                /* Copy the bytes before the match into the build buffer. */\n                BUILD(sp, s + offset, match[0].rm_so - offset);\n\n                /* Substitute the matching bytes. */\n                didsub = 1;\n                if (re_sub(sp, s, &lb, &lbclen, &lblen, match))\n                        goto err;\n\n                /* Set the change flag so we know this line was modified. */\n                linechanged = 1;\n\n                /* Move past the matched bytes. */\nskip:           offset = match[0].rm_eo;\n                len = llen - match[0].rm_eo;\n\n                /* A match cannot be followed by an empty pattern. */\n                nempty = match[0].rm_eo;\n\n                /*\n                 * If doing a global change with confirmation, we have to\n                 * update the screen.  The basic idea is to store the line\n                 * so the screen update routines can find it, and restart.\n                 */\n                if (didsub && sp->c_suffix && sp->g_suffix) {\n                        /*\n                         * The new search offset will be the end of the\n                         * modified line.\n                         */\n                        saved_offset = lbclen;\n\n                        /* Copy the rest of the line. */\n                        if (len)\n                                BUILD(sp, s + offset, len)\n\n                        /* Set the new offset. */\n                        offset = saved_offset;\n\n                        /* Store inserted lines, adjusting the build buffer. */\n                        last = 0;\n                        if (sp->newl_cnt) {\n                                for (cnt = 0;\n                                    cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {\n                                        if (db_insert(sp, lno,\n                                            lb + last, sp->newl[cnt] - last))\n                                                goto err;\n                                        last = sp->newl[cnt] + 1;\n                                        ++sp->rptlines[L_ADDED];\n                                }\n                                lbclen -= last;\n                                offset -= last;\n                                sp->newl_cnt = 0;\n                        }\n\n                        /* Store and retrieve the line. */\n                        if (db_set(sp, lno, lb + last, lbclen))\n                                goto err;\n                        if (db_get(sp, lno, DBG_FATAL, &s, &llen))\n                                goto err;\n                        ADD_SPACE_RET(sp, bp, blen, llen)\n                        memcpy(bp, s, llen);\n                        s = bp;\n                        len = llen - offset;\n\n                        /* Restart the build. */\n                        lbclen = 0;\n                        BUILD(sp, s, offset);\n\n                        /*\n                         * If we haven't already done the after-the-string\n                         * match, do one.  Set REG_NOTEOL so the '$' pattern\n                         * only matches once.\n                         */\n                        if (!do_eol_match)\n                                goto endmatch;\n                        if (offset == len) {\n                                do_eol_match = 0;\n                                eflags |= REG_NOTEOL;\n                        }\n                        goto nextmatch;\n                }\n\n                /*\n                 * If it's a global:\n                 *\n                 * If at the end of the string, do a test for the after\n                 * the string match.  Set REG_NOTEOL so the '$' pattern\n                 * only matches once.\n                 */\n                if (sp->g_suffix && do_eol_match) {\n                        if (len == 0) {\n                                do_eol_match = 0;\n                                eflags |= REG_NOTEOL;\n                        }\n                        goto nextmatch;\n                }\n\nendmatch:       if (!linechanged)\n                        continue;\n\n                /* Copy any remaining bytes into the build buffer. */\n                if (len)\n                        BUILD(sp, s + offset, len)\n\n                /* Store inserted lines, adjusting the build buffer. */\n                last = 0;\n                if (sp->newl_cnt) {\n                        for (cnt = 0;\n                            cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {\n                                if (db_insert(sp,\n                                    lno, lb + last, sp->newl[cnt] - last))\n                                        goto err;\n                                last = sp->newl[cnt] + 1;\n                                ++sp->rptlines[L_ADDED];\n                        }\n                        lbclen -= last;\n                        sp->newl_cnt = 0;\n                }\n\n                /* Store the changed line. */\n                if (db_set(sp, lno, lb + last, lbclen))\n                        goto err;\n\n                /* Update changed line counter. */\n                if (sp->rptlchange != lno) {\n                        sp->rptlchange = lno;\n                        ++sp->rptlines[L_CHANGED];\n                }\n\n                /*\n                 * !!!\n                 * Display as necessary.  Historic practice is to only\n                 * display the last line of a line split into multiple\n                 * lines.\n                 */\n                if (lflag || nflag || pflag) {\n                        from.lno = to.lno = lno;\n                        from.cno = to.cno = 0;\n                        if (lflag)\n                                (void)ex_print(sp, cmdp, &from, &to, E_C_LIST);\n                        if (nflag)\n                                (void)ex_print(sp, cmdp, &from, &to, E_C_HASH);\n                        if (pflag)\n                                (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT);\n                }\n        }\n\n        /*\n         * !!!\n         * Historically, vi attempted to leave the cursor at the same place if\n         * the substitution was done at the current cursor position.  Otherwise\n         * it moved it to the first non-blank of the last line changed.  There\n         * were some problems: for example, :s/$/foo/ with the cursor on the\n         * last character of the line left the cursor on the last character, or\n         * the & command with multiple occurrences of the matching string in the\n         * line usually left the cursor in a fairly random position.\n         *\n         * We try to do the same thing, with the exception that if the user is\n         * doing substitution with confirmation, we move to the last line about\n         * which the user was consulted, as opposed to the last line that they\n         * actually changed.  This prevents a screen flash if the user doesn't\n         * change many of the possible lines.\n         */\n        if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) {\n                sp->cno = 0;\n                (void)nonblank(sp, sp->lno, &sp->cno);\n        }\n\n        /*\n         * If not in a global command, and nothing matched, say so.\n         * Else, if none of the lines displayed, put something up.\n         */\n        rval = 0;\n        if (!matched) {\n                if (!F_ISSET(sp, SC_EX_GLOBAL)) {\n                        msgq(sp, M_ERR, \"No match found\");\n                        goto err;\n                }\n        } else if (!lflag && !nflag && !pflag)\n                F_SET(cmdp, E_AUTOPRINT);\n\n        if (0) {\nerr:            rval = 1;\n        }\n\n        if (bp != NULL)\n                FREE_SPACE(sp, bp, blen);\n        free(lb);\n        return (rval);\n}\n\n/*\n * re_compile --\n *      Compile the RE.\n *\n * PUBLIC: int re_compile(SCR *,\n * PUBLIC:     char *, size_t, char **, size_t *, regex_t *, unsigned int);\n */\nint\nre_compile(SCR *sp, char *ptrn, size_t plen, char **ptrnp, size_t *lenp,\n    regex_t *rep, unsigned int flags)\n{\n        size_t len;\n        int reflags, replaced, rval;\n        char *p;\n\n        /* Set RE flags. */\n        reflags = 0;\n        if (!LF_ISSET(RE_C_TAG)) {\n                if (O_ISSET(sp, O_EXTENDED))\n                        reflags |= REG_EXTENDED;\n                if (O_ISSET(sp, O_IGNORECASE))\n                        reflags |= REG_ICASE;\n                if (O_ISSET(sp, O_ICLOWER)) {\n                        for (p = ptrn, len = plen; len > 0; ++p, --len)\n                                if (isupper(*p))\n                                        break;\n                        if (len == 0)\n                                reflags |= REG_ICASE;\n                }\n        }\n\n        /* If we're replacing a saved value, clear the old one. */\n        if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) {\n                regfree(&sp->re_c);\n                F_CLR(sp, SC_RE_SEARCH);\n        }\n        if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) {\n                regfree(&sp->subre_c);\n                F_CLR(sp, SC_RE_SUBST);\n        }\n\n        /*\n         * If we're saving the string, it's a pattern we haven't seen before,\n         * so convert the vi-style RE's to POSIX 1003.2 RE's.  Save a copy for\n         * later recompilation.   Free any previously saved value.\n         */\n        if (ptrnp != NULL) {\n                if (LF_ISSET(RE_C_TAG)) {\n                        if (re_tag_conv(sp, &ptrn, &plen, &replaced))\n                                return (1);\n                } else\n                        if (re_conv(sp, &ptrn, &plen, &replaced))\n                                return (1);\n\n                /* Discard previous pattern. */\n                free(*ptrnp);\n                *ptrnp = NULL;\n                if (lenp != NULL)\n                        *lenp = plen;\n\n                /*\n                 * Copy the string into allocated memory.\n                 *\n                 * XXX\n                 * Regcomp isn't 8-bit clean, so the pattern is NULL-terminated\n                 * for now.  There's just no other solution.\n                 */\n                MALLOC(sp, *ptrnp, plen + 1);\n                if (*ptrnp != NULL) {\n                        memcpy(*ptrnp, ptrn, plen);\n                        (*ptrnp)[plen] = '\\0';\n                }\n\n                /* Free up conversion-routine-allocated memory. */\n                if (replaced)\n                        FREE_SPACE(sp, ptrn, 0);\n\n                if (*ptrnp == NULL)\n                        return (1);\n\n                ptrn = *ptrnp;\n        }\n\n        /*\n         * XXX\n         * Regcomp isn't 8-bit clean, so we just lost if the pattern\n         * contained a NULL.  Bummer!\n         */\n        if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) {\n                if (!LF_ISSET(RE_C_SILENT))\n                        re_error(sp, rval, rep);\n                return (1);\n        }\n\n        if (LF_ISSET(RE_C_SEARCH))\n                F_SET(sp, SC_RE_SEARCH);\n        if (LF_ISSET(RE_C_SUBST))\n                F_SET(sp, SC_RE_SUBST);\n\n        return (0);\n}\n\n/*\n * re_conv --\n *      Convert vi's regular expressions into something that the\n *      the POSIX 1003.2 RE functions can handle.\n *\n * There are two conversions we make to make vi's RE's (specifically\n * the global, search, and substitute patterns) work with POSIX RE's.\n * We assume that \\<ptrn\\> does \"word\" searches, which is non-standard\n * but supported by most regexp libraries..\n *\n * 1: If O_MAGIC is not set, strip backslashes from the magic character\n *    set (.[*~) that have them, and add them to the ones that don't.\n * 2: If O_MAGIC is not set, the string \"\\~\" is replaced with the text\n *    from the last substitute command's replacement string.  If O_MAGIC\n *    is set, it's the string \"~\".\n *\n * !!!/XXX\n * This doesn't exactly match the historic behavior of vi because we do\n * the ~ substitution before calling the RE engine, so magic characters\n * in the replacement string will be expanded by the RE engine, and they\n * weren't historically.  It's a bug.\n */\nstatic int\nre_conv(SCR *sp, char **ptrnp, size_t *plenp, int *replacedp)\n{\n        size_t blen, len, needlen;\n        int magic;\n        char *bp, *p, *t;\n\n        /*\n         * First pass through, we figure out how much space we'll need.\n         * We do it in two passes, on the grounds that most of the time\n         * the user is doing a search and won't have magic characters.\n         * That way we can skip most of the memory allocation and copies.\n         */\n        magic = 0;\n        for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len)\n                switch (*p) {\n                case '\\\\':\n                        if (len > 1) {\n                                --len;\n                                switch (*++p) {\n                                case '~':\n                                        if (!O_ISSET(sp, O_MAGIC)) {\n                                                magic = 1;\n                                                needlen += sp->repl_len;\n                                        }\n                                        break;\n                                case '.':\n                                case '[':\n                                case '*':\n                                        if (!O_ISSET(sp, O_MAGIC)) {\n                                                magic = 1;\n                                                needlen += 1;\n                                        }\n                                        break;\n                                default:\n                                        needlen += 2;\n                                }\n                        } else\n                                needlen += 1;\n                        break;\n                case '~':\n                        if (O_ISSET(sp, O_MAGIC)) {\n                                magic = 1;\n                                needlen += sp->repl_len;\n                        }\n                        break;\n                case '.':\n                case '[':\n                case '*':\n                        if (!O_ISSET(sp, O_MAGIC)) {\n                                magic = 1;\n                                needlen += 2;\n                        }\n                        break;\n                default:\n                        needlen += 1;\n                        break;\n                }\n\n        if (!magic) {\n                *replacedp = 0;\n                return (0);\n        }\n\n        /* Get enough memory to hold the final pattern. */\n        *replacedp = 1;\n        GET_SPACE_RET(sp, bp, blen, needlen);\n\n        for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len)\n                switch (*p) {\n                case '\\\\':\n                        if (len > 1) {\n                                --len;\n                                switch (*++p) {\n                                case '~':\n                                        if (O_ISSET(sp, O_MAGIC))\n                                                *t++ = '~';\n                                        else {\n                                                memcpy(t,\n                                                    sp->repl, sp->repl_len);\n                                                t += sp->repl_len;\n                                        }\n                                        break;\n                                case '.':\n                                case '[':\n                                case '*':\n                                        if (O_ISSET(sp, O_MAGIC))\n                                                *t++ = '\\\\';\n                                        *t++ = *p;\n                                        break;\n                                default:\n                                        *t++ = '\\\\';\n                                        *t++ = *p;\n                                }\n                        } else\n                                *t++ = '\\\\';\n                        break;\n                case '~':\n                        if (O_ISSET(sp, O_MAGIC)) {\n                                memcpy(t, sp->repl, sp->repl_len);\n                                t += sp->repl_len;\n                        } else\n                                *t++ = '~';\n                        break;\n                case '.':\n                case '[':\n                case '*':\n                        if (!O_ISSET(sp, O_MAGIC))\n                                *t++ = '\\\\';\n                        *t++ = *p;\n                        break;\n                default:\n                        *t++ = *p;\n                        break;\n                }\n\n        *ptrnp = bp;\n        *plenp = t - bp;\n        return (0);\n}\n\n/*\n * re_tag_conv --\n *      Convert a tags search path into something that the POSIX\n *      1003.2 RE functions can handle.\n */\nstatic int\nre_tag_conv(SCR *sp, char **ptrnp, size_t *plenp, int *replacedp)\n{\n        size_t blen, len;\n        int lastdollar;\n        char *bp, *p, *t;\n\n        len = *plenp;\n\n        /* Max memory usage is 2 times the length of the string. */\n        *replacedp = 1;\n        GET_SPACE_RET(sp, bp, blen, len * 2);\n\n        p = *ptrnp;\n        t = bp;\n\n        /* If the last character is a '/' or '?', we just strip it. */\n        if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?'))\n                --len;\n\n        /* If the next-to-last or last character is a '$', it's magic. */\n        if (len > 0 && p[len - 1] == '$') {\n                --len;\n                lastdollar = 1;\n        } else\n                lastdollar = 0;\n\n        /* If the first character is a '/' or '?', we just strip it. */\n        if (len > 0 && (p[0] == '/' || p[0] == '?')) {\n                ++p;\n                --len;\n        }\n\n        /* If the first or second character is a '^', it's magic. */\n        if (p[0] == '^') {\n                *t++ = *p++;\n                --len;\n        }\n\n        /*\n         * Escape every other magic character we can find, meanwhile stripping\n         * the backslashes ctags inserts when escaping the search delimiter\n         * characters.\n         */\n        for (; len > 0; --len) {\n                if (p[0] == '\\\\' && (p[1] == '/' || p[1] == '?')) {\n                        ++p;\n                        if (len > 1) {\n                                --len;\n                        }\n                } else if (strchr(\"^.[]$*\", p[0]))\n                        *t++ = '\\\\';\n                *t++ = *p++;\n                if (len == 0)\n                        break;\n        }\n        if (lastdollar)\n                *t++ = '$';\n\n        *ptrnp = bp;\n        *plenp = t - bp;\n        return (0);\n}\n\n/*\n * re_error --\n *      Report a regular expression error.\n *\n * PUBLIC: void re_error(SCR *, int, regex_t *);\n */\nvoid\nre_error(SCR *sp, int errcode, regex_t *preg)\n{\n        size_t s;\n        char *oe;\n\n        s = regerror(errcode, preg, \"\", 0);\n        if ((oe = malloc(s)) == NULL)\n                msgq(sp, M_SYSERR, NULL);\n        else {\n                (void)regerror(errcode, preg, oe, s);\n                msgq(sp, M_ERR, \"RE error: %s\", oe);\n                free(oe);\n        }\n}\n\n/*\n * re_sub --\n *      Do the substitution for a regular expression.\n */\nstatic int\nre_sub(SCR *sp, char *ip, char **lbp, size_t *lbclenp, size_t *lblenp,\n    regmatch_t match[10])\n{\n        enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv;\n        size_t lbclen, lblen;           /* Local copies. */\n        size_t mlen;                    /* Match length. */\n        size_t rpl;                     /* Remaining replacement length. */\n        char *rp;                       /* Replacement pointer. */\n        int ch;\n        int no;                         /* Match replacement offset. */\n        char *p, *t;                    /* Buffer pointers. */\n        char *lb;                       /* Local copies. */\n\n        lb = *lbp;                      /* Get local copies. */\n        lbclen = *lbclenp;\n        lblen = *lblenp;\n\n        /*\n         * QUOTING NOTE:\n         *\n         * There are some special sequences that vi provides in the\n         * replacement patterns.\n         *       & string the RE matched (\\& if nomagic set)\n         *      \\# n-th regular subexpression\n         *      \\E end \\U, \\L conversion\n         *      \\e end \\U, \\L conversion\n         *      \\l convert the next character to lower-case\n         *      \\L convert to lower-case, until \\E, \\e, or end of replacement\n         *      \\u convert the next character to upper-case\n         *      \\U convert to upper-case, until \\E, \\e, or end of replacement\n         *\n         * Otherwise, since this is the lowest level of replacement, discard\n         * all escaping characters.  This (hopefully) matches historic practice.\n         */\n#define OUTCH(ch, nltrans) {                                            \\\n        CHAR_T __ch = (ch);                                             \\\n        unsigned int __value = KEY_VAL(sp, __ch);                       \\\n        if ((nltrans) && (__value == K_CR || __value == K_NL)) {        \\\n                NEEDNEWLINE(sp);                                        \\\n                sp->newl[sp->newl_cnt++] = lbclen;                      \\\n        } else if (conv != C_NOTSET) {                                  \\\n                switch (conv) {                                         \\\n                case C_ONELOWER:                                        \\\n                        conv = C_NOTSET;                                \\\n                        /* FALLTHROUGH */                               \\\n                case C_LOWER:                                           \\\n                        if (isupper(__ch))                              \\\n                                __ch = tolower(__ch);                   \\\n                        break;                                          \\\n                case C_ONEUPPER:                                        \\\n                        conv = C_NOTSET;                                \\\n                        /* FALLTHROUGH */                               \\\n                case C_UPPER:                                           \\\n                        if (islower(__ch))                              \\\n                                __ch = toupper(__ch);                   \\\n                        break;                                          \\\n                default:                                                \\\n                        abort();                                        \\\n                }                                                       \\\n        }                                                               \\\n        NEEDSP(sp, 1, p);                                               \\\n        *p++ = __ch;                                                    \\\n        ++lbclen;                                                       \\\n}\n        conv = C_NOTSET;\n        for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) {\n                switch (ch = *rp++) {\n                case '&':\n                        if (O_ISSET(sp, O_MAGIC)) {\n                                no = 0;\n                                goto subzero;\n                        }\n                        break;\n                case '\\\\':\n                        if (rpl == 0)\n                                break;\n                        --rpl;\n                        switch (ch = *rp) {\n                        case '&':\n                                ++rp;\n                                if (!O_ISSET(sp, O_MAGIC)) {\n                                        no = 0;\n                                        goto subzero;\n                                }\n                                break;\n                        case '0': case '1': case '2': case '3': case '4':\n                        case '5': case '6': case '7': case '8': case '9':\n                                no = *rp++ - '0';\nsubzero:                        if (match[no].rm_so == -1 ||\n                                    match[no].rm_eo == -1)\n                                        break;\n                                mlen = match[no].rm_eo - match[no].rm_so;\n                                for (t = ip + match[no].rm_so; mlen--; ++t)\n                                        OUTCH(*t, 0);\n                                continue;\n                        case 'e':\n                        case 'E':\n                                ++rp;\n                                conv = C_NOTSET;\n                                continue;\n                        case 'l':\n                                ++rp;\n                                conv = C_ONELOWER;\n                                continue;\n                        case 'L':\n                                ++rp;\n                                conv = C_LOWER;\n                                continue;\n                        case 'u':\n                                ++rp;\n                                conv = C_ONEUPPER;\n                                continue;\n                        case 'U':\n                                ++rp;\n                                conv = C_UPPER;\n                                continue;\n                        default:\n                                ++rp;\n                                break;\n                        }\n                }\n                OUTCH(ch, 1);\n        }\n\n        *lbp = lb;                      /* Update caller's information. */\n        *lbclenp = lbclen;\n        *lblenp = lblen;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_tag.c",
    "content": "/*      $OpenBSD: ex_tag.c,v 1.26 2021/10/24 21:24:17 deraadt Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * This code is derived from software contributed to Berkeley by\n * David Hitz of Auspex Systems, Inc.\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/mman.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n\n#ifdef __solaris__\n# undef _STRICT_STDC\n# undef __EXTENSIONS__\n# define __EXTENSIONS__\n#endif /* ifdef __solaris__ */\n\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n#include \"tag.h\"\n\n#undef open\n\nstatic char     *binary_search(char *, char *, char *);\nstatic int       compare(char *, char *, char *);\nstatic void      ctag_file(SCR *, TAGF *, char *, char **, size_t *);\nstatic int       ctag_search(SCR *, char *, size_t, char *);\nstatic int       ctag_sfile(SCR *, TAGF *, TAGQ *, char *);\nstatic TAGQ     *ctag_slist(SCR *, char *);\nstatic char     *linear_search(char *, char *, char *, long);\nstatic int       tag_copy(SCR *, TAG *, TAG **);\nstatic int       tag_pop(SCR *, TAGQ *, int);\nstatic int       tagf_copy(SCR *, TAGF *, TAGF **);\nstatic int       tagf_free(SCR *, TAGF *);\nstatic int       tagq_copy(SCR *, TAGQ *, TAGQ **);\n\n/*\n * ex_tag_first --\n *      The tag code can be entered from main, e.g., \"vi -t tag\".\n *\n * PUBLIC: int ex_tag_first(SCR *, char *);\n */\nint\nex_tag_first(SCR *sp, char *tagarg)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        /* Build an argument for the ex :tag command. */\n        ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap);\n        ex_cadd(&cmd, &a, tagarg, strlen(tagarg));\n\n        /*\n         * XXX\n         * Historic vi went ahead and created a temporary file when it failed\n         * to find the tag.  We match historic practice, but don't distinguish\n         * between real error and failure to find the tag.\n         */\n        if (ex_tag_push(sp, &cmd))\n                return (0);\n\n        /* Display tags in the center of the screen. */\n        F_CLR(sp, SC_SCR_TOP);\n        F_SET(sp, SC_SCR_CENTER);\n\n        return (0);\n}\n\n/*\n * ex_tag_push -- ^]\n *                :tag[!] [string]\n *\n * Enter a new TAGQ context based on a ctag string.\n *\n * PUBLIC: int ex_tag_push(SCR *, EXCMD *);\n */\nint\nex_tag_push(SCR *sp, EXCMD *cmdp)\n{\n        EX_PRIVATE *exp;\n        FREF *frp;\n        TAG *rtp;\n        TAGQ *rtqp, *tqp;\n        recno_t lno;\n        size_t cno;\n        long tl;\n        int force, istmp;\n\n        exp = EXP(sp);\n        switch (cmdp->argc) {\n        case 1:\n                free(exp->tag_last);\n\n                if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {\n                        msgq(sp, M_SYSERR, NULL);\n                        return (1);\n                }\n\n                /* Taglength may limit the number of characters. */\n                if ((tl =\n                    O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl)\n                        exp->tag_last[tl] = '\\0';\n                break;\n        case 0:\n                if (exp->tag_last == NULL) {\n                        msgq(sp, M_ERR, \"No previous tag entered\");\n                        return (1);\n                }\n                break;\n        default:\n                abort();\n        }\n\n        /* Get the tag information. */\n        if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)\n                return (1);\n\n        /*\n         * Allocate all necessary memory before swapping screens.  Initialize\n         * flags so we know what to free.\n         */\n        rtp = NULL;\n        rtqp = NULL;\n        if (TAILQ_EMPTY(&exp->tq)) {\n                /* Initialize the `local context' tag queue structure. */\n                CALLOC_GOTO(sp, rtqp, 1, sizeof(TAGQ));\n                TAILQ_INIT(&rtqp->tagq);\n\n                /* Initialize and link in its tag structure. */\n                CALLOC_GOTO(sp, rtp, 1, sizeof(TAG));\n                TAILQ_INSERT_HEAD(&rtqp->tagq, rtp, q);\n                rtqp->current = rtp;\n        }\n\n        /*\n         * Stick the current context information in a convenient place, we're\n         * about to lose it.  Note, if we're called on editor startup, there\n         * will be no FREF structure.\n         */\n        frp = sp->frp;\n        lno = sp->lno;\n        cno = sp->cno;\n        istmp = frp == NULL ||\n            (F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN));\n\n        /* Try to switch to the tag. */\n        force = FL_ISSET(cmdp->iflags, E_C_FORCE);\n        if (F_ISSET(cmdp, E_NEWSCREEN)) {\n                if (ex_tag_Nswitch(sp, TAILQ_FIRST(&tqp->tagq), force))\n                        goto err;\n\n                /* Everything else gets done in the new screen. */\n                sp = sp->nextdisp;\n                exp = EXP(sp);\n        } else\n                if (ex_tag_nswitch(sp, TAILQ_FIRST(&tqp->tagq), force))\n                        goto err;\n\n        /*\n         * If this is the first tag, put a `current location' queue entry\n         * in place, so we can pop all the way back to the current mark.\n         * Note, it doesn't point to much of anything, it's a placeholder.\n         */\n        if (TAILQ_EMPTY(&exp->tq)) {\n                TAILQ_INSERT_HEAD(&exp->tq, rtqp, q);\n        } else\n                rtqp = TAILQ_FIRST(&exp->tq);\n\n        /* Link the new TAGQ structure into place. */\n        TAILQ_INSERT_HEAD(&exp->tq, tqp, q);\n\n        (void)ctag_search(sp,\n            tqp->current->search, tqp->current->slen, tqp->tag);\n\n        /*\n         * Move the current context from the temporary save area into the\n         * right structure.\n         *\n         * If we were in a temporary file, we don't have a context to which\n         * we can return, so just make it be the same as what we're moving\n         * to.  It will be a little odd that ^T doesn't change anything, but\n         * I don't think it's a big deal.\n         */\n        if (istmp) {\n                rtqp->current->frp = sp->frp;\n                rtqp->current->lno = sp->lno;\n                rtqp->current->cno = sp->cno;\n        } else {\n                rtqp->current->frp = frp;\n                rtqp->current->lno = lno;\n                rtqp->current->cno = cno;\n        }\n        return (0);\n\nerr:\nalloc_err:\n        free(rtqp);\n        free(rtp);\n        tagq_free(sp, tqp);\n        return (1);\n}\n\n/*\n * ex_tag_next --\n *      Switch context to the next TAG.\n *\n * PUBLIC: int ex_tag_next(SCR *, EXCMD *);\n */\nint\nex_tag_next(SCR *sp, EXCMD *cmdp)\n{\n        EX_PRIVATE *exp;\n        TAG *tp;\n        TAGQ *tqp;\n\n        exp = EXP(sp);\n        if ((tqp = TAILQ_FIRST(&exp->tq)) == NULL) {\n                tag_msg(sp, TAG_EMPTY, NULL);\n                return (1);\n        }\n        if ((tp = TAILQ_NEXT(tqp->current, q)) == NULL) {\n                msgq(sp, M_ERR, \"Already at the last tag of this group\");\n                return (1);\n        }\n        if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))\n                return (1);\n        tqp->current = tp;\n\n        (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);\n\n        return (0);\n}\n\n/*\n * ex_tag_prev --\n *      Switch context to the next TAG.\n *\n * PUBLIC: int ex_tag_prev(SCR *, EXCMD *);\n */\nint\nex_tag_prev(SCR *sp, EXCMD *cmdp)\n{\n        EX_PRIVATE *exp;\n        TAG *tp;\n        TAGQ *tqp;\n\n        exp = EXP(sp);\n        if ((tqp = TAILQ_FIRST(&exp->tq)) == NULL) {\n                tag_msg(sp, TAG_EMPTY, NULL);\n                return (0);\n        }\n        if ((tp = TAILQ_PREV(tqp->current, _tagqh, q)) == NULL) {\n                msgq(sp, M_ERR, \"Already at the first tag of this group\");\n                return (1);\n        }\n        if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))\n                return (1);\n        tqp->current = tp;\n\n        (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);\n\n        return (0);\n}\n\n/*\n * ex_tag_nswitch --\n *      Switch context to the specified TAG.\n *\n * PUBLIC: int ex_tag_nswitch(SCR *, TAG *, int);\n */\nint\nex_tag_nswitch(SCR *sp, TAG *tp, int force)\n{\n        /* Get a file structure. */\n        if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)\n                return (1);\n\n        /* If not changing files, return, we're done. */\n        if (tp->frp == sp->frp)\n                return (0);\n\n        /* Check for permission to leave. */\n        if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))\n                return (1);\n\n        /* Initialize the new file. */\n        if (file_init(sp, tp->frp, NULL, FS_SETALT))\n                return (1);\n\n        /* Display tags in the center of the screen. */\n        F_CLR(sp, SC_SCR_TOP);\n        F_SET(sp, SC_SCR_CENTER);\n\n        /* Switch. */\n        F_SET(sp, SC_FSWITCH);\n        return (0);\n}\n\n/*\n * ex_tag_Nswitch --\n *      Switch context to the specified TAG in a new screen.\n *\n * PUBLIC: int ex_tag_Nswitch(SCR *, TAG *, int);\n */\nint\nex_tag_Nswitch(SCR *sp, TAG *tp, int force)\n{\n        SCR *new;\n\n        /* Get a file structure. */\n        if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)\n                return (1);\n\n        /* Get a new screen. */\n        if (screen_init(sp->gp, sp, &new))\n                return (1);\n        if (vs_split(sp, new, 0)) {\n                (void)file_end(new, new->ep, 1);\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* Get a backing file. */\n        if (tp->frp == sp->frp) {\n                /* Copy file state. */\n                new->ep = sp->ep;\n                ++new->ep->refcnt;\n\n                new->frp = tp->frp;\n                new->frp->flags = sp->frp->flags;\n        } else if (file_init(new, tp->frp, NULL, force)) {\n                (void)vs_discard(new, NULL);\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* Create the argument list. */\n        new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);\n\n        /* Display tags in the center of the screen. */\n        F_CLR(new, SC_SCR_TOP);\n        F_SET(new, SC_SCR_CENTER);\n\n        /* Switch. */\n        sp->nextdisp = new;\n        F_SET(sp, SC_SSWITCH);\n\n        return (0);\n}\n\n/*\n * ex_tag_pop -- ^T\n *               :tagp[op][!] [number | file]\n *\n *      Pop to a previous TAGQ context.\n *\n * PUBLIC: int ex_tag_pop(SCR *, EXCMD *);\n */\nint\nex_tag_pop(SCR *sp, EXCMD *cmdp)\n{\n        EX_PRIVATE *exp;\n        TAGQ *tqp, *dtqp = NULL;\n        size_t arglen;\n        long off;\n        char *arg, *p, *t;\n\n        /* Check for an empty stack. */\n        exp = EXP(sp);\n        if (TAILQ_EMPTY(&exp->tq)) {\n                tag_msg(sp, TAG_EMPTY, NULL);\n                return (1);\n        }\n\n        /* Find the last TAG structure that we're going to DISCARD! */\n        switch (cmdp->argc) {\n        case 0:                         /* Pop one tag. */\n                dtqp = TAILQ_FIRST(&exp->tq);\n                break;\n        case 1:                         /* Name or number. */\n                arg = cmdp->argv[0]->bp;\n                off = strtol(arg, &p, 10);\n                if (*p != '\\0')\n                        goto filearg;\n\n                /* Number: pop that many queue entries. */\n                if (off < 1)\n                        return (0);\n                TAILQ_FOREACH(tqp, &exp->tq, q) {\n                        if (--off <= 1)\n                                break;\n                }\n                if (tqp == NULL) {\n                        msgq(sp, M_ERR,\n        \"Less than %s entries on the tags stack; use :display t[ags]\",\n                            arg);\n                        return (1);\n                }\n                dtqp = tqp;\n                break;\n\n                /* File argument: pop to that queue entry. */\nfilearg:        arglen = strlen(arg);\n                for (tqp = TAILQ_FIRST(&exp->tq); tqp;\n                    dtqp = tqp, tqp = TAILQ_NEXT(tqp, q)) {\n                        /* Don't pop to the current file. */\n                        if (tqp == TAILQ_FIRST(&exp->tq))\n                                continue;\n                        p = tqp->current->frp->name;\n                        if ((t = strrchr(p, '/')) == NULL)\n                                t = p;\n                        else\n                                ++t;\n                        if (!strncmp(arg, t, arglen))\n                                break;\n                }\n                if (tqp == NULL) {\n                        msgq_str(sp, M_ERR, arg,\n        \"No file %s on the tags stack to return to; use :display t[ags]\");\n                        return (1);\n                }\n                if (tqp == TAILQ_FIRST(&exp->tq))\n                        return (0);\n                break;\n        default:\n                abort();\n                /* NOTREACHED */\n        }\n\n        return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));\n}\n\n/*\n * ex_tag_top -- :tagt[op][!]\n *      Clear the tag stack.\n *\n * PUBLIC: int ex_tag_top(SCR *, EXCMD *);\n */\nint\nex_tag_top(SCR *sp, EXCMD *cmdp)\n{\n        EX_PRIVATE *exp;\n\n        exp = EXP(sp);\n\n        /* Check for an empty stack. */\n        if (TAILQ_EMPTY(&exp->tq)) {\n                tag_msg(sp, TAG_EMPTY, NULL);\n                return (1);\n        }\n\n        /* Return to the oldest information. */\n        return (tag_pop(sp,\n            TAILQ_PREV(TAILQ_LAST(&exp->tq, _tqh), _tqh, q),\n            FL_ISSET(cmdp->iflags, E_C_FORCE)));\n}\n\n/*\n * tag_pop --\n *      Pop up to and including the specified TAGQ context.\n */\nstatic int\ntag_pop(SCR *sp, TAGQ *dtqp, int force)\n{\n        EX_PRIVATE *exp;\n        TAG *tp;\n        TAGQ *tqp;\n\n        exp = EXP(sp);\n\n        /*\n         * Update the cursor from the saved TAG information of the TAG\n         * structure we're moving to.\n         */\n        tp = TAILQ_NEXT(dtqp, q)->current;\n        if (tp->frp == sp->frp) {\n                sp->lno = tp->lno;\n                sp->cno = tp->cno;\n        } else {\n                if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))\n                        return (1);\n\n                tp->frp->lno = tp->lno;\n                tp->frp->cno = tp->cno;\n                F_SET(sp->frp, FR_CURSORSET);\n                if (file_init(sp, tp->frp, NULL, FS_SETALT))\n                        return (1);\n\n                F_SET(sp, SC_FSWITCH);\n        }\n\n        /* Pop entries off the queue up to and including dtqp. */\n        do {\n                tqp = TAILQ_FIRST(&exp->tq);\n                if (tagq_free(sp, tqp))\n                        return (0);\n        } while (tqp != dtqp);\n\n        /*\n         * If only a single tag left, we've returned to the first tag point,\n         * and the stack is now empty.\n         */\n        if (TAILQ_NEXT(TAILQ_FIRST(&exp->tq), q) == NULL)\n                tagq_free(sp, TAILQ_FIRST(&exp->tq));\n\n        return (0);\n}\n\n/*\n * ex_tag_display --\n *      Display the list of tags.\n *\n * PUBLIC: int ex_tag_display(SCR *);\n */\nint\nex_tag_display(SCR *sp)\n{\n        EX_PRIVATE *exp;\n        TAG *tp;\n        TAGQ *tqp;\n        int cnt;\n        size_t len;\n        char *p;\n\n        exp = EXP(sp);\n        if (TAILQ_EMPTY(&exp->tq)) {\n                tag_msg(sp, TAG_EMPTY, NULL);\n                return (0);\n        }\n        tqp = TAILQ_FIRST(&exp->tq);\n\n        /*\n         * We give the file name 20 columns and the search string the rest.\n         * If there's not enough room, we don't do anything special, it's\n         * not worth the effort, it just makes the display more confusing.\n         *\n         * We also assume that characters in file names map 1-1 to printing\n         * characters.  This might not be true, but I don't think it's worth\n         * fixing.  (The obvious fix is to pass the filenames through the\n         * msg_print function.)\n         */\n#define L_NAME  30              /* Name. */\n#define L_SLOP   4              /* Leading number plus trailing *. */\n#define L_SPACE  5              /* Spaces after name, before tag. */\n#define L_TAG   20              /* Tag. */\n        if (sp->cols <= L_NAME + L_SLOP) {\n                msgq(sp, M_ERR, \"Display too small.\");\n                return (0);\n        }\n\n        /*\n         * Display the list of tags for each queue entry.  The first entry\n         * is numbered, and the current tag entry has an asterisk appended.\n         */\n        cnt = 0;\n        TAILQ_FOREACH(tqp, &exp->tq, q) {\n                if (INTERRUPTED(sp))\n                        break;\n                ++cnt;\n                TAILQ_FOREACH(tp, &tqp->tagq, q) {\n                        if (tp == TAILQ_FIRST(&tqp->tagq))\n                                (void)ex_printf(sp, \"%2d \", cnt);\n                        else\n                                (void)ex_printf(sp, \"   \");\n                        p = tp->frp == NULL ? tp->fname : tp->frp->name;\n                        if ((len = strlen(p)) > L_NAME) {\n                                len = len - (L_NAME - 4);\n                                (void)ex_printf(sp, \"   ... %*.*s\",\n                                    L_NAME - 4, L_NAME - 4, p + len);\n                        } else\n                                (void)ex_printf(sp,\n                                    \"   %*.*s\", L_NAME, L_NAME, p);\n                        if (tqp->current == tp)\n                                (void)ex_printf(sp, \"*\");\n\n                        if (tp == TAILQ_FIRST(&tqp->tagq) && tqp->tag != NULL &&\n                            (sp->cols - L_NAME) >= L_TAG + L_SPACE) {\n                                len = strlen(tqp->tag);\n                                if (len > sp->cols - (L_NAME + L_SPACE))\n                                        len = sp->cols - (L_NAME + L_SPACE);\n                                (void)ex_printf(sp, \"%s%.*s\",\n                                    tqp->current == tp ? \"    \" : \"     \",\n                                    (int)len, tqp->tag);\n                        }\n                        (void)ex_printf(sp, \"\\n\");\n                }\n        }\n        return (0);\n}\n\n/*\n * ex_tag_copy --\n *      Copy a screen's tag structures.\n *\n * PUBLIC: int ex_tag_copy(SCR *, SCR *);\n */\nint\nex_tag_copy(SCR *orig, SCR *sp)\n{\n        EX_PRIVATE *oexp, *nexp;\n        TAGQ *aqp, *tqp;\n        TAG *ap, *tp;\n        TAGF *atfp, *tfp;\n\n        oexp = EXP(orig);\n        nexp = EXP(sp);\n\n        /* Copy tag queue and tags stack. */\n        TAILQ_FOREACH(aqp, &oexp->tq, q) {\n                if (tagq_copy(sp, aqp, &tqp))\n                        return (1);\n                TAILQ_FOREACH(ap, &aqp->tagq, q) {\n                        if (tag_copy(sp, ap, &tp))\n                                return (1);\n                        /* Set the current pointer. */\n                        if (aqp->current == ap)\n                                tqp->current = tp;\n                        TAILQ_INSERT_TAIL(&tqp->tagq, tp, q);\n                }\n                TAILQ_INSERT_TAIL(&nexp->tq, tqp, q);\n        }\n\n        /* Copy list of tag files. */\n        TAILQ_FOREACH(atfp, &oexp->tagfq, q) {\n                if (tagf_copy(sp, atfp, &tfp))\n                        return (1);\n                TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);\n        }\n\n        /* Copy the last tag. */\n        if (oexp->tag_last != NULL &&\n            (nexp->tag_last = strdup(oexp->tag_last)) == NULL) {\n                msgq(sp, M_SYSERR, NULL);\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * tagf_copy --\n *      Copy a TAGF structure and return it in new memory.\n */\nstatic int\ntagf_copy(SCR *sp, TAGF *otfp, TAGF **tfpp)\n{\n        TAGF *tfp;\n\n        MALLOC_RET(sp, tfp, sizeof(TAGF));\n        *tfp = *otfp;\n\n        /* XXX: Allocate as part of the TAGF structure!!! */\n        if ((tfp->name = strdup(otfp->name)) == NULL) {\n                free(tfp);\n                return (1);\n        }\n\n        *tfpp = tfp;\n        return (0);\n}\n\n/*\n * tagq_copy --\n *      Copy a TAGQ structure and return it in new memory.\n */\nstatic int\ntagq_copy(SCR *sp, TAGQ *otqp, TAGQ **tqpp)\n{\n        TAGQ *tqp;\n        size_t len;\n\n        len = sizeof(TAGQ);\n        if (otqp->tag != NULL)\n                len += otqp->tlen + 1;\n        MALLOC_RET(sp, tqp, len);\n        memcpy(tqp, otqp, len);\n\n        TAILQ_INIT(&tqp->tagq);\n        tqp->current = NULL;\n        if (otqp->tag != NULL)\n                tqp->tag = tqp->buf;\n\n        *tqpp = tqp;\n        return (0);\n}\n\n/*\n * tag_copy --\n *      Copy a TAG structure and return it in new memory.\n */\nstatic int\ntag_copy(SCR *sp, TAG *otp, TAG **tpp)\n{\n        TAG *tp;\n        size_t len;\n\n        len = sizeof(TAG);\n        if (otp->fname != NULL)\n                len += otp->fnlen + 1;\n        if (otp->search != NULL)\n                len += otp->slen + 1;\n        MALLOC_RET(sp, tp, len);\n        memcpy(tp, otp, len);\n\n        if (otp->fname != NULL)\n                tp->fname = tp->buf;\n        if (otp->search != NULL)\n                tp->search = tp->fname + otp->fnlen + 1;\n\n        *tpp = tp;\n        return (0);\n}\n\n/*\n * tagf_free --\n *      Free a TAGF structure.\n */\nstatic int\ntagf_free(SCR *sp, TAGF *tfp)\n{\n        EX_PRIVATE *exp;\n\n        exp = EXP(sp);\n        TAILQ_REMOVE(&exp->tagfq, tfp, q);\n        free(tfp->name);\n        free(tfp);\n        return (0);\n}\n\n/*\n * tagq_free --\n *      Free a TAGQ structure (and associated TAG structures).\n *\n * PUBLIC: int tagq_free(SCR *, TAGQ *);\n */\nint\ntagq_free(SCR *sp, TAGQ *tqp)\n{\n        EX_PRIVATE *exp;\n        TAGQ *ttqp;\n        TAG *tp;\n\n        exp = EXP(sp);\n        while ((tp = TAILQ_FIRST(&tqp->tagq))) {\n                TAILQ_REMOVE(&tqp->tagq, tp, q);\n                free(tp);\n        }\n        /*\n         * !!!\n         * If allocated and then the user failed to switch files, the TAGQ\n         * structure was never attached to any list.\n         */\n        TAILQ_FOREACH(ttqp, &exp->tq, q) {\n                if (ttqp == tqp) {\n                        TAILQ_REMOVE(&exp->tq, tqp, q);\n                        break;\n                }\n        }\n        free(tqp);\n        return (0);\n}\n\n/*\n * tag_msg\n *      A few common messages.\n *\n * PUBLIC: void tag_msg(SCR *, tagmsg_t, char *);\n */\nvoid\ntag_msg(SCR *sp, tagmsg_t msg, char *tag)\n{\n        switch (msg) {\n        case TAG_BADLNO:\n                msgq_str(sp, M_ERR, tag,\n            \"%s: the tag's line number is past the end of the file\");\n                break;\n        case TAG_EMPTY:\n                msgq(sp, M_INFO, \"The tags stack is empty\");\n                break;\n        case TAG_SEARCH:\n                msgq_str(sp, M_ERR, tag, \"%s: search pattern not found\");\n                break;\n        default:\n                abort();\n        }\n}\n\n/*\n * ex_tagf_alloc --\n *      Create a new list of ctag files.\n *\n * PUBLIC: int ex_tagf_alloc(SCR *, char *);\n */\nint\nex_tagf_alloc(SCR *sp, char *str)\n{\n        EX_PRIVATE *exp;\n        TAGF *tfp;\n        size_t len;\n        char *p, *t;\n\n        /* Free current queue. */\n        exp = EXP(sp);\n        while ((tfp = TAILQ_FIRST(&exp->tagfq)) != NULL)\n                tagf_free(sp, tfp);\n\n        /* Create new queue. */\n        for (p = t = str;; ++p) {\n                if (*p == '\\0' || isblank(*p)) {\n                        if ((len = p - t) > 1) {\n                                MALLOC_RET(sp, tfp, sizeof(TAGF));\n                                MALLOC(sp, tfp->name, len + 1);\n                                if (tfp->name == NULL) {\n                                        free(tfp);\n                                        return (1);\n                                }\n                                memcpy(tfp->name, t, len);\n                                tfp->name[len] = '\\0';\n                                tfp->flags = 0;\n                                TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);\n                        }\n                        t = p + 1;\n                }\n                if (*p == '\\0')\n                         break;\n        }\n        return (0);\n}\n                                                /* Free previous queue. */\n/*\n * ex_tag_free --\n *      Free the ex tag information.\n *\n * PUBLIC: int ex_tag_free(SCR *);\n */\nint\nex_tag_free(SCR *sp)\n{\n        EX_PRIVATE *exp;\n        TAGF *tfp;\n        TAGQ *tqp;\n\n        /* Free up tag information. */\n        exp = EXP(sp);\n        while ((tqp = TAILQ_FIRST(&exp->tq)))\n                tagq_free(sp, tqp);     /* tagq_free removes tqp from queue. */\n        while ((tfp = TAILQ_FIRST(&exp->tagfq)) != NULL)\n                tagf_free(sp, tfp);\n        free(exp->tag_last);\n        return (0);\n}\n\n/*\n * ctag_search --\n *      Search a file for a tag.\n */\nstatic int\nctag_search(SCR *sp, char *search, size_t slen, char *tag)\n{\n        MARK m;\n        char *p;\n\n        /*\n         * !!!\n         * The historic tags file format (from a long, long time ago...)\n         * used a line number, not a search string.  I got complaints, so\n         * people are still using the format.  POSIX 1003.2 permits it.\n         */\n        if (isdigit(search[0])) {\n                m.lno = atoi(search);\n                if (!db_exist(sp, m.lno)) {\n                        tag_msg(sp, TAG_BADLNO, tag);\n                        return (1);\n                }\n        } else {\n                /*\n                 * Search for the tag; cheap fallback for C functions\n                 * if the name is the same but the arguments have changed.\n                 */\n                m.lno = 1;\n                m.cno = 0;\n                if (f_search(sp, &m, &m,\n                    search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) {\n                        if ((p = strrchr(search, '(')) != NULL) {\n                                slen = p - search;\n                                if (f_search(sp, &m, &m, search, slen,\n                                    NULL, SEARCH_FILE | SEARCH_TAG))\n                                        goto notfound;\n                        } else {\nnotfound:                       tag_msg(sp, TAG_SEARCH, tag);\n                                return (1);\n                        }\n                }\n                /*\n                 * !!!\n                 * Historically, tags set the search direction if it wasn't\n                 * already set.\n                 */\n                if (sp->searchdir == NOTSET)\n                        sp->searchdir = FORWARD;\n        }\n\n        /*\n         * !!!\n         * Tags move to the first non-blank, NOT the search pattern start.\n         */\n        sp->lno = m.lno;\n        sp->cno = 0;\n        (void)nonblank(sp, sp->lno, &sp->cno);\n        return (0);\n}\n\n/*\n * ctag_slist --\n *      Search the list of tags files for a tag, and return tag queue.\n */\nstatic TAGQ *\nctag_slist(SCR *sp, char *tag)\n{\n        EX_PRIVATE *exp;\n        TAGF *tfp;\n        TAGQ *tqp;\n        size_t len;\n        int echk;\n\n        exp = EXP(sp);\n\n        /* Allocate and initialize the tag queue structure. */\n        len = strlen(tag);\n        CALLOC_GOTO(sp, tqp, 1, sizeof(TAGQ) + len + 1);\n        TAILQ_INIT(&tqp->tagq);\n        tqp->tag = tqp->buf;\n        memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);\n\n        /*\n         * Find the tag, only display missing file messages once, and\n         * then only if we didn't find the tag.\n         */\n        echk = 0;\n        TAILQ_FOREACH(tfp, &exp->tagfq, q)\n                if (ctag_sfile(sp, tfp, tqp, tag)) {\n                        echk = 1;\n                        F_SET(tfp, TAGF_ERR);\n                } else\n                        F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);\n\n        /* Check to see if we found anything. */\n        if (TAILQ_EMPTY(&tqp->tagq)) {\n                msgq_str(sp, M_ERR, tag, \"%s: tag not found\");\n                if (echk)\n                        TAILQ_FOREACH(tfp, &exp->tagfq, q)\n                                if (F_ISSET(tfp, TAGF_ERR) &&\n                                    !F_ISSET(tfp, TAGF_ERR_WARN)) {\n                                        errno = tfp->errnum;\n                                        msgq_str(sp, M_SYSERR, tfp->name, \"%s\");\n                                        F_SET(tfp, TAGF_ERR_WARN);\n                                }\n                free(tqp);\n                return (NULL);\n        }\n\n        tqp->current = TAILQ_FIRST(&tqp->tagq);\n        return (tqp);\n\nalloc_err:\n        return (NULL);\n}\n\n/*\n * ctag_sfile --\n *      Search a tags file for a tag, adding any found to the tag queue.\n */\nstatic int\nctag_sfile(SCR *sp, TAGF *tfp, TAGQ *tqp, char *tname)\n{\n        struct stat sb;\n        TAG *tp;\n        size_t dlen, nlen, slen;\n        int fd, i, nf1, nf2;\n        char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;\n        long tl;\n\n        if ((fd = open(tfp->name, O_RDONLY)) < 0) {\n                tfp->errnum = errno;\n                return (1);\n        }\n\n        /*\n         * XXX\n         * We'd like to test if the file is too big to mmap.  Since we don't\n         * know what size or type off_t's or size_t's are, what the largest\n         * unsigned integral type is, or what random insanity the local C\n         * compiler will perpetrate, doing the comparison in a portable way\n         * is flatly impossible.  Hope mmap fails if the file is too large.\n         */\n        if (fstat(fd, &sb) != 0 ||\n            (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,\n            MAP_PRIVATE, fd, (off_t)0)) == MAP_FAILED) {\n                tfp->errnum = errno;\n                (void)close(fd);\n                return (1);\n        }\n\n        tl = O_VAL(sp, O_TAGLENGTH);\n        front = map;\n        back = front + sb.st_size;\n        front = binary_search(tname, front, back);\n        front = linear_search(tname, front, back, tl);\n        if (front == NULL)\n                goto done;\n\n        /*\n         * Initialize and link in the tag structure(s).  The historic ctags\n         * file format only permitted a single tag location per tag.  The\n         * obvious extension to permit multiple tags locations per tag is to\n         * output multiple records in the standard format.  Unfortunately,\n         * this won't work correctly with historic ex/vi implementations,\n         * because their binary search assumes that there's only one record\n         * per tag, and so will use a random tag entry if there si more than\n         * one.  This code handles either format.\n         *\n         * The tags file is in the following format:\n         *\n         *      <tag> <filename> <line number> | <pattern>\n         *\n         * Figure out how long everything is so we can allocate in one swell\n         * foop, but discard anything that looks wrong.\n         */\n        for (;;) {\n                /* Nul-terminate the end of the line. */\n                for (p = front; p < back && *p != '\\n'; ++p);\n                if (p == back || *p != '\\n')\n                        break;\n                *p = '\\0';\n\n                /* Update the pointers for the next time. */\n                t = p + 1;\n                p = front;\n                front = t;\n\n                /* Break the line into tokens. */\n                for (i = 0; i < 2 && (t = strsep(&p, \"\\t \")) != NULL; ++i)\n                        switch (i) {\n                        case 0:                 /* Tag. */\n                                cname = t;\n                                break;\n                        case 1:                 /* Filename. */\n                                name = t;\n                                nlen = strlen(name);\n                                break;\n                        }\n\n                /* Check for corruption. */\n                if (i != 2 || p == NULL || t == NULL)\n                        goto corrupt;\n\n                /* The rest of the string is the search pattern. */\n                search = p;\n                if ((slen = strlen(p)) == 0) {\ncorrupt:                p = msg_print(sp, tname, &nf1);\n                        t = msg_print(sp, tfp->name, &nf2);\n                        msgq(sp, M_ERR, \"%s: corrupted tag in %s\", p, t);\n                        if (nf1)\n                                FREE_SPACE(sp, p, 0);\n                        if (nf2)\n                                FREE_SPACE(sp, t, 0);\n                        continue;\n                }\n\n                /* Check for passing the last entry. */\n                if (tl ? strncmp(tname, cname, tl) : strcmp(tname, cname))\n                        break;\n\n                /* Resolve the file name. */\n                ctag_file(sp, tfp, name, &dname, &dlen);\n\n                CALLOC_GOTO(sp, tp,\n                    1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);\n                tp->fname = tp->buf;\n                if (dlen != 0) {\n                        memcpy(tp->fname, dname, dlen);\n                        tp->fname[dlen] = '/';\n                        ++dlen;\n                }\n                memcpy(tp->fname + dlen, name, nlen + 1);\n                tp->fnlen = dlen + nlen;\n                tp->search = tp->fname + tp->fnlen + 1;\n                memcpy(tp->search, search, (tp->slen = slen) + 1);\n                TAILQ_INSERT_TAIL(&tqp->tagq, tp, q);\n        }\n\nalloc_err:\ndone:   if (munmap(map, (size_t)sb.st_size))\n                msgq(sp, M_SYSERR, \"munmap\");\n        if (close(fd))\n                msgq(sp, M_SYSERR, \"close\");\n        return (0);\n}\n\n/*\n * ctag_file --\n *      Search for the right path to this file.\n */\nstatic void\nctag_file(SCR *sp, TAGF *tfp, char *name, char **dirp, size_t *dlenp)\n{\n        struct stat sb;\n        char *p, buf[PATH_MAX];\n\n        /*\n         * !!!\n         * If the tag file path is a relative path, see if it exists.  If it\n         * doesn't, look relative to the tags file path.  It's okay for a tag\n         * file to not exist, and historically, vi simply displayed a \"new\"\n         * file.  However, if the path exists relative to the tag file, it's\n         * pretty clear what's happening, so we may as well get it right.\n         */\n        *dlenp = 0;\n        if (name[0] != '/' &&\n            stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {\n                *p = '\\0';\n                (void)snprintf(buf, sizeof(buf), \"%s/%s\", tfp->name, name);\n                if (stat(buf, &sb) == 0) {\n                        *dirp = tfp->name;\n                        *dlenp = strlen(*dirp);\n                }\n                *p = '/';\n        }\n}\n\n/*\n * Binary search for \"string\" in memory between \"front\" and \"back\".\n *\n * This routine is expected to return a pointer to the start of a line at\n * *or before* the first word matching \"string\".  Relaxing the constraint\n * this way simplifies the algorithm.\n *\n * Invariants:\n *      front points to the beginning of a line at or before the first\n *      matching string.\n *\n *      back points to the beginning of a line at or after the first\n *      matching line.\n *\n * Base of the Invariants.\n *      front = NULL;\n *      back = EOF;\n *\n * Advancing the Invariants:\n *\n *      p = first newline after halfway point from front to back.\n *\n *      If the string at \"p\" is not greater than the string to match,\n *      p is the new front.  Otherwise it is the new back.\n *\n * Termination:\n *\n *      The definition of the routine allows it return at any point,\n *      since front is always at or before the line to print.\n *\n *      In fact, it returns when the chosen \"p\" equals \"back\".  This\n *      implies that there exists a string is least half as long as\n *      (back - front), which in turn implies that a linear search will\n *      be no more expensive than the cost of simply printing a string or two.\n *\n *      Trying to continue with binary search at this point would be\n *      more trouble than it's worth.\n */\n#define EQUAL           0\n#define GREATER         1\n#define LESS            (-1)\n\n#define SKIP_PAST_NEWLINE(p, back)      while ((p) < (back) && *(p)++ != '\\n');\n\nstatic char *\nbinary_search(char *string, char *front, char *back)\n{\n        char *p;\n\n        p = front + (back - front) / 2;\n        SKIP_PAST_NEWLINE(p, back);\n\n        while (p != back) {\n                if (compare(string, p, back) == GREATER)\n                        front = p;\n                else\n                        back = p;\n                p = front + (back - front) / 2;\n                SKIP_PAST_NEWLINE(p, back);\n        }\n        return (front);\n}\n\n/*\n * Find the first line that starts with string, linearly searching from front\n * to back.\n *\n * Return NULL for no such line.\n *\n * This routine assumes:\n *\n *      o front points at the first character in a line.\n *      o front is before or at the first line to be printed.\n */\nstatic char *\nlinear_search(char *string, char *front, char *back, long tl)\n{\n        char *end;\n\n        while (front < back) {\n                end = tl && back-front > tl ? front+tl : back;\n                switch (compare(string, front, end)) {\n                case EQUAL:             /* Found it. */\n                        return (front);\n                case LESS:              /* No such string. */\n                        return (NULL);\n                case GREATER:           /* Keep going. */\n                        break;\n                }\n                SKIP_PAST_NEWLINE(front, back);\n        }\n        return (NULL);\n}\n\n/*\n * Return LESS, GREATER, or EQUAL depending on how the string1 compares\n * with string2 (s1 ??? s2).\n *\n *      o Matches up to len(s1) are EQUAL.\n *      o Matches up to len(s2) are GREATER.\n *\n * The string \"s1\" is NULL terminated.  The string s2 is '\\t', space, (or\n * \"back\") terminated.\n *\n * !!!\n * Reasonably modern ctags programs use tabs as separators, not spaces.\n * However, historic programs did use spaces, and, I got complaints.\n */\nstatic int\ncompare(char *s1, char *s2, char *back)\n{\n        for (; *s1 && s2 < back && (*s2 != '\\t' && *s2 != ' '); ++s1, ++s2)\n                if (*s1 != *s2)\n                        return (*s1 < *s2 ? LESS : GREATER);\n        return (*s1 ? GREATER : s2 < back &&\n            (*s2 != '\\t' && *s2 != ' ') ? LESS : EQUAL);\n}\n"
  },
  {
    "path": "ex/ex_txt.c",
    "content": "/*      $OpenBSD: ex_txt.c,v 1.17 2020/04/30 10:40:21 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * !!!\n * The backslash characters was special when it preceded a newline as part of\n * a substitution replacement pattern.  For example, the input \":a\\<cr>\" would\n * failed immediately with an error, as the <cr> wasn't part of a substitution\n * replacement pattern.  This implies a frightening integration of the editor\n * and the parser and/or the RE engine.  There's no way I'm going to reproduce\n * those semantics.\n *\n * So, if backslashes are special, this code inserts the backslash and the next\n * character into the string, without regard for the character or the command\n * being entered.  Since \"\\<cr>\" was illegal historically (except for the one\n * special case), and the command will fail eventually, no historical scripts\n * should break (presuming they didn't depend on the failure mode itself or the\n * characters remaining when failure occurred.\n */\n\nstatic int      txt_dent(SCR *, TEXT *);\nstatic void     txt_prompt(SCR *, TEXT *, CHAR_T, u_int32_t);\n\n/*\n * ex_txt --\n *      Get lines from the terminal for ex.\n *\n * PUBLIC: int ex_txt(SCR *, TEXTH *, CHAR_T, u_int32_t);\n */\nint\nex_txt(SCR *sp, TEXTH *tiqh, CHAR_T prompt, u_int32_t flags)\n{\n        EVENT ev;\n        GS *gp;\n        TEXT ait, *ntp, *tp;\n        carat_t carat_st;\n        size_t cnt;\n        int rval;\n        int nochange;\n\n        rval = 0;\n\n        /*\n         * Get a TEXT structure with some initial buffer space, reusing the\n         * last one if it's big enough.  (All TEXT bookkeeping fields default\n         * to 0 -- text_init() handles this.)\n         */\n        if (!TAILQ_EMPTY(tiqh)) {\n                tp = TAILQ_FIRST(tiqh);\n                if (TAILQ_NEXT(tp, q) || tp->lb_len < 32) {\n                        text_lfree(tiqh);\n                        goto newtp;\n                }\n                tp->len = 0;\n        } else {\nnewtp:          if ((tp = text_init(sp, NULL, 0, 32)) == NULL)\n                        goto err;\n                TAILQ_INSERT_HEAD(tiqh, tp, q);\n        }\n\n        /* Set the starting line number. */\n        tp->lno = sp->lno + 1;\n\n        /*\n         * If it's a terminal, set up autoindent, put out the prompt, and\n         * set it up so we know we were suspended.  Otherwise, turn off\n         * the autoindent flag, as that requires less special casing below.\n         *\n         * XXX\n         * Historic practice is that ^Z suspended command mode (but, because\n         * it ran in cooked mode, it was unaffected by the autowrite option.)\n         * On restart, any \"current\" input was discarded, whether in insert\n         * mode or not, and ex was in command mode.  This code matches historic\n         * practice, but not 'cause it's easier.\n         */\n        gp = sp->gp;\n        if (F_ISSET(gp, G_SCRIPTED))\n                LF_CLR(TXT_AUTOINDENT);\n        else {\n                if (LF_ISSET(TXT_AUTOINDENT)) {\n                        LF_SET(TXT_EOFCHAR);\n                        if (v_txt_auto(sp, sp->lno, NULL, 0, tp))\n                                goto err;\n                }\n                txt_prompt(sp, tp, prompt, flags);\n        }\n\n        for (carat_st = C_NOTSET, nochange = 0;;) {\n                if (v_event_get(sp, &ev, 0, 0))\n                        goto err;\n\n                /* Deal with all non-character events. */\n                switch (ev.e_event) {\n                case E_CHARACTER:\n                        break;\n                case E_ERR:\n                        goto err;\n                case E_REPAINT:\n                case E_WRESIZE:\n                        continue;\n                case E_EOF:\n                        rval = 1;\n                        /* FALLTHROUGH */\n                case E_INTERRUPT:\n                        /*\n                         * Handle EOF/SIGINT events by discarding partially\n                         * entered text and returning.  EOF returns failure,\n                         * E_INTERRUPT returns success.\n                         */\n                        goto notlast;\n                default:\n                        v_event_err(sp, &ev);\n                        goto notlast;\n                }\n\n                /*\n                 * Deal with character events.\n                 *\n                 * Check to see if the character fits into the input buffer.\n                 * (Use tp->len, ignore overwrite and non-printable chars.)\n                 */\n                BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1);\n\n                switch (ev.e_value) {\n                case K_CR:\n                        /*\n                         * !!!\n                         * Historically, <carriage-return>'s in the command\n                         * weren't special, so the ex parser would return an\n                         * unknown command error message.  However, if they\n                         * terminated the command if they were in a map.  I'm\n                         * pretty sure this still isn't right, but it handles\n                         * what I've seen so far.\n                         */\n                        if (!F_ISSET(&ev.e_ch, CH_MAPPED))\n                                goto ins_ch;\n                        /* FALLTHROUGH */\n                case K_NL:\n                        /*\n                         * '\\' can escape <carriage-return>/<newline>.  We\n                         * don't discard the backslash because we need it\n                         * to get the <newline> through the ex parser.\n                         */\n                        if (LF_ISSET(TXT_BACKSLASH) &&\n                            tp->len != 0 && tp->lb[tp->len - 1] == '\\\\')\n                                goto ins_ch;\n\n                        /*\n                         * CR returns from the ex command line.\n                         *\n                         * XXX\n                         * Terminate with a nul, needed by filter.\n                         */\n                        if (LF_ISSET(TXT_CR)) {\n                                tp->lb[tp->len] = '\\0';\n                                goto done;\n                        }\n\n                        /*\n                         * '.' may terminate text input mode; free the current\n                         * TEXT.\n                         */\n                        if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 &&\n                            tp->lb[tp->len - 1] == '.') {\nnotlast:                        TAILQ_REMOVE(tiqh, tp, q);\n                                text_free(tp);\n                                goto done;\n                        }\n\n                        /* Set up bookkeeping for the new line. */\n                        if ((ntp = text_init(sp, NULL, 0, 32)) == NULL)\n                                goto err;\n                        ntp->lno = tp->lno + 1;\n\n                        /*\n                         * Reset the autoindent line value.  0^D keeps the ai\n                         * line from changing, ^D changes the level, even if\n                         * there were no characters in the old line.  Note, if\n                         * using the current tp structure, use the cursor as\n                         * the length, the autoindent characters may have been\n                         * erased.\n                         */\n                        if (LF_ISSET(TXT_AUTOINDENT)) {\n                                if (nochange) {\n                                        nochange = 0;\n                                        if (v_txt_auto(sp,\n                                            OOBLNO, &ait, ait.ai, ntp))\n                                                goto err;\n                                        free(ait.lb);\n                                } else\n                                        if (v_txt_auto(sp,\n                                            OOBLNO, tp, tp->len, ntp))\n                                                goto err;\n                                carat_st = C_NOTSET;\n                        }\n                        txt_prompt(sp, ntp, prompt, flags);\n\n                        /*\n                         * Swap old and new TEXT's, and insert the new TEXT\n                         * into the queue.\n                         */\n                        tp = ntp;\n                        TAILQ_INSERT_TAIL(tiqh, tp, q);\n                        break;\n                case K_CARAT:                   /* Delete autoindent chars. */\n                        if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT))\n                                carat_st = C_CARATSET;\n                        goto ins_ch;\n                case K_ZERO:                    /* Delete autoindent chars. */\n                        if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT))\n                                carat_st = C_ZEROSET;\n                        goto ins_ch;\n                case K_CNTRLD:                  /* Delete autoindent char. */\n                        /*\n                         * !!!\n                         * Historically, the ^D command took (but then ignored)\n                         * a count.  For simplicity, we don't return it unless\n                         * it's the first character entered.  The check for len\n                         * equal to 0 is okay, TXT_AUTOINDENT won't be set.\n                         */\n                        if (LF_ISSET(TXT_CNTRLD)) {\n                                for (cnt = 0; cnt < tp->len; ++cnt)\n                                        if (!isblank(tp->lb[cnt]))\n                                                break;\n                                if (cnt == tp->len) {\n                                        tp->len = 1;\n                                        tp->lb[0] = ev.e_c;\n                                        tp->lb[1] = '\\0';\n\n                                        /*\n                                         * Put out a line separator, in case\n                                         * the command fails.\n                                         */\n                                        (void)putchar('\\n');\n                                        goto done;\n                                }\n                        }\n\n                        /*\n                         * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that\n                         * the EOF characters are discarded if there are other\n                         * characters to process in the line, i.e. if the EOF\n                         * is not the first character in the line.  For this\n                         * reason, historic ex discarded the EOF characters,\n                         * even if occurring in the middle of the input line.\n                         * We match that historic practice.\n                         *\n                         * !!!\n                         * The test for discarding in the middle of the line is\n                         * done in the switch, because the CARAT forms are N+1,\n                         * not N.\n                         *\n                         * !!!\n                         * There's considerable magic to make the terminal code\n                         * return the EOF character at all.  See that code for\n                         * details.\n                         */\n                        if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0)\n                                continue;\n                        switch (carat_st) {\n                        case C_CARATSET:                /* ^^D */\n                                if (tp->len > tp->ai + 1)\n                                        continue;\n\n                                /* Save the ai string for later. */\n                                ait.lb = NULL;\n                                ait.lb_len = 0;\n                                BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai);\n                                memcpy(ait.lb, tp->lb, tp->ai);\n                                ait.ai = ait.len = tp->ai;\n\n                                carat_st = C_NOTSET;\n                                nochange = 1;\n                                goto leftmargin;\n                        case C_ZEROSET:                 /* 0^D */\n                                if (tp->len > tp->ai + 1)\n                                        continue;\n\n                                carat_st = C_NOTSET;\nleftmargin:                     (void)gp->scr_ex_adjust(sp, EX_TERM_CE);\n                                tp->ai = tp->len = 0;\n                                break;\n                        case C_NOTSET:                  /* ^D */\n                                if (tp->len > tp->ai)\n                                        continue;\n\n                                if (txt_dent(sp, tp))\n                                        goto err;\n                                break;\n                        default:\n                                abort();\n                        }\n\n                        /* Clear and redisplay the line. */\n                        (void)gp->scr_ex_adjust(sp, EX_TERM_CE);\n                        txt_prompt(sp, tp, prompt, flags);\n                        break;\n                default:\n                        /*\n                         * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c.\n                         *\n                         * Silently eliminate any iscntrl() character that was\n                         * not already handled specially, except for <tab> and\n                         * <ff>.\n                         */\nins_ch:                 if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(ev.e_c) &&\n                            ev.e_value != K_FORMFEED && ev.e_value != K_TAB)\n                                break;\n\n                        tp->lb[tp->len++] = ev.e_c;\n                        break;\n                }\n        }\n        /* NOTREACHED */\n\ndone:   return (rval);\n\nerr:\nalloc_err:\n        return (1);\n}\n\n/*\n * txt_prompt --\n *      Display the ex prompt, line number, ai characters.  Characters had\n *      better be printable by the terminal driver, but that's its problem,\n *      not ours.\n */\nstatic void\ntxt_prompt(SCR *sp, TEXT *tp, CHAR_T prompt, u_int32_t flags)\n{\n        /* Display the prompt. */\n        if (LF_ISSET(TXT_PROMPT))\n                (void)printf(\"%c\", prompt);\n\n        /* Display the line number. */\n        if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER))\n                (void)printf(\"%6lu  \", (unsigned long)tp->lno);\n\n        /* Print out autoindent string. */\n        if (LF_ISSET(TXT_AUTOINDENT))\n                (void)printf(\"%.*s\", (int)tp->ai, tp->lb);\n        (void)fflush(stdout);\n}\n\n/*\n * txt_dent --\n *      Handle ^D outdents.\n *\n * Ex version of vi/v_ntext.c:txt_dent().  See that code for the (usual)\n * ranting and raving.  This is a fair bit simpler as ^T isn't special.\n */\nstatic int\ntxt_dent(SCR *sp, TEXT *tp)\n{\n        unsigned long sw, ts;\n        size_t cno, off, scno, spaces, tabs;\n\n        ts = O_VAL(sp, O_TABSTOP);\n        sw = O_VAL(sp, O_SHIFTWIDTH);\n\n        /* Get the current screen column. */\n        for (off = scno = 0; off < tp->len; ++off)\n                if (tp->lb[off] == '\\t')\n                        scno += COL_OFF(scno, ts);\n                else\n                        ++scno;\n\n        /* Get the previous shiftwidth column. */\n        cno = scno--;\n        scno -= scno % sw;\n\n        /*\n         * Since we don't know what comes before the character(s) being\n         * deleted, we have to resolve the autoindent characters .  The\n         * example is a <tab>, which doesn't take up a full shiftwidth\n         * number of columns because it's preceded by <space>s.  This is\n         * easy to get if the user sets shiftwidth to a value less than\n         * tabstop, and then uses ^T to indent, and ^D to outdent.\n         *\n         * Count up spaces/tabs needed to get to the target.\n         */\n        cno = 0;\n        tabs = 0;\n        if (!O_ISSET(sp, O_EXPANDTAB)) {\n                for (; cno + COL_OFF(cno, ts) <= scno; ++tabs)\n                        cno += COL_OFF(cno, ts);\n        }\n        spaces = scno - cno;\n\n        /* Make sure there's enough room. */\n        BINC_RET(sp, tp->lb, tp->lb_len, tabs + spaces + 1);\n\n        /* Adjust the final ai character count. */\n        tp->ai = tabs + spaces;\n\n        /* Enter the replacement characters. */\n        for (tp->len = 0; tabs > 0; --tabs)\n                tp->lb[tp->len++] = '\\t';\n        for (; spaces > 0; --spaces)\n                tp->lb[tp->len++] = ' ';\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_undo.c",
    "content": "/*      $OpenBSD: ex_undo.c,v 1.6 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_undo -- u\n *      Undo the last change.\n *\n * PUBLIC: int ex_undo(SCR *, EXCMD *);\n */\nint\nex_undo(SCR *sp, EXCMD *cmdp)\n{\n        EXF *ep;\n        MARK m;\n\n        /*\n         * !!!\n         * Historic undo always set the previous context mark.\n         */\n        m.lno = sp->lno;\n        m.cno = sp->cno;\n        if (mark_set(sp, ABSMARK1, &m, 1))\n                return (1);\n\n        /*\n         * !!!\n         * Multiple undo isn't available in ex, as there's no '.' command.\n         * Whether 'u' is undo or redo is toggled each time, unless there\n         * was a change since the last undo, in which case it's an undo.\n         */\n        ep = sp->ep;\n        if (!F_ISSET(ep, F_UNDO)) {\n                F_SET(ep, F_UNDO);\n                ep->lundo = FORWARD;\n        }\n        switch (ep->lundo) {\n        case BACKWARD:\n                if (log_forward(sp, &m))\n                        return (1);\n                ep->lundo = FORWARD;\n                break;\n        case FORWARD:\n                if (log_backward(sp, &m))\n                        return (1);\n                ep->lundo = BACKWARD;\n                break;\n        case NOTSET:\n                abort();\n        }\n        sp->lno = m.lno;\n        sp->cno = m.cno;\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_usage.c",
    "content": "/*      $OpenBSD: ex_usage.c,v 1.10 2018/07/13 09:02:07 krw Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * ex_help -- :help\n *      Display help message.\n *\n * PUBLIC: int ex_help(SCR *, EXCMD *);\n */\nint\nex_help(SCR *sp, EXCMD *cmdp)\n{\n        (void)ex_puts(sp,\n            \"To see the list of vi commands, enter \\\":viusage<CR>\\\"\\n\");\n        (void)ex_puts(sp,\n            \"To see the list of ex commands, enter \\\":exusage<CR>\\\"\\n\");\n        (void)ex_puts(sp,\n            \"For an ex command usage statement enter \\\":exusage [cmd]<CR>\\\"\\n\");\n        (void)ex_puts(sp,\n            \"For a vi key usage statement enter \\\":viusage [key]<CR>\\\"\\n\");\n        (void)ex_puts(sp, \"To exit, enter \\\":q!\\\"\\n\");\n        return (0);\n}\n\n/*\n * ex_usage -- :exusage [cmd]\n *      Display ex usage strings.\n *\n * PUBLIC: int ex_usage(SCR *, EXCMD *);\n */\nint\nex_usage(SCR *sp, EXCMD *cmdp)\n{\n        ARGS *ap;\n        EXCMDLIST const *cp;\n        int newscreen;\n\n        switch (cmdp->argc) {\n        case 1:\n                ap = cmdp->argv[0];\n                if (isupper(ap->bp[0])) {\n                        newscreen = 1;\n                        ap->bp[0] = tolower(ap->bp[0]);\n                } else\n                        newscreen = 0;\n                for (cp = cmds; cp->name != NULL &&\n                    memcmp(ap->bp, cp->name, ap->len); ++cp);\n                if (cp->name == NULL ||\n                    (newscreen && !F_ISSET(cp, E_NEWSCREEN))) {\n                        if (newscreen)\n                                ap->bp[0] = toupper(ap->bp[0]);\n                        (void)ex_printf(sp, \"The %.*s command is unknown\\n\",\n                            (int)ap->len, ap->bp);\n                } else {\n                        (void)ex_printf(sp,\n                            \"Command: %s\\n  Usage: %s\\n\", cp->help, cp->usage);\n                        /*\n                         * !!!\n                         * The \"visual\" command has two modes, one from ex,\n                         * one from the vi colon line.  Don't ask.\n                         */\n                        if (cp != &cmds[C_VISUAL_EX] &&\n                            cp != &cmds[C_VISUAL_VI])\n                                break;\n                        if (cp == &cmds[C_VISUAL_EX])\n                                cp = &cmds[C_VISUAL_VI];\n                        else\n                                cp = &cmds[C_VISUAL_EX];\n                        (void)ex_printf(sp,\n                            \"Command: %s\\n  Usage: %s\\n\", cp->help, cp->usage);\n                }\n                break;\n        case 0:\n                for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp)\n                        (void)ex_printf(sp, \"%*s: %s\\n\", MAXCMDNAMELEN,\n                            /* The ^D command has an unprintable name. */\n                            cp == &cmds[C_SCROLL] ? \"^D\" : cp->name,\n                            cp->help);\n                break;\n        default:\n                abort();\n        }\n        return (0);\n}\n\n/*\n * ex_viusage -- :viusage [key]\n *      Display vi usage strings.\n *\n * PUBLIC: int ex_viusage(SCR *, EXCMD *);\n */\nint\nex_viusage(SCR *sp, EXCMD *cmdp)\n{\n        VIKEYS const *kp;\n        int key;\n\n        switch (cmdp->argc) {\n        case 1:\n                if (cmdp->argv[0]->len != 1) {\n                        ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                        return (1);\n                }\n                key = cmdp->argv[0]->bp[0];\n                if (key > MAXVIKEY)\n                        goto nokey;\n\n                /* Special case: '[' and ']' commands. */\n                if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key)\n                        goto nokey;\n\n                /* Special case: ~ command. */\n                if (key == '~' && O_ISSET(sp, O_TILDEOP))\n                        kp = &tmotion;\n                else\n                        kp = &vikeys[key];\n\n                if (kp->usage == NULL)\nnokey:                  (void)ex_printf(sp,\n                            \"The %s key has no current meaning\\n\",\n                            KEY_NAME(sp, key));\n                else\n                        (void)ex_printf(sp,\n                            \"  Key:%s%s\\nUsage: %s\\n\",\n                            isblank(*kp->help) ? \"\" : \" \", kp->help, kp->usage);\n                break;\n        case 0:\n                for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) {\n                        /* Special case: ~ command. */\n                        if (key == '~' && O_ISSET(sp, O_TILDEOP))\n                                kp = &tmotion;\n                        else\n                                kp = &vikeys[key];\n                        if (kp->help != NULL)\n                                (void)ex_printf(sp, \"%s\\n\", kp->help);\n                }\n                break;\n        default:\n                abort();\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_util.c",
    "content": "/*      $OpenBSD: ex_util.c,v 1.9 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_cinit --\n *      Create an EX command structure.\n *\n * PUBLIC: void ex_cinit(EXCMD *, int, int, recno_t, recno_t, int, ARGS **);\n */\nvoid\nex_cinit(EXCMD *cmdp, int cmd_id, int naddr, recno_t lno1, recno_t lno2,\n    int force, ARGS **ap)\n{\n        memset(cmdp, 0, sizeof(EXCMD));\n        cmdp->cmd = &cmds[cmd_id];\n        cmdp->addrcnt = naddr;\n        cmdp->addr1.lno = lno1;\n        cmdp->addr2.lno = lno2;\n        cmdp->addr1.cno = cmdp->addr2.cno = 1;\n        if (force)\n                cmdp->iflags |= E_C_FORCE;\n        cmdp->argc = 0;\n        if ((cmdp->argv = ap) != NULL)\n                cmdp->argv[0] = NULL;\n}\n\n/*\n * ex_cadd --\n *      Add an argument to an EX command structure.\n *\n * PUBLIC: void ex_cadd(EXCMD *, ARGS *, char *, size_t);\n */\nvoid\nex_cadd(EXCMD *cmdp, ARGS *ap, char *arg, size_t len)\n{\n        cmdp->argv[cmdp->argc] = ap;\n        ap->bp = arg;\n        ap->len = len;\n        cmdp->argv[++cmdp->argc] = NULL;\n}\n\n/*\n * ex_getline --\n *      Return a line from the file.\n *\n * PUBLIC: int ex_getline(SCR *, FILE *, size_t *);\n */\nint\nex_getline(SCR *sp, FILE *fp, size_t *lenp)\n{\n        EX_PRIVATE *exp;\n        size_t off;\n        int ch;\n        char *p;\n\n        exp = EXP(sp);\n        for (errno = 0, off = 0, p = exp->ibp;;) {\n                if (off >= exp->ibp_len) {\n                        BINC_RET(sp, exp->ibp, exp->ibp_len, off + 1);\n                        p = exp->ibp + off;\n                }\n                if ((ch = getc(fp)) == EOF && !feof(fp)) {\n                        if (errno == EINTR) {\n                                errno = 0;\n                                clearerr(fp);\n                                continue;\n                        }\n                        return (1);\n                }\n                if (ch == EOF || ch == '\\n') {\n                        if (ch == EOF && !off)\n                                return (1);\n                        *lenp = off;\n                        return (0);\n                }\n                *p++ = ch;\n                ++off;\n        }\n        /* NOTREACHED */\n}\n\n/*\n * ex_ncheck --\n *      Check for more files to edit.\n *\n * PUBLIC: int ex_ncheck(SCR *, int);\n */\nint\nex_ncheck(SCR *sp, int force)\n{\n        char **ap;\n\n        /*\n         * !!!\n         * Historic practice: quit! or two quit's done in succession\n         * (where ZZ counts as a quit) didn't check for other files.\n         */\n        if (!force && sp->ccnt != sp->q_ccnt + 1 &&\n            sp->cargv != NULL && sp->cargv[1] != NULL) {\n                sp->q_ccnt = sp->ccnt;\n\n                for (ap = sp->cargv + 1; *ap != NULL; ++ap);\n                msgq(sp, M_ERR,\n                    \"%d more files to edit\", (ap - sp->cargv) - 1);\n\n                return (1);\n        }\n        return (0);\n}\n\n/*\n * ex_init --\n *      Init the screen for ex.\n *\n * PUBLIC: int ex_init(SCR *);\n */\nint\nex_init(SCR *sp)\n{\n        GS *gp;\n\n        gp = sp->gp;\n\n        if (gp->scr_screen(sp, SC_EX))\n                return (1);\n        (void)gp->scr_attr(sp, SA_ALTERNATE, 0);\n\n        sp->rows = O_VAL(sp, O_LINES);\n        sp->cols = O_VAL(sp, O_COLUMNS);\n\n        F_CLR(sp, SC_VI);\n        F_SET(sp, SC_EX | SC_SCR_EX);\n        return (0);\n}\n\n/*\n * ex_emsg --\n *      Display a few common ex and vi error messages.\n *\n * PUBLIC: void ex_emsg(SCR *, char *, exm_t);\n */\nvoid\nex_emsg(SCR *sp, char *p, exm_t which)\n{\n        switch (which) {\n        case EXM_EMPTYBUF:\n                msgq(sp, M_ERR, \"Buffer %s is empty\", p);\n                break;\n        case EXM_FILECOUNT:\n                 msgq_str(sp, M_ERR, p,\n                     \"%s: expanded into too many file names\");\n                break;\n        case EXM_NOCANON:\n                msgq(sp, M_ERR,\n                    \"The %s command requires the ex terminal interface\", p);\n                break;\n        case EXM_NOCANON_F:\n                msgq(sp, M_ERR,\n                    \"That form of %s requires the ex terminal interface\",\n                    p);\n                break;\n        case EXM_NOFILEYET:\n                if (p == NULL)\n                        msgq(sp, M_ERR,\n                            \"Command failed, no file read in yet.\");\n                else\n                        msgq(sp, M_ERR,\n        \"The %s command requires that a file have already been read in\", p);\n                break;\n        case EXM_NOPREVBUF:\n                msgq(sp, M_ERR, \"No previous buffer to execute\");\n                break;\n        case EXM_NOPREVRE:\n                msgq(sp, M_ERR, \"No previous regular expression\");\n                break;\n        case EXM_NOSUSPEND:\n                msgq(sp, M_ERR, \"This screen may not be suspended\");\n                break;\n        case EXM_SECURE:\n                msgq(sp, M_ERR,\n\"The %s command is not supported when the secure edit option is set\", p);\n                break;\n        case EXM_SECURE_F:\n                msgq(sp, M_ERR,\n\"That form of %s is not supported when the secure edit option is set\", p);\n                break;\n        case EXM_USAGE:\n                msgq(sp, M_ERR, \"Usage: %s\", p);\n                break;\n        }\n}\n"
  },
  {
    "path": "ex/ex_version.c",
    "content": "/*      $OpenBSD: ex_version.c,v 1.10 2014/11/12 04:28:41 bentley Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1991, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"version.h\"\n\n/*\n * ex_version -- :version\n *      Display the program version.\n *\n * PUBLIC: int ex_version(SCR *, EXCMD *);\n */\n\nint\nex_version(SCR *sp, EXCMD *cmdp)\n{\n        msgq(sp, M_XINFO, VI_VERSION);\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_visual.c",
    "content": "/*      $OpenBSD: ex_visual.c,v 1.10 2016/01/06 22:28:52 millert Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"../vi/vi.h\"\n\n/*\n * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags]\n *      Switch to visual mode.\n *\n * PUBLIC: int ex_visual(SCR *, EXCMD *);\n */\nint\nex_visual(SCR *sp, EXCMD *cmdp)\n{\n        SCR *tsp;\n        size_t len;\n        int pos;\n        char buf[256];\n\n        /* If open option off, disallow visual command. */\n        if (!O_ISSET(sp, O_OPEN)) {\n                msgq(sp, M_ERR,\n            \"The visual command requires that the open option be set\");\n                return (1);\n        }\n\n        /* Move to the address. */\n        sp->lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno;\n\n        /*\n         * Push a command based on the line position flags.  If no\n         * flag specified, the line goes at the top of the screen.\n         */\n        switch (FL_ISSET(cmdp->iflags,\n            E_C_CARAT | E_C_DASH | E_C_DOT | E_C_PLUS)) {\n        case E_C_CARAT:\n                pos = '^';\n                break;\n        case E_C_DASH:\n                pos = '-';\n                break;\n        case E_C_DOT:\n                pos = '.';\n                break;\n        case E_C_PLUS:\n                pos = '+';\n                break;\n        default:\n                sp->frp->lno = sp->lno;\n                sp->frp->cno = 0;\n                (void)nonblank(sp, sp->lno, &sp->cno);\n                F_SET(sp->frp, FR_CURSORSET);\n                goto nopush;\n        }\n\n        if (FL_ISSET(cmdp->iflags, E_C_COUNT))\n                len = snprintf(buf, sizeof(buf),\n                     \"%luz%c%lu\", (unsigned long)sp->lno, pos, cmdp->count);\n        else\n                len = snprintf(buf, sizeof(buf), \"%luz%c\", (unsigned long)sp->lno, pos);\n        if (len >= sizeof(buf))\n                len = sizeof(buf) - 1;\n        (void)v_event_push(sp, NULL, buf, len, CH_NOMAP | CH_QUOTED);\n\n        /*\n         * !!!\n         * Historically, if no line address was specified, the [p#l] flags\n         * caused the cursor to be moved to the last line of the file, which\n         * was then positioned as described above.  This seems useless, so\n         * I haven't implemented it.\n         */\n        switch (FL_ISSET(cmdp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)) {\n        case E_C_HASH:\n                O_SET(sp, O_NUMBER);\n                break;\n        case E_C_LIST:\n                O_SET(sp, O_LIST);\n                break;\n        case E_C_PRINT:\n                break;\n        }\n\nnopush: /*\n         * !!!\n         * You can call the visual part of the editor from within an ex\n         * global command.\n         *\n         * XXX\n         * Historically, undoing a visual session was a single undo command,\n         * i.e. you could undo all of the changes you made in visual mode.\n         * We don't get this right; I'm waiting for the new logging code to\n         * be available.\n         *\n         * It's explicit, don't have to wait for the user, unless there's\n         * already a reason to wait.\n         */\n        if (!F_ISSET(sp, SC_SCR_EXWROTE))\n                F_SET(sp, SC_EX_WAIT_NO);\n\n        if (F_ISSET(sp, SC_EX_GLOBAL)) {\n                /*\n                 * When the vi screen(s) exit, we don't want to lose our hold\n                 * on this screen or this file, otherwise we're going to fail\n                 * fairly spectacularly.\n                 */\n                ++sp->refcnt;\n                ++sp->ep->refcnt;\n\n                /*\n                 * Fake up a screen pointer -- vi doesn't get to change our\n                 * underlying file, regardless.\n                 */\n                tsp = sp;\n                if (vi(&tsp))\n                        return (1);\n\n                /*\n                 * !!!\n                 * Historically, if the user exited the vi screen(s) using an\n                 * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if\n                 * they exited vi using the Q command that ex continued.  Some\n                 * early versions of nvi continued in ex regardless, but users\n                 * didn't like the semantic.\n                 *\n                 * Reset the screen.\n                 */\n                if (ex_init(sp))\n                        return (1);\n\n                /* Move out of the vi screen. */\n                (void)ex_puts(sp, \"\\n\");\n        } else {\n                F_CLR(sp, SC_EX | SC_SCR_EX);\n                F_SET(sp, SC_VI);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "ex/ex_write.c",
    "content": "/*      $OpenBSD: ex_write.c,v 1.13 2016/01/06 22:28:52 millert Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n\nenum which {WN, WQ, WRITE, XIT};\nstatic int exwr(SCR *, EXCMD *, enum which);\n\n/*\n * ex_wn --     :wn[!] [>>] [file]\n *      Write to a file and switch to the next one.\n *\n * PUBLIC: int ex_wn(SCR *, EXCMD *);\n */\nint\nex_wn(SCR *sp, EXCMD *cmdp)\n{\n        if (exwr(sp, cmdp, WN))\n                return (1);\n        if (file_m3(sp, 0))\n                return (1);\n\n        /* The file name isn't a new file to edit. */\n        cmdp->argc = 0;\n\n        return (ex_next(sp, cmdp));\n}\n\n/*\n * ex_wq --     :wq[!] [>>] [file]\n *      Write to a file and quit.\n *\n * PUBLIC: int ex_wq(SCR *, EXCMD *);\n */\nint\nex_wq(SCR *sp, EXCMD *cmdp)\n{\n        int force;\n\n        if (exwr(sp, cmdp, WQ))\n                return (1);\n        if (file_m3(sp, 0))\n                return (1);\n\n        force = FL_ISSET(cmdp->iflags, E_C_FORCE);\n\n        if (ex_ncheck(sp, force))\n                return (1);\n\n        F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);\n        return (0);\n}\n\n/*\n * ex_write --  :write[!] [>>] [file]\n *              :write [!] [cmd]\n *      Write to a file.\n *\n * PUBLIC: int ex_write(SCR *, EXCMD *);\n */\nint\nex_write(SCR *sp, EXCMD *cmdp)\n{\n        return (exwr(sp, cmdp, WRITE));\n}\n\n/*\n * ex_xit -- :x[it]! [file]\n *      Write out any modifications and quit.\n *\n * PUBLIC: int ex_xit(SCR *, EXCMD *);\n */\nint\nex_xit(SCR *sp, EXCMD *cmdp)\n{\n        int force;\n\n        NEEDFILE(sp, cmdp);\n\n        if (F_ISSET(sp->ep, F_MODIFIED) && exwr(sp, cmdp, XIT))\n                return (1);\n        if (file_m3(sp, 0))\n                return (1);\n\n        force = FL_ISSET(cmdp->iflags, E_C_FORCE);\n\n        if (ex_ncheck(sp, force))\n                return (1);\n\n        F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);\n        return (0);\n}\n\n/*\n * exwr --\n *      The guts of the ex write commands.\n */\nstatic int\nexwr(SCR *sp, EXCMD *cmdp, enum which cmd)\n{\n        MARK rm;\n        int flags;\n        char *name, *p = NULL;\n\n        NEEDFILE(sp, cmdp);\n\n        /* All write commands can have an associated '!'. */\n        LF_INIT(FS_POSSIBLE);\n        if (FL_ISSET(cmdp->iflags, E_C_FORCE))\n                LF_SET(FS_FORCE);\n\n        /* Skip any leading whitespace. */\n        if (cmdp->argc != 0)\n                for (p = cmdp->argv[0]->bp; isblank(*p); ++p)\n                        ;\n\n        /* If \"write !\" it's a pipe to a utility. */\n        if (cmdp->argc != 0 && cmd == WRITE && *p == '!') {\n                /* Secure means no shell access. */\n                if (O_ISSET(sp, O_SECURE)) {\n                        ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F);\n                        return (1);\n                }\n\n                /* Expand the argument. */\n                for (++p; isblank(*p); ++p);\n                if (*p == '\\0') {\n                        ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);\n                        return (1);\n                }\n                if (argv_exp1(sp, cmdp, p, strlen(p), 1))\n                        return (1);\n\n                /*\n                 * Historically, vi waited after a write filter even if there\n                 * wasn't any output from the command.  People complained when\n                 * nvi waited only if there was output, wanting the visual cue\n                 * that the program hadn't written anything.\n                 */\n                F_SET(sp, SC_EX_WAIT_YES);\n\n                /*\n                 * !!!\n                 * Ignore the return cursor position, the cursor doesn't\n                 * move.\n                 */\n                if (ex_filter(sp, cmdp, &cmdp->addr1,\n                    &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE))\n                        return (1);\n\n                /* Ex terminates with a bang, even if the command fails. */\n                if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT))\n                        (void)ex_puts(sp, \"!\\n\");\n\n                return (0);\n        }\n\n        /* Set the FS_ALL flag if we're writing the entire file. */\n        if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1))\n                LF_SET(FS_ALL);\n\n        /* If \"write >>\" it's an append to a file. */\n        if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') {\n                LF_SET(FS_APPEND);\n\n                /* Skip \">>\" and whitespace. */\n                for (p += 2; isblank(*p); ++p);\n        }\n\n        /* If no other arguments, just write the file back. */\n        if (cmdp->argc == 0 || *p == '\\0')\n                return (file_write(sp,\n                    &cmdp->addr1, &cmdp->addr2, NULL, flags));\n\n        /* Build an argv so we get an argument count and file expansion. */\n        if (argv_exp2(sp, cmdp, p, strlen(p)))\n                return (1);\n\n        /*\n         *  0 args: impossible.\n         *  1 args: impossible (I hope).\n         *  2 args: read it.\n         * >2 args: object, too many args.\n         *\n         * The 1 args case depends on the argv_sexp() function refusing\n         * to return success without at least one non-blank character.\n         */\n        switch (cmdp->argc) {\n        case 0:\n        case 1:\n                abort();\n                /* NOTREACHED */\n        case 2:\n                name = cmdp->argv[1]->bp;\n\n                /*\n                 * !!!\n                 * Historically, the read and write commands renamed\n                 * \"unnamed\" files, or, if the file had a name, set\n                 * the alternate file name.\n                 */\n                if (F_ISSET(sp->frp, FR_TMPFILE) &&\n                    !F_ISSET(sp->frp, FR_EXNAMED)) {\n                        if ((p = v_strdup(sp,\n                            cmdp->argv[1]->bp, cmdp->argv[1]->len)) != NULL) {\n                                free(sp->frp->name);\n                                sp->frp->name = p;\n                        }\n                        /*\n                         * The file has a real name, it's no longer a\n                         * temporary, clear the temporary file flags.\n                         *\n                         * !!!\n                         * If we're writing the whole file, FR_NAMECHANGE\n                         * will be cleared by the write routine -- this is\n                         * historic practice.\n                         */\n                        F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE);\n                        F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED);\n\n                        /* Notify the screen. */\n                        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n                } else\n                        set_alt_name(sp, name);\n                break;\n        default:\n                ex_emsg(sp, p, EXM_FILECOUNT);\n                return (1);\n        }\n\n        return (file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags));\n}\n\n/*\n * ex_writefp --\n *      Write a range of lines to a FILE *.\n *\n * PUBLIC: int ex_writefp(SCR *,\n * PUBLIC:    char *, FILE *, MARK *, MARK *, unsigned long *, unsigned long *, int);\n */\nint\nex_writefp(SCR *sp, char *name, FILE *fp, MARK *fm, MARK *tm, unsigned long *nlno,\n    unsigned long *nch, int silent)\n{\n        struct stat sb;\n        GS *gp;\n        unsigned long ccnt;                    /* XXX: can't print off_t portably. */\n        recno_t fline, tline, lcnt;\n        size_t len;\n        int rval;\n        char *msg, *p;\n\n        gp = sp->gp;\n        fline = fm->lno;\n        tline = tm->lno;\n\n        if (nlno != NULL) {\n                *nch = 0;\n                *nlno = 0;\n        }\n\n        /*\n         * The vi filter code has multiple processes running simultaneously,\n         * and one of them calls ex_writefp().  The \"unsafe\" function calls\n         * in this code are to db_get() and msgq().  Db_get() is safe, see\n         * the comment in ex_filter.c:ex_filter() for details.  We don't call\n         * msgq if the multiple process bit in the EXF is set.\n         *\n         * !!!\n         * Historic vi permitted files of 0 length to be written.  However,\n         * since the way vi got around dealing with \"empty\" files was to\n         * always have a line in the file no matter what, it wrote them as\n         * files of a single, empty line.  We write empty files.\n         *\n         * \"Alex, I'll take vi trivia for $1000.\"\n         */\n        ccnt = 0;\n        lcnt = 0;\n        msg = \"Writing...\";\n        if (tline != 0)\n                for (; fline <= tline; ++fline, ++lcnt) {\n                        /* Caller has to provide any interrupt message. */\n                        if ((lcnt + 1) % INTERRUPT_CHECK == 0) {\n                                if (INTERRUPTED(sp))\n                                        break;\n                                if (!silent) {\n                                        gp->scr_busy(sp, msg, msg == NULL ?\n                                            BUSY_UPDATE : BUSY_ON);\n                                        msg = NULL;\n                                }\n                        }\n                        if (db_get(sp, fline, DBG_FATAL, &p, &len))\n                                goto err;\n                        if (fwrite(p, 1, len, fp) != len)\n                                goto err;\n                        ccnt += len;\n                        if (putc('\\n', fp) != '\\n')\n                                break;\n                        ++ccnt;\n                }\n\n        if (fflush(fp))\n                goto err;\n        /*\n         * XXX\n         * I don't trust NFS -- check to make sure that we're talking to\n         * a regular file and sync so that NFS is forced to flush.\n         */\n        if (!fstat(fileno(fp), &sb) &&\n            S_ISREG(sb.st_mode) && fsync(fileno(fp)))\n                goto err;\n\n        if (fclose(fp)) {\n                fp = NULL;\n                goto err;\n        }\n\n        rval = 0;\n        if (0) {\nerr:            if (!F_ISSET(sp->ep, F_MULTILOCK))\n                        msgq_str(sp, M_SYSERR, name, \"%s\");\n                if (fp != NULL)\n                        (void)fclose(fp);\n                rval = 1;\n        }\n\n        if (!silent)\n                gp->scr_busy(sp, NULL, BUSY_OFF);\n\n        /* Report the possibly partial transfer. */\n        if (nlno != NULL) {\n                *nch = ccnt;\n                *nlno = lcnt;\n        }\n        return (rval);\n}\n"
  },
  {
    "path": "ex/ex_yank.c",
    "content": "/*      $OpenBSD: ex_yank.c,v 1.6 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_yank -- :[line [,line]] ya[nk] [buffer] [count]\n *      Yank the lines into a buffer.\n *\n * PUBLIC: int ex_yank(SCR *, EXCMD *);\n */\nint\nex_yank(SCR *sp, EXCMD *cmdp)\n{\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * !!!\n         * Historically, yanking lines in ex didn't count toward the\n         * number-of-lines-yanked report.\n         */\n        return (cut(sp,\n            FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL,\n            &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE));\n}\n"
  },
  {
    "path": "ex/ex_z.c",
    "content": "/*      $OpenBSD: ex_z.c,v 1.8 2015/03/29 01:04:23 bcallah Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n\n/*\n * ex_z -- :[line] z [^-.+=] [count] [flags]\n *      Adjust window.\n *\n * PUBLIC: int ex_z(SCR *, EXCMD *);\n */\nint\nex_z(SCR *sp, EXCMD *cmdp)\n{\n        MARK mark_abs;\n        recno_t cnt, equals, lno;\n        int eofcheck;\n\n        NEEDFILE(sp, cmdp);\n\n        /*\n         * !!!\n         * If no count specified, use either two times the size of the\n         * scrolling region, or the size of the window option.  POSIX\n         * 1003.2 claims that the latter is correct, but historic ex/vi\n         * documentation and practice appear to use the scrolling region.\n         * I'm using the window size as it means that the entire screen\n         * is used instead of losing a line to roundoff.  Note, we drop\n         * a line from the cnt if using the window size to leave room for\n         * the next ex prompt.\n         */\n        if (FL_ISSET(cmdp->iflags, E_C_COUNT))\n                cnt = cmdp->count;\n        else\n                cnt = O_VAL(sp, O_WINDOW) - 1;\n\n        equals = 0;\n        eofcheck = 0;\n        lno = cmdp->addr1.lno;\n\n        switch (FL_ISSET(cmdp->iflags,\n            E_C_CARAT | E_C_DASH | E_C_DOT | E_C_EQUAL | E_C_PLUS)) {\n        case E_C_CARAT:         /* Display cnt * 2 before the line. */\n                eofcheck = 1;\n                if (lno > cnt * 2)\n                        cmdp->addr1.lno = (lno - cnt * 2) + 1;\n                else\n                        cmdp->addr1.lno = 1;\n                cmdp->addr2.lno = (cmdp->addr1.lno + cnt) - 1;\n                break;\n        case E_C_DASH:          /* Line goes at the bottom of the screen. */\n                cmdp->addr1.lno = lno > cnt ? (lno - cnt) + 1 : 1;\n                cmdp->addr2.lno = lno;\n                break;\n        case E_C_DOT:           /* Line goes in the middle of the screen. */\n                /*\n                 * !!!\n                 * Historically, the \"middleness\" of the line overrode the\n                 * count, so that \"3z.19\" or \"3z.20\" would display the first\n                 * 12 lines of the file, i.e. (N - 1) / 2 lines before and\n                 * after the specified line.\n                 */\n                eofcheck = 1;\n                cnt = (cnt - 1) / 2;\n                cmdp->addr1.lno = lno > cnt ? lno - cnt : 1;\n                cmdp->addr2.lno = lno + cnt;\n\n                /*\n                 * !!!\n                 * Historically, z. set the absolute cursor mark.\n                 */\n                mark_abs.lno = sp->lno;\n                mark_abs.cno = sp->cno;\n                (void)mark_set(sp, ABSMARK1, &mark_abs, 1);\n                break;\n        case E_C_EQUAL:         /* Center with hyphens. */\n                /*\n                 * !!!\n                 * Strangeness.  The '=' flag is like the '.' flag (see the\n                 * above comment, it applies here as well) but with a special\n                 * little hack.  Print out lines of hyphens before and after\n                 * the specified line.  Additionally, the cursor remains set\n                 * on that line.\n                 */\n                eofcheck = 1;\n                cnt = (cnt - 1) / 2;\n                cmdp->addr1.lno = lno > cnt ? lno - cnt : 1;\n                cmdp->addr2.lno = lno - 1;\n                if (ex_pr(sp, cmdp))\n                        return (1);\n                (void)ex_puts(sp, \"----------------------------------------\\n\");\n                cmdp->addr2.lno = cmdp->addr1.lno = equals = lno;\n                if (ex_pr(sp, cmdp))\n                        return (1);\n                (void)ex_puts(sp, \"----------------------------------------\\n\");\n                cmdp->addr1.lno = lno + 1;\n                cmdp->addr2.lno = (lno + cnt) - 1;\n                break;\n        default:\n                /* If no line specified, move to the next one. */\n                if (F_ISSET(cmdp, E_ADDR_DEF))\n                        ++lno;\n                /* FALLTHROUGH */\n        case E_C_PLUS:          /* Line goes at the top of the screen. */\n                eofcheck = 1;\n                cmdp->addr1.lno = lno;\n                cmdp->addr2.lno = (lno + cnt) - 1;\n                break;\n        }\n\n        if (eofcheck) {\n                if (db_last(sp, &lno))\n                        return (1);\n                if (cmdp->addr2.lno > lno)\n                        cmdp->addr2.lno = lno;\n        }\n\n        if (ex_pr(sp, cmdp))\n                return (1);\n        if (equals)\n                sp->lno = equals;\n        return (0);\n}\n"
  },
  {
    "path": "ex/script.h",
    "content": "/*      $OpenBSD: script.h,v 1.4 2014/11/12 16:29:04 millert Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)script.h    10.2 (Berkeley) 3/6/96\n */\n\nstruct _script {\n        pid_t    sh_pid;                /* Shell pid.            */\n        int      sh_master;             /* Master pty fd.        */\n        int      sh_slave;              /* Slave pty fd.         */\n        char    *sh_prompt;             /* Prompt.               */\n        size_t   sh_prompt_len;         /* Prompt length.        */\n        char     sh_name[64];           /* Pty name              */\n        struct   winsize sh_win;        /* Window size.          */\n        struct   termios sh_term;       /* Terminal information. */\n};\n"
  },
  {
    "path": "ex/tag.h",
    "content": "/*      $OpenBSD: tag.h,v 1.7 2015/11/19 07:53:31 bentley Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 1994, 1996\n *      Rob Mayoff.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)tag.h       10.5 (Berkeley) 5/15/96\n */\n\n/*\n * Tag file information.  One of these is maintained per tag file, linked\n * from the EXPRIVATE structure.\n */\nstruct _tagf {                  /* Tag files. */\n        TAILQ_ENTRY(_tagf) q;   /* Linked list of tag files. */\n        char    *name;          /* Tag file name. */\n        int      errnum;        /* Errno. */\n\n#define TAGF_ERR        0x01    /* Error occurred. */\n#define TAGF_ERR_WARN   0x02    /* Error reported. */\n        u_int8_t flags;\n};\n\n/*\n * Tags are structured internally as follows:\n *\n * +----+    +----+     +----+     +----+\n * | EP | -> | Q1 | <-- | T1 | <-- | T2 |\n * +----+    +----+ --> +----+ --> +----+\n *           |\n *           +----+     +----+\n *           | Q2 | <-- | T1 |\n *           +----+ --> +----+\n *           |\n *           +----+     +----+\n *           | Q3 | <-- | T1 |\n *           +----+ --> +----+\n *\n * Each Q is a TAGQ, or tag \"query\", which is the result of one tag.\n * Each Q references one or more TAG's, or tagged file locations.\n *\n * tag:         put a new Q at the head (^])\n * tagnext:     T1 -> T2 inside Q       (^N)\n * tagprev:     T2 -> T1 inside Q       (^P)\n * tagpop:      discard Q               (^T)\n * tagtop:      discard all Q\n */\nstruct _tag {                   /* Tag list. */\n        TAILQ_ENTRY(_tag) q;    /* Linked list of tags. */\n\n                                /* Tag pop/return information. */\n        FREF    *frp;           /* Saved file. */\n        recno_t  lno;           /* Saved line number. */\n        size_t   cno;           /* Saved column number. */\n\n        char    *fname;         /* Filename. */\n        size_t   fnlen;         /* Filename length. */\n        recno_t  slno;          /* Search line number. */\n        char    *search;        /* Search string. */\n        size_t   slen;          /* Search string length. */\n\n        char     buf[1];        /* Variable length buffer. */\n};\n\nstruct _tagq {                  /* Tag queue. */\n        TAILQ_ENTRY(_tagq) q;   /* Linked list of tag queues. */\n                                /* This queue's tag list. */\n        TAILQ_HEAD(_tagqh, _tag) tagq;\n\n        TAG     *current;       /* Current TAG within the queue. */\n\n        char    *tag;           /* Tag string. */\n        size_t   tlen;          /* Tag string length. */\n\n        u_int8_t flags;\n\n        char     buf[1];        /* Variable length buffer. */\n};\n"
  },
  {
    "path": "ex/version.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _VERSION_H\n# define _VERSION_H\n\n# define VI_VERSION \\\n        \"Version 7.8.33-dev (OpenVi) 08/23/2025\"\n\n#endif /* ifndef _VERSION_H */\n"
  },
  {
    "path": "include/bitstring.h",
    "content": "/*      $OpenBSD: bitstring.h,v 1.6 2020/05/10 00:56:06 guenther Exp $  */\n/*      $NetBSD:  bitstring.h,v 1.5 1997/05/14 15:49:55 pk Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1989, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Paul Vixie.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)bitstring.h 8.1 (Berkeley) 7/19/93\n */\n\n#ifndef _BITSTRING_H_\n# define _BITSTRING_H_\n\n/* modified for SV/AT and bitstring bugfix by M.R.Murphy, 11oct91\n * bitstr_size changed gratuitously, but shorter\n * bit_alloc   spelling error fixed\n * the following were efficient, but didn't work, they've been made to\n * work, but are no longer as efficient :-)\n * bit_nclear, bit_nset, bit_ffc, bit_ffs\n */\ntypedef unsigned char bitstr_t;\n\n/* internal macros */\n                                /* byte of the bitstring bit is in */\n# define _bit_byte(bit) \\\n        ((bit) >> 3)\n\n                                /* mask for the bit within its byte */\n# define _bit_mask(bit) \\\n        (1 << ((bit)&0x7))\n\n/* external macros */\n                                /* bytes in a bitstring of nbits bits */\n# define bitstr_size(nbits) \\\n        (((nbits) + 7) >> 3)\n\n                                /* allocate a bitstring */\n# define bit_alloc(nbits) \\\n        (bitstr_t *)calloc((size_t)bitstr_size(nbits), sizeof(bitstr_t))\n\n                                /* allocate a bitstring on the stack */\n# define bit_decl(name, nbits) \\\n        ((name)[bitstr_size(nbits)])\n\n                                /* is bit N of bitstring name set? */\n# define bit_test(name, bit) \\\n        ((name)[_bit_byte(bit)] & _bit_mask(bit))\n\n                                /* set bit N of bitstring name */\n# define bit_set(name, bit) \\\n        ((name)[_bit_byte(bit)] |= _bit_mask(bit))\n\n                                /* clear bit N of bitstring name */\n# define bit_clear(name, bit) \\\n        ((name)[_bit_byte(bit)] &= ~_bit_mask(bit))\n\n                                /* clear bits start ... stop in bitstring */\n# define bit_nclear(name, start, stop) do { \\\n        register bitstr_t *__name = (name); \\\n        register int __start = (start), __stop = (stop); \\\n        while (__start <= __stop) { \\\n                bit_clear(__name, __start); \\\n                __start++; \\\n                } \\\n} while(0)\n\n                                /* set bits start ... stop in bitstring */\n# define bit_nset(name, start, stop) do { \\\n        register bitstr_t *__name = (name); \\\n        register int __start = (start), __stop = (stop); \\\n        while (__start <= __stop) { \\\n                bit_set(__name, __start); \\\n                __start++; \\\n                } \\\n} while(0)\n\n                                /* find first bit clear in name */\n# define bit_ffc(name, nbits, value) do { \\\n        register bitstr_t *__name = (name); \\\n        register int __bit, __nbits = (nbits), __value = -1; \\\n        for (__bit = 0; __bit < __nbits; ++__bit) \\\n                if (!bit_test(__name, __bit)) { \\\n                        __value = __bit; \\\n                        break; \\\n                } \\\n        *(value) = __value; \\\n} while(0)\n\n                                /* find first bit set in name */\n# define bit_ffs(name, nbits, value) do { \\\n        register bitstr_t *__name = (name); \\\n        register int __bit, __nbits = (nbits), __value = -1; \\\n        for (__bit = 0; __bit < __nbits; ++__bit) \\\n                if (bit_test(__name, __bit)) { \\\n                        __value = __bit; \\\n                        break; \\\n                } \\\n        *(value) = __value; \\\n} while(0)\n\n#endif /* !_BITSTRING_H_ */\n"
  },
  {
    "path": "include/bsd_db.h",
    "content": "/*      $OpenBSD: db.h,v 1.12 2015/10/17 21:48:42 guenther Exp $        */\n/*      $NetBSD:  db.h,v 1.13 1994/10/26 00:55:48 cgd Exp $             */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)db.h        8.7 (Berkeley) 6/16/94\n */\n\n#ifndef _DB_H_\n# define _DB_H_\n\n# include \"../include/compat.h\"\n\n# include <sys/types.h>\n\n# include <limits.h>\n\n# undef open\n\n# define RET_ERROR      -1              /* Return values. */\n# define RET_SUCCESS     0\n# define RET_SPECIAL     1\n\n# define MAX_PAGE_NUMBER        0xffffffff      /* >= # of pages in a file */\ntypedef u_int32_t               pgno_t;\n# define MAX_PAGE_OFFSET        65535           /* >= # of bytes in a page */\ntypedef u_int16_t               indx_t;\n# define MAX_REC_NUMBER         0xffffffff      /* >= # of records in a tree */\ntypedef u_int32_t               recno_t;\n\n/* Key/data structure -- a Data-Base Thang. */\ntypedef struct {\n        void    *data;                  /* data        */\n        size_t   size;                  /* data length */\n} DBT;\n\n/* Routine flags. */\n# define R_CURSOR       1               /* del, put, seq      */\n# define __R_UNUSED     2               /* UNUSED             */\n# define R_FIRST        3               /* seq                */\n# define R_IAFTER       4               /* put (RECNO)        */\n# define R_IBEFORE      5               /* put (RECNO)        */\n# define R_LAST         6               /* seq (BTREE, RECNO) */\n# define R_NEXT         7               /* seq                */\n# define R_NOOVERWRITE  8               /* put                */\n# define R_PREV         9               /* seq (BTREE, RECNO) */\n# define R_SETCURSOR    10              /* put (RECNO)        */\n# define R_RECNOSYNC    11              /* sync (RECNO)       */\n\ntypedef enum { DB_BTREE, DB_HASH, DB_RECNO } DBTYPE;\n\n/*\n * !!!\n * The following flags are included in the dbopen(3) call as part of the\n * open(2) flags.  In order to avoid conflicts with the open flags, start\n * at the top of the 16 or 32-bit number space and work our way down.  If\n * the open flags were significantly expanded in the future, it could be\n * a problem.  Wish I'd left another flags word in the dbopen call.\n *\n * !!!\n * None of this stuff is implemented yet.  The only reason that it's here\n * is so that the access methods can skip copying the key/data pair when\n * the DB_LOCK flag isn't set.\n */\n# if UINT_MAX > 65535\n#  define DB_LOCK               0x20000000      /* Do locking.        */\n#  define DB_SHMEM              0x40000000      /* Use shared memory. */\n#  define DB_TXN                0x80000000      /* Do transactions.   */\n# else\n#  define DB_LOCK                   0x2000      /* Do locking.        */\n#  define DB_SHMEM                  0x4000      /* Use shared memory. */\n#  define DB_TXN                    0x8000      /* Do transactions.   */\n# endif /* if UINT_MAX > 65535 */\n\n/* Access method description structure. */\ntypedef struct __db {\n        DBTYPE type;                    /* Underlying db type. */\n        int (*close)(struct __db *);\n        int (*del)(const struct __db *, const DBT *, unsigned int);\n        int (*get)(const struct __db *, const DBT *, DBT *, unsigned int);\n        int (*put)(const struct __db *, DBT *, const DBT *, unsigned int);\n        int (*seq)(const struct __db *, DBT *, DBT *, unsigned int);\n        int (*sync)(const struct __db *, unsigned int);\n        void *internal;                 /* Access method private. */\n        int (*fd)(const struct __db *);\n} DB;\n\n# define BTREEMAGIC     0x053162\n# define BTREEVERSION   3\n\n/* Structure used to pass parameters to the btree routines. */\ntypedef struct {\n# define R_DUP          0x01            /* duplicate keys        */\n        unsigned long   flags;          /* ...                   */\n        unsigned int    cachesize;      /* bytes to cache        */\n        int             maxkeypage;     /* maximum keys per page */\n        int             minkeypage;     /* minimum keys per page */\n        unsigned int    psize;          /* page size             */\n        int             (*compare)      /* comparison function   */\n                            (const DBT *, const DBT *); /* ...   */\n        size_t          (*prefix)       /* prefix function       */\n                            (const DBT *, const DBT *); /* ...   */\n        int             lorder;         /* byte order            */\n} BTREEINFO;\n\n# define HASHMAGIC      0x061561\n# define HASHVERSION    2\n\n/* Structure used to pass parameters to the hashing routines. */\ntypedef struct {\n        unsigned int    bsize;          /* bucket size        */\n        unsigned int    ffactor;        /* fill factor        */\n        unsigned int    nelem;          /* number of elements */\n        unsigned int    cachesize;      /* bytes to cache     */\n        u_int32_t                       /* hash function      */\n                        (*hash)(const void *, size_t); /* ... */\n        int             lorder;         /* byte order         */\n} HASHINFO;\n\n/* Structure used to pass parameters to the record routines. */\ntypedef struct {\n# define R_FIXEDLEN             0x01    /* fixed-length records      */\n# define R_NOKEY                0x02    /* key not required          */\n# define R_SNAPSHOT             0x04    /* snapshot the input        */\n        unsigned long   flags;          /* ...                       */\n        unsigned int    cachesize;      /* bytes to cache            */\n        unsigned int    psize;          /* page size                 */\n        int             lorder;         /* byte order                */\n        size_t          reclen;         /* record length             */\n                                        /* (fixed-length records)    */\n        unsigned char   bval;           /* delimiting byte           */\n                                        /* (variable-length records) */\n        char    *bfname;                /* btree file name           */\n} RECNOINFO;\n\nDB *dbopen(const char *, int, int, DBTYPE, const void *);\n#endif /* !_DB_H_ */\n"
  },
  {
    "path": "include/bsd_err.h",
    "content": "/*      $OpenBSD: err.h,v 1.13 2015/08/31 02:53:56 guenther Exp $       */\n/*      $NetBSD:  err.h,v 1.11 1994/10/26 00:55:52 cgd Exp $            */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)err.h       8.1 (Berkeley) 6/2/93\n */\n\n#ifndef _COMPAT_ERR_H_\n# define _COMPAT_ERR_H_\n\n# include <stdarg.h> /* for va_list */\n\nvoid openbsd_errc(int, int, const char *, ...)\n                  __attribute__((__format__ (printf, 3, 4)));\nvoid openbsd_verrc(int, int, const char *, va_list)\n                   __attribute__((__format__ (printf, 3, 0)));\nvoid openbsd_warnc(int, const char *, ...)\n                   __attribute__((__format__ (printf, 2, 3)));\nvoid openbsd_vwarnc(int, const char *, va_list)\n                    __attribute__((__format__ (printf, 2, 0)));\n#endif /* !_COMPAT_ERR_H_ */\n"
  },
  {
    "path": "include/bsd_fcntl.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include_next <fcntl.h>\n\n#ifndef _COMPAT_FCNTL_H_\n# define _COMPAT_FCNTL_H_\n\n# define open openbsd_open\n\n# ifdef O_EXLOCK\n#  undef O_EXLOCK\n# endif /* ifdef O_EXLOCK */\n# define O_EXLOCK 10000000\n\n# ifdef O_SHLOCK\n#  undef O_SHLOCK\n# endif /* ifdef O_SHLOCK */\n# define O_SHLOCK 20000000\n\nint openbsd_open(const char *, int, ...);\n\n#endif /* !_COMPAT_FCNTL_H_ */\n"
  },
  {
    "path": "include/bsd_regex.h",
    "content": "/*      $OpenBSD: regex.h,v 1.7 2012/12/05 23:19:57 deraadt Exp $       */\n/*      $NetBSD:  regex.h,v 1.4.6.1 1996/06/10 18:57:07 explorer Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992 Henry Spencer.\n * Copyright (c) 1992, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer of the University of Toronto.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)regex.h     8.1 (Berkeley) 6/2/93\n */\n\n#ifndef _REGEX_H_\n# define _REGEX_H_\n\n# include <sys/types.h>\n\n# define regoff_t       openbsd_regoff_t\n# define regex_t        openbsd_regex_t\n# define regmatch_t     openbsd_regmatch_t\n\n/* types */\ntypedef off_t regoff_t;\n\ntypedef struct {\n        int re_magic;\n        size_t re_nsub;         /* number of parenthesized subexpressions */\n        const char *re_endp;    /* end pointer for REG_PEND               */\n        struct re_guts *re_g;   /* none of your business :-)              */\n} regex_t;\n\ntypedef struct {\n        regoff_t rm_so;         /* start of match */\n        regoff_t rm_eo;         /* end of match   */\n} regmatch_t;\n\n/* regcomp() flags */\n# define REG_BASIC      0000\n# define REG_EXTENDED   0001\n# define REG_ICASE      0002\n# define REG_NOSUB      0004\n# define REG_NEWLINE    0010\n# define REG_NOSPEC     0020\n# define REG_PEND       0040\n# define REG_DUMP       0200\n\n/* regerror() flags */\n# define REG_NOMATCH     1\n# define REG_BADPAT      2\n# define REG_ECOLLATE    3\n# define REG_ECTYPE      4\n# define REG_EESCAPE     5\n# define REG_ESUBREG     6\n# define REG_EBRACK      7\n# define REG_EPAREN      8\n# define REG_EBRACE      9\n# define REG_BADBR      10\n# define REG_ERANGE     11\n# define REG_ESPACE     12\n# define REG_BADRPT     13\n# define REG_EMPTY      14\n# define REG_ASSERT     15\n# define REG_INVARG     16\n# define REG_ATOI       255     /* convert name to number (!) */\n# define REG_ITOA       0400    /* convert number to name (!) */\n\n/* regexec() flags */\n# define REG_NOTBOL     00001\n# define REG_NOTEOL     00002\n# define REG_STARTEND   00004\n# define REG_TRACE      00400   /* tracing of execution       */\n# define REG_LARGE      01000   /* force large representation */\n# define REG_BACKR      02000   /* force use of backref code  */\n\n# define regcomp        openbsd_regcomp\n# define regerror       openbsd_regerror\n# define regexec        openbsd_regexec\n# define regfree        openbsd_regfree\n\nint     regcomp(regex_t *, const char *, int);\nsize_t  regerror(int, const regex_t *, char *, size_t);\nint     regexec(const regex_t *, const char *, size_t, regmatch_t [], int);\nvoid    regfree(regex_t *);\n\n#endif /* !_REGEX_H_ */\n"
  },
  {
    "path": "include/bsd_stdlib.h",
    "content": "/*      $OpenBSD: stdlib.h,v 1.67 2016/09/20 21:10:22 fcambus Exp $     */\n/*      $NetBSD:  stdlib.h,v 1.25 1995/12/27 21:19:08 jtc Exp $         */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)stdlib.h    5.13 (Berkeley) 6/4/91\n */\n\n#ifndef _COMPAT_STDLIB_H_\n# define _COMPAT_STDLIB_H_\n\n# ifdef __solaris__\n#  undef _TIMESPEC_UTIL_H\n#  define _TIMESPEC_UTIL_H 1\n# endif /* ifdef __solaris__ */\n\n# include <sys/types.h>\n# include <stdint.h>\n\nextern char  *__progname;\nconst  char  *bsd_getprogname(void);\n\nvoid *openbsd_reallocarray(void *, size_t, size_t);\nvoid qsort(void *, size_t, size_t, int (*)(const void *, const void *));\nlong long strtonum(const char *, long long, long long, const char **);\n\n#endif /* _COMPAT_STDLIB_H_ */\n\n#include_next <stdlib.h>\n"
  },
  {
    "path": "include/bsd_string.h",
    "content": "/*      $OpenBSD: string.h,v 1.31 2016/09/09 18:12:37 millert Exp $     */\n/*      $NetBSD:  string.h,v 1.6  1994/10/26 00:56:30 cgd Exp $         */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1990 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)string.h    5.10 (Berkeley) 3/9/91\n */\n\n#ifndef _COMPAT_STRING_H_\n# define _COMPAT_STRING_H_\n\nsize_t openbsd_strlcpy(char *, const char *, size_t);\nsize_t openbsd_strlcat(char *dst, const char *src, size_t dsize);\n\n# ifndef __OpenBSD__\n\n#  include <sys/types.h>\n\n# endif /* ifndef __OpenBSD__ */\n\n#endif /* _COMPAT_STRING_H_ */\n\n#include_next <string.h>\n"
  },
  {
    "path": "include/bsd_termios.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1988, 1989, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)termios.h   8.3 (Berkeley) 3/28/94\n */\n\n#include_next <termios.h>\n\n#ifndef _COMPAT_TERMIOS_H\n# define _COMPAT_TERMIOS_H\n\n# ifndef TCSASOFT\n#  define TCSASOFT 0\n# endif /* ifndef TCSASOFT */\n\n# ifndef CCEQ\n#  define CCEQ(val, c) (c == val ? val != _POSIX_VDISABLE : 0)\n# endif /* ifndef CCEQ */\n\n#endif /* _COMPAT_TERMIOS_H */\n"
  },
  {
    "path": "include/bsd_unistd.h",
    "content": "/*      $OpenBSD: unistd.h,v 1.103    2016/09/12 19:36:26 guenther Exp $ */\n/*      $NetBSD:  unistd.h,v 1.26.4.1 1996/05/28 02:31:51 mrg Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)unistd.h    5.13 (Berkeley) 6/17/91\n */\n\n#ifndef _COMPAT_UNISTD_H_\n# define _COMPAT_UNISTD_H_\n\nint      openbsd_pledge(const char *, const char *);\nint      openbsd_getopt(int, char * const *, const char *);\nint      openbsd_getopt(int, char * const *, const char *);\nextern   char *openbsd_optarg;\nextern   int openbsd_opterr, openbsd_optind, \\\n             openbsd_optopt, openbsd_optreset;\n\n# ifdef __OpenBSD__\n#  include <sys/types.h>\n# else\n\n#  include <sys/types.h>\n\n#  define STDIN_FILENO  0       /* standard input file descriptor  */\n#  define STDOUT_FILENO 1       /* standard output file descriptor */\n#  define STDERR_FILENO 2       /* standard error file descriptor  */\n\n# endif /* _COMPAT_UNISTD_H_ */\n\n#endif /* ifdef __OpenBSD__ */\n\n#define _COMPAT_GETOPT_H_       /* glibc includes getopt.h */\n#include_next <unistd.h>\n#undef  _COMPAT_GETOPT_H_\n"
  },
  {
    "path": "include/com_extern.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nint cut(SCR *, CHAR_T *, MARK *, MARK *, int);\nint cut_line(SCR *, recno_t, size_t, size_t, CB *);\nvoid cut_close(GS *);\nTEXT *text_init(SCR *, const char *, size_t, size_t);\nvoid text_lfree(TEXTH *);\nvoid text_free(TEXT *);\nint del(SCR *, MARK *, MARK *, int);\nFREF *file_add(SCR *, CHAR_T *);\nint file_init(SCR *, FREF *, char *, int);\nint file_end(SCR *, EXF *, int);\nint file_write(SCR *, MARK *, MARK *, char *, int);\nint file_m1(SCR *, int, int);\nint file_m2(SCR *, int);\nint file_m3(SCR *, int);\nint file_aw(SCR *, int);\nvoid set_alt_name(SCR *, char *);\nlockr_t file_lock(SCR *, char *, int *, int, int);\nint v_key_init(SCR *);\nvoid v_key_ilookup(SCR *);\nsize_t v_key_len(SCR *, CHAR_T);\nCHAR_T *v_key_name(SCR *, CHAR_T);\nint v_key_val(SCR *, CHAR_T);\nint v_event_push(SCR *, EVENT *, CHAR_T *, size_t, unsigned int);\nint v_event_get(SCR *, EVENT *, int, u_int32_t);\nvoid v_event_err(SCR *, EVENT *);\nint v_event_flush(SCR *, unsigned int);\nint db_eget(SCR *, recno_t, char **, size_t *, int *);\nint db_get(SCR *, recno_t, u_int32_t, char **, size_t *);\nint db_delete(SCR *, recno_t);\nint db_append(SCR *, int, recno_t, char *, size_t);\nint db_insert(SCR *, recno_t, char *, size_t);\nint db_set(SCR *, recno_t, char *, size_t);\nint db_exist(SCR *, recno_t);\nint db_last(SCR *, recno_t *);\nvoid db_err(SCR *, recno_t);\nint log_init(SCR *, EXF *);\nint log_end(SCR *, EXF *);\nint log_cursor(SCR *);\nint log_line(SCR *, recno_t, unsigned int);\nint log_mark(SCR *, LMARK *);\nint log_backward(SCR *, MARK *);\nint log_setline(SCR *);\nint log_forward(SCR *, MARK *);\nint editor(GS *, int, char *[]);\nvoid v_end(GS *);\nint mark_init(SCR *, EXF *);\nint mark_end(SCR *, EXF *);\nint mark_get(SCR *, CHAR_T, MARK *, mtype_t);\nint mark_set(SCR *, CHAR_T, MARK *, int);\nint mark_insdel(SCR *, lnop_t, recno_t);\nvoid msgq(SCR *, mtype_t, const char *, ...);\nvoid msgq_str(SCR *, mtype_t, char *, char *);\nvoid mod_rpt(SCR *);\nvoid msgq_status(SCR *, recno_t, unsigned int);\nconst char *msg_cmsg(SCR *, cmsg_t, size_t *);\nchar *msg_print(SCR *, const char *, int *);\nint opts_init(SCR *, int *);\nint opts_set(SCR *, ARGS *[], char *);\nint o_set(SCR *, int, unsigned int, char *, unsigned long);\nint opts_empty(SCR *, int, int);\nvoid opts_dump(SCR *, enum optdisp);\nint opts_save(SCR *, FILE *);\nOPTLIST const *opts_search(char *);\nvoid opts_nomatch(SCR *, char *);\nint opts_copy(SCR *, SCR *);\nvoid opts_free(SCR *);\nint f_altwerase(SCR *, OPTION *, char *, unsigned long *);\nint f_columns(SCR *, OPTION *, char *, unsigned long *);\nint f_lines(SCR *, OPTION *, char *, unsigned long *);\nint f_paragraph(SCR *, OPTION *, char *, unsigned long *);\nint f_print(SCR *, OPTION *, char *, unsigned long *);\nint f_readonly(SCR *, OPTION *, char *, unsigned long *);\nint f_recompile(SCR *, OPTION *, char *, unsigned long *);\nint f_reformat(SCR *, OPTION *, char *, unsigned long *);\nint f_section(SCR *, OPTION *, char *, unsigned long *);\nint f_secure(SCR *, OPTION *, char *, unsigned long *);\nint f_ttywerase(SCR *, OPTION *, char *, unsigned long *);\nint f_w300(SCR *, OPTION *, char *, unsigned long *);\nint f_w1200(SCR *, OPTION *, char *, unsigned long *);\nint f_w9600(SCR *, OPTION *, char *, unsigned long *);\nint f_window(SCR *, OPTION *, char *, unsigned long *);\nint put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int, int);\nint rcv_tmp(SCR *, EXF *, char *);\nint rcv_init(SCR *);\nint rcv_sync(SCR *, unsigned int);\nint rcv_list(SCR *);\nint rcv_read(SCR *, FREF *);\nint screen_init(GS *, SCR *, SCR **);\nint screen_end(SCR *);\nSCR *screen_next(SCR *);\nint f_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int);\nint b_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int);\nvoid search_busy(SCR *, busy_t);\nint seq_set(SCR *, CHAR_T *,\nsize_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int);\nint seq_delete(SCR *, CHAR_T *, size_t, seq_t);\nint seq_mdel(SEQ *);\nSEQ *seq_find\n(SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *);\nvoid seq_close(GS *);\nint seq_dump(SCR *, seq_t, int);\nint seq_save(SCR *, FILE *, char *, seq_t);\nint e_memcmp(CHAR_T *, EVENT *, size_t);\nvoid *binc(SCR *, void *, size_t *, size_t);\nint nonblank(SCR *, recno_t, size_t *);\nCHAR_T *v_strdup(SCR *, const CHAR_T *, size_t);\nenum nresult nget_uslong(unsigned long *, const char *, char **, int);\nenum nresult nget_slong(long *, const char *, char **, int);\nvoid TRACE(SCR *, const char *, ...);\n"
  },
  {
    "path": "include/compat.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _COMPAT_H_\n# define _COMPAT_H_\n\n# ifdef __solaris__\n#  undef _TIMESPEC_UTIL_H\n#  define _TIMESPEC_UTIL_H 1\n# endif /* ifdef __solaris__ */\n\n# ifdef _AIX\n#  include <sys/limits.h>\n# endif /* ifdef _AIX */\n\n# if !defined( _GNU_SOURCE )\n#  define _GNU_SOURCE\n# endif /* ifndef _GNU_SOURCE */\n\n# if !defined( DEF_STRONG )\n#  define DEF_STRONG(x)\n# endif /* if !defined( DEF_STRONG ) */\n\n# if !defined( PROTO_NORMAL )\n#  define PROTO_NORMAL(x)\n# endif /* if !defined( PROTO_NORMAL ) */\n\n# if !defined( DEF_WEAK )\n#  define DEF_WEAK(x)\n# endif /* if !defined( DEF_WEAK ) */\n\n/* #define d_namlen d_reclen */\n# if !defined D_NAMLEN\n#  if !defined( _DIRENT_HAVE_NAMLEN )\n#   define D_NAMLEN(x) strnlen(( x )->d_name, ( x )->d_reclen)\n#  else  /* if !defined( _DIRENT_HAVE_NAMLEN ) */\n#   define D_NAMLEN(x) ( x )->d_namlen\n#  endif /* if !defined( _DIRENT_HAVE_NAMLEN ) */\n# endif /* if !defined D_NAMLEN */\n\n# ifndef __OpenBSD__\n\n#  ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK\n#   define FAIL_INSTEAD_OF_TRYING_FALLBACK\n#  endif /* ifndef FAIL_INSTEAD_OF_TRYING_FALLBACK */\n\n#  if !defined( HAVE_ATTRIBUTE__BOUNDED__ )\n#   define __bounded__(x, y, z)\n#  endif /* if !defined( HAVE_ATTRIBUTE__BOUNDED__ ) */\n\n#  ifndef __warn_references\n#   define __warn_references(x, y)\n#  endif /* ifndef __warn_references */\n\n#  ifndef __UNUSED\n#   define __UNUSED __attribute__ (( unused ))\n#  endif /* ifndef __UNUSED */\n\n#  ifndef __dead\n#   define __dead __attribute__ (( __noreturn__ ))\n#  endif /* ifndef __dead */\n\n#  ifndef __BEGIN_DECLS\n#   define __BEGIN_DECLS\n#  endif /* ifndef __BEGIN_DECLS */\n\n#  ifndef __END_DECLS\n#   define __END_DECLS\n#  endif /* ifndef __END_DECLS */\n\n#  ifndef __BEGIN_HIDDEN_DECLS\n#   define __BEGIN_HIDDEN_DECLS\n#  endif /* ifndef __BEGIN_HIDDEN_DECLS */\n\n#  ifndef __END_HIDDEN_DECLS\n#   define __END_HIDDEN_DECLS\n#  endif /* ifndef __END_HIDDEN_DECLS */\n\n#  ifndef __CONCAT\n#   define __CONCAT(x, y) x ## y\n#  endif /* ifndef __CONCAT */\n#  ifndef __STRING\n#   define __STRING(x) #x\n#  endif /* ifndef __STRING */\n\n#  ifndef __NetBSD__\n#   undef __weak_alias\n#   define __weak_alias(new, old)                                             \\\n      extern __typeof ( old ) new __attribute__ (( weak, alias(#old)))\n\n#   define __strong_alias(new, old)                                           \\\n      extern __typeof ( old ) new __attribute__ (( alias(#old)))\n#  endif /* ifndef __NetBSD__ */\n\n#  ifndef SA_LEN\n#   define SA_LEN(X)                                                          \\\n  (((struct sockaddr *)( X ))->sa_family == AF_INET                           \\\n       ? sizeof ( struct sockaddr_in )                                        \\\n   : ((struct sockaddr *)( X ))->sa_family == AF_INET6                        \\\n       ? sizeof ( struct sockaddr_in6 )                                       \\\n       : sizeof ( struct sockaddr ))\n#  endif /* ifndef SA_LEN */\n\n#  ifndef SS_LEN\n#   define SS_LEN(X)                                                          \\\n  (((struct sockaddr_storage *)( X ))->ss_family == AF_INET                   \\\n       ? sizeof ( struct sockaddr_in )                                        \\\n   : ((struct sockaddr_storage *)( X ))->ss_family == AF_INET6                \\\n       ? sizeof ( struct sockaddr_in6 )                                       \\\n       : sizeof ( struct sockaddr ))\n#  endif /* ifndef SS_LEN */\n\n#  define _PW_BUF_LEN 1024\n#  define _GR_BUF_LEN 1024\n\n#  define NOFILE_MAX NOFILE\n\n/* sys/sys/param.h */\n/*\n * File system parameters and macros.\n *\n * The file system is made out of blocks of at most MAXBSIZE units, with\n * smaller units (fragments) only in the last direct block.  MAXBSIZE\n * primarily determines the size of buffers in the buffer pool.  It may be\n * made larger without any effect on existing file systems; however making\n * it smaller makes some file systems unmountable.\n */\n#  define MAXBSIZE ( 64 * 1024 )\n\n#  ifndef NL_TEXTMAX\n#   define NL_TEXTMAX 2048\n#  endif /* ifndef NL_TEXTMAX */\n\n#  ifndef howmany\n#   define howmany(n, d) ((( n ) + (( d ) - 1 )) / ( d ))\n#  endif /* ifndef howmany */\n\n/* pwd.h */\n#  define _PW_NAME_LEN 63\n\n#  ifndef S_BLKSIZE\n#   define S_BLKSIZE 512\n#  endif /* ifndef S_BLKSIZE */\n\n#  ifndef MAXNAMLEN\n#   define MAXNAMLEN 255\n#  endif /* ifndef MAXNAMLEN */\n\n#  ifndef UID_MAX\n#   define UID_MAX 60000\n#  endif /* ifndef UID_MAX */\n\n#  ifndef GID_MAX\n#   define GID_MAX 60000\n#  endif /* ifndef GID_MAX */\n\n/* sys/sys/stat.h */\n#  ifndef ACCESSPERMS\n#   define ACCESSPERMS                                                        \\\n  ( S_IRWXU | S_IRWXG | S_IRWXO ) /* 00777 */\n#  endif /* ifndef ACCESSPERMS */\n\n#  ifndef ALLPERMS\n#   define ALLPERMS                                                           \\\n  ( S_ISUID | S_ISGID | S_ISTXT | S_IRWXU | S_IRWXG | S_IRWXO ) /* 00666 */\n#  endif /* ifndef ALLPERMS */\n\n#  ifndef DEFFILEMODE\n#   define DEFFILEMODE                                                        \\\n  ( S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH )\n#  endif /* ifndef DEFFILEMODE */\n\n#  ifdef S_ISVTX\n#   ifndef S_ISTXT\n#    define S_ISTXT S_ISVTX\n#   endif /* ifndef S_ISVTX */\n#  endif /* ifdef S_ISVTX */\n\n/* lib/libc/include/thread_private.h */\n#  define _MUTEX_LOCK(mutex)                                                  \\\n  do                                                                          \\\n    {                                                                         \\\n    } while ( 0 )\n#  define _MUTEX_UNLOCK(mutex)                                                \\\n  do                                                                          \\\n    {                                                                         \\\n    } while ( 0 )\n\n#  define SHA512_Update SHA512Update\n#  define SHA512_CTX SHA2_CTX\n#  define SHA512_Init SHA512Init\n#  define SHA512_Final SHA512Final\n\n/* sys/socket.h */\n#  ifndef RT_TABLEID_MAX\n#   define RT_TABLEID_MAX 255\n#  endif /* ifndef RT_TABLEID_MAX */\n\n/* sys/syslimits.h */\n#  ifndef CHILD_MAX\n#   define CHILD_MAX 80 /* max simultaneous processes */\n#  endif /* ifndef CHILD_MAX */\n\n/* pw_dup.c */\nstruct passwd *pw_dup(const struct passwd *);\n\nint issetugid(void);\n\n#  ifndef HOST_NAME_MAX\n#   define HOST_NAME_MAX 255\n#  endif /* ifndef HOST_NAME_MAX */\n\n#  ifndef OPEN_MAX\n#   define OPEN_MAX 64 /* max open files per process */\n#  endif /* ifndef OPEN_MAX */\n\n# endif /* ifndef __OpenBSD__ */\n\n/* pledge */\nint openbsd_pledge(const char *, const char *);\n\n#endif /* ifndef _COMPAT_H_ */\n"
  },
  {
    "path": "include/compat_bsd_db.h",
    "content": "/*      $OpenBSD: db.h,v 1.4 2016/05/29 20:47:49 guenther Exp $ */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 2015 Philip Guenther <guenther@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#ifndef _LIBC_DB_H_\n# define _LIBC_DB_H_\n\n/*\n * Little endian <==> big endian 32-bit swap macros.\n *      M_32_SWAP       swap a memory location\n *      P_32_SWAP       swap a referenced memory location\n *      P_32_COPY       swap from one location to another\n */\n\n# define M_32_SWAP(a) {                                                 \\\n        u_int32_t _tmp = a;                                             \\\n        ((char *)&a)[0] = ((char *)&_tmp)[3];                           \\\n        ((char *)&a)[1] = ((char *)&_tmp)[2];                           \\\n        ((char *)&a)[2] = ((char *)&_tmp)[1];                           \\\n        ((char *)&a)[3] = ((char *)&_tmp)[0];                           \\\n}\n\n# define P_32_SWAP(a) {                                                 \\\n        u_int32_t _tmp = *(u_int32_t *)a;                               \\\n        ((char *)a)[0] = ((char *)&_tmp)[3];                            \\\n        ((char *)a)[1] = ((char *)&_tmp)[2];                            \\\n        ((char *)a)[2] = ((char *)&_tmp)[1];                            \\\n        ((char *)a)[3] = ((char *)&_tmp)[0];                            \\\n}\n\n# define P_32_COPY(a, b) {                                              \\\n        ((char *)&(b))[0] = ((char *)&(a))[3];                          \\\n        ((char *)&(b))[1] = ((char *)&(a))[2];                          \\\n        ((char *)&(b))[2] = ((char *)&(a))[1];                          \\\n        ((char *)&(b))[3] = ((char *)&(a))[0];                          \\\n}\n\n/*\n * Little endian <==> big endian 16-bit swap macros.\n *      M_16_SWAP       swap a memory location\n *      P_16_SWAP       swap a referenced memory location\n *      P_16_COPY       swap from one location to another\n */\n\n# define M_16_SWAP(a) {                                                 \\\n        u_int16_t _tmp = a;                                             \\\n        ((char *)&a)[0] = ((char *)&_tmp)[1];                           \\\n        ((char *)&a)[1] = ((char *)&_tmp)[0];                           \\\n}\n\n# define P_16_SWAP(a) {                                                 \\\n        u_int16_t _tmp = *(u_int16_t *)a;                               \\\n        ((char *)a)[0] = ((char *)&_tmp)[1];                            \\\n        ((char *)a)[1] = ((char *)&_tmp)[0];                            \\\n}\n\n# define P_16_COPY(a, b) {                                              \\\n        ((char *)&(b))[0] = ((char *)&(a))[1];                          \\\n        ((char *)&(b))[1] = ((char *)&(a))[0];                          \\\n}\n\n__BEGIN_HIDDEN_DECLS\nDB      *__bt_open(const char *, int, int, const BTREEINFO *, int);\nDB      *__hash_open(const char *, int, int, const HASHINFO *, int);\nDB      *__rec_open(const char *, int, int, const RECNOINFO *, int);\nvoid    __dbpanic(DB *dbp);\n\n/* Default hash function, from db/hash/hash_func.c */\nu_int32_t       __default_hash(const void *, size_t);\n__END_HIDDEN_DECLS\n\nPROTO_NORMAL(dbopen);\n\n#endif /* !_LIBC_DB_H_ */\n"
  },
  {
    "path": "include/ex_extern.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nint ex(SCR **);\nint ex_cmd(SCR *);\nint ex_range(SCR *, EXCMD *, int *);\nint ex_is_abbrev(char *, size_t);\nint ex_is_unmap(char *, size_t);\nvoid ex_badaddr(SCR *, EXCMDLIST const *, enum badaddr, enum nresult);\nint ex_abbr(SCR *, EXCMD *);\nint ex_unabbr(SCR *, EXCMD *);\nint ex_append(SCR *, EXCMD *);\nint ex_change(SCR *, EXCMD *);\nint ex_insert(SCR *, EXCMD *);\nint ex_next(SCR *, EXCMD *);\nint ex_prev(SCR *, EXCMD *);\nint ex_rew(SCR *, EXCMD *);\nint ex_retab(SCR *sp, EXCMD *cmdp);\nint ex_args(SCR *, EXCMD *);\nchar **ex_buildargv(SCR *, EXCMD *, char *);\nint argv_init(SCR *, EXCMD *);\nint argv_exp0(SCR *, EXCMD *, char *, size_t);\nint argv_exp1(SCR *, EXCMD *, char *, size_t, int);\nint argv_exp2(SCR *, EXCMD *, char *, size_t);\nint argv_exp3(SCR *, EXCMD *, char *, size_t);\nint argv_free(SCR *);\nint ex_at(SCR *, EXCMD *);\nint ex_bang(SCR *, EXCMD *);\nint ex_cd(SCR *, EXCMD *);\nint ex_delete(SCR *, EXCMD *);\nint ex_display(SCR *, EXCMD *);\nint ex_edit(SCR *, EXCMD *);\nint ex_equal(SCR *, EXCMD *);\nint ex_file(SCR *, EXCMD *);\nint ex_filter(SCR *, EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype);\nint ex_global(SCR *, EXCMD *);\nint ex_v(SCR *, EXCMD *);\nint ex_g_insdel(SCR *, lnop_t, recno_t);\nint ex_screen_copy(SCR *, SCR *);\nint ex_screen_end(SCR *);\nint ex_optchange(SCR *, int, char *, unsigned long *);\nint ex_exrc(SCR *);\nint ex_run_str(SCR *, char *, char *, size_t, int, int);\nint ex_join(SCR *, EXCMD *);\nint ex_map(SCR *, EXCMD *);\nint ex_unmap(SCR *, EXCMD *);\nint ex_mark(SCR *, EXCMD *);\nint ex_mkexrc(SCR *, EXCMD *);\nint ex_copy(SCR *, EXCMD *);\nint ex_move(SCR *, EXCMD *);\nint ex_open(SCR *, EXCMD *);\nint ex_preserve(SCR *, EXCMD *);\nint ex_recover(SCR *, EXCMD *);\nint ex_list(SCR *, EXCMD *);\nint ex_number(SCR *, EXCMD *);\nint ex_pr(SCR *, EXCMD *);\nint ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t);\nint ex_ldisplay(SCR *, const char *, size_t, size_t, unsigned int);\nint ex_scprint(SCR *, MARK *, MARK *);\nint ex_printf(SCR *, const char *, ...);\nint ex_puts(SCR *, const char *);\nint ex_fflush(SCR *sp);\nint ex_put(SCR *, EXCMD *);\nint ex_quit(SCR *, EXCMD *);\nint ex_read(SCR *, EXCMD *);\nint ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int);\nint ex_bg(SCR *, EXCMD *);\nint ex_fg(SCR *, EXCMD *);\nint ex_resize(SCR *, EXCMD *);\nint ex_sdisplay(SCR *);\nint ex_script(SCR *, EXCMD *);\nint sscr_exec(SCR *, recno_t);\nint sscr_check_input(SCR *);\nint sscr_input(SCR *);\nint sscr_end(SCR *);\nint ex_set(SCR *, EXCMD *);\nint ex_shell(SCR *, EXCMD *);\nint ex_exec_proc(SCR *, EXCMD *, char *, const char *, int);\nint proc_wait(SCR *, pid_t, const char *, int, int);\nint ex_shiftl(SCR *, EXCMD *);\nint ex_shiftr(SCR *, EXCMD *);\nint ex_source(SCR *, EXCMD *);\nint ex_sourcefd(SCR *, EXCMD *, int);\nint ex_stop(SCR *, EXCMD *);\nint ex_s(SCR *, EXCMD *);\nint ex_subagain(SCR *, EXCMD *);\nint ex_subtilde(SCR *, EXCMD *);\nint re_compile(SCR *, char *, size_t, char **, size_t *, regex_t *, unsigned int);\nvoid re_error(SCR *, int, regex_t *);\nint ex_tag_first(SCR *, char *);\nint ex_tag_push(SCR *, EXCMD *);\nint ex_tag_next(SCR *, EXCMD *);\nint ex_tag_prev(SCR *, EXCMD *);\nint ex_tag_nswitch(SCR *, TAG *, int);\nint ex_tag_Nswitch(SCR *, TAG *, int);\nint ex_tag_pop(SCR *, EXCMD *);\nint ex_tag_top(SCR *, EXCMD *);\nint ex_tag_display(SCR *);\nint ex_tag_copy(SCR *, SCR *);\nint tagq_free(SCR *, TAGQ *);\nvoid tag_msg(SCR *, tagmsg_t, char *);\nint ex_tagf_alloc(SCR *, char *);\nint ex_tag_free(SCR *);\nint ex_txt(SCR *, TEXTH *, CHAR_T, u_int32_t);\nint ex_undo(SCR *, EXCMD *);\nint ex_help(SCR *, EXCMD *);\nint ex_usage(SCR *, EXCMD *);\nint ex_viusage(SCR *, EXCMD *);\nvoid ex_cinit(EXCMD *, int, int, recno_t, recno_t, int, ARGS **);\nvoid ex_cadd(EXCMD *, ARGS *, char *, size_t);\nint ex_getline(SCR *, FILE *, size_t *);\nint ex_ncheck(SCR *, int);\nint ex_init(SCR *);\nvoid ex_emsg(SCR *, char *, exm_t);\nint ex_version(SCR *, EXCMD *);\nint ex_visual(SCR *, EXCMD *);\nint ex_wn(SCR *, EXCMD *);\nint ex_wq(SCR *, EXCMD *);\nint ex_write(SCR *, EXCMD *);\nint ex_xit(SCR *, EXCMD *);\nint ex_writefp(SCR *, char *, FILE *, MARK *, MARK *, unsigned long *, unsigned long *, int);\nint ex_yank(SCR *, EXCMD *);\nint ex_z(SCR *, EXCMD *);\n"
  },
  {
    "path": "include/libgen.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Workarounds for header buglets */\n#ifdef __solaris__\n# undef _TIMESPEC_UTIL_H\n# define _TIMESPEC_UTIL_H 1\n#endif /* ifdef __solaris__ */\n\n#ifndef _COMPAT_LIBGEN_H_\n# define _COMPAT_LIBGEN_H_\n\nchar    *openbsd_basename(char *);\nchar    *openbsd_dirname(char *);\n\n# include_next <libgen.h>\n\n#endif /* _COMPAT_LIBGEN_H_ */\n"
  },
  {
    "path": "include/mpool.h",
    "content": "/*      $OpenBSD: mpool.h,v 1.1 2015/09/09 15:35:24 guenther Exp $      */\n/*      $NetBSD:  mpool.h,v 1.7 1996/05/03 21:13:41 cgd Exp $           */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)mpool.h     8.4 (Berkeley) 11/2/95\n */\n\n#ifndef _MPOOL_H_\n# define _MPOOL_H_\n\n# include <sys/queue.h>\n\n/*\n * The memory pool scheme is a simple one.  Each in-memory page is referenced\n * by a bucket which is threaded in up to two of three ways.  All active pages\n * are threaded on a hash chain (hashed by page number) and an lru chain.\n * Inactive pages are threaded on a free chain.  Each reference to a memory\n * pool is handed an opaque MPOOL cookie which stores all of this information.\n */\n# define HASHSIZE       128\n# define HASHKEY(pgno)  ((pgno - 1 + HASHSIZE) % HASHSIZE)\n\n/* The BKT structures are the elements of the queues... */\ntypedef struct _bkt {\n        TAILQ_ENTRY(_bkt) hq;           /* hash queue   */\n        TAILQ_ENTRY(_bkt) q;            /* lru queue    */\n        void    *page;                  /* page         */\n        pgno_t   pgno;                  /* page number. */\n\n# define MPOOL_DIRTY    0x01            /* page needs to be written   */\n# define MPOOL_PINNED   0x02            /* page is pinned into memory */\n# define MPOOL_INUSE    0x04            /* page address is valid      */\n        u_int8_t flags;                 /* flags                      */\n} BKT;\n\ntypedef struct MPOOL {\n        TAILQ_HEAD(_lqh, _bkt) lqh;     /* lru queue head                  */\n                                        /* hash queue array                */\n        TAILQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; /* ...                       */\n        pgno_t  curcache;               /* current number of cached pages  */\n        pgno_t  maxcache;               /* max number of cached pages      */\n        pgno_t  npages;                 /* number of pages in the file     */\n        unsigned long   pagesize;       /* file page size                  */\n        int     fd;                     /* file descriptor                 */\n                                        /* page in conversion routine      */\n        void    (*pgin)(void *, pgno_t, void *); /* ...                    */\n                                        /* page out conversion routine     */\n        void    (*pgout)(void *, pgno_t, void *); /* ...                   */\n        void    *pgcookie;              /* cookie for page in/out routines */\n# ifdef STATISTICS\n        unsigned long   cachehit;\n        unsigned long   cachemiss;\n        unsigned long   pagealloc;\n        unsigned long   pageflush;\n        unsigned long   pageget;\n        unsigned long   pagenew;\n        unsigned long   pageput;\n        unsigned long   pageread;\n        unsigned long   pagewrite;\n# endif /* ifdef STATISTICS */\n} MPOOL;\n\n# define MPOOL_IGNOREPIN     0x01       /* Ignore if the page is pinned.    */\n# define MPOOL_PAGE_REQUEST  0x01       /* New page w/ specific page number */\n# define MPOOL_PAGE_NEXT     0x02       /* New page w/ the next page number */\n\n__BEGIN_HIDDEN_DECLS\nMPOOL   *mpool_open(void *, int, pgno_t, pgno_t);\nvoid     mpool_filter(MPOOL *, void (*)(void *, pgno_t, void *),\nvoid     (*)(void *, pgno_t, void *), void *);\nvoid    *mpool_new(MPOOL *, pgno_t *, unsigned int);\nvoid    *mpool_get(MPOOL *, pgno_t, unsigned int);\nint      mpool_delete(MPOOL *, void *);\nint      mpool_put(MPOOL *, void *, unsigned int);\nint      mpool_sync(MPOOL *);\nint      mpool_close(MPOOL *);\n\nPROTO_NORMAL(mpool_open);\nPROTO_NORMAL(mpool_filter);\nPROTO_NORMAL(mpool_new);\nPROTO_NORMAL(mpool_get);\nPROTO_NORMAL(mpool_delete);\nPROTO_NORMAL(mpool_put);\nPROTO_NORMAL(mpool_sync);\nPROTO_NORMAL(mpool_close);\n\n# ifdef STATISTICS\nvoid     mpool_stat(MPOOL *);\nPROTO_NORMAL(mpool_stat);\n# endif /* ifdef STATISTICS */\n__END_HIDDEN_DECLS\n\n#endif /* ifndef _MPOOL_H_ */\n"
  },
  {
    "path": "include/pathnames.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _PATHNAMES_H\n# define _PATHNAMES_H\n\n# ifdef _PATH_EXRC\n#  undef _PATH_EXRC\n# endif /* ifdef _PATH_EXRC */\n\n# define _PATH_EXRC \".exrc\"\n\n# ifdef _PATH_NEXRC\n#  undef _PATH_NEXRC\n# endif /* ifdef _PATH_NEXRC */\n\n# define _PATH_NEXRC \".nexrc\"\n\n# ifdef _PATH_PRESERVE\n#  undef _PATH_PRESERVE\n# endif /* ifdef _PATH_PRESERVE */\n\n# define _PATH_PRESERVE \"/var/tmp/vi.recover\"\n\n# ifdef _PATH_SYSEXRC\n#  undef _PATH_TAGS\n# endif /* ifdef _PATH_SYSEXRC */\n\n# define _PATH_SYSEXRC \"/etc/vi.exrc\"\n\n# ifdef _PATH_TAGS\n#  undef _PATH_TAGS\n# endif /* ifdef _PATH_TAGS */\n\n# define _PATH_TAGS \"tags\"\n\n# ifndef _PATH_SENDMAIL\n#  define _PATH_SENDMAIL \"/usr/sbin/sendmail\"\n# endif /* ifndef _PATH_SENDMAIL */\n\n# ifndef _PATH_STRIP\n#  define _PATH_STRIP \"/usr/bin/strip\"\n# endif /* ifndef _PATH_STRIP */\n\n# ifndef _PATH_SYSV_PTY\n#  define _PATH_SYSV_PTY \"/dev/ptmx\"\n# endif /* ifndef _PATH_SYSV_PTY */\n\n#endif /* ifndef _PATHNAMES_H */\n"
  },
  {
    "path": "include/poll.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include_next <poll.h>\n\n#ifndef _COMPAT_POLL_H_\n# define _COMPAT_POLL_H_\n\n# ifndef INFTIM\n#  define INFTIM        (-1)\n# endif /* ifndef INFTIM */\n\n#endif /* ifndef _COMPAT_POLL_H */\n"
  },
  {
    "path": "include/sys/proc.h",
    "content": "/*      $OpenBSD: proc.h,v 1.235 2017/02/14 10:31:15 mpi Exp $  */\n/*      $NetBSD: proc.h,v 1.44 1996/04/22 01:23:21 christos Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1986, 1989, 1991, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1989, 1990, 1991, 1992, 1993 UNIX System Laboratories, Inc.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * All or some portions of this file are derived from material licensed\n * to the University of California by American Telephone and Telegraph\n * Co. or Unix System Laboratories, Inc. and are reproduced herein with\n * the permission of UNIX System Laboratories, Inc.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)proc.h      8.8 (Berkeley) 1/21/94\n */\n\n#ifndef _SYS_PROC_H_\n# define _SYS_PROC_H_\n\n# include <sys/queue.h>\n\n/*\n * These flags are kept in ps_flags.\n */\n# define PS_CONTROLT        0x00000001 /* Has a controlling terminal.        */\n# define PS_EXEC            0x00000002 /* Process called exec.               */\n# define PS_INEXEC          0x00000004 /* Process is doing an exec right now */\n# define PS_EXITING         0x00000008 /* Process is exiting.                */\n# define PS_SUGID           0x00000010 /* Had set id privs since last exec.  */\n# define PS_SUGIDEXEC       0x00000020 /* last execve() was set[ug]id        */\n# define PS_PPWAIT          0x00000040 /* Parent waits for exec/exit.        */\n# define PS_ISPWAIT         0x00000080 /* Is parent of PPWAIT child.         */\n# define PS_PROFIL          0x00000100 /* Has started profiling.             */\n# define PS_TRACED          0x00000200 /* Being ptraced.                     */\n# define PS_WAITED          0x00000400 /* Stopped proc was waited for.       */\n# define PS_COREDUMP        0x00000800 /* Busy coredumping                   */\n# define PS_SINGLEEXIT      0x00001000 /* Other threads must die.            */\n# define PS_SINGLEUNWIND    0x00002000 /* Other threads must unwind.         */\n# define PS_NOZOMBIE        0x00004000 /* No signal or zombie at exit.       */\n# define PS_STOPPED         0x00008000 /* Just stopped, need sig to parent.  */\n# define PS_SYSTEM          0x00010000 /* No sigs, stats or swapping.        */\n# define PS_EMBRYO          0x00020000 /* New process, not yet fledged       */\n# define PS_ZOMBIE          0x00040000 /* Dead and ready to be waited for    */\n# define PS_NOBROADCASTKILL 0x00080000 /* Process excluded from kill -1.     */\n# define PS_PLEDGE          0x00100000 /* Has called pledge(2)               */\n# define PS_WXNEEDED        0x00200000 /* Process may violate W^X            */\n\n# define PS_BITS \\\n    (\"\\20\" \"\\01CONTROLT\" \"\\02EXEC\" \"\\03INEXEC\" \"\\04EXITING\" \"\\05SUGID\"   \\\n     \"\\06SUGIDEXEC\" \"\\07PPWAIT\" \"\\010ISPWAIT\" \"\\011PROFIL\" \"\\012TRACED\"  \\\n     \"\\013WAITED\" \"\\014COREDUMP\" \"\\015SINGLEEXIT\" \"\\016SINGLEUNWIND\"     \\\n     \"\\017NOZOMBIE\" \"\\020STOPPED\" \"\\021SYSTEM\" \"\\022EMBRYO\" \"\\023ZOMBIE\" \\\n     \"\\024NOBROADCASTKILL\" \"\\025PLEDGE\" \"\\026WXNEEDED\")\n\n/* Status values. */\n# define SIDL    1               /* Thread being created by fork. */\n# define SRUN    2               /* Currently runnable.           */\n# define SSLEEP  3               /* Sleeping on an address.       */\n# define SSTOP   4               /* Debugging or suspension.      */\n# define SZOMB   5               /* unused                        */\n# define SDEAD   6               /* Thread is almost gone         */\n# define SONPROC 7               /* Thread is currently on a CPU. */\n\n# define P_ZOMBIE(p)    ((p)->p_stat == SDEAD)\n# define P_HASSIBLING(p)        (TAILQ_FIRST(&(p)->p_p->ps_threads) != (p) || \\\n                         TAILQ_NEXT((p), p_thr_link) != NULL)\n\n/*\n * These flags are per-thread and kept in p_flag\n */\n# define P_INKTR      0x00000001 /* In a ktrace op, don't recurse            */\n# define P_PROFPEND   0x00000002 /* SIGPROF needs to be posted               */\n# define P_ALRMPEND   0x00000004 /* SIGVTALRM needs to be posted             */\n# define P_SIGSUSPEND 0x00000008 /* Need to restore before-suspend mask      */\n# define P_CANTSLEEP  0x00000010 /* insomniac thread                         */\n# define P_SELECT     0x00000040 /* Selecting; wakeup/waiting danger.        */\n# define P_SINTR      0x00000080 /* Sleep is interruptible.                  */\n# define P_SYSTEM     0x00000200 /* No sigs, stats or swapping.              */\n# define P_TIMEOUT    0x00000400 /* Timing out during sleep.                 */\n# define P_WEXIT      0x00002000 /* Working on exiting.                      */\n# define P_OWEUPC     0x00008000 /* Owe proc an addupc() at next ast.        */\n# define P_SUSPSINGLE 0x00080000 /* Need to stop for single threading.       */\n# define P_CONTINUED  0x00800000 /* Proc has continued from a stopped state. */\n# define P_THREAD     0x04000000 /* Only a thread, not a real process        */\n# define P_SUSPSIG    0x08000000 /* Stopped from signal.                     */\n# define P_SOFTDEP    0x10000000 /* Stuck processing softdep worklist        */\n# define P_CPUPEG     0x40000000 /* Do not move to another cpu.              */\n\n# define P_BITS \\\n    (\"\\20\" \"\\01INKTR\" \"\\02PROFPEND\" \"\\03ALRMPEND\" \"\\04SIGSUSPEND\"         \\\n     \"\\05CANTSLEEP\" \"\\07SELECT\" \"\\010SINTR\" \"\\012SYSTEM\" \"\\013TIMEOUT\"    \\\n     \"\\016WEXIT\" \"\\020OWEUPC\" \"\\024SUSPSINGLE\" \"\\027XX\"                   \\\n     \"\\030CONTINUED\" \"\\033THREAD\" \"\\034SUSPSIG\" \"\\035SOFTDEP\" \"\\037CPUPEG\")\n\n# define THREAD_PID_OFFSET      100000\n\n#endif  /* !_SYS_PROC_H_ */\n"
  },
  {
    "path": "include/sys/queue.h",
    "content": "/*      $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $      */\n/*      $NetBSD:  queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1991, 1993\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)queue.h     8.5 (Berkeley) 8/20/94\n */\n\n#ifndef _SYS_QUEUE_H_\n# define _SYS_QUEUE_H_\n\n/*\n * This file defines five types of data structures: singly-linked lists,\n * lists, simple queues, tail queues and XOR simple queues.\n *\n *\n * A singly-linked list is headed by a single forward pointer. The elements\n * are singly linked for minimum space and pointer manipulation overhead at\n * the expense of O(n) removal for arbitrary elements. New elements can be\n * added to the list after an existing element or at the head of the list.\n * Elements being removed from the head of the list should use the explicit\n * macro for this purpose for optimum efficiency. A singly-linked list may\n * only be traversed in the forward direction.  Singly-linked lists are ideal\n * for applications with large datasets and few or no removals or for\n * implementing a LIFO queue.\n *\n * A list is headed by a single forward pointer (or an array of forward\n * pointers for a hash table header). The elements are doubly linked\n * so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before\n * or after an existing element or at the head of the list. A list\n * may only be traversed in the forward direction.\n *\n * A simple queue is headed by a pair of pointers, one to the head of the\n * list and the other to the tail of the list. The elements are singly\n * linked to save space, so elements can only be removed from the\n * head of the list. New elements can be added to the list before or after\n * an existing element, at the head of the list, or at the end of the\n * list. A simple queue may only be traversed in the forward direction.\n *\n * A tail queue is headed by a pair of pointers, one to the head of the\n * list and the other to the tail of the list. The elements are doubly\n * linked so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before or\n * after an existing element, at the head of the list, or at the end of\n * the list. A tail queue may be traversed in either direction.\n *\n * An XOR simple queue is used in the same way as a regular simple queue.\n * The difference is that the head structure also includes a \"cookie\" that\n * is XOR'd with the queue pointer (first, last or next) to generate the\n * real pointer value.\n *\n * For details on the use of these macros, see the queue(3) manual page.\n */\n\n# if defined(QUEUE_MACRO_DEBUG) || \\\n    (defined(_KERNEL) && defined(DIAGNOSTIC))\n#  define _Q_INVALIDATE(a) (a) = ((void *)-1)\n# else\n#  define _Q_INVALIDATE(a)\n# endif /* if defined(QUEUE_MACRO_DEBUG) ||\n          (defined(_KERNEL) && defined(DIAGNOSTIC)) */\n\n/*\n * Singly-linked List definitions.\n */\n# define SLIST_HEAD(name, type)                                         \\\nstruct name {                                                           \\\n        struct type *slh_first; /* first element */                     \\\n}\n\n# define SLIST_HEAD_INITIALIZER(head)                                   \\\n        { NULL }\n\n# define SLIST_ENTRY(type)                                              \\\nstruct {                                                                \\\n        struct type *sle_next;  /* next element */                      \\\n}\n\n/*\n * Singly-linked List access methods.\n */\n# define SLIST_FIRST(head)      ((head)->slh_first)\n\n# define SLIST_END(head)                NULL\n\n# define SLIST_EMPTY(head)      (SLIST_FIRST(head) == SLIST_END(head))\n\n# define SLIST_NEXT(elm, field) ((elm)->field.sle_next)\n\n# define SLIST_FOREACH(var, head, field)                                \\\n        for((var) = SLIST_FIRST(head);                                  \\\n            (var) != SLIST_END(head);                                   \\\n            (var) = SLIST_NEXT(var, field))\n\n# define SLIST_FOREACH_SAFE(var, head, field, tvar)                     \\\n        for ((var) = SLIST_FIRST(head);                                 \\\n            (var) && ((tvar) = SLIST_NEXT(var, field), 1);              \\\n            (var) = (tvar))\n\n/*\n * Singly-linked List functions.\n */\n# define SLIST_INIT(head) {                                             \\\n        SLIST_FIRST(head) = SLIST_END(head);                            \\\n}\n\n# define SLIST_INSERT_AFTER(slistelm, elm, field) do {                  \\\n        (elm)->field.sle_next = (slistelm)->field.sle_next;             \\\n        (slistelm)->field.sle_next = (elm);                             \\\n} while (0)\n\n# define SLIST_INSERT_HEAD(head, elm, field) do {                       \\\n        (elm)->field.sle_next = (head)->slh_first;                      \\\n        (head)->slh_first = (elm);                                      \\\n} while (0)\n\n# define SLIST_REMOVE_AFTER(elm, field) do {                            \\\n        (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;  \\\n} while (0)\n\n# define SLIST_REMOVE_HEAD(head, field) do {                            \\\n        (head)->slh_first = (head)->slh_first->field.sle_next;          \\\n} while (0)\n\n# define SLIST_REMOVE(head, elm, type, field) do {                      \\\n        if ((head)->slh_first == (elm)) {                               \\\n                SLIST_REMOVE_HEAD((head), field);                       \\\n        } else {                                                        \\\n                struct type *curelm = (head)->slh_first;                \\\n                                                                        \\\n                while (curelm->field.sle_next != (elm))                 \\\n                        curelm = curelm->field.sle_next;                \\\n                curelm->field.sle_next =                                \\\n                    curelm->field.sle_next->field.sle_next;             \\\n        }                                                               \\\n        _Q_INVALIDATE((elm)->field.sle_next);                           \\\n} while (0)\n\n/*\n * List definitions.\n */\n# define LIST_HEAD(name, type)                                          \\\nstruct name {                                                           \\\n        struct type *lh_first;  /* first element */                     \\\n}\n\n# define LIST_HEAD_INITIALIZER(head)                                    \\\n        { NULL }\n\n# define LIST_ENTRY(type)                                               \\\nstruct {                                                                \\\n        struct type *le_next;   /* next element */                      \\\n        struct type **le_prev;  /* address of previous next element */  \\\n}\n\n/*\n * List access methods.\n */\n# define LIST_FIRST(head)               ((head)->lh_first)\n\n# define LIST_END(head)                 NULL\n\n# define LIST_EMPTY(head)               (LIST_FIRST(head) == LIST_END(head))\n\n# define LIST_NEXT(elm, field)          ((elm)->field.le_next)\n\n# define LIST_FOREACH(var, head, field)                                 \\\n        for((var) = LIST_FIRST(head);                                   \\\n            (var)!= LIST_END(head);                                     \\\n            (var) = LIST_NEXT(var, field))\n\n# define LIST_FOREACH_SAFE(var, head, field, tvar)                      \\\n        for ((var) = LIST_FIRST(head);                                  \\\n            (var) && ((tvar) = LIST_NEXT(var, field), 1);               \\\n            (var) = (tvar))\n\n/*\n * List functions.\n */\n# define LIST_INIT(head) do {                                           \\\n        LIST_FIRST(head) = LIST_END(head);                              \\\n} while (0)\n\n# define LIST_INSERT_AFTER(listelm, elm, field) do {                    \\\n        if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)  \\\n                (listelm)->field.le_next->field.le_prev =               \\\n                    &(elm)->field.le_next;                              \\\n        (listelm)->field.le_next = (elm);                               \\\n        (elm)->field.le_prev = &(listelm)->field.le_next;               \\\n} while (0)\n\n# define LIST_INSERT_BEFORE(listelm, elm, field) do {                   \\\n        (elm)->field.le_prev = (listelm)->field.le_prev;                \\\n        (elm)->field.le_next = (listelm);                               \\\n        *(listelm)->field.le_prev = (elm);                              \\\n        (listelm)->field.le_prev = &(elm)->field.le_next;               \\\n} while (0)\n\n# define LIST_INSERT_HEAD(head, elm, field) do {                        \\\n        if (((elm)->field.le_next = (head)->lh_first) != NULL)          \\\n                (head)->lh_first->field.le_prev = &(elm)->field.le_next;\\\n        (head)->lh_first = (elm);                                       \\\n        (elm)->field.le_prev = &(head)->lh_first;                       \\\n} while (0)\n\n# define LIST_REMOVE(elm, field) do {                                   \\\n        if ((elm)->field.le_next != NULL)                               \\\n                (elm)->field.le_next->field.le_prev =                   \\\n                    (elm)->field.le_prev;                               \\\n        *(elm)->field.le_prev = (elm)->field.le_next;                   \\\n        _Q_INVALIDATE((elm)->field.le_prev);                            \\\n        _Q_INVALIDATE((elm)->field.le_next);                            \\\n} while (0)\n\n# define LIST_REPLACE(elm, elm2, field) do {                            \\\n        if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \\\n                (elm2)->field.le_next->field.le_prev =                  \\\n                    &(elm2)->field.le_next;                             \\\n        (elm2)->field.le_prev = (elm)->field.le_prev;                   \\\n        *(elm2)->field.le_prev = (elm2);                                \\\n        _Q_INVALIDATE((elm)->field.le_prev);                            \\\n        _Q_INVALIDATE((elm)->field.le_next);                            \\\n} while (0)\n\n/*\n * Tail queue definitions.\n */\n# define TAILQ_HEAD(name, type)                                         \\\nstruct name {                                                           \\\n        struct type *tqh_first; /* first element */                     \\\n        struct type **tqh_last; /* addr of last next element */         \\\n}\n\n# define TAILQ_HEAD_INITIALIZER(head)                                   \\\n        { NULL, &(head).tqh_first }\n\n# define TAILQ_ENTRY(type)                                              \\\nstruct {                                                                \\\n        struct type *tqe_next;  /* next element */                      \\\n        struct type **tqe_prev; /* address of previous next element */  \\\n}\n\n/*\n * Tail queue access methods.\n */\n# define TAILQ_FIRST(head)              ((head)->tqh_first)\n\n# define TAILQ_END(head)                        NULL\n\n# define TAILQ_NEXT(elm, field)         ((elm)->field.tqe_next)\n\n# define TAILQ_LAST(head, headname)                                     \\\n        (*(((struct headname *)((head)->tqh_last))->tqh_last))\n\n# define TAILQ_PREV(elm, headname, field)                               \\\n        (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))\n\n# define TAILQ_EMPTY(head)                                              \\\n        (TAILQ_FIRST(head) == TAILQ_END(head))\n\n# define TAILQ_FOREACH(var, head, field)                                \\\n        for((var) = TAILQ_FIRST(head);                                  \\\n            (var) != TAILQ_END(head);                                   \\\n            (var) = TAILQ_NEXT(var, field))\n\n# define TAILQ_FOREACH_SAFE(var, head, field, tvar)                     \\\n        for ((var) = TAILQ_FIRST(head);                                 \\\n            (var) != TAILQ_END(head) &&                                 \\\n            ((tvar) = TAILQ_NEXT(var, field), 1);                       \\\n            (var) = (tvar))\n\n# define TAILQ_FOREACH_REVERSE(var, head, headname, field)              \\\n        for((var) = TAILQ_LAST(head, headname);                         \\\n            (var) != TAILQ_END(head);                                   \\\n            (var) = TAILQ_PREV(var, headname, field))\n\n# define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)   \\\n        for ((var) = TAILQ_LAST(head, headname);                        \\\n            (var) != TAILQ_END(head) &&                                 \\\n            ((tvar) = TAILQ_PREV(var, headname, field), 1);             \\\n            (var) = (tvar))\n\n/*\n * Tail queue functions.\n */\n# define TAILQ_INIT(head) do {                                          \\\n        (head)->tqh_first = NULL;                                       \\\n        (head)->tqh_last = &(head)->tqh_first;                          \\\n} while (0)\n\n# define TAILQ_INSERT_HEAD(head, elm, field) do {                       \\\n        if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \\\n                (head)->tqh_first->field.tqe_prev =                     \\\n                    &(elm)->field.tqe_next;                             \\\n        else                                                            \\\n                (head)->tqh_last = &(elm)->field.tqe_next;              \\\n        (head)->tqh_first = (elm);                                      \\\n        (elm)->field.tqe_prev = &(head)->tqh_first;                     \\\n} while (0)\n\n# define TAILQ_INSERT_TAIL(head, elm, field) do {                       \\\n        (elm)->field.tqe_next = NULL;                                   \\\n        (elm)->field.tqe_prev = (head)->tqh_last;                       \\\n        *(head)->tqh_last = (elm);                                      \\\n        (head)->tqh_last = &(elm)->field.tqe_next;                      \\\n} while (0)\n\n# define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \\\n        if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\\\n                (elm)->field.tqe_next->field.tqe_prev =                 \\\n                    &(elm)->field.tqe_next;                             \\\n        else                                                            \\\n                (head)->tqh_last = &(elm)->field.tqe_next;              \\\n        (listelm)->field.tqe_next = (elm);                              \\\n        (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \\\n} while (0)\n\n# define TAILQ_INSERT_BEFORE(listelm, elm, field) do {                  \\\n        (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \\\n        (elm)->field.tqe_next = (listelm);                              \\\n        *(listelm)->field.tqe_prev = (elm);                             \\\n        (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \\\n} while (0)\n\n# define TAILQ_REMOVE(head, elm, field) do {                            \\\n        if (((elm)->field.tqe_next) != NULL)                            \\\n                (elm)->field.tqe_next->field.tqe_prev =                 \\\n                    (elm)->field.tqe_prev;                              \\\n        else                                                            \\\n                (head)->tqh_last = (elm)->field.tqe_prev;               \\\n        *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \\\n        _Q_INVALIDATE((elm)->field.tqe_prev);                           \\\n        _Q_INVALIDATE((elm)->field.tqe_next);                           \\\n} while (0)\n\n# define TAILQ_REPLACE(head, elm, elm2, field) do {                     \\\n        if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)   \\\n                (elm2)->field.tqe_next->field.tqe_prev =                \\\n                    &(elm2)->field.tqe_next;                            \\\n        else                                                            \\\n                (head)->tqh_last = &(elm2)->field.tqe_next;             \\\n        (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                 \\\n        *(elm2)->field.tqe_prev = (elm2);                               \\\n        _Q_INVALIDATE((elm)->field.tqe_prev);                           \\\n        _Q_INVALIDATE((elm)->field.tqe_next);                           \\\n} while (0)\n\n# define TAILQ_CONCAT(head1, head2, field) do {                         \\\n        if (!TAILQ_EMPTY(head2)) {                                      \\\n                *(head1)->tqh_last = (head2)->tqh_first;                \\\n                (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \\\n                (head1)->tqh_last = (head2)->tqh_last;                  \\\n                TAILQ_INIT((head2));                                    \\\n        }                                                               \\\n} while (0)\n\n#endif  /* !_SYS_QUEUE_H_ */\n"
  },
  {
    "path": "include/sys/stat.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _COMPAT_SYS_STAT_H_\n# define _COMPAT_SYS_STAT_H_\n\n# ifndef S_ISTXT\n#  define S_ISTXT  S_ISVTX      /* sticky bit */\n# endif /* ifndef S_ISTXT */\n\n# ifndef st_atimespec\n#  define st_atimespec          st_atim\n# endif /* ifndef st_atimespec */\n\n# ifndef st_atimensec\n#  define st_atimensec          st_atim.tv_nsec\n# endif /* ifndef st_atimensec */\n\n# ifndef st_mtimespec\n#  define st_mtimespec          st_mtim\n# endif /* ifndef st_mtimespec */\n\n# ifndef st_mtimensec\n#  define st_mtimensec          st_mtim.tv_nsec\n# endif /* ifndef st_mtimensec */\n\n# ifndef st_ctimespec\n#  define st_ctimespec          st_ctim\n# endif /* ifndef st_ctimespec */\n\n# ifndef st_ctimensec\n#  define st_ctimensec          st_ctim.tv_nsec\n# endif /* ifndef st_ctimensec */\n\n#endif /* !_COMPAT_SYS_STAT_H_ */\n\n#include_next <sys/stat.h>\n"
  },
  {
    "path": "include/sys/time.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include_next <sys/time.h>\n\n#ifndef _COMPAT_SYS_TIME_H_\n# define _COMPAT_SYS_TIME_H_\n# include <time.h>\n\n# ifndef TIMEVAL_TO_TIMESPEC\n#  define TIMEVAL_TO_TIMESPEC(tv, ts) {                                 \\\n        (ts)->tv_sec = (tv)->tv_sec;                                    \\\n        (ts)->tv_nsec = (tv)->tv_usec * 1000;                           \\\n}\n# endif /* ifndef TIMEVAL_TO_TIMESPEC */\n\n# ifndef TIMESPEC_TO_TIMEVAL\n#  define TIMESPEC_TO_TIMEVAL(tv, ts) {                                 \\\n        (tv)->tv_sec = (ts)->tv_sec;                                    \\\n        (tv)->tv_usec = (ts)->tv_nsec / 1000;                           \\\n}\n# endif /* ifndef TIMESPEC_TO_TIMEVAL */\n\n# ifdef timespecclear\n#  undef timespecclear\n# endif /* ifdef timespecclear */\n# define timespecclear(tsp)             (tsp)->tv_sec = (tsp)->tv_nsec = 0\n\n# ifdef timespecisset\n#  undef timespecisset\n# endif /* ifdef timespecisset */\n# define timespecisset(tsp)             ((tsp)->tv_sec || (tsp)->tv_nsec)\n\n# ifdef timespeccmp\n#  undef timespeccmp\n# endif /* ifdef timespeccmp */\n# define timespeccmp(tsp, usp, cmp)                                     \\\n        (((tsp)->tv_sec == (usp)->tv_sec) ?                             \\\n            ((tsp)->tv_nsec cmp (usp)->tv_nsec) :                       \\\n            ((tsp)->tv_sec cmp (usp)->tv_sec))\n\n# ifdef timespecadd\n#  undef timespecadd\n# endif /* ifdef timespecadd */\n# define timespecadd(tsp, usp, vsp)                                     \\\n        do {                                                            \\\n                (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec;          \\\n                (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec;       \\\n                if ((vsp)->tv_nsec >= 1000000000L) {                    \\\n                        (vsp)->tv_sec++;                                \\\n                        (vsp)->tv_nsec -= 1000000000L;                  \\\n                }                                                       \\\n        } while (0)\n\n# ifdef timespecsub\n#  undef timespecsub\n# endif /* ifdef timespecsub */\n# define timespecsub(tsp, usp, vsp)                                     \\\n        do {                                                            \\\n                (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec;          \\\n                (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec;       \\\n                if ((vsp)->tv_nsec < 0) {                               \\\n                        (vsp)->tv_sec--;                                \\\n                        (vsp)->tv_nsec += 1000000000L;                  \\\n                }                                                       \\\n        } while (0)\n\n#endif /* _COMPAT_SYS_TIME_H_ */\n"
  },
  {
    "path": "include/sys/tree.h",
    "content": "/*      $OpenBSD: tree.h,v 1.14 2015/05/25 03:07:49 deraadt Exp $       */\n\n/* SPDX-License-Identifier: BSD-2-Clause */\n\n/*\n * Copyright (c) 2002 Niels Provos <provos@citi.umich.edu>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _SYS_TREE_H_\n# define _SYS_TREE_H_\n\n/*\n * This file defines data structures for different types of trees:\n * splay trees and red-black trees.\n *\n * A splay tree is a self-organizing data structure.  Every operation\n * on the tree causes a splay to happen.  The splay moves the requested\n * node to the root of the tree and partly rebalances it.\n *\n * This has the benefit that request locality causes faster lookups as\n * the requested nodes move to the top of the tree.  On the other hand,\n * every lookup causes memory writes.\n *\n * The Balance Theorem bounds the total access time for m operations\n * and n inserts on an initially empty tree as O((m + n)lg n).  The\n * amortized cost for a sequence of m accesses to a splay tree is O(lg n);\n *\n * A red-black tree is a binary search tree with the node color as an\n * extra attribute.  It fulfills a set of conditions:\n *\n *   - every search path from the root to a leaf consists of the\n *     same number of black nodes,\n *   - each red node (except for the root) has a black parent,\n *   - each leaf node is black.\n *\n * Every operation on a red-black tree is bounded as O(lg n).\n * The maximum height of a red-black tree is 2lg (n+1).\n */\n\n# define SPLAY_HEAD(name, type)                                         \\\nstruct name {                                                           \\\n        struct type *sph_root; /* root of the tree */                   \\\n}\n\n# define SPLAY_INITIALIZER(root)                                        \\\n        { NULL }\n\n# define SPLAY_INIT(root) do {                                          \\\n        (root)->sph_root = NULL;                                        \\\n} while (0)\n\n# define SPLAY_ENTRY(type)                                              \\\nstruct {                                                                \\\n        struct type *spe_left; /* left element */                       \\\n        struct type *spe_right; /* right element */                     \\\n}\n\n# define SPLAY_LEFT(elm, field)         (elm)->field.spe_left\n# define SPLAY_RIGHT(elm, field)                (elm)->field.spe_right\n# define SPLAY_ROOT(head)               (head)->sph_root\n# define SPLAY_EMPTY(head)              (SPLAY_ROOT(head) == NULL)\n\n/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */\n# define SPLAY_ROTATE_RIGHT(head, tmp, field) do {                      \\\n        SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);  \\\n        SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \\\n        (head)->sph_root = tmp;                                         \\\n} while (0)\n\n# define SPLAY_ROTATE_LEFT(head, tmp, field) do {                       \\\n        SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);  \\\n        SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \\\n        (head)->sph_root = tmp;                                         \\\n} while (0)\n\n# define SPLAY_LINKLEFT(head, tmp, field) do {                          \\\n        SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \\\n        tmp = (head)->sph_root;                                         \\\n        (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);         \\\n} while (0)\n\n# define SPLAY_LINKRIGHT(head, tmp, field) do {                         \\\n        SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \\\n        tmp = (head)->sph_root;                                         \\\n        (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);        \\\n} while (0)\n\n# define SPLAY_ASSEMBLE(head, node, left, right, field) do {            \\\n        SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \\\n        SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\\\n        SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \\\n        SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \\\n} while (0)\n\n/* Generates prototypes and inline functions */\n\n# define SPLAY_PROTOTYPE(name, type, field, cmp)                        \\\nvoid name##_SPLAY(struct name *, struct type *);                        \\\nvoid name##_SPLAY_MINMAX(struct name *, int);                           \\\nstruct type *name##_SPLAY_INSERT(struct name *, struct type *);         \\\nstruct type *name##_SPLAY_REMOVE(struct name *, struct type *);         \\\n                                                                        \\\n/* Finds the node with the same key as elm */                           \\\nstatic __inline struct type *                                           \\\nname##_SPLAY_FIND(struct name *head, struct type *elm)                  \\\n{                                                                       \\\n        if (SPLAY_EMPTY(head))                                          \\\n                return(NULL);                                           \\\n        name##_SPLAY(head, elm);                                        \\\n        if ((cmp)(elm, (head)->sph_root) == 0)                          \\\n                return (head->sph_root);                                \\\n        return (NULL);                                                  \\\n}                                                                       \\\n                                                                        \\\nstatic __inline struct type *                                           \\\nname##_SPLAY_NEXT(struct name *head, struct type *elm)                  \\\n{                                                                       \\\n        name##_SPLAY(head, elm);                                        \\\n        if (SPLAY_RIGHT(elm, field) != NULL) {                          \\\n                elm = SPLAY_RIGHT(elm, field);                          \\\n                while (SPLAY_LEFT(elm, field) != NULL) {                \\\n                        elm = SPLAY_LEFT(elm, field);                   \\\n                }                                                       \\\n        } else                                                          \\\n                elm = NULL;                                             \\\n        return (elm);                                                   \\\n}                                                                       \\\n                                                                        \\\nstatic __inline struct type *                                           \\\nname##_SPLAY_MIN_MAX(struct name *head, int val)                        \\\n{                                                                       \\\n        name##_SPLAY_MINMAX(head, val);                                 \\\n        return (SPLAY_ROOT(head));                                      \\\n}\n\n/* Main splay operation.\n * Moves node close to the key of elm to top\n */\n# define SPLAY_GENERATE(name, type, field, cmp)                         \\\nstruct type *                                                           \\\nname##_SPLAY_INSERT(struct name *head, struct type *elm)                \\\n{                                                                       \\\n    if (SPLAY_EMPTY(head)) {                                            \\\n            SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;    \\\n    } else {                                                            \\\n            int __comp;                                                 \\\n            name##_SPLAY(head, elm);                                    \\\n            __comp = (cmp)(elm, (head)->sph_root);                      \\\n            if(__comp < 0) {                                            \\\n                    SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\\\n                    SPLAY_RIGHT(elm, field) = (head)->sph_root;         \\\n                    SPLAY_LEFT((head)->sph_root, field) = NULL;         \\\n            } else if (__comp > 0) {                                    \\\n                    SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\\\n                    SPLAY_LEFT(elm, field) = (head)->sph_root;          \\\n                    SPLAY_RIGHT((head)->sph_root, field) = NULL;        \\\n            } else                                                      \\\n                    return ((head)->sph_root);                          \\\n    }                                                                   \\\n    (head)->sph_root = (elm);                                           \\\n    return (NULL);                                                      \\\n}                                                                       \\\n                                                                        \\\nstruct type *                                                           \\\nname##_SPLAY_REMOVE(struct name *head, struct type *elm)                \\\n{                                                                       \\\n        struct type *__tmp;                                             \\\n        if (SPLAY_EMPTY(head))                                          \\\n                return (NULL);                                          \\\n        name##_SPLAY(head, elm);                                        \\\n        if ((cmp)(elm, (head)->sph_root) == 0) {                        \\\n                if (SPLAY_LEFT((head)->sph_root, field) == NULL) {      \\\n                        (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\\\n                } else {                                                \\\n                        __tmp = SPLAY_RIGHT((head)->sph_root, field);   \\\n                        (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\\\n                        name##_SPLAY(head, elm);                        \\\n                        SPLAY_RIGHT((head)->sph_root, field) = __tmp;   \\\n                }                                                       \\\n                return (elm);                                           \\\n        }                                                               \\\n        return (NULL);                                                  \\\n}                                                                       \\\n                                                                        \\\nvoid                                                                    \\\nname##_SPLAY(struct name *head, struct type *elm)                       \\\n{                                                                       \\\n        struct type __node, *__left, *__right, *__tmp;                  \\\n        int __comp;                                                     \\\n                                                                        \\\n        SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\\\n        __left = __right = &__node;                                     \\\n                                                                        \\\n        while ((__comp = (cmp)(elm, (head)->sph_root))) {               \\\n                if (__comp < 0) {                                       \\\n                        __tmp = SPLAY_LEFT((head)->sph_root, field);    \\\n                        if (__tmp == NULL)                              \\\n                                break;                                  \\\n                        if ((cmp)(elm, __tmp) < 0){                     \\\n                                SPLAY_ROTATE_RIGHT(head, __tmp, field); \\\n                                if (SPLAY_LEFT((head)->sph_root, field) == NULL)\\\n                                        break;                          \\\n                        }                                               \\\n                        SPLAY_LINKLEFT(head, __right, field);           \\\n                } else if (__comp > 0) {                                \\\n                        __tmp = SPLAY_RIGHT((head)->sph_root, field);   \\\n                        if (__tmp == NULL)                              \\\n                                break;                                  \\\n                        if ((cmp)(elm, __tmp) > 0){                     \\\n                                SPLAY_ROTATE_LEFT(head, __tmp, field);  \\\n                                if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\\\n                                        break;                          \\\n                        }                                               \\\n                        SPLAY_LINKRIGHT(head, __left, field);           \\\n                }                                                       \\\n        }                                                               \\\n        SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \\\n}                                                                       \\\n                                                                        \\\n/* Splay with either the minimum or the maximum element                 \\\n * Used to find minimum or maximum element in tree.                     \\\n */                                                                     \\\nvoid name##_SPLAY_MINMAX(struct name *head, int __comp)                 \\\n{                                                                       \\\n        struct type __node, *__left, *__right, *__tmp;                  \\\n                                                                        \\\n        SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\\\n        __left = __right = &__node;                                     \\\n                                                                        \\\n        while (1) {                                                     \\\n                if (__comp < 0) {                                       \\\n                        __tmp = SPLAY_LEFT((head)->sph_root, field);    \\\n                        if (__tmp == NULL)                              \\\n                                break;                                  \\\n                        if (__comp < 0){                                \\\n                                SPLAY_ROTATE_RIGHT(head, __tmp, field); \\\n                                if (SPLAY_LEFT((head)->sph_root, field) == NULL)\\\n                                        break;                          \\\n                        }                                               \\\n                        SPLAY_LINKLEFT(head, __right, field);           \\\n                } else if (__comp > 0) {                                \\\n                        __tmp = SPLAY_RIGHT((head)->sph_root, field);   \\\n                        if (__tmp == NULL)                              \\\n                                break;                                  \\\n                        if (__comp > 0) {                               \\\n                                SPLAY_ROTATE_LEFT(head, __tmp, field);  \\\n                                if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\\\n                                        break;                          \\\n                        }                                               \\\n                        SPLAY_LINKRIGHT(head, __left, field);           \\\n                }                                                       \\\n        }                                                               \\\n        SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \\\n}\n\n# define SPLAY_NEGINF   -1\n# define SPLAY_INF      1\n\n# define SPLAY_INSERT(name, x, y)       name##_SPLAY_INSERT(x, y)\n# define SPLAY_REMOVE(name, x, y)       name##_SPLAY_REMOVE(x, y)\n# define SPLAY_FIND(name, x, y)         name##_SPLAY_FIND(x, y)\n# define SPLAY_NEXT(name, x, y)         name##_SPLAY_NEXT(x, y)\n# define SPLAY_MIN(name, x)             (SPLAY_EMPTY(x) ? NULL  \\\n                                        : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))\n# define SPLAY_MAX(name, x)             (SPLAY_EMPTY(x) ? NULL  \\\n                                        : name##_SPLAY_MIN_MAX(x, SPLAY_INF))\n\n# define SPLAY_FOREACH(x, name, head)                                   \\\n        for ((x) = SPLAY_MIN(name, head);                               \\\n             (x) != NULL;                                               \\\n             (x) = SPLAY_NEXT(name, head, x))\n\n/* Macros that define a red-black tree */\n# define RB_HEAD(name, type)                                            \\\nstruct name {                                                           \\\n        struct type *rbh_root; /* root of the tree */                   \\\n}\n\n# define RB_INITIALIZER(root)                                           \\\n        { NULL }\n\n# define RB_INIT(root) do {                                             \\\n        (root)->rbh_root = NULL;                                        \\\n} while (0)\n\n# define RB_BLACK       0\n# define RB_RED         1\n# define RB_ENTRY(type)                                                 \\\nstruct {                                                                \\\n        struct type *rbe_left;          /* left element */              \\\n        struct type *rbe_right;         /* right element */             \\\n        struct type *rbe_parent;        /* parent element */            \\\n        int rbe_color;                  /* node color */                \\\n}\n\n# define RB_LEFT(elm, field)            (elm)->field.rbe_left\n# define RB_RIGHT(elm, field)           (elm)->field.rbe_right\n# define RB_PARENT(elm, field)          (elm)->field.rbe_parent\n# define RB_COLOR(elm, field)           (elm)->field.rbe_color\n# define RB_ROOT(head)                  (head)->rbh_root\n# define RB_EMPTY(head)                 (RB_ROOT(head) == NULL)\n\n# define RB_SET(elm, parent, field) do {                                \\\n        RB_PARENT(elm, field) = parent;                                 \\\n        RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;              \\\n        RB_COLOR(elm, field) = RB_RED;                                  \\\n} while (0)\n\n# define RB_SET_BLACKRED(black, red, field) do {                        \\\n        RB_COLOR(black, field) = RB_BLACK;                              \\\n        RB_COLOR(red, field) = RB_RED;                                  \\\n} while (0)\n\n# ifndef RB_AUGMENT\n#  define RB_AUGMENT(x) do {} while (0)\n# endif /* ifndef RB_AUGMENT */\n\n# define RB_ROTATE_LEFT(head, elm, tmp, field) do {                     \\\n        (tmp) = RB_RIGHT(elm, field);                                   \\\n        if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) {             \\\n                RB_PARENT(RB_LEFT(tmp, field), field) = (elm);          \\\n        }                                                               \\\n        RB_AUGMENT(elm);                                                \\\n        if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {          \\\n                if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \\\n                        RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \\\n                else                                                    \\\n                        RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \\\n        } else                                                          \\\n                (head)->rbh_root = (tmp);                               \\\n        RB_LEFT(tmp, field) = (elm);                                    \\\n        RB_PARENT(elm, field) = (tmp);                                  \\\n        RB_AUGMENT(tmp);                                                \\\n        if ((RB_PARENT(tmp, field)))                                    \\\n                RB_AUGMENT(RB_PARENT(tmp, field));                      \\\n} while (0)\n\n# define RB_ROTATE_RIGHT(head, elm, tmp, field) do {                    \\\n        (tmp) = RB_LEFT(elm, field);                                    \\\n        if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) {             \\\n                RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);         \\\n        }                                                               \\\n        RB_AUGMENT(elm);                                                \\\n        if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {          \\\n                if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \\\n                        RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \\\n                else                                                    \\\n                        RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \\\n        } else                                                          \\\n                (head)->rbh_root = (tmp);                               \\\n        RB_RIGHT(tmp, field) = (elm);                                   \\\n        RB_PARENT(elm, field) = (tmp);                                  \\\n        RB_AUGMENT(tmp);                                                \\\n        if ((RB_PARENT(tmp, field)))                                    \\\n                RB_AUGMENT(RB_PARENT(tmp, field));                      \\\n} while (0)\n\n/* Generates prototypes and inline functions */\n# define RB_PROTOTYPE(name, type, field, cmp)                           \\\n        RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)\n# define RB_PROTOTYPE_STATIC(name, type, field, cmp)                    \\\n        RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)\n# define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)            \\\nattr void name##_RB_INSERT_COLOR(struct name *, struct type *);         \\\nattr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\\\nattr struct type *name##_RB_REMOVE(struct name *, struct type *);       \\\nattr struct type *name##_RB_INSERT(struct name *, struct type *);       \\\nattr struct type *name##_RB_FIND(struct name *, struct type *);         \\\nattr struct type *name##_RB_NFIND(struct name *, struct type *);        \\\nattr struct type *name##_RB_NEXT(struct type *);                        \\\nattr struct type *name##_RB_PREV(struct type *);                        \\\nattr struct type *name##_RB_MINMAX(struct name *, int);                 \\\n                                                                        \\\n\n/* Main rb operation.\n * Moves node close to the key of elm to top\n */\n# define RB_GENERATE(name, type, field, cmp)                            \\\n        RB_GENERATE_INTERNAL(name, type, field, cmp,)\n# define RB_GENERATE_STATIC(name, type, field, cmp)                     \\\n        RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)\n# define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)             \\\nattr void                                                               \\\nname##_RB_INSERT_COLOR(struct name *head, struct type *elm)             \\\n{                                                                       \\\n        struct type *parent, *gparent, *tmp;                            \\\n        while ((parent = RB_PARENT(elm, field)) &&                      \\\n            RB_COLOR(parent, field) == RB_RED) {                        \\\n                gparent = RB_PARENT(parent, field);                     \\\n                if (parent == RB_LEFT(gparent, field)) {                \\\n                        tmp = RB_RIGHT(gparent, field);                 \\\n                        if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \\\n                                RB_COLOR(tmp, field) = RB_BLACK;        \\\n                                RB_SET_BLACKRED(parent, gparent, field);\\\n                                elm = gparent;                          \\\n                                continue;                               \\\n                        }                                               \\\n                        if (RB_RIGHT(parent, field) == elm) {           \\\n                                RB_ROTATE_LEFT(head, parent, tmp, field);\\\n                                tmp = parent;                           \\\n                                parent = elm;                           \\\n                                elm = tmp;                              \\\n                        }                                               \\\n                        RB_SET_BLACKRED(parent, gparent, field);        \\\n                        RB_ROTATE_RIGHT(head, gparent, tmp, field);     \\\n                } else {                                                \\\n                        tmp = RB_LEFT(gparent, field);                  \\\n                        if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \\\n                                RB_COLOR(tmp, field) = RB_BLACK;        \\\n                                RB_SET_BLACKRED(parent, gparent, field);\\\n                                elm = gparent;                          \\\n                                continue;                               \\\n                        }                                               \\\n                        if (RB_LEFT(parent, field) == elm) {            \\\n                                RB_ROTATE_RIGHT(head, parent, tmp, field);\\\n                                tmp = parent;                           \\\n                                parent = elm;                           \\\n                                elm = tmp;                              \\\n                        }                                               \\\n                        RB_SET_BLACKRED(parent, gparent, field);        \\\n                        RB_ROTATE_LEFT(head, gparent, tmp, field);      \\\n                }                                                       \\\n        }                                                               \\\n        RB_COLOR(head->rbh_root, field) = RB_BLACK;                     \\\n}                                                                       \\\n                                                                        \\\nattr void                                                               \\\nname##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \\\n{                                                                       \\\n        struct type *tmp;                                               \\\n        while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&     \\\n            elm != RB_ROOT(head)) {                                     \\\n                if (RB_LEFT(parent, field) == elm) {                    \\\n                        tmp = RB_RIGHT(parent, field);                  \\\n                        if (RB_COLOR(tmp, field) == RB_RED) {           \\\n                                RB_SET_BLACKRED(tmp, parent, field);    \\\n                                RB_ROTATE_LEFT(head, parent, tmp, field);\\\n                                tmp = RB_RIGHT(parent, field);          \\\n                        }                                               \\\n                        if ((RB_LEFT(tmp, field) == NULL ||             \\\n                            RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\\\n                            (RB_RIGHT(tmp, field) == NULL ||            \\\n                            RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\\\n                                RB_COLOR(tmp, field) = RB_RED;          \\\n                                elm = parent;                           \\\n                                parent = RB_PARENT(elm, field);         \\\n                        } else {                                        \\\n                                if (RB_RIGHT(tmp, field) == NULL ||     \\\n                                    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\\\n                                        struct type *oleft;             \\\n                                        if ((oleft = RB_LEFT(tmp, field)))\\\n                                                RB_COLOR(oleft, field) = RB_BLACK;\\\n                                        RB_COLOR(tmp, field) = RB_RED;  \\\n                                        RB_ROTATE_RIGHT(head, tmp, oleft, field);\\\n                                        tmp = RB_RIGHT(parent, field);  \\\n                                }                                       \\\n                                RB_COLOR(tmp, field) = RB_COLOR(parent, field);\\\n                                RB_COLOR(parent, field) = RB_BLACK;     \\\n                                if (RB_RIGHT(tmp, field))               \\\n                                        RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\\\n                                RB_ROTATE_LEFT(head, parent, tmp, field);\\\n                                elm = RB_ROOT(head);                    \\\n                                break;                                  \\\n                        }                                               \\\n                } else {                                                \\\n                        tmp = RB_LEFT(parent, field);                   \\\n                        if (RB_COLOR(tmp, field) == RB_RED) {           \\\n                                RB_SET_BLACKRED(tmp, parent, field);    \\\n                                RB_ROTATE_RIGHT(head, parent, tmp, field);\\\n                                tmp = RB_LEFT(parent, field);           \\\n                        }                                               \\\n                        if ((RB_LEFT(tmp, field) == NULL ||             \\\n                            RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\\\n                            (RB_RIGHT(tmp, field) == NULL ||            \\\n                            RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\\\n                                RB_COLOR(tmp, field) = RB_RED;          \\\n                                elm = parent;                           \\\n                                parent = RB_PARENT(elm, field);         \\\n                        } else {                                        \\\n                                if (RB_LEFT(tmp, field) == NULL ||      \\\n                                    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\\\n                                        struct type *oright;            \\\n                                        if ((oright = RB_RIGHT(tmp, field)))\\\n                                                RB_COLOR(oright, field) = RB_BLACK;\\\n                                        RB_COLOR(tmp, field) = RB_RED;  \\\n                                        RB_ROTATE_LEFT(head, tmp, oright, field);\\\n                                        tmp = RB_LEFT(parent, field);   \\\n                                }                                       \\\n                                RB_COLOR(tmp, field) = RB_COLOR(parent, field);\\\n                                RB_COLOR(parent, field) = RB_BLACK;     \\\n                                if (RB_LEFT(tmp, field))                \\\n                                        RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\\\n                                RB_ROTATE_RIGHT(head, parent, tmp, field);\\\n                                elm = RB_ROOT(head);                    \\\n                                break;                                  \\\n                        }                                               \\\n                }                                                       \\\n        }                                                               \\\n        if (elm)                                                        \\\n                RB_COLOR(elm, field) = RB_BLACK;                        \\\n}                                                                       \\\n                                                                        \\\nattr struct type *                                                      \\\nname##_RB_REMOVE(struct name *head, struct type *elm)                   \\\n{                                                                       \\\n        struct type *child, *parent, *old = elm;                        \\\n        int color;                                                      \\\n        if (RB_LEFT(elm, field) == NULL)                                \\\n                child = RB_RIGHT(elm, field);                           \\\n        else if (RB_RIGHT(elm, field) == NULL)                          \\\n                child = RB_LEFT(elm, field);                            \\\n        else {                                                          \\\n                struct type *left;                                      \\\n                elm = RB_RIGHT(elm, field);                             \\\n                while ((left = RB_LEFT(elm, field)))                    \\\n                        elm = left;                                     \\\n                child = RB_RIGHT(elm, field);                           \\\n                parent = RB_PARENT(elm, field);                         \\\n                color = RB_COLOR(elm, field);                           \\\n                if (child)                                              \\\n                        RB_PARENT(child, field) = parent;               \\\n                if (parent) {                                           \\\n                        if (RB_LEFT(parent, field) == elm)              \\\n                                RB_LEFT(parent, field) = child;         \\\n                        else                                            \\\n                                RB_RIGHT(parent, field) = child;        \\\n                        RB_AUGMENT(parent);                             \\\n                } else                                                  \\\n                        RB_ROOT(head) = child;                          \\\n                if (RB_PARENT(elm, field) == old)                       \\\n                        parent = elm;                                   \\\n                (elm)->field = (old)->field;                            \\\n                if (RB_PARENT(old, field)) {                            \\\n                        if (RB_LEFT(RB_PARENT(old, field), field) == old)\\\n                                RB_LEFT(RB_PARENT(old, field), field) = elm;\\\n                        else                                            \\\n                                RB_RIGHT(RB_PARENT(old, field), field) = elm;\\\n                        RB_AUGMENT(RB_PARENT(old, field));              \\\n                } else                                                  \\\n                        RB_ROOT(head) = elm;                            \\\n                RB_PARENT(RB_LEFT(old, field), field) = elm;            \\\n                if (RB_RIGHT(old, field))                               \\\n                        RB_PARENT(RB_RIGHT(old, field), field) = elm;   \\\n                if (parent) {                                           \\\n                        left = parent;                                  \\\n                        do {                                            \\\n                                RB_AUGMENT(left);                       \\\n                        } while ((left = RB_PARENT(left, field)));      \\\n                }                                                       \\\n                goto color;                                             \\\n        }                                                               \\\n        parent = RB_PARENT(elm, field);                                 \\\n        color = RB_COLOR(elm, field);                                   \\\n        if (child)                                                      \\\n                RB_PARENT(child, field) = parent;                       \\\n        if (parent) {                                                   \\\n                if (RB_LEFT(parent, field) == elm)                      \\\n                        RB_LEFT(parent, field) = child;                 \\\n                else                                                    \\\n                        RB_RIGHT(parent, field) = child;                \\\n                RB_AUGMENT(parent);                                     \\\n        } else                                                          \\\n                RB_ROOT(head) = child;                                  \\\ncolor:                                                                  \\\n        if (color == RB_BLACK)                                          \\\n                name##_RB_REMOVE_COLOR(head, parent, child);            \\\n        return (old);                                                   \\\n}                                                                       \\\n                                                                        \\\n/* Inserts a node into the RB tree */                                   \\\nattr struct type *                                                      \\\nname##_RB_INSERT(struct name *head, struct type *elm)                   \\\n{                                                                       \\\n        struct type *tmp;                                               \\\n        struct type *parent = NULL;                                     \\\n        int comp = 0;                                                   \\\n        tmp = RB_ROOT(head);                                            \\\n        while (tmp) {                                                   \\\n                parent = tmp;                                           \\\n                comp = (cmp)(elm, parent);                              \\\n                if (comp < 0)                                           \\\n                        tmp = RB_LEFT(tmp, field);                      \\\n                else if (comp > 0)                                      \\\n                        tmp = RB_RIGHT(tmp, field);                     \\\n                else                                                    \\\n                        return (tmp);                                   \\\n        }                                                               \\\n        RB_SET(elm, parent, field);                                     \\\n        if (parent != NULL) {                                           \\\n                if (comp < 0)                                           \\\n                        RB_LEFT(parent, field) = elm;                   \\\n                else                                                    \\\n                        RB_RIGHT(parent, field) = elm;                  \\\n                RB_AUGMENT(parent);                                     \\\n        } else                                                          \\\n                RB_ROOT(head) = elm;                                    \\\n        name##_RB_INSERT_COLOR(head, elm);                              \\\n        return (NULL);                                                  \\\n}                                                                       \\\n                                                                        \\\n/* Finds the node with the same key as elm */                           \\\nattr struct type *                                                      \\\nname##_RB_FIND(struct name *head, struct type *elm)                     \\\n{                                                                       \\\n        struct type *tmp = RB_ROOT(head);                               \\\n        int comp;                                                       \\\n        while (tmp) {                                                   \\\n                comp = cmp(elm, tmp);                                   \\\n                if (comp < 0)                                           \\\n                        tmp = RB_LEFT(tmp, field);                      \\\n                else if (comp > 0)                                      \\\n                        tmp = RB_RIGHT(tmp, field);                     \\\n                else                                                    \\\n                        return (tmp);                                   \\\n        }                                                               \\\n        return (NULL);                                                  \\\n}                                                                       \\\n                                                                        \\\n/* Finds the first node greater than or equal to the search key */      \\\nattr struct type *                                                      \\\nname##_RB_NFIND(struct name *head, struct type *elm)                    \\\n{                                                                       \\\n        struct type *tmp = RB_ROOT(head);                               \\\n        struct type *res = NULL;                                        \\\n        int comp;                                                       \\\n        while (tmp) {                                                   \\\n                comp = cmp(elm, tmp);                                   \\\n                if (comp < 0) {                                         \\\n                        res = tmp;                                      \\\n                        tmp = RB_LEFT(tmp, field);                      \\\n                }                                                       \\\n                else if (comp > 0)                                      \\\n                        tmp = RB_RIGHT(tmp, field);                     \\\n                else                                                    \\\n                        return (tmp);                                   \\\n        }                                                               \\\n        return (res);                                                   \\\n}                                                                       \\\n                                                                        \\\n/* ARGSUSED */                                                          \\\nattr struct type *                                                      \\\nname##_RB_NEXT(struct type *elm)                                        \\\n{                                                                       \\\n        if (RB_RIGHT(elm, field)) {                                     \\\n                elm = RB_RIGHT(elm, field);                             \\\n                while (RB_LEFT(elm, field))                             \\\n                        elm = RB_LEFT(elm, field);                      \\\n        } else {                                                        \\\n                if (RB_PARENT(elm, field) &&                            \\\n                    (elm == RB_LEFT(RB_PARENT(elm, field), field)))     \\\n                        elm = RB_PARENT(elm, field);                    \\\n                else {                                                  \\\n                        while (RB_PARENT(elm, field) &&                 \\\n                            (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\\\n                                elm = RB_PARENT(elm, field);            \\\n                        elm = RB_PARENT(elm, field);                    \\\n                }                                                       \\\n        }                                                               \\\n        return (elm);                                                   \\\n}                                                                       \\\n                                                                        \\\n/* ARGSUSED */                                                          \\\nattr struct type *                                                      \\\nname##_RB_PREV(struct type *elm)                                        \\\n{                                                                       \\\n        if (RB_LEFT(elm, field)) {                                      \\\n                elm = RB_LEFT(elm, field);                              \\\n                while (RB_RIGHT(elm, field))                            \\\n                        elm = RB_RIGHT(elm, field);                     \\\n        } else {                                                        \\\n                if (RB_PARENT(elm, field) &&                            \\\n                    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))    \\\n                        elm = RB_PARENT(elm, field);                    \\\n                else {                                                  \\\n                        while (RB_PARENT(elm, field) &&                 \\\n                            (elm == RB_LEFT(RB_PARENT(elm, field), field)))\\\n                                elm = RB_PARENT(elm, field);            \\\n                        elm = RB_PARENT(elm, field);                    \\\n                }                                                       \\\n        }                                                               \\\n        return (elm);                                                   \\\n}                                                                       \\\n                                                                        \\\nattr struct type *                                                      \\\nname##_RB_MINMAX(struct name *head, int val)                            \\\n{                                                                       \\\n        struct type *tmp = RB_ROOT(head);                               \\\n        struct type *parent = NULL;                                     \\\n        while (tmp) {                                                   \\\n                parent = tmp;                                           \\\n                if (val < 0)                                            \\\n                        tmp = RB_LEFT(tmp, field);                      \\\n                else                                                    \\\n                        tmp = RB_RIGHT(tmp, field);                     \\\n        }                                                               \\\n        return (parent);                                                \\\n}\n\n# define RB_NEGINF      -1\n# define RB_INF 1\n\n# define RB_INSERT(name, x, y)  name##_RB_INSERT(x, y)\n# define RB_REMOVE(name, x, y)  name##_RB_REMOVE(x, y)\n# define RB_FIND(name, x, y)    name##_RB_FIND(x, y)\n# define RB_NFIND(name, x, y)   name##_RB_NFIND(x, y)\n# define RB_NEXT(name, x, y)    name##_RB_NEXT(y)\n# define RB_PREV(name, x, y)    name##_RB_PREV(y)\n# define RB_MIN(name, x)                name##_RB_MINMAX(x, RB_NEGINF)\n# define RB_MAX(name, x)                name##_RB_MINMAX(x, RB_INF)\n\n# define RB_FOREACH(x, name, head)                                      \\\n        for ((x) = RB_MIN(name, head);                                  \\\n             (x) != NULL;                                               \\\n             (x) = name##_RB_NEXT(x))\n\n# define RB_FOREACH_SAFE(x, name, head, y)                              \\\n        for ((x) = RB_MIN(name, head);                                  \\\n            ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1);              \\\n             (x) = (y))\n\n# define RB_FOREACH_REVERSE(x, name, head)                              \\\n        for ((x) = RB_MAX(name, head);                                  \\\n             (x) != NULL;                                               \\\n             (x) = name##_RB_PREV(x))\n\n# define RB_FOREACH_REVERSE_SAFE(x, name, head, y)                      \\\n        for ((x) = RB_MAX(name, head);                                  \\\n            ((x) != NULL) && ((y) = name##_RB_PREV(x), 1);              \\\n             (x) = (y))\n\n#endif  /* _SYS_TREE_H_ */\n"
  },
  {
    "path": "include/sys/types.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _COMPAT_SYS_TYPES_H_\n# define _COMPAT_SYS_TYPES_H_\n/*\n * for major() / minor() macros with glibc, needs to be included\n *   before <sys/types.h>\n */\n# if defined(__GNU_LIBRARY__) && defined(__GLIBC_PREREQ)\n#  include <sys/sysmacros.h>\n# endif /* if defined(__GNU_LIBRARY__) && defined(__GLIBC_PREREQ) */\n#endif /* _COMPAT_SYS_TYPES_H_ */\n\n#include_next <sys/types.h>\n"
  },
  {
    "path": "include/util.h",
    "content": "/*      $OpenBSD: util.h,v 1.5 2022/12/26 19:16:03 jmc Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)util.h      10.5 (Berkeley) 3/16/96\n */\n\n#ifndef _UTIL_H\n# define _UTIL_H\n\n/* Macros to init/set/clear/test flags. */\n# define FL_INIT(l, f)  (l) = (f)                 /* Specific flags location. */\n# define FL_SET(l, f)   ((l) |= (f))\n# define FL_CLR(l, f)   ((l) &= ~(f))\n# define FL_ISSET(l, f) ((l) & (f))\n\n# define LF_INIT(f)     FL_INIT(flags, (f))       /* Local variable flags. */\n# define LF_SET(f)      FL_SET(flags, (f))\n# define LF_CLR(f)      FL_CLR(flags, (f))\n# define LF_ISSET(f)    FL_ISSET(flags, (f))\n\n# define F_INIT(p, f)   FL_INIT((p)->flags, (f))  /* Structure element flags. */\n# define F_SET(p, f)    FL_SET((p)->flags, (f))\n# define F_CLR(p, f)    FL_CLR((p)->flags, (f))\n# define F_ISSET(p, f)  FL_ISSET((p)->flags, (f))\n\n/* Offset to next column of stop size, e.g. tab offsets. */\n# define COL_OFF(c, stop)       ((stop) - ((c) % (stop)))\n\n/* Busy message types. */\ntypedef enum { B_NONE, B_OFF, B_READ, B_RECOVER, B_SEARCH, B_WRITE } bmsg_t;\n\n/*\n * Number handling defines and prototypes.\n *\n * NNFITS:      test for addition of two negative numbers under a limit\n * NPFITS:      test for addition of two positive numbers under a limit\n * NADD_SLONG:  test for addition of two signed longs\n * NADD_USLONG: test for addition of two unsigned longs\n */\nenum nresult { NUM_ERR, NUM_OK, NUM_OVER, NUM_UNDER };\n\n# define NNFITS(min, cur, add)                                          \\\n        (((long)(min)) - (cur) <= (add))\n\n# define NPFITS(max, cur, add)                                          \\\n        (((unsigned long)(max)) - (cur) >= (add))\n\n# define NADD_SLONG(v1, v2)                                             \\\n        ((v1) < 0 ?                                                     \\\n            ((v2) < 0 &&                                                \\\n            NNFITS(LONG_MIN, (v1), (v2))) ? NUM_UNDER : NUM_OK :        \\\n         (v1) > 0 ?                                                     \\\n            (v2) > 0 &&                                                 \\\n            NPFITS(LONG_MAX, (v1), (v2)) ? NUM_OK : NUM_OVER :          \\\n         NUM_OK)\n\n#endif /* ifndef _UTIL_H */\n"
  },
  {
    "path": "include/vi_extern.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nint cs_init(SCR *, VCS *);\nint cs_next(SCR *, VCS *);\nint cs_fspace(SCR *, VCS *);\nint cs_fblank(SCR *, VCS *);\nint cs_prev(SCR *, VCS *);\nint cs_bblank(SCR *, VCS *);\nint v_at(SCR *, VICMD *);\nint v_chrepeat(SCR *, VICMD *);\nint v_chrrepeat(SCR *, VICMD *);\nint v_cht(SCR *, VICMD *);\nint v_chf(SCR *, VICMD *);\nint v_chT(SCR *, VICMD *);\nint v_chF(SCR *, VICMD *);\nint v_delete(SCR *, VICMD *);\nint v_again(SCR *, VICMD *);\nint v_exmode(SCR *, VICMD *);\nint v_join(SCR *, VICMD *);\nint v_shiftl(SCR *, VICMD *);\nint v_shiftr(SCR *, VICMD *);\nint v_suspend(SCR *, VICMD *);\nint v_switch(SCR *, VICMD *);\nint v_tagpush(SCR *, VICMD *);\nint v_tagpop(SCR *, VICMD *);\nint v_filter(SCR *, VICMD *);\nint v_event_exec(SCR *, VICMD *);\nint v_ex(SCR *, VICMD *);\nint v_ecl_exec(SCR *);\nint v_increment(SCR *, VICMD *);\nint v_screen_copy(SCR *, SCR *);\nint v_screen_end(SCR *);\nint v_optchange(SCR *, int, char *, unsigned long *);\nint v_iA(SCR *, VICMD *);\nint v_ia(SCR *, VICMD *);\nint v_iI(SCR *, VICMD *);\nint v_ii(SCR *, VICMD *);\nint v_iO(SCR *, VICMD *);\nint v_io(SCR *, VICMD *);\nint v_change(SCR *, VICMD *);\nint v_Replace(SCR *, VICMD *);\nint v_subst(SCR *, VICMD *);\nint v_left(SCR *, VICMD *);\nint v_cfirst(SCR *, VICMD *);\nint v_first(SCR *, VICMD *);\nint v_ncol(SCR *, VICMD *);\nint v_zero(SCR *, VICMD *);\nint v_mark(SCR *, VICMD *);\nint v_bmark(SCR *, VICMD *);\nint v_fmark(SCR *, VICMD *);\nint v_match(SCR *, VICMD *);\nint v_paragraphf(SCR *, VICMD *);\nint v_paragraphb(SCR *, VICMD *);\nint v_buildps(SCR *, char *, char *);\nint v_Put(SCR *, VICMD *);\nint v_put(SCR *, VICMD *);\nint v_redraw(SCR *, VICMD *);\nint v_replace(SCR *, VICMD *);\nint v_right(SCR *, VICMD *);\nint v_dollar(SCR *, VICMD *);\nint v_screen(SCR *, VICMD *);\nint v_lgoto(SCR *, VICMD *);\nint v_home(SCR *, VICMD *);\nint v_middle(SCR *, VICMD *);\nint v_bottom(SCR *, VICMD *);\nint v_up(SCR *, VICMD *);\nint v_cr(SCR *, VICMD *);\nint v_down(SCR *, VICMD *);\nint v_hpageup(SCR *, VICMD *);\nint v_hpagedown(SCR *, VICMD *);\nint v_pagedown(SCR *, VICMD *);\nint v_pageup(SCR *, VICMD *);\nint v_lineup(SCR *, VICMD *);\nint v_linedown(SCR *, VICMD *);\nint v_searchb(SCR *, VICMD *);\nint v_searchf(SCR *, VICMD *);\nint v_searchN(SCR *, VICMD *);\nint v_searchn(SCR *, VICMD *);\nint v_searchw(SCR *, VICMD *);\nint v_correct(SCR *, VICMD *, int);\nint v_sectionf(SCR *, VICMD *);\nint v_sectionb(SCR *, VICMD *);\nint v_sentencef(SCR *, VICMD *);\nint v_sentenceb(SCR *, VICMD *);\nint v_status(SCR *, VICMD *);\nint v_tcmd(SCR *, VICMD *, CHAR_T, unsigned int);\nint v_txt(SCR *, VICMD *, MARK *,\nconst char *, size_t, CHAR_T, recno_t, unsigned long, u_int32_t);\nint v_txt_auto(SCR *, recno_t, TEXT *, size_t, TEXT *);\nint v_ulcase(SCR *, VICMD *);\nint v_mulcase(SCR *, VICMD *);\nint v_Undo(SCR *, VICMD *);\nint v_undo(SCR *, VICMD *);\nvoid v_eof(SCR *, MARK *);\nvoid v_eol(SCR *, MARK *);\nvoid v_nomove(SCR *);\nvoid v_sof(SCR *, MARK *);\nvoid v_sol(SCR *);\nint v_isempty(char *, size_t);\nvoid v_emsg(SCR *, char *, vim_t);\nint v_wordW(SCR *, VICMD *);\nint v_wordw(SCR *, VICMD *);\nint v_wordE(SCR *, VICMD *);\nint v_worde(SCR *, VICMD *);\nint v_wordB(SCR *, VICMD *);\nint v_wordb(SCR *, VICMD *);\nint v_xchar(SCR *, VICMD *);\nint v_Xchar(SCR *, VICMD *);\nint v_yank(SCR *, VICMD *);\nint v_z(SCR *, VICMD *);\nint vs_crel(SCR *, long);\nint v_zexit(SCR *, VICMD *);\nint vi(SCR **);\nint vs_line(SCR *, SMAP *, size_t *, size_t *);\nint vs_number(SCR *);\nvoid vs_busy(SCR *, const char *, busy_t);\nvoid vs_home(SCR *);\nvoid vs_update(SCR *, const char *, const char *);\nvoid vs_msg(SCR *, mtype_t, char *, size_t);\nint vs_ex_resolve(SCR *, int *);\nint vs_resolve(SCR *, SCR *, int);\nint vs_repaint(SCR *, EVENT *);\nint vs_refresh(SCR *, int);\nint vs_column(SCR *, size_t *);\nsize_t vs_screens(SCR *, recno_t, size_t *);\nsize_t vs_columns(SCR *, char *, recno_t, size_t *, size_t *);\nsize_t vs_rcm(SCR *, recno_t, int);\nsize_t vs_colpos(SCR *, recno_t, size_t);\nint vs_change(SCR *, recno_t, lnop_t);\nint vs_sm_fill(SCR *, recno_t, pos_t);\nint vs_sm_scroll(SCR *, MARK *, recno_t, scroll_t);\nint vs_sm_1up(SCR *);\nint vs_sm_1down(SCR *);\nint vs_sm_next(SCR *, SMAP *, SMAP *);\nint vs_sm_prev(SCR *, SMAP *, SMAP *);\nint vs_sm_cursor(SCR *, SMAP **);\nint vs_sm_position(SCR *, MARK *, unsigned long, pos_t);\nrecno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t);\nint vs_split(SCR *, SCR *, int);\nint vs_discard(SCR *, SCR **);\nint vs_fg(SCR *, SCR **, CHAR_T *, int);\nint vs_bg(SCR *);\nint vs_swap(SCR *, SCR **, char *);\nint vs_resize(SCR *, long, adj_t);\n"
  },
  {
    "path": "openbsd/basename.c",
    "content": "/*      $OpenBSD: basename.c,v 1.17 2020/10/20 19:30:14 naddy Exp $     */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 1997, 2004 Todd C. Miller <millert@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <errno.h>\n#include <libgen.h>\n#include <limits.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#undef open\n\nchar *\nopenbsd_basename(char *path)\n{\n  static char bname[PATH_MAX];\n  size_t len;\n  const char *endp, *startp;\n\n  /* Empty or NULL string gets treated as \".\" */\n  if (path == NULL || *path == '\\0')\n    {\n      bname[0] = '.';\n      bname[1] = '\\0';\n      return bname;\n    }\n\n  /* Strip any trailing slashes */\n  endp = path + strlen(path) - 1;\n  while (endp > path && *endp == '/')\n    {\n      endp--;\n    }\n\n  /* All slashes becomes \"/\" */\n  if (endp == path && *endp == '/')\n    {\n      bname[0] = '/';\n      bname[1] = '\\0';\n      return bname;\n    }\n\n  /* Find the start of the base */\n  startp = endp;\n  while (startp > path && *( startp - 1 ) != '/')\n    {\n      startp--;\n    }\n\n  len = endp - startp + 1;\n  if (len >= sizeof ( bname ))\n    {\n      errno = ENAMETOOLONG;\n      return NULL;\n    }\n\n  memcpy(bname, startp, len);\n  bname[len] = '\\0';\n  return bname;\n}\n"
  },
  {
    "path": "openbsd/dirname.c",
    "content": "/*      $OpenBSD: dirname.c,v 1.17 2020/10/20 19:30:14 naddy Exp $           */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 1997, 2004 Todd C. Miller <millert@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <errno.h>\n#include <libgen.h>\n#include <limits.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#undef open\n\nchar *\nopenbsd_dirname(char *path)\n{\n  static char dname[PATH_MAX];\n  size_t len;\n  const char *endp;\n\n  /* Empty or NULL string gets treated as \".\" */\n  if (path == NULL || *path == '\\0')\n    {\n      dname[0] = '.';\n      dname[1] = '\\0';\n      return dname;\n    }\n\n  /* Strip any trailing slashes */\n  endp = path + strlen(path) - 1;\n  while (endp > path && *endp == '/')\n    {\n      endp--;\n    }\n\n  /* Find the start of the dir */\n  while (endp > path && *endp != '/')\n    {\n      endp--;\n    }\n\n  /* Either the dir is \"/\" or there are no slashes */\n  if (endp == path)\n    {\n      dname[0] = *endp == '/' ? '/' : '.';\n      dname[1] = '\\0';\n      return dname;\n    }\n  else\n    {\n      /* Move forward past the separating slashes */\n      do\n        {\n          endp--;\n        }\n      while ( endp > path && *endp == '/' );\n    }\n\n  len = endp - path + 1;\n  if (len >= sizeof ( dname ))\n    {\n      errno = ENAMETOOLONG;\n      return NULL;\n    }\n\n  memcpy(dname, path, len);\n  dname[len] = '\\0';\n  return dname;\n}\n"
  },
  {
    "path": "openbsd/err.c",
    "content": "/*        $OpenBSD: err.c,v 1.12 2015/08/31 02:53:57 guenther Exp $          */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_err(int eval, const char *fmt, ...)\n{\n  va_list ap;\n\n  va_start(ap, fmt);\n  openbsd_verr(eval, fmt, ap);\n  va_end(ap);\n}\n"
  },
  {
    "path": "openbsd/errc.c",
    "content": "/*      $OpenBSD: errc.c,v 1.2 2015/08/31 02:53:57 guenther Exp $            */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n\n#include \"errc.h\"\n\n#undef open\n\nvoid\nopenbsd_errc(int eval, int code, const char *fmt, ...)\n{\n  va_list ap;\n\n  va_start(ap, fmt);\n  openbsd_verrc(eval, code, fmt, ap);\n  va_end(ap);\n}\n"
  },
  {
    "path": "openbsd/errc.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _ERRC_H\n# define _ERRC_H\n\n# include \"../include/compat.h\"\n\n# ifndef __solaris__\n#  include <sys/stat.h>\n#  include <sys/types.h>\n# else\n#  undef _TIMESPEC_UTIL_H\n#  define _TIMESPEC_UTIL_H 1\n# endif /* ifndef __solaris__ */\n\n# include <stdarg.h>\n# include <stddef.h>\n# include <grp.h>\n\n# undef open\n\nvoid openbsd_errc(int eval, int code, const char *fmt, ...);\nvoid openbsd_err(int eval, const char *fmt, ...);\nvoid openbsd_errx(int eval, const char *fmt, ...);\nvoid openbsd_verrc(int eval, int code, const char *fmt, va_list ap);\nvoid openbsd_verr(int eval, const char *fmt, va_list ap);\nvoid openbsd_verrx(int eval, const char *fmt, va_list ap);\nvoid openbsd_vwarnc(int code, const char *fmt, va_list ap);\nvoid openbsd_vwarn(const char *fmt, va_list ap);\nvoid openbsd_vwarnx(const char *fmt, va_list ap);\nvoid openbsd_warnc(int code, const char *fmt, ...);\nvoid openbsd_warn(const char *fmt, ...);\nvoid openbsd_warnx(const char *fmt, ...);\n\n#endif /* ifndef _ERRC_H */\n"
  },
  {
    "path": "openbsd/errx.c",
    "content": "/*         $OpenBSD: errx.c,v 1.11 2015/08/31 02:53:57 guenther Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_errx(int eval, const char *fmt, ...)\n{\n  va_list ap;\n\n  va_start(ap, fmt);\n  openbsd_verrx(eval, fmt, ap);\n  va_end(ap);\n}\n"
  },
  {
    "path": "openbsd/getopt_long.c",
    "content": "/*      $OpenBSD: getopt_long.c,v 1.32 2020/05/27 22:25:09 schwarze Exp $       */\n/*      $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $      */\n\n/* SPDX-License-Identifier: ISC AND BSD-2-Clause */\n\n/*\n * Copyright (c) 2002 Todd C. Miller <millert@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n *\n * Sponsored in part by the Defense Advanced Research Projects\n * Agency (DARPA) and Air Force Research Laboratory, Air Force\n * Materiel Command, USAF, under agreement number F39502-99-1-0512.\n */\n\n/*\n * Copyright (c) 2000 The NetBSD Foundation, Inc.\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to The NetBSD Foundation\n * by Dieter Baron and Thomas Klausner.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS\n * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"libgen.h\"\n#include \"getopt_long.h\"\n\n#include <bsd_err.h>\n#include <errno.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"errc.h\"\n\n#undef open\n\nint   openbsd_opterr = 1;    /* if error message should be printed */\nint   openbsd_optind = 1;    /* index into parent argv vector      */\nint   openbsd_optopt = '?';  /* character checked for validity     */\nint   openbsd_optreset;      /* reset getopt                       */\nchar *openbsd_optarg;        /* argument associated with option    */\n\n#define PRINT_ERROR (( openbsd_opterr ) && ( *options != ':' ))\n\n#define FLAG_PERMUTE  0x01  /* permute non-options to the end of argv   */\n#define FLAG_ALLARGS  0x02  /* treat non-options as args to option \"-1\" */\n#define FLAG_LONGONLY 0x04  /* operate as getopt_long_only              */\n\n/* return values */\n#define BADCH (int)'?'\n#define BADARG (( *options == ':' ) ? (int)':' : (int)'?' )\n#define INORDER (int)1\n\n#define EMSG \"\"\n\nstatic int openbsd_getopt_internal(int, char *const *, const char *,\n                                   const struct option *, int *, int);\n\nstatic int openbsd_parse_long_options(char *const *nargv, const char *options,\n                                      const struct option *long_options,\n                                      int *idx, int short_too, int flags);\nstatic int openbsd_gcd(int, int);\nstatic void openbsd_permute_args(int, int, int, char *const *);\n\nstatic char *place = EMSG; /* option letter processing */\n\nstatic int openbsd_nonopt_start = -1; /* first non option argument (for permute)      */\nstatic int openbsd_nonopt_end   = -1; /* first option after non options (for permute) */\n\n/* Error messages */\nstatic const char openbsd_recargchar[]   = \"option requires an argument -- %c\";\nstatic const char openbsd_recargstring[] = \"option requires an argument -- %s\";\nstatic const char openbsd_ambig[]        = \"ambiguous option -- %.*s\";\nstatic const char openbsd_noarg[]        = \"option doesn't take an argument -- %.*s\";\nstatic const char openbsd_illoptchar[]   = \"unknown option -- %c\";\nstatic const char openbsd_illoptstring[] = \"unknown option -- %s\";\n\n/*\n * Compute the greatest common divisor of a and b.\n */\n\nstatic int\nopenbsd_gcd(int a, int b)\n{\n  int c;\n\n  c = a % b;\n  while (c != 0)\n    {\n      a = b;\n      b = c;\n      c = a % b;\n    }\n\n  return b;\n}\n\n/*\n * Exchange the block from nonopt_start to nonopt_end with the block\n * from nonopt_end to opt_end (keeping the same order of arguments\n * in each block).\n */\n\nstatic void\nopenbsd_permute_args(int panonopt_start, int panonopt_end, int opt_end,\n                     char *const *nargv)\n{\n  int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;\n  char *swap;\n\n  /*\n   * Compute lengths of blocks and number and size of cycles ...\n   */\n\n  nnonopts = panonopt_end - panonopt_start;\n  nopts = opt_end - panonopt_end;\n  ncycle = openbsd_gcd(nnonopts, nopts);\n  cyclelen = ( opt_end - panonopt_start ) / ncycle;\n\n  for (i = 0; i < ncycle; i++)\n    {\n      cstart = panonopt_end + i;\n      pos = cstart;\n      for (j = 0; j < cyclelen; j++)\n        {\n          if (pos >= panonopt_end)\n            {\n              pos -= nnonopts;\n            }\n          else\n            {\n              pos += nopts;\n            }\n\n          swap = nargv[pos];\n          ((char **)nargv )[pos] = nargv[cstart];\n          ((char **)nargv )[cstart] = swap;\n        }\n    }\n}\n\n/*\n * openbsd_parse_long_options --\n *      Parse long options in argc/argv argument vector.\n *\n * Returns -1 if short_too is set and the option does not match long_options.\n */\n\nstatic int\nopenbsd_parse_long_options(char *const *nargv, const char *options,\n                           const struct option *long_options, int *idx,\n                           int short_too, int flags)\n{\n  char *current_argv, *has_equal;\n  size_t current_argv_len;\n  int i, match, exact_match, second_partial_match;\n\n  current_argv = place;\n  match = -1;\n  exact_match = 0;\n  second_partial_match = 0;\n\n  openbsd_optind++;\n\n  if (( has_equal = strchr(current_argv, '=')) != NULL)\n    {\n      /* argument found (--option=arg) */\n      current_argv_len = has_equal - current_argv;\n      has_equal++;\n    }\n  else\n    {\n      current_argv_len = strlen(current_argv);\n    }\n\n  for (i = 0; long_options[i].name; i++)\n    {\n      /* find matching long option */\n      if (strncmp(current_argv, long_options[i].name, current_argv_len))\n        {\n          continue;\n        }\n\n      if (strlen(long_options[i].name) == current_argv_len)\n        {\n          /* exact match */\n          match = i;\n          exact_match = 1;\n          break;\n        }\n\n      /*\n       * If this is a known short option, don't allow\n       * a partial match of a single character.\n       */\n\n      if (short_too && current_argv_len == 1)\n        {\n          continue;\n        }\n\n      if (match == -1) /* first partial match */\n        {\n          match = i;\n        }\n      else if (( flags & FLAG_LONGONLY )\n               || long_options[i].has_arg != long_options[match].has_arg\n               || long_options[i].flag != long_options[match].flag\n               || long_options[i].val != long_options[match].val)\n        {\n          second_partial_match = 1;\n        }\n    }\n\n  if (!exact_match && second_partial_match)\n    {\n      /* ambiguous abbreviation */\n      if (PRINT_ERROR)\n        {\n          openbsd_warnx(openbsd_ambig, (int)current_argv_len, current_argv);\n        }\n\n      openbsd_optopt = 0;\n      return BADCH;\n    }\n\n  if (match != -1)\n    { /* option found */\n      if (long_options[match].has_arg == no_argument && has_equal)\n        {\n          if (PRINT_ERROR)\n            {\n              openbsd_warnx(openbsd_noarg, (int)current_argv_len, current_argv);\n            }\n\n          /*\n           * XXX: GNU sets optopt to val regardless of flag\n           */\n\n          if (long_options[match].flag == NULL)\n            {\n              openbsd_optopt = long_options[match].val;\n            }\n          else\n            {\n              openbsd_optopt = 0;\n            }\n\n          return BADARG;\n        }\n\n      if (long_options[match].has_arg == required_argument\n          || long_options[match].has_arg == optional_argument)\n        {\n          if (has_equal)\n            {\n              openbsd_optarg = has_equal;\n            }\n          else if (long_options[match].has_arg == required_argument)\n            {\n\n              /*\n               * Optional argument doesn't use next nargv ...\n               */\n\n              openbsd_optarg = nargv[openbsd_optind++];\n            }\n        }\n\n      if (( long_options[match].has_arg == required_argument ) && ( openbsd_optarg == NULL ))\n        {\n\n          /*\n           * Missing argument; leading ':' indicates no error\n           * should be generated.\n           */\n\n          if (PRINT_ERROR)\n            {\n              openbsd_warnx(openbsd_recargstring, current_argv);\n            }\n\n          /*\n           * XXX: GNU sets optopt to val regardless of flag\n           */\n\n          if (long_options[match].flag == NULL)\n            {\n              openbsd_optopt = long_options[match].val;\n            }\n          else\n            {\n              openbsd_optopt = 0;\n            }\n\n          --openbsd_optind;\n          return BADARG;\n        }\n    }\n  else\n    { /* unknown option */\n      if (short_too)\n        {\n          --openbsd_optind;\n          return -1;\n        }\n\n      if (PRINT_ERROR)\n        {\n          openbsd_warnx(openbsd_illoptstring, current_argv);\n        }\n\n      openbsd_optopt = 0;\n      return BADCH;\n    }\n\n  if (idx)\n    {\n      *idx = match;\n    }\n\n  if (long_options[match].flag)\n    {\n      *long_options[match].flag = long_options[match].val;\n      return 0;\n    }\n  else\n    {\n      return long_options[match].val;\n    }\n}\n\n/*\n * openbsd_getopt_internal --\n *      Parse argc/argv argument vector.  Called by user level routines.\n */\n\nstatic int\nopenbsd_getopt_internal(int nargc, char *const *nargv, const char *options,\n                        const struct option *long_options, int *idx, int flags)\n{\n  char *oli; /* option letter list index */\n  int openbsd_optchar, short_too;\n  static int posixly_correct = -1;\n\n  if (options == NULL)\n    {\n      return -1;\n    }\n\n  /*\n   * Some GNU programs (like cvs) set optind to 0 instead of\n   * using optreset.  Work around this braindamage.\n   */\n\n  if (openbsd_optind == 0)\n    {\n      openbsd_optind = openbsd_optreset = 1;\n    }\n\n  /*\n   * Disable GNU extensions if POSIXLY_CORRECT or POSIX_ME_HARDER\n   * is set, or if the options string begins with a '+'.\n   */\n\n  if (posixly_correct == -1 || openbsd_optreset)\n    {\n      posixly_correct = ( getenv(\"POSIXLY_CORRECT\") != NULL );\n    }\n\n  if (posixly_correct == -1 || openbsd_optreset)\n    {\n      posixly_correct = ( getenv(\"POSIX_ME_HARDER\") != NULL );\n    }\n\n  if (*options == '-')\n    {\n      flags |= FLAG_ALLARGS;\n    }\n  else if (posixly_correct || *options == '+')\n    {\n      flags &= ~FLAG_PERMUTE;\n    }\n\n  if (*options == '+' || *options == '-')\n    {\n      options++;\n    }\n\n  openbsd_optarg = NULL;\n  if (openbsd_optreset)\n    {\n      openbsd_nonopt_start = openbsd_nonopt_end = -1;\n    }\n\nstart:\n  if (openbsd_optreset || !*place)\n    { /* update scanning pointer */\n      openbsd_optreset = 0;\n      if (openbsd_optind >= nargc)\n        { /* end of argument vector */\n          place = EMSG;\n          if (openbsd_nonopt_end != -1)\n            {\n              /* do permutation, if we have to */\n              openbsd_permute_args(openbsd_nonopt_start, openbsd_nonopt_end, openbsd_optind, nargv);\n              openbsd_optind -= openbsd_nonopt_end - openbsd_nonopt_start;\n            }\n          else if (openbsd_nonopt_start != -1)\n            {\n\n              /*\n               * If we skipped non-options, set optind\n               * to the first of them.\n               */\n\n              openbsd_optind = openbsd_nonopt_start;\n            }\n\n          openbsd_nonopt_start = openbsd_nonopt_end = -1;\n          return -1;\n        }\n\n      if (*( place = nargv[openbsd_optind] ) != '-'\n          || ( place[1] == '\\0' && strchr(options, '-') == NULL ))\n        {\n          place = EMSG; /* found non-option */\n          if (flags & FLAG_ALLARGS)\n            {\n\n              /*\n               * GNU extension:\n               * return non-option as argument to option 1\n               */\n\n              openbsd_optarg = nargv[openbsd_optind++];\n              return INORDER;\n            }\n\n          if (!( flags & FLAG_PERMUTE ))\n            {\n\n              /*\n               * If no permutation wanted, stop parsing\n               * at first non-option.\n               */\n\n              return -1;\n            }\n\n          /* do permutation */\n          if (openbsd_nonopt_start == -1)\n            {\n              openbsd_nonopt_start = openbsd_optind;\n            }\n          else if (openbsd_nonopt_end != -1)\n            {\n              openbsd_permute_args(openbsd_nonopt_start, openbsd_nonopt_end, openbsd_optind, nargv);\n              openbsd_nonopt_start = openbsd_optind - ( openbsd_nonopt_end - openbsd_nonopt_start );\n              openbsd_nonopt_end = -1;\n            }\n\n          openbsd_optind++;\n          /* process next argument */\n          goto start;\n        }\n\n      if (openbsd_nonopt_start != -1 && openbsd_nonopt_end == -1)\n        {\n          openbsd_nonopt_end = openbsd_optind;\n        }\n\n      /*\n       * If we have \"-\" do nothing, if \"--\" we are done.\n       */\n\n      if (place[1] != '\\0' && *++place == '-' && place[1] == '\\0')\n        {\n          openbsd_optind++;\n          place = EMSG;\n\n          /*\n           * We found an option (--), so if we skipped\n           * non-options, we have to permute.\n           */\n\n          if (openbsd_nonopt_end != -1)\n            {\n              openbsd_permute_args(openbsd_nonopt_start, openbsd_nonopt_end, openbsd_optind, nargv);\n              openbsd_optind -= openbsd_nonopt_end - openbsd_nonopt_start;\n            }\n\n          openbsd_nonopt_start = openbsd_nonopt_end = -1;\n          return -1;\n        }\n    }\n\n  /*\n   * Check long options if:\n   *  1) we were passed some\n   *  2) the arg is not just \"-\"\n   *  3) either the arg starts with -- we are openbsd_getopt_long_only()\n   */\n\n  if (long_options != NULL && place != nargv[openbsd_optind]\n      && ( *place == '-' || ( flags & FLAG_LONGONLY )))\n    {\n      short_too = 0;\n      if (*place == '-')\n        {\n          place++; /* --foo long option */\n        }\n      else if (*place != ':' && strchr(options, *place) != NULL)\n        {\n          short_too = 1; /* could be short option too */\n        }\n\n      openbsd_optchar = openbsd_parse_long_options(\n        nargv,\n        options,\n        long_options,\n        idx,\n        short_too,\n        flags);\n      if (openbsd_optchar != -1)\n        {\n          place = EMSG;\n          return openbsd_optchar;\n        }\n    }\n\n  if (( openbsd_optchar = (int)*place++ ) == (int)':'\n      || ( oli = strchr(options, openbsd_optchar)) == NULL)\n    {\n      if (!*place)\n        {\n          ++openbsd_optind;\n        }\n\n      if (PRINT_ERROR)\n        {\n          openbsd_warnx(openbsd_illoptchar, openbsd_optchar);\n        }\n\n      openbsd_optopt = openbsd_optchar;\n      return BADCH;\n    }\n\n  if (long_options != NULL && openbsd_optchar == 'W' && oli[1] == ';')\n    {\n      /* -W long-option */\n      if (*place) /* no space */\n      /* NOTHING */ {\n          ;\n        }\n      else if (++openbsd_optind >= nargc)\n        { /* no arg */\n          place = EMSG;\n          if (PRINT_ERROR)\n            {\n              openbsd_warnx(openbsd_recargchar, openbsd_optchar);\n            }\n\n          openbsd_optopt = openbsd_optchar;\n          return BADARG;\n        }\n      else /* white space */\n        {\n          place = nargv[openbsd_optind];\n        }\n\n      openbsd_optchar = openbsd_parse_long_options(nargv, options, long_options,\n                                           idx, 0, flags);\n      place = EMSG;\n      return openbsd_optchar;\n    }\n\n  if (*++oli != ':')\n    { /* doesn't take argument */\n      if (!*place)\n        {\n          ++openbsd_optind;\n        }\n    }\n  else\n    { /* takes (optional) argument */\n      openbsd_optarg = NULL;\n      if (*place) /* no white space */\n        {\n          openbsd_optarg = place;\n        }\n      else if (oli[1] != ':')\n        { /* arg not optional */\n          if (++openbsd_optind >= nargc)\n            { /* no arg */\n              place = EMSG;\n              if (PRINT_ERROR)\n                {\n                  openbsd_warnx(openbsd_recargchar, openbsd_optchar);\n                }\n\n              openbsd_optopt = openbsd_optchar;\n              return BADARG;\n            }\n          else\n            {\n              openbsd_optarg = nargv[openbsd_optind];\n            }\n        }\n\n      place = EMSG;\n      ++openbsd_optind;\n    }\n\n  /* dump back option letter */\n  return openbsd_optchar;\n}\n\n/*\n * opembsd_getopt --\n *      Parse argc/argv argument vector.\n */\n\nint\nopenbsd_getopt(int nargc, char *const *nargv, const char *options)\n{\n\n  /*\n   * We don't pass FLAG_PERMUTE to openbsd_getopt_internal() since\n   * the BSD getopt(3) (unlike GNU) has never done this.\n   *\n   * Furthermore, since many privileged programs call openbsd_getopt()\n   * before dropping privileges it makes sense to keep things\n   * as simple (and bug-free) as possible.\n   */\n\n  return openbsd_getopt_internal(nargc, nargv, options, NULL, NULL, 0);\n}\n\n/*\n * openbsd_getopt_long --\n *      Parse argc/argv argument vector.\n */\n\nint\nopenbsd_getopt_long(int nargc, char *const *nargv, const char *options,\n                    const struct option *long_options, int *idx)\n{\n  return openbsd_getopt_internal(\n    nargc,\n    nargv,\n    options,\n    long_options,\n    idx,\n    FLAG_PERMUTE);\n}\n\n/*\n * getopt_long_only --\n *      Parse argc/argv argument vector.\n */\n\nint\nopenbsd_getopt_long_only(int nargc, char *const *nargv, const char *options,\n                         const struct option *long_options, int *idx)\n{\n  return openbsd_getopt_internal(\n    nargc,\n    nargv,\n    options,\n    long_options,\n    idx,\n    FLAG_PERMUTE | FLAG_LONGONLY);\n}\n"
  },
  {
    "path": "openbsd/getopt_long.h",
    "content": "/*      $OpenBSD: getopt.h,v 1.3 2013/11/22 21:32:49 millert Exp $           */\n/*      $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $                 */\n\n/* SPDX-License-Identifier: BSD-2-Clause */\n\n/*\n * Copyright (c) 2000 The NetBSD Foundation, Inc.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to The NetBSD Foundation\n * by Dieter Baron and Thomas Klausner.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS\n * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _OBSD_GETOPT_H_\n# define _OBSD_GETOPT_H_\n\n/*\n * GNU-like getopt_long()\n */\n# define no_argument       0\n# define required_argument 1\n# define optional_argument 2\n\nstruct option\n{\n  /* name of long option */\n  const char *name;\n  /*\n   * one of no_argument, required_argument, and optional_argument:\n   * whether option takes an argument\n   */\n  int has_arg;\n  /* if not NULL, set *flag to val when option found */\n  int *flag;\n  /* if flag not NULL, value to set *flag to; else return value */\n  int val;\n};\n\nint openbsd_getopt_long (int, char *const *, const char *, const struct option *,\n                         int *);\nint openbsd_getopt_long_only (int, char *const *, const char *, const struct option *,\n                              int *);\n# ifndef _GETOPT_DEFINED_\n#  define _GETOPT_DEFINED_\nint openbsd_getopt (int, char *const *, const char *);\n\nextern char *openbsd_optarg; /* getopt(3) external variables */\nextern int   openbsd_opterr;\nextern int   openbsd_optind;\nextern int   openbsd_optopt;\nextern int   openbsd_optreset;\n# endif /* _GETOPT_DEFINED_ */\n\n#endif /* !_OBSD_GETOPT_H_ */\n"
  },
  {
    "path": "openbsd/getprogname.c",
    "content": "/* $OpenBSD: getprogname.c,v 1.4 2016/03/13 18:34:20 guenther Exp $ */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 2013 Antoine Jacoutot <ajacoutot@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <bsd_stdlib.h>\n\n#ifdef _AIX\n# include <bsd_unistd.h>\n# include <bsd_string.h>\n# include <procinfo.h>\n#else\nextern char *__progname;\n#endif /* ifdef _AIX */\n\nconst char *\nbsd_getprogname(void)\n{\n#ifdef _AIX\n  static char *p;\n  static int first = 1;\n  if (first)\n    {\n      first = 0;\n      pid_t pid = getpid();\n      struct procentry64 procs;\n      p = (0 < getprocs64 (&procs, sizeof procs, NULL, 0, &pid, 1)\n           ? strdup (procs.pi_comm) : NULL);\n      if (!p)\n        p = \"?\";\n    }\n  return p;\n#else\n  return (__progname);\n#endif /* ifdef _AIX */\n}\n"
  },
  {
    "path": "openbsd/issetugid.c",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022 Ørjan Malde <red@foxi.me>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <errno.h>\n\n#undef open\n\n#if ( defined(__GLIBC__) && defined(__GLIBC_MINOR__) ) \\\n         || defined(__linux__) || defined(__midipix__) \\\n         || defined(__illumos__)\n# include <sys/auxv.h>\n#else\n# include <unistd.h>\n#endif /* if ( defined(__GLIBC__) && defined(__GLIBC_MINOR__) )\n                  || defined(__linux__) || defined(__midipix__)\n                  || defined(__illumos__) */\n\n#if defined(__FreeBSD__) || defined(__OpenBSD__) \\\n       || ( defined(__APPLE__ ) && defined(__MACH__) ) \\\n       || defined(__CYGWIN__) || defined(__NetBSD__)\n# include <unistd.h>\n#else\n\n# if defined(__linux__) || defined(__illumos__)\n#  include <elf.h>\n# endif /* if defined(__linux__) || defined(__illumos__) */\n\nint\nissetugid(void)\n{\n  int rv = 0;\n\n  errno = 0;\n# if !defined(_AIX) \\\n  && !defined(__illumos__) && !defined(__solaris__) \\\n  && !defined(__managarm__)\n  rv = getauxval(AT_SECURE) != 0;\n# endif /* if !defined(_AIX)\n           && !defined(__illumos__) && !defined(__solaris__) */\n  if (errno)\n    {\n      errno = 0;\n      rv = 1;\n    }\n\n  return rv;\n}\n#endif /* if defined(__FreeBSD__) || defined(__OpenBSD__)\n                || ( defined(__APPLE__) && defined(__MACH__) )\n                || defined(__CYGWIN__) || defined(__NetBSD__) */\n"
  },
  {
    "path": "openbsd/minpwcache.c",
    "content": "/*      $OpenBSD: pwcache.c,v 1.16 2022/12/27 17:10:06 jmc Exp $         */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992 Keith Muller\n * Copyright (c) 1992, 1993 The Regents of the University of California\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Keith Muller of the University of California, San Diego.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <assert.h>\n\n#if defined(__illumos__) && !defined(_POSIX_PTHREAD_SEMANTICS)\n# define _POSIX_PTHREAD_SEMANTICS\n#endif /* if defined(__illumos__) && !defined(_POSIX_PTHREAD_SEMANTICS) */\n\n\n#include <grp.h>\n#include <pwd.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n/*\n * Constants and data structures used to implement group and password file\n * caches.  Name lengths have been chosen to be as large as those supported\n * by the passwd and group files as well as the standard archive formats.\n * CACHE SIZES MUST BE PRIME\n */\n#define UNMLEN  32    /* >= user name found in any protocol  */\n#define GNMLEN  32    /* >= group name found in any protocol */\n#define UID_SZ 317    /* size of uid to user_name cache      */\n#define UNM_SZ 317    /* size of user_name to uid cache      */\n#define GID_SZ 251    /* size of gid to group_name cache     */\n#define GNM_SZ 251    /* size of group_name to gid cache     */\n#define VALID    1    /* entry and name are valid            */\n#define INVALID  2    /* entry valid, name NOT valid         */\n\n/*\n * Node structures used in the user, group, uid, and gid caches.\n */\n\ntypedef struct uidc\n{\n  int valid;         /* is this a valid or a miss entry */\n  char name[UNMLEN]; /* uid name */\n  uid_t uid;         /* cached uid */\n} UIDC;\n\ntypedef struct gidc\n{\n  int valid;         /* is this a valid or a miss entry */\n  char name[GNMLEN]; /* gid name */\n  gid_t gid;         /* cached gid */\n} GIDC;\n\n/*\n * Routines that control user, group, uid and gid caches.\n * Traditional passwd/group cache routines perform quite poorly with\n * archives. The chances of hitting a valid lookup with an archive is quite a\n * bit worse than with files already resident on the file system. These misses\n * create a MAJOR performance cost. To address this problem, these routines\n * cache both hits and misses.\n */\n\nstatic UIDC **usrtb; /* user name to uid cache */\nstatic GIDC **grptb; /* group name to gid cache */\n\nstatic unsigned int\nst_hash(const char *name, size_t len, int tabsz)\n{\n  unsigned int key = 0;\n\n  assert(name != NULL);\n\n  while (len--)\n    {\n      key += *name++;\n      key = ( key << 8 ) | ( key >> 24 );\n    }\n\n  return key % tabsz;\n}\n\n/*\n * usrtb_start\n *      creates an an empty usrtb\n * Return:\n *      0 if ok, -1 otherwise\n */\nstatic int\nusrtb_start(void)\n{\n  static int fail = 0;\n\n  if (usrtb != NULL)\n    {\n      return 0;\n    }\n\n  if (fail)\n    {\n      return -1;\n    }\n\n  if (( usrtb = calloc(UNM_SZ, sizeof ( UIDC * ))) == NULL)\n    {\n      ++fail;\n      return -1;\n    }\n\n  return 0;\n}\n\n/*\n * grptb_start\n *      creates an an empty grptb\n * Return:\n *      0 if ok, -1 otherwise\n */\nstatic int\ngrptb_start(void)\n{\n  static int fail = 0;\n\n  if (grptb != NULL)\n    {\n      return 0;\n    }\n\n  if (fail)\n    {\n      return -1;\n    }\n\n  if (( grptb = calloc(GNM_SZ, sizeof ( GIDC * ))) == NULL)\n    {\n      ++fail;\n      return -1;\n    }\n\n  return 0;\n}\n\n/*\n * uid_from_user()\n *      caches the uid for a given user name. We use a simple hash table.\n * Return:\n *      0 if the user name is found (filling in uid), -1 otherwise\n */\nint\nopenbsd_uid_from_user(const char *name, uid_t *uid)\n{\n  struct passwd pwstore, *pw = NULL;\n  char pwbuf[_PW_BUF_LEN];\n  UIDC **pptr, *ptr = NULL;\n  size_t namelen;\n\n  /*\n   * return -1 for mangled names\n   */\n  if (name == NULL || (( namelen = strlen(name)) == 0 ))\n    {\n      return -1;\n    }\n\n  if (( usrtb != NULL ) || ( usrtb_start() == 0 ))\n    {\n      /*\n       * look up in hash table, if found and valid return the uid,\n       * if found and invalid, return a -1\n       */\n      pptr = usrtb + st_hash(name, namelen, UNM_SZ);\n      ptr = *pptr;\n\n      if (( ptr != NULL ) && ( ptr->valid > 0 ) && strcmp(name, ptr->name) == 0)\n        {\n          if (ptr->valid == INVALID)\n            {\n              return -1;\n            }\n\n          *uid = ptr->uid;\n          return 0;\n        }\n\n      if (ptr == NULL)\n        {\n          *pptr = ptr = malloc(sizeof ( UIDC ));\n        }\n    }\n\n  /*\n   * no match, look it up, if no match store it as an invalid entry,\n   * or store the matching uid\n   */\n  getpwnam_r(name, &pwstore, pwbuf, sizeof ( pwbuf ), &pw);\n  if (ptr == NULL)\n    {\n      if (pw == NULL)\n        {\n          return -1;\n        }\n\n      *uid = pw->pw_uid;\n      return 0;\n    }\n\n  (void)openbsd_strlcpy(ptr->name, name, sizeof ( ptr->name ));\n  if (pw == NULL)\n    {\n      ptr->valid = INVALID;\n      return -1;\n    }\n\n  ptr->valid = VALID;\n  *uid = ptr->uid = pw->pw_uid;\n  return 0;\n}\n\n/*\n * gid_from_group()\n *      caches the gid for a given group name. We use a simple hash table.\n * Return:\n *      0 if the group name is found (filling in gid), -1 otherwise\n */\nint\nopenbsd_gid_from_group(const char *name, gid_t *gid)\n{\n  struct group grstore, *gr = NULL;\n  char grbuf[_GR_BUF_LEN];\n  GIDC **pptr, *ptr = NULL;\n  size_t namelen;\n\n  /*\n   * return -1 for mangled names\n   */\n  if (name == NULL || (( namelen = strlen(name)) == 0 ))\n    {\n      return -1;\n    }\n\n  if (( grptb != NULL ) || ( grptb_start() == 0 ))\n    {\n      /*\n       * look up in hash table, if found and valid return the uid,\n       * if found and invalid, return a -1\n       */\n      pptr = grptb + st_hash(name, namelen, GID_SZ);\n      ptr = *pptr;\n\n      if (( ptr != NULL ) && ( ptr->valid > 0 ) && strcmp(name, ptr->name) == 0)\n        {\n          if (ptr->valid == INVALID)\n            {\n              return -1;\n            }\n\n          *gid = ptr->gid;\n          return 0;\n        }\n\n      if (ptr == NULL)\n        {\n          *pptr = ptr = malloc(sizeof ( GIDC ));\n        }\n    }\n\n  /*\n   * no match, look it up, if no match store it as an invalid entry,\n   * or store the matching gid\n   */\n  getgrnam_r(name, &grstore, grbuf, sizeof ( grbuf ), &gr);\n  if (ptr == NULL)\n    {\n      if (gr == NULL)\n        {\n          return -1;\n        }\n\n      *gid = gr->gr_gid;\n      return 0;\n    }\n\n  (void)openbsd_strlcpy(ptr->name, name, sizeof ( ptr->name ));\n  if (gr == NULL)\n    {\n      ptr->valid = INVALID;\n      return -1;\n    }\n\n  ptr->valid = VALID;\n  *gid = ptr->gid = gr->gr_gid;\n  return 0;\n}\n"
  },
  {
    "path": "openbsd/minpwcache.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _MINPWCAC_H\n# define _MINPWCAC_H\n\n# include \"../include/compat.h\"\n\n# include <sys/stat.h>\n# include <sys/types.h>\n\n# include <grp.h>\n# include <stdarg.h>\n# include <stddef.h>\n\n# undef open\n\nint openbsd_gid_from_group(const char *name, gid_t *gid);\nint openbsd_uid_from_user(const char *name, uid_t *uid);\n\n#endif /* ifndef _MINPWCAC_H */\n"
  },
  {
    "path": "openbsd/open.c",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/file.h>\n\n#include <bsd_fcntl.h>\n#include <bsd_unistd.h>\n#include <stdarg.h>\n#include <stdio.h>\n\n#undef open\n\nint\nopenbsd_open(const char *path, int flags, ...)\n{\n  va_list ap;\n  int fd, lock;\n\n  lock = flags & ( O_EXLOCK | O_SHLOCK );\n  flags &= ~lock;\n\n  va_start(ap, flags);\n  if (( fd = open(path, flags, ap)) == -1)\n    {\n      return -1;\n    }\n\n  va_end(ap);\n\n  if (lock == 0)\n    {\n      return fd;\n    }\n\n#ifndef __solaris__\n  if (flock(fd, lock & O_EXLOCK ? LOCK_EX : LOCK_SH) == -1)\n    {\n      close(fd);\n      return -1;\n    }\n#endif /* ifndef __solaris__ */\n\n  return fd;\n}\n"
  },
  {
    "path": "openbsd/pledge.c",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2021-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <bsd_unistd.h>\n\n#undef open\n\n#ifdef __OpenBSD__\n# include <sys/param.h>\n#endif /* ifdef __OpenBSD__ */\n\nint\nopenbsd_pledge(const char *promises, const char *execpromises)\n{\n#if defined(__OpenBSD__) || defined(__serenity__)\n# if ( defined(OpenBSD) && OpenBSD >= 201605 ) \\\n    || defined(OpenBSD5_9) || defined(__serenity__)\n        return(pledge(promises, execpromises));\n# else\n        return 0;\n# endif /* if ( defined(OpenBSD) && OpenBSD >= 201605 )\n             || defined(OpenBSD5_9) || defined(__serenity__) */\n#else\n        return 0;\n#endif /* if defined(__OpenBSD__) || defined(__serenity__) */\n}\n"
  },
  {
    "path": "openbsd/reallocarray.c",
    "content": "/*      $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $       */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <errno.h>\n#include <stdint.h>\n#include <bsd_stdlib.h>\n\n#undef open\n\n/*\n * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX\n * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW\n */\n\n#ifdef MUL_NO_OVERFLOW\n# undef MUL_NO_OVERFLOW\n#endif /* ifdef MUL_NO_OVERFLOW */\n#define MUL_NO_OVERFLOW ((size_t)1 << ( sizeof ( size_t ) * 4 ))\n\nvoid *\nopenbsd_reallocarray(void *optr, size_t nmemb, size_t size)\n{\n  if (( nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW ) && nmemb > 0\n      && SIZE_MAX / nmemb < size)\n    {\n      errno = ENOMEM;\n      return NULL;\n    }\n\n  return realloc(optr, size * nmemb);\n}\n"
  },
  {
    "path": "openbsd/setmode.c",
    "content": "/*      $OpenBSD: setmode.c,v 1.22 2014/10/11 04:14:35 deraadt Exp $         */\n/*      $NetBSD: setmode.c,v 1.15 1997/02/07 22:21:06 christos Exp $         */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1989, 1993, 1994 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Dave Borman at Cray Research, Inc.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#include <bsd_stdlib.h>\n#include <bsd_unistd.h>\n#include <ctype.h>\n#include <errno.h>\n#ifdef __solaris__\n# undef __EXTENSIONS__\n# define __EXTENSIONS__\n# define _XPG7\n# include <sys/procset.h>\n# undef __EXTENSIONS__\n# include <signal.h>\n#else\n# include <signal.h>\n#endif /* ifdef __solaris__ */\n\n#undef open\n\n#define SET_LEN 6      /* initial # of bitcmd struct to malloc */\n#define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */\n\ntypedef struct bitcmd\n{\n  char cmd;\n  char cmd2;\n  mode_t bits;\n} BITCMD;\n\n#define CMD2_CLR 0x01\n#define CMD2_SET 0x02\n#define CMD2_GBITS 0x04\n#define CMD2_OBITS 0x08\n#define CMD2_UBITS 0x10\n\nstatic BITCMD *addcmd(BITCMD *, int, int, int, unsigned int);\nstatic void compress_mode(BITCMD *);\n\n/*\n * Given the old mode and an array of bitcmd structures, apply the operations\n * described in the bitcmd structures to the old mode, and return the new mode.\n * Note that there is no '=' command; a strict assignment is just a '-' (clear\n * bits) followed by a '+' (set bits).\n */\nmode_t\nopenbsd_getmode(const void *bbox, mode_t omode)\n{\n  const BITCMD *set;\n  mode_t clrval, newmode, value;\n\n  set = (const BITCMD *)bbox;\n  newmode = omode;\n  for (value = 0;; set++)\n    {\n      (void)value;\n      switch (set->cmd)\n        {\n        /*\n         * When copying the user, group or other bits around, we \"know\"\n         * where the bits are in the mode so that we can do shifts to\n         * copy them around.  If we don't use shifts, it gets real\n         * grundgy with lots of single bit checks and bit sets.\n         */\n        case 'u':\n          value = ( newmode & S_IRWXU ) >> 6;\n          (void)value;\n          goto common;\n\n        case 'g':\n          value = ( newmode & S_IRWXG ) >> 3;\n          (void)value;\n          goto common;\n\n        case 'o':\n          value = newmode & S_IRWXO;\n          (void)value;\ncommon:\n          if (set->cmd2 & CMD2_CLR)\n            {\n              clrval = ( set->cmd2 & CMD2_SET ) ? S_IRWXO : value;\n              if (set->cmd2 & CMD2_UBITS)\n                {\n                  newmode &= ~(( clrval << 6 ) & set->bits );\n                }\n\n              if (set->cmd2 & CMD2_GBITS)\n                {\n                  newmode &= ~(( clrval << 3 ) & set->bits );\n                }\n\n              if (set->cmd2 & CMD2_OBITS)\n                {\n                  newmode &= ~( clrval & set->bits );\n                }\n            }\n\n          if (set->cmd2 & CMD2_SET)\n            {\n              if (set->cmd2 & CMD2_UBITS)\n                {\n                  newmode |= ( value << 6 ) & set->bits;\n                }\n\n              if (set->cmd2 & CMD2_GBITS)\n                {\n                  newmode |= ( value << 3 ) & set->bits;\n                }\n\n              if (set->cmd2 & CMD2_OBITS)\n                {\n                  newmode |= value & set->bits;\n                }\n            }\n\n          break;\n\n        case '+':\n          newmode |= set->bits;\n          break;\n\n        case '-':\n          newmode &= ~set->bits;\n          break;\n\n        case 'X':\n          if (omode & ( S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH ))\n            {\n              newmode |= set->bits;\n            }\n\n          break;\n\n        case '\\0':\n        default:\n          return newmode;\n        }\n    }\n}\n\n#define ADDCMD(a, b, c, d)                                                    \\\n  if (set >= endset)                                                          \\\n    {                                                                         \\\n      BITCMD *newset;                                                         \\\n      setlen += SET_LEN_INCR;                                                 \\\n      newset = openbsd_reallocarray(saveset, setlen, sizeof ( BITCMD ));      \\\n      if (newset == NULL)                                                     \\\n        {                                                                     \\\n          free(saveset);                                                      \\\n          return NULL;                                                        \\\n        }                                                                     \\\n      set = newset + ( set - saveset );                                       \\\n      saveset = newset;                                                       \\\n      endset = newset + ( setlen - 2 );                                       \\\n    }                                                                         \\\n  set = addcmd(set, ( a ), ( b ), ( c ), ( d ))\n\n#define STANDARD_BITS ( S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO )\n\nvoid *\nopenbsd_setmode(const char *p)\n{\n  char op, *ep;\n  BITCMD *set, *saveset, *endset;\n  sigset_t sigset, sigoset;\n  mode_t mask, perm, permXbits, who;\n  int equalopdone, setlen;\n  unsigned long perml;\n\n  if (!*p)\n    {\n      errno = EINVAL;\n      return NULL;\n    }\n\n  /*\n   * Get a copy of the mask for the permissions that are mask relative.\n   * Flip the bits, we want what's not set.  Since it's possible that\n   * the caller is opening files inside a signal handler, protect them\n   * as best we can.\n   */\n  sigfillset(&sigset);\n  (void)sigprocmask(SIG_BLOCK, &sigset, &sigoset);\n  (void)umask(mask = umask(0));\n  mask = ~mask;\n  (void)sigprocmask(SIG_SETMASK, &sigoset, NULL);\n\n  setlen = SET_LEN + 2;\n\n  if (( set = calloc((unsigned int)sizeof ( BITCMD ), setlen)) == NULL)\n    {\n      return NULL;\n    }\n\n  saveset = set;\n  endset = set + ( setlen - 2 );\n\n  /*\n   * If an absolute number, get it and return; disallow non-octal digits\n   * or illegal bits.\n   */\n  if (isdigit((unsigned char)*p))\n    {\n      perml = strtoul(p, &ep, 8);\n      /* The test on perml will also catch overflow. */\n      if (*ep != '\\0' || ( perml & ~( STANDARD_BITS | S_ISTXT )))\n        {\n          free(saveset);\n          errno = ERANGE;\n          return NULL;\n        }\n\n      perm = (mode_t)perml;\n      (void)perm;\n      ADDCMD('=', ( STANDARD_BITS | S_ISTXT ), perm, mask);\n      set->cmd = 0;\n      return saveset;\n    }\n\n  /*\n   * Build list of structures to set/clear/copy bits as described by\n   * each clause of the symbolic mode.\n   */\n  for (;;)\n    {\n      /* First, find out which bits might be modified. */\n      for (who = 0;; ++p)\n        {\n          switch (*p)\n            {\n            case 'a':\n              who |= STANDARD_BITS;\n              break;\n\n            case 'u':\n              who |= S_ISUID | S_IRWXU;\n              break;\n\n            case 'g':\n              who |= S_ISGID | S_IRWXG;\n              break;\n\n            case 'o':\n              who |= S_IRWXO;\n              break;\n\n            default:\n              goto getop;\n            }\n        }\n\ngetop:\n      if (( op = *p++ ) != '+' && op != '-' && op != '=')\n        {\n          free(saveset);\n          errno = EINVAL;\n          return NULL;\n        }\n\n      if (op == '=')\n        {\n          equalopdone = 0;\n        }\n\n      who &= ~S_ISTXT;\n      for (perm = 0, permXbits = 0;; ++p)\n        {\n          switch (*p)\n            {\n            case 'r':\n              perm |= S_IRUSR | S_IRGRP | S_IROTH;\n              break;\n\n            case 's':\n              /*\n               * If specific bits where requested and\n               * only \"other\" bits ignore set-id.\n               */\n              if (who == 0 || ( who & ~S_IRWXO ))\n                {\n                  perm |= S_ISUID | S_ISGID;\n                }\n\n              break;\n\n            case 't':\n              /*\n               * If specific bits where requested and\n               * only \"other\" bits ignore sticky.\n               */\n              if (who == 0 || ( who & ~S_IRWXO ))\n                {\n                  who |= S_ISTXT;\n                  perm |= S_ISTXT;\n                }\n\n              break;\n\n            case 'w':\n              perm |= S_IWUSR | S_IWGRP | S_IWOTH;\n              break;\n\n            case 'X':\n              permXbits = S_IXUSR | S_IXGRP | S_IXOTH;\n              (void)permXbits;\n              break;\n\n            case 'x':\n              perm |= S_IXUSR | S_IXGRP | S_IXOTH;\n              break;\n\n            case 'u':\n            case 'g':\n            case 'o':\n              /*\n               * When ever we hit 'u', 'g', or 'o', we have\n               * to flush out any partial mode that we have,\n               * and then do the copying of the mode bits.\n               */\n              if (perm)\n                {\n                  ADDCMD(op, who, perm, mask);\n                  perm = 0;\n                  (void)perm;\n                }\n\n              if (op == '=')\n                {\n                  equalopdone = 1;\n                }\n\n              if (op == '+' && permXbits)\n                {\n                  ADDCMD('X', who, permXbits, mask);\n                  permXbits = 0;\n                  (void)permXbits;\n                }\n\n              ADDCMD(*p, who, op, mask);\n              break;\n\n            default:\n              /*\n               * Add any permissions that we haven't already\n               * done.\n               */\n              if (perm || ( op == '=' && !equalopdone ))\n                {\n                  if (op == '=')\n                    {\n                      equalopdone = 1;\n                    }\n\n                  ADDCMD(op, who, perm, mask);\n                  perm = 0;\n                  (void)perm;\n                }\n\n              if (permXbits)\n                {\n                  ADDCMD('X', who, permXbits, mask);\n                  permXbits = 0;\n                  (void)permXbits;\n                }\n\n              goto apply;\n            }\n        }\n\napply:\n      if (!*p)\n        {\n          break;\n        }\n\n      if (*p != ',')\n        {\n          goto getop;\n        }\n\n      ++p;\n    }\n\n  set->cmd = 0;\n  compress_mode(saveset);\n  return saveset;\n}\n\nstatic BITCMD *\naddcmd(BITCMD *set, int op, int who, int oparg, unsigned int mask)\n{\n  switch (op)\n    {\n    case '=':\n      set->cmd = '-';\n      set->bits = who ? who : STANDARD_BITS;\n      set++;\n\n      op = '+';\n\n    /* FALLTHROUGH */\n    case '+':\n    case '-':\n    case 'X':\n      set->cmd = op;\n      set->bits = ( who ? who : mask ) & oparg;\n      break;\n\n    case 'u':\n    case 'g':\n    case 'o':\n      set->cmd = op;\n      if (who)\n        {\n          set->cmd2 = (( who & S_IRUSR ) ? CMD2_UBITS : 0 )\n                      | (( who & S_IRGRP ) ? CMD2_GBITS : 0 )\n                      | (( who & S_IROTH ) ? CMD2_OBITS : 0 );\n          set->bits = ( mode_t ) ~0;\n        }\n      else\n        {\n          set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;\n          set->bits = mask;\n        }\n\n      if (oparg == '+')\n        {\n          set->cmd2 |= CMD2_SET;\n        }\n      else if (oparg == '-')\n        {\n          set->cmd2 |= CMD2_CLR;\n        }\n      else if (oparg == '=')\n        {\n          set->cmd2 |= CMD2_SET | CMD2_CLR;\n        }\n\n      break;\n    }\n  return set + 1;\n}\n\n/*\n * Given an array of bitcmd structures, compress by compacting consecutive\n * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',\n * 'g' and 'o' commands continue to be separate.  They could probably be\n * compacted, but it's not worth the effort.\n */\nstatic void\ncompress_mode(BITCMD *set)\n{\n  BITCMD *nset;\n  int setbits, clrbits, Xbits, op;\n\n  for (nset = set;;)\n    {\n      /* Copy over any 'u', 'g' and 'o' commands. */\n      while (( op = nset->cmd ) != '+' && op != '-' && op != 'X')\n        {\n          *set++ = *nset++;\n          if (!op)\n            {\n              return;\n            }\n        }\n\n      for (setbits = clrbits = Xbits = 0;; nset++)\n        {\n          if (( op = nset->cmd ) == '-')\n            {\n              clrbits |= nset->bits;\n              setbits &= ~nset->bits;\n              Xbits &= ~nset->bits;\n            }\n          else if (op == '+')\n            {\n              setbits |= nset->bits;\n              clrbits &= ~nset->bits;\n              Xbits &= ~nset->bits;\n            }\n          else if (op == 'X')\n            {\n              Xbits |= nset->bits & ~setbits;\n            }\n          else\n            {\n              break;\n            }\n        }\n\n      if (clrbits)\n        {\n          set->cmd = '-';\n          set->cmd2 = 0;\n          set->bits = clrbits;\n          set++;\n        }\n\n      if (setbits)\n        {\n          set->cmd = '+';\n          set->cmd2 = 0;\n          set->bits = setbits;\n          set++;\n        }\n\n      if (Xbits)\n        {\n          set->cmd = 'X';\n          set->cmd2 = 0;\n          set->bits = Xbits;\n          set++;\n        }\n    }\n}\n"
  },
  {
    "path": "openbsd/setmode.h",
    "content": "/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of the copyright holders nor the names of any\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS\n * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n * THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef _SETMODE_H\n# define _SETMODE_H\n\n# include \"../include/compat.h\"\n\n# include <sys/stat.h>\n# include <sys/types.h>\n\n# include <grp.h>\n# include <stdarg.h>\n# include <stddef.h>\n\n# undef open\n\nvoid   *openbsd_setmode(const char *p);\nmode_t  openbsd_getmode(const void *bbox, mode_t omode);\n\n#endif /* ifndef _SETMODE_H */\n"
  },
  {
    "path": "openbsd/strlcat.c",
    "content": "/*      $OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $         */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n\n#include <stddef.h>\n#include <bsd_string.h>\n\n#undef open\n\n/*\n * Appends src to string dst of size dsize (unlike strncat, dsize is the\n * full size of dst, not space left).  At most dsize-1 characters\n * will be copied.  Always NUL terminates (unless dsize <= strlen(dst)).\n * Returns strlen(src) + MIN(dsize, strlen(initial dst)).\n * If retval >= dsize, truncation occurred.\n */\nsize_t\nopenbsd_strlcat(char *dst, const char *src, size_t dsize)\n{\n  const char *odst = dst;\n  const char *osrc = src;\n  size_t n = dsize;\n  size_t dlen;\n\n  /* Find the end of dst and adjust bytes left but don't go past end. */\n  while (n-- != 0 && *dst != '\\0')\n    {\n      dst++;\n    }\n  dlen = dst - odst;\n  n = dsize - dlen;\n\n  if (n-- == 0)\n    {\n      return dlen + strlen(src);\n    }\n\n  while (*src != '\\0')\n    {\n      if (n != 0)\n        {\n          *dst++ = *src;\n          n--;\n        }\n\n      src++;\n    }\n  *dst = '\\0';\n\n  return dlen + ( src - osrc ); /* count does not include NUL */\n}\n"
  },
  {
    "path": "openbsd/strlcpy.c",
    "content": "/*      $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $    */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <bsd_string.h>\n\n#undef open\n\n/*\n * Copy string src to buffer dst of size dsize.  At most dsize-1\n * chars will be copied.  Always NUL terminates (unless dsize == 0).\n * Returns strlen(src); if retval >= dsize, truncation occurred.\n */\nsize_t\nopenbsd_strlcpy(char *dst, const char *src, size_t dsize)\n{\n  const char *osrc = src;\n  size_t nleft = dsize;\n\n  /* Copy as many bytes as will fit. */\n  if (nleft != 0)\n    {\n      while (--nleft != 0)\n        {\n          if (( *dst++ = *src++ ) == '\\0')\n            {\n              break;\n            }\n        }\n    }\n\n  /* Not enough room in dst, add NUL and traverse rest of src. */\n  if (nleft == 0)\n    {\n      if (dsize != 0)\n        {\n          *dst = '\\0'; /* NUL-terminate dst */\n        }\n\n      while (*src++)\n        {\n          ;\n        }\n    }\n\n  return src - osrc - 1; /* count does not include NUL */\n}\n"
  },
  {
    "path": "openbsd/strtonum.c",
    "content": "/*      $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $   */\n\n/* SPDX-License-Identifier: ISC */\n\n/*\n * Copyright (c) 2004 Ted Unangst and Todd Miller\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <errno.h>\n#include <limits.h>\n#include <bsd_stdlib.h>\n\n#define INVALID         1\n#define TOOSMALL        2\n#define TOOLARGE        3\n\nlong long\nstrtonum(const char *numstr, long long minval, long long maxval,\n         const char **errstrp)\n{\n        long long ll = 0;\n        int error = 0;\n        char *ep;\n        struct errval {\n                const char *errstr;\n                int err;\n        } ev[4] = {\n                { NULL,         0      },\n                { \"invalid\",    EINVAL },\n                { \"too small\",  ERANGE },\n                { \"too large\",  ERANGE },\n        };\n\n        ev[0].err = errno;\n        errno = 0;\n        if (minval > maxval) {\n                error = INVALID;\n        } else {\n                ll = strtoll(numstr, &ep, 10);\n                if (numstr == ep || *ep != '\\0')\n                        error = INVALID;\n                else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)\n                        error = TOOSMALL;\n                else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)\n                        error = TOOLARGE;\n        }\n        if (errstrp != NULL)\n                *errstrp = ev[error].errstr;\n        errno = ev[error].err;\n        if (error)\n                ll = 0;\n\n        return (ll);\n}\n"
  },
  {
    "path": "openbsd/verr.c",
    "content": "/*         $OpenBSD: verr.c,v 1.11 2016/03/13 18:34:20 guenther Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_verr(int eval, const char *fmt, va_list ap)\n{\n  int sverrno;\n\n  sverrno = errno;\n  (void)fprintf(stderr, \"%s: \", bsd_getprogname());\n  if (fmt != NULL) {\n    (void)vfprintf(stderr, fmt, ap);\n    (void)fprintf(stderr, \": \");\n  }\n  (void)fprintf(stderr, \"%s\\n\", strerror(sverrno));\n  exit(eval);\n}\n"
  },
  {
    "path": "openbsd/verrc.c",
    "content": "/*      $OpenBSD: verrc.c,v 1.3 2016/03/13 18:34:20 guenther Exp $           */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n#include \"bsd_err.h\"\n\n#include <stdarg.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#undef open\n\nvoid\nopenbsd_verrc(int eval, int code, const char *fmt, va_list ap)\n{\n  (void)fprintf(stderr, \"%s: \", bsd_getprogname());\n  if (fmt != NULL)\n    {\n      (void)vfprintf(stderr, fmt, ap);\n      (void)fprintf(stderr, \": \");\n    }\n\n  (void)fprintf(stderr, \"%s\\n\", strerror(code));\n  exit(eval);\n}\n"
  },
  {
    "path": "openbsd/verrx.c",
    "content": "/*         $OpenBSD: verrx.c,v 1.11 2016/03/13 18:34:20 guenther Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_verrx(int eval, const char *fmt, va_list ap)\n{\n  (void)fprintf(stderr, \"%s: \", bsd_getprogname());\n  if (fmt != NULL)\n    (void)vfprintf(stderr, fmt, ap);\n  (void)fprintf(stderr, \"\\n\");\n  exit(eval);\n}\n"
  },
  {
    "path": "openbsd/vwarn.c",
    "content": "/*         $OpenBSD: vwarn.c,v 1.11 2016/03/13 18:34:20 guenther Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <errno.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_vwarn(const char *fmt, va_list ap)\n{\n  int sverrno;\n\n  sverrno = errno;\n  (void)fprintf(stderr, \"%s: \", bsd_getprogname());\n  if (fmt != NULL) {\n    (void)vfprintf(stderr, fmt, ap);\n    (void)fprintf(stderr, \": \");\n  }\n  (void)fprintf(stderr, \"%s\\n\", strerror(sverrno));\n}\n"
  },
  {
    "path": "openbsd/vwarnc.c",
    "content": "/*      $OpenBSD: vwarnc.c,v 1.3 2016/03/13 18:34:20 guenther Exp $          */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#undef open\n\nvoid\nopenbsd_vwarnc(int code, const char *fmt, va_list ap)\n{\n  (void)fprintf(stderr, \"%s: \", bsd_getprogname());\n  if (fmt != NULL)\n    {\n      (void)vfprintf(stderr, fmt, ap);\n      (void)fprintf(stderr, \": \");\n    }\n\n  (void)fprintf(stderr, \"%s\\n\", strerror(code));\n}\n"
  },
  {
    "path": "openbsd/vwarnx.c",
    "content": "/*         $OpenBSD: vwarnx.c,v 1.11 2016/03/13 18:34:20 guenther Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_vwarnx(const char *fmt, va_list ap)\n{\n  (void)fprintf(stderr, \"%s: \", bsd_getprogname());\n  if (fmt != NULL)\n    (void)vfprintf(stderr, fmt, ap);\n  (void)fprintf(stderr, \"\\n\");\n}\n"
  },
  {
    "path": "openbsd/warn.c",
    "content": "/*         $OpenBSD: warn.c,v 1.11 2015/08/31 02:53:57 guenther Exp $        */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_warn(const char *fmt, ...)\n{\n  va_list ap;\n\n  va_start(ap, fmt);\n  openbsd_vwarn(fmt, ap);\n  va_end(ap);\n}\n"
  },
  {
    "path": "openbsd/warnc.c",
    "content": "/*      $OpenBSD: warnc.c,v 1.2 2015/08/31 02:53:57 guenther Exp $           */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_warnc(int code, const char *fmt, ...)\n{\n  va_list ap;\n\n  va_start(ap, fmt);\n  openbsd_vwarnc(code, fmt, ap);\n  va_end(ap);\n}\n"
  },
  {
    "path": "openbsd/warnx.c",
    "content": "/*         $OpenBSD: warnx.c,v 1.10 2015/08/31 02:53:57 guenther Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993 The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#include \"errc.h\"\n\n#include <bsd_err.h>\n#include <stdarg.h>\n\n#undef open\n\nvoid\nopenbsd_warnx(const char *fmt, ...)\n{\n  va_list ap;\n\n  va_start(ap, fmt);\n  openbsd_vwarnx(fmt, ap);\n  va_end(ap);\n}\n"
  },
  {
    "path": "regex/bsd_regex2.h",
    "content": "/*      $OpenBSD: regex2.h,v 1.12 2021/01/03 17:07:58 tb Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)regex2.h    8.4 (Berkeley) 3/20/94\n */\n\n/*\n * internals of regex_t\n */\n#define MAGIC1 ((( 'r' ^ 0200 ) << 8 ) | 'e' )\n\n/*\n * The internal representation is a *strip*, a sequence of\n * operators ending with an endmarker.  (Some terminology etc. is a\n * historical relic of earlier versions which used multiple strips.)\n * Certain oddities in the representation are there to permit running\n * the machinery backwards; in particular, any deviation from sequential\n * flow must be marked at both its source and its destination.  Some\n * fine points:\n *\n * - OPLUS_ and O_PLUS are *inside* the loop they create.\n * - OQUEST_ and O_QUEST are *outside* the bypass they create.\n * - OCH_ and O_CH are *outside* the multi-way branch they create, while\n *   OOR1 and OOR2 are respectively the end and the beginning of one of\n *   the branches.  Note that there is an implicit OOR2 following OCH_\n *   and an implicit OOR1 preceding O_CH.\n *\n * In state representations, an operator's bit is on to signify a state\n * immediately *preceding* \"execution\" of that operator.\n */\ntypedef unsigned long sop; /* strip operator */\ntypedef long sopno;\n\n#define OPRMASK       0xf8000000LU\n#define OPDMASK       0x07ffffffLU\n#define OPSHIFT       ((unsigned)27 )\n#define OP(n)         (( n ) & OPRMASK )\n#define OPND(n)       (( n ) & OPDMASK )\n#define SOP(op, opnd) (( op ) | ( opnd ))\n\n/* operators                           meaning  operand                 */\n/*                                              (back, fwd are offsets) */\n#define OEND    (  1LU << OPSHIFT ) /* endmarker        -               */\n#define OCHAR   (  2LU << OPSHIFT ) /* character        unsigned char   */\n#define OBOL    (  3LU << OPSHIFT ) /* left anchor      -               */\n#define OEOL    (  4LU << OPSHIFT ) /* right anchor     -               */\n#define OANY    (  5LU << OPSHIFT ) /* .                -               */\n#define OANYOF  (  6LU << OPSHIFT ) /* [...]    set number              */\n#define OBACK_  (  7LU << OPSHIFT ) /* begin \\d paren number            */\n#define O_BACK  (  8LU << OPSHIFT ) /* end \\d   paren number            */\n#define OPLUS_  (  9LU << OPSHIFT ) /* + prefix fwd to suffix           */\n#define O_PLUS  ( 10LU << OPSHIFT ) /* + suffix back to prefix          */\n#define OQUEST_ ( 11LU << OPSHIFT ) /* ? prefix fwd to suffix           */\n#define O_QUEST ( 12LU << OPSHIFT ) /* ? suffix back to prefix          */\n#define OLPAREN ( 13LU << OPSHIFT ) /* (                fwd to )        */\n#define ORPAREN ( 14LU << OPSHIFT ) /* )                back to (       */\n#define OCH_    ( 15LU << OPSHIFT ) /* begin choice     fwd to OOR2     */\n#define OOR1    ( 16LU << OPSHIFT ) /* | pt. 1  back to OOR1 or OCH_    */\n#define OOR2    ( 17LU << OPSHIFT ) /* | pt. 2  fwd to OOR2 or O_CH     */\n#define O_CH    ( 18LU << OPSHIFT ) /* end choice       back to OOR1    */\n#define OBOW    ( 19LU << OPSHIFT ) /* begin word       -               */\n#define OEOW    ( 20LU << OPSHIFT ) /* end word         -               */\n\n/*\n * Structure for [] character-set representation.  Character sets are\n * done as bit vectors, grouped 8 to a byte vector for compactness.\n * The individual set therefore has both a pointer to the byte vector\n * and a mask to pick out the relevant bit of each byte.  A hash code\n * simplifies testing whether two sets could be identical.\n *\n * This will get trickier for multicharacter collating elements.  As\n * preliminary hooks for dealing with such things, we also carry along\n * a string of multi-character elements, and decide the size of the\n * vectors at run time.\n */\ntypedef struct\n{\n  uch *ptr; /* -> uch [csetsize] */\n  uch mask; /* bit within array */\n  uch hash; /* hash code */\n} cset;\n\nstatic inline void\nCHadd(cset *cs, char c)\n{\n  cs->ptr[(uch)c] |= cs->mask;\n  cs->hash += c;\n}\n\nstatic inline void\nCHsub(cset *cs, char c)\n{\n  cs->ptr[(uch)c] &= ~cs->mask;\n  cs->hash -= c;\n}\n\nstatic inline int\nCHIN(const cset *cs, char c)\n{\n  return ( cs->ptr[(uch)c] & cs->mask ) != 0;\n}\n\n/*\n * main compiled-expression structure\n */\nstruct re_guts\n{\n  int magic;\n#define MAGIC2 ((( 'R' ^ 0200 ) << 8 ) | 'E' )\n  sop *strip;       /* malloced area for strip */\n  int csetsize;     /* number of bits in a cset vector */\n  int ncsets;       /* number of csets in use */\n  cset *sets;       /* -> cset [ncsets] */\n  uch *setbits;     /* -> uch[csetsize][ncsets/CHAR_BIT] */\n  int cflags;       /* copy of regcomp() cflags argument */\n  sopno nstates;    /* = number of sops */\n  sopno firststate; /* the initial OEND (normally 0) */\n  sopno laststate;  /* the final OEND */\n  int iflags;       /* internal flags */\n#define USEBOL 01   /* used ^ */\n#define USEEOL 02   /* used $ */\n#define BAD    04   /* something wrong */\n  int nbol;         /* number of ^ used */\n  int neol;         /* number of $ used */\n  char *must;       /* match must contain this string */\n  int mlen;         /* length of must */\n  size_t nsub;      /* copy of re_nsub */\n  int backrefs;     /* does it use back references? */\n  sopno nplus;      /* how deep does it nest +s? */\n};\n\n/* misc utilities */\n#define OUT ( CHAR_MAX + 1 ) /* a non-character value */\n#define ISWORD(c) ( isalnum(c) || ( c ) == '_' )\n"
  },
  {
    "path": "regex/cclass.h",
    "content": "/*      $OpenBSD: cclass.h,v 1.7 2020/12/30 08:54:42 tb Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)cclass.h    8.3 (Berkeley) 3/20/94\n */\n\n/* character-class table */\nstatic const struct cclass\n{\n  const char *name;\n  const char *chars;\n} cclasses[]\n  = { { \"alnum\",  \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\\\n0123456789\"                                                              },\n      { \"alpha\",  \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\" },\n      { \"blank\",  \" \\t\"                                                  },\n      { \"cntrl\",  \"\\007\\b\\t\\n\\v\\f\\r\\1\\2\\3\\4\\5\\6\\16\\17\\20\\21\\22\\23\\24\\\n\\25\\26\\27\\30\\31\\32\\33\\34\\35\\36\\37\\177\"                                   },\n      { \"digit\",  \"0123456789\"                                           },\n      { \"graph\",  \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\\\n0123456789!\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\"                            },\n      { \"lower\",  \"abcdefghijklmnopqrstuvwxyz\"                           },\n      { \"print\",  \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\\\n0123456789!\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~ \"                           },\n      { \"punct\",  \"!\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\"                   },\n      { \"space\",  \"\\t\\n\\v\\f\\r \"                                          },\n      { \"upper\",  \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"                           },\n      { \"xdigit\", \"0123456789ABCDEFabcdef\"                               },\n      { NULL,                                                          0 } };\n"
  },
  {
    "path": "regex/cname.h",
    "content": "/*      $OpenBSD: cname.h,v 1.6 2020/12/30 08:53:30 tb Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)cname.h     8.3 (Berkeley) 3/20/94\n */\n\n/* character-name table */\nstatic const struct cname\n{\n  const char *name;\n  char code;\n} cnames[] = { { \"NUL\",                  '\\0'   },\n               { \"SOH\",                  '\\001' },\n               { \"STX\",                  '\\002' },\n               { \"ETX\",                  '\\003' },\n               { \"EOT\",                  '\\004' },\n               { \"ENQ\",                  '\\005' },\n               { \"ACK\",                  '\\006' },\n               { \"BEL\",                  '\\007' },\n               { \"alert\",                '\\007' },\n               { \"BS\",                   '\\010' },\n               { \"backspace\",            '\\b'   },\n               { \"HT\",                   '\\011' },\n               { \"tab\",                  '\\t'   },\n               { \"LF\",                   '\\012' },\n               { \"newline\",              '\\n'   },\n               { \"VT\",                   '\\013' },\n               { \"vertical-tab\",         '\\v'   },\n               { \"FF\",                   '\\014' },\n               { \"form-feed\",            '\\f'   },\n               { \"CR\",                   '\\015' },\n               { \"carriage-return\",      '\\r'   },\n               { \"SO\",                   '\\016' },\n               { \"SI\",                   '\\017' },\n               { \"DLE\",                  '\\020' },\n               { \"DC1\",                  '\\021' },\n               { \"DC2\",                  '\\022' },\n               { \"DC3\",                  '\\023' },\n               { \"DC4\",                  '\\024' },\n               { \"NAK\",                  '\\025' },\n               { \"SYN\",                  '\\026' },\n               { \"ETB\",                  '\\027' },\n               { \"CAN\",                  '\\030' },\n               { \"EM\",                   '\\031' },\n               { \"SUB\",                  '\\032' },\n               { \"ESC\",                  '\\033' },\n               { \"IS4\",                  '\\034' },\n               { \"FS\",                   '\\034' },\n               { \"IS3\",                  '\\035' },\n               { \"GS\",                   '\\035' },\n               { \"IS2\",                  '\\036' },\n               { \"RS\",                   '\\036' },\n               { \"IS1\",                  '\\037' },\n               { \"US\",                   '\\037' },\n               { \"space\",                ' '    },\n               { \"exclamation-mark\",     '!'    },\n               { \"quotation-mark\",       '\"'    },\n               { \"number-sign\",          '#'    },\n               { \"dollar-sign\",          '$'    },\n               { \"percent-sign\",         '%'    },\n               { \"ampersand\",            '&'    },\n               { \"apostrophe\",           '\\''   },\n               { \"left-parenthesis\",     '('    },\n               { \"right-parenthesis\",    ')'    },\n               { \"asterisk\",             '*'    },\n               { \"plus-sign\",            '+'    },\n               { \"comma\",                ','    },\n               { \"hyphen\",               '-'    },\n               { \"hyphen-minus\",         '-'    },\n               { \"period\",               '.'    },\n               { \"full-stop\",            '.'    },\n               { \"slash\",                '/'    },\n               { \"solidus\",              '/'    },\n               { \"zero\",                 '0'    },\n               { \"one\",                  '1'    },\n               { \"two\",                  '2'    },\n               { \"three\",                '3'    },\n               { \"four\",                 '4'    },\n               { \"five\",                 '5'    },\n               { \"six\",                  '6'    },\n               { \"seven\",                '7'    },\n               { \"eight\",                '8'    },\n               { \"nine\",                 '9'    },\n               { \"colon\",                ':'    },\n               { \"semicolon\",            ';'    },\n               { \"less-than-sign\",       '<'    },\n               { \"equals-sign\",          '='    },\n               { \"greater-than-sign\",    '>'    },\n               { \"question-mark\",        '?'    },\n               { \"commercial-at\",        '@'    },\n               { \"left-square-bracket\",  '['    },\n               { \"backslash\",            '\\\\'   },\n               { \"reverse-solidus\",      '\\\\'   },\n               { \"right-square-bracket\", ']'    },\n               { \"circumflex\",           '^'    },\n               { \"circumflex-accent\",    '^'    },\n               { \"underscore\",           '_'    },\n               { \"low-line\",             '_'    },\n               { \"grave-accent\",         '`'    },\n               { \"left-brace\",           '{'    },\n               { \"left-curly-bracket\",   '{'    },\n               { \"vertical-line\",        '|'    },\n               { \"right-brace\",          '}'    },\n               { \"right-curly-bracket\",  '}'    },\n               { \"tilde\",                '~'    },\n               { \"DEL\",                  '\\177' },\n               { NULL,                        0 } };\n"
  },
  {
    "path": "regex/engine.c",
    "content": "/*      $OpenBSD: engine.c,v 1.26 2020/12/28 21:41:55 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)engine.c    8.5 (Berkeley) 3/20/94\n */\n\n/*\n * The matching engine and friends.  This file is #included by regexec.c\n * after suitable #defines of a variety of macros used herein, so that\n * different state representations can be used without duplicating masses\n * of code.\n */\n\n#ifdef SNAMES\n# define matcher smatcher\n# define fast sfast\n# define slow sslow\n# define dissect sdissect\n# define backref sbackref\n# define step sstep\n# define print sprint\n# define at sat\n# define match smat\n# define nope snope\n#endif /* ifdef SNAMES */\n#ifdef LNAMES\n# define matcher lmatcher\n# define fast lfast\n# define slow lslow\n# define dissect ldissect\n# define backref lbackref\n# define step lstep\n# define print lprint\n# define at lat\n# define match lmat\n# define nope lnope\n#endif /* ifdef LNAMES */\n\n/* another structure passed up and down to avoid zillions of parameters */\nstruct match\n{\n  struct re_guts *g;\n  int eflags;\n  regmatch_t *pmatch;   /* [nsub+1] (0 element unused) */\n  const char *offp;     /* offsets work from here */\n  const char *beginp;   /* start of string -- virtual NUL precedes */\n  const char *endp;     /* end of string -- virtual NUL here */\n  const char *coldp;    /* can be no match starting before here */\n  const char **lastpos; /* [nplus+1] */\n  STATEVARS;\n  states st;    /* current states */\n  states fresh; /* states for a fresh start */\n  states tmp;   /* temporary */\n  states empty; /* empty set of states */\n};\n\nstatic int matcher(struct re_guts *, const char *, size_t, regmatch_t[], int);\nstatic const char *dissect(struct match *, const char *, const char *, sopno,\n                           sopno);\nstatic const char *backref(struct match *, const char *, const char *, sopno,\n                           sopno, sopno, int);\nstatic const char *fast(struct match *, const char *, const char *, sopno,\n                        sopno);\nstatic const char *slow(struct match *, const char *, const char *, sopno,\n                        sopno);\nstatic states step(struct re_guts *, sopno, sopno, states, int, states);\n\n#define MAX_RECURSION 100\n#define BOL ( OUT + 1 )\n#define EOL ( BOL + 1 )\n#define BOLEOL ( BOL + 2 )\n#define NOTHING ( BOL + 3 )\n#define BOW ( BOL + 4 )\n#define EOW ( BOL + 5 )\n\n/* update nonchars[] array below when adding fake chars here */\n#define CODEMAX ( BOL + 5 ) /* highest code used */\n#define NONCHAR(c) (( c ) > CHAR_MAX )\n#define NNONCHAR ( CODEMAX - CHAR_MAX )\n\n#ifdef REDEBUG\nstatic void print(struct match *, const char *, states, int, FILE *);\n#endif /* ifdef REDEBUG */\n\n#ifdef REDEBUG\nstatic void at(struct match *, const char *, const char *, const char *,\n               sopno, sopno);\n#endif /* ifdef REDEBUG */\n\n#ifdef REDEBUG\nstatic const char *pchar(int);\n#endif /* ifdef REDEBUG */\n\n#ifdef REDEBUG\n# define SP(t, s, c) print(m, t, s, c, stdout)\n# define AT(t, p1, p2, s1, s2) at(m, t, p1, p2, s1, s2)\n# define NOTE(str)                                                            \\\n  {                                                                           \\\n    if (m->eflags & REG_TRACE)                                                \\\n    (void)printf(\"=%s\\n\", ( str ));                                           \\\n  }\nstatic int nope = 0;\n#else  /* ifdef REDEBUG */\n# define SP(t, s, c)           /* nothing */\n# define AT(t, p1, p2, s1, s2) /* nothing */\n# define NOTE(s)               /* nothing */\n#endif /* ifdef REDEBUG */\n\n/*\n * - matcher - the actual matching engine\n */\nstatic int /* 0 success, REG_NOMATCH failure */\nmatcher(struct re_guts *g, const char *string, size_t nmatch,\n        regmatch_t pmatch[], int eflags)\n{\n  const char *endp;\n  int i;\n  struct match mv;\n  struct match *m = &mv;\n  const char *dp;\n  const sopno gf = g->firststate + 1; /* +1 for OEND */\n  const sopno gl = g->laststate;\n  const char *start;\n  const char *stop;\n\n  /* simplify the situation where possible */\n  if (g->cflags & REG_NOSUB)\n    {\n      nmatch = 0;\n    }\n\n  if (eflags & REG_STARTEND)\n    {\n      start = string + pmatch[0].rm_so;\n      stop = string + pmatch[0].rm_eo;\n    }\n  else\n    {\n      start = string;\n      stop = start + strlen(start);\n    }\n\n  if (stop < start)\n    {\n      return REG_INVARG;\n    }\n\n  /* prescreening; this does wonders for this rather slow code */\n  if (g->must != NULL)\n    {\n      for (dp = start; dp < stop; dp++)\n        {\n          if (*dp == g->must[0] && stop - dp >= g->mlen\n              && memcmp(dp, g->must, g->mlen) == 0)\n            {\n              break;\n            }\n        }\n\n      if (dp == stop) /* we didn't find g->must */\n        {\n          return REG_NOMATCH;\n        }\n    }\n\n  /* match struct setup */\n  m->g = g;\n  m->eflags = eflags;\n  m->pmatch = NULL;\n  m->lastpos = NULL;\n  m->offp = string;\n  m->beginp = start;\n  m->endp = stop;\n  STATESETUP(m, 4);\n  SETUP(m->st);\n  SETUP(m->fresh);\n  SETUP(m->tmp);\n  SETUP(m->empty);\n  CLEAR(m->empty);\n\n  /* this loop does only one repetition except for backrefs */\n  for (;;)\n    {\n      endp = fast(m, start, stop, gf, gl);\n      if (endp == NULL)\n        { /* a miss */\n          free(m->pmatch);\n          free(m->lastpos);\n          STATETEARDOWN(m);\n          return REG_NOMATCH;\n        }\n\n      if (nmatch == 0 && !g->backrefs)\n        {\n          break; /* no further info needed */\n        }\n\n      /* where? */\n      assert(m->coldp != NULL);\n      for (;;)\n        {\n          NOTE(\"finding start\");\n          endp = slow(m, m->coldp, stop, gf, gl);\n          if (endp != NULL)\n            {\n              break;\n            }\n\n          assert(m->coldp < m->endp);\n          m->coldp++;\n        }\n\n      if (nmatch == 1 && !g->backrefs)\n        {\n          break; /* no further info needed */\n        }\n\n      /* oh my, he wants the subexpressions... */\n      if (m->pmatch == NULL)\n        {\n          m->pmatch = openbsd_reallocarray(\n            NULL, m->g->nsub + 1, sizeof ( regmatch_t ) );\n        }\n\n      if (m->pmatch == NULL)\n        {\n          STATETEARDOWN(m);\n          return REG_ESPACE;\n        }\n\n      for (i = 1; i <= m->g->nsub; i++)\n        {\n          m->pmatch[i].rm_so = m->pmatch[i].rm_eo = -1;\n        }\n\n      if (!g->backrefs && !( m->eflags & REG_BACKR ))\n        {\n          NOTE(\"dissecting\");\n          dp = dissect(m, m->coldp, endp, gf, gl);\n        }\n      else\n        {\n          if (g->nplus > 0 && m->lastpos == NULL)\n            {\n              m->lastpos = openbsd_reallocarray(\n                NULL, g->nplus + 1, sizeof ( char * ) );\n            }\n\n          if (g->nplus > 0 && m->lastpos == NULL)\n            {\n              free(m->pmatch);\n              STATETEARDOWN(m);\n              return REG_ESPACE;\n            }\n\n          NOTE(\"backref dissect\");\n          dp = backref(m, m->coldp, endp, gf, gl, (sopno)0, 0);\n        }\n\n      if (dp != NULL)\n        {\n          break;\n        }\n\n      /* uh-oh... we couldn't find a subexpression-level match */\n      assert(g->backrefs); /* must be back references doing it */\n      assert(g->nplus == 0 || m->lastpos != NULL);\n      for (;;)\n        {\n          if (dp != NULL || endp <= m->coldp)\n            {\n              break; /* defeat */\n            }\n\n          NOTE(\"backoff\");\n          endp = slow(m, m->coldp, endp - 1, gf, gl);\n          if (endp == NULL)\n            {\n              break; /* defeat */\n            }\n\n          /* try it on a shorter possibility */\n#ifndef NDEBUG\n          for (i = 1; i <= m->g->nsub; i++)\n            {\n              assert(m->pmatch[i].rm_so == -1);\n              assert(m->pmatch[i].rm_eo == -1);\n            }\n\n#endif /* ifndef NDEBUG */\n          NOTE(\"backoff dissect\");\n          dp = backref(m, m->coldp, endp, gf, gl, (sopno)0, 0);\n        }\n\n      assert(dp == NULL || dp == endp);\n      if (dp != NULL) /* found a shorter one */\n        {\n          break;\n        }\n\n      /* despite initial appearances, there is no match here */\n      NOTE(\"false alarm\");\n      if (m->coldp == stop)\n        {\n          break;\n        }\n\n      start = m->coldp + 1; /* recycle starting later */\n    }\n\n  /* fill in the details if requested */\n  if (nmatch > 0)\n    {\n      pmatch[0].rm_so = m->coldp - m->offp;\n      pmatch[0].rm_eo = endp - m->offp;\n    }\n\n  if (nmatch > 1)\n    {\n      assert(m->pmatch != NULL);\n      for (i = 1; i < nmatch; i++)\n        {\n          if (i <= m->g->nsub)\n            {\n              pmatch[i] = m->pmatch[i];\n            }\n          else\n            {\n              pmatch[i].rm_so = -1;\n              pmatch[i].rm_eo = -1;\n            }\n        }\n    }\n\n  free(m->pmatch);\n  free(m->lastpos);\n  STATETEARDOWN(m);\n  return 0;\n}\n\n/*\n * - dissect - figure out what matched what, no back references\n */\nstatic const char * /* == stop (success) always */\ndissect(struct match *m, const char *start, const char *stop, sopno startst,\n        sopno stopst)\n{\n  int i;\n  sopno ss;           /* start sop of current subRE */\n  sopno es;           /* end sop of current subRE */\n  const char *sp;     /* start of string matched by it */\n  const char *stp;    /* string matched by it cannot pass here */\n  const char *rest;   /* start of rest of string */\n  const char *tail;   /* string unmatched by rest of RE */\n  sopno ssub;         /* start sop of subsubRE */\n  sopno esub;         /* end sop of subsubRE */\n  const char *ssp;    /* start of string matched by subsubRE */\n  const char *sep;    /* end of string matched by subsubRE */\n  const char *oldssp; /* previous ssp */\n  const char *dp;\n\n  AT(\"diss\", start, stop, startst, stopst);\n  sp = start;\n  for (ss = startst; ss < stopst; ss = es)\n    {\n      /* identify end of subRE */\n      es = ss;\n      switch (OP(m->g->strip[es]))\n        {\n        case OPLUS_:\n        case OQUEST_:\n          es += OPND(m->g->strip[es]);\n          break;\n\n        case OCH_:\n          while (OP(m->g->strip[es]) != O_CH)\n            {\n              es += OPND(m->g->strip[es]);\n            }\n          break;\n        }\n      es++;\n\n      /* figure out what it matched */\n      switch (OP(m->g->strip[ss]))\n        {\n        case OEND:\n          assert(nope);\n          break;\n\n        case OCHAR:\n          sp++;\n          break;\n\n        case OBOL:\n        case OEOL:\n        case OBOW:\n        case OEOW:\n          break;\n\n        case OANY:\n        case OANYOF:\n          sp++;\n          break;\n\n        case OBACK_:\n        case O_BACK:\n          assert(nope);\n          break;\n\n        /* cases where length of match is hard to find */\n        case OQUEST_:\n          stp = stop;\n          for (;;)\n            {\n              /* how long could this one be? */\n              rest = slow(m, sp, stp, ss, es);\n              assert(rest != NULL); /* it did match */\n              /* could the rest match the rest? */\n              tail = slow(m, rest, stop, es, stopst);\n              if (tail == stop)\n                {\n                  break; /* yes! */\n                }\n\n              /* no -- try a shorter match for this one */\n              stp = rest - 1;\n              assert(stp >= sp); /* it did work */\n            }\n\n          ssub = ss + 1;\n          esub = es - 1;\n          /* did innards match? */\n          if (slow(m, sp, rest, ssub, esub) != NULL)\n            {\n              dp = dissect(m, sp, rest, ssub, esub);\n              (void)dp;\n              assert(dp == rest);\n            }\n          else /* no */\n            {\n              assert(sp == rest);\n            }\n\n          sp = rest;\n          break;\n\n        case OPLUS_:\n          stp = stop;\n          for (;;)\n            {\n              /* how long could this one be? */\n              rest = slow(m, sp, stp, ss, es);\n              assert(rest != NULL); /* it did match */\n              /* could the rest match the rest? */\n              tail = slow(m, rest, stop, es, stopst);\n              if (tail == stop)\n                {\n                  break; /* yes! */\n                }\n\n              /* no -- try a shorter match for this one */\n              stp = rest - 1;\n              assert(stp >= sp); /* it did work */\n            }\n\n          ssub = ss + 1;\n          esub = es - 1;\n          ssp = sp;\n          oldssp = ssp;\n          for (;;)\n            { /* find last match of innards */\n              sep = slow(m, ssp, rest, ssub, esub);\n              if (sep == NULL || sep == ssp)\n                {\n                  break; /* failed or matched null */\n                }\n\n              oldssp = ssp; /* on to next try */\n              ssp = sep;\n            }\n\n          if (sep == NULL)\n            {\n              /* last successful match */\n              sep = ssp;\n              ssp = oldssp;\n            }\n\n          assert(sep == rest); /* must exhaust substring */\n          assert(slow(m, ssp, sep, ssub, esub) == rest);\n          dp = dissect(m, ssp, sep, ssub, esub);\n          (void)dp;\n          assert(dp == sep);\n          sp = rest;\n          break;\n\n        case OCH_:\n          stp = stop;\n          for (;;)\n            {\n              /* how long could this one be? */\n              rest = slow(m, sp, stp, ss, es);\n              assert(rest != NULL); /* it did match */\n              /* could the rest match the rest? */\n              tail = slow(m, rest, stop, es, stopst);\n              if (tail == stop)\n                {\n                  break; /* yes! */\n                }\n\n              /* no -- try a shorter match for this one */\n              stp = rest - 1;\n              assert(stp >= sp); /* it did work */\n            }\n\n          ssub = ss + 1;\n          esub = ss + OPND(m->g->strip[ss]) - 1;\n          assert(OP(m->g->strip[esub]) == OOR1);\n          for (;;)\n            { /* find first matching branch */\n              if (slow(m, sp, rest, ssub, esub) == rest)\n                {\n                  break; /* it matched all of it */\n                }\n\n              /* that one missed, try next one */\n              assert(OP(m->g->strip[esub]) == OOR1);\n              esub++;\n              assert(OP(m->g->strip[esub]) == OOR2);\n              ssub = esub + 1;\n              esub += OPND(m->g->strip[esub]);\n              if (OP(m->g->strip[esub]) == OOR2)\n                {\n                  esub--;\n                }\n              else\n                {\n                  assert(OP(m->g->strip[esub]) == O_CH);\n                }\n            }\n\n          dp = dissect(m, sp, rest, ssub, esub);\n          assert(dp == rest);\n          sp = rest;\n          break;\n\n        case O_PLUS:\n        case O_QUEST:\n        case OOR1:\n        case OOR2:\n        case O_CH:\n          assert(nope);\n          break;\n\n        case OLPAREN:\n          i = OPND(m->g->strip[ss]);\n          assert(0 < i && i <= m->g->nsub);\n          m->pmatch[i].rm_so = sp - m->offp;\n          break;\n\n        case ORPAREN:\n          i = OPND(m->g->strip[ss]);\n          assert(0 < i && i <= m->g->nsub);\n          m->pmatch[i].rm_eo = sp - m->offp;\n          break;\n\n        default: /* uh oh */\n          assert(nope);\n          break;\n        }\n    }\n\n  assert(sp == stop);\n  return sp;\n}\n\n/*\n * - backref - figure out what matched what, figuring in back references\n */\nstatic const char * /* == stop (success) or NULL (failure) */\nbackref(struct match *m, const char *start, const char *stop, sopno startst,\n        sopno stopst, sopno lev, int rec)  /* PLUS nesting level */\n{\n  int i;\n  sopno ss;        /* start sop of current subRE */\n  const char *sp;  /* start of string matched by it */\n  sopno ssub;      /* start sop of subsubRE */\n  sopno esub;      /* end sop of subsubRE */\n  const char *ssp; /* start of string matched by subsubRE */\n  const char *dp;\n  size_t len;\n  int hard;\n  sop s;\n  regoff_t offsave;\n  cset *cs;\n\n  AT(\"back\", start, stop, startst, stopst);\n  sp = start;\n\n  /* get as far as we can with easy stuff */\n  hard = 0;\n  for (ss = startst; !hard && ss < stopst; ss++)\n    {\n      switch (OP(s = m->g->strip[ss]))\n        {\n        case OCHAR:\n          if (sp == stop || *sp++ != (char)OPND(s))\n            {\n              return NULL;\n            }\n\n          break;\n\n        case OANY:\n          if (sp == stop)\n            {\n              return NULL;\n            }\n\n          sp++;\n          break;\n\n        case OANYOF:\n          cs = &m->g->sets[OPND(s)];\n          if (sp == stop || !CHIN(cs, *sp++))\n            {\n              return NULL;\n            }\n\n          break;\n\n        case OBOL:\n          if (( sp == m->beginp && !( m->eflags & REG_NOTBOL ))\n              || ( sp > m->offp && sp < m->endp && *( sp - 1 ) == '\\n'\n                   && ( m->g->cflags & REG_NEWLINE )))\n            { /* yes */\n            }\n          else\n            {\n              return NULL;\n            }\n\n          break;\n\n        case OEOL:\n          if (( sp == m->endp && !( m->eflags & REG_NOTEOL ))\n              || ( sp < m->endp && *sp == '\\n' && ( m->g->cflags & REG_NEWLINE )))\n            { /* yes */\n            }\n          else\n            {\n              return NULL;\n            }\n\n          break;\n\n        case OBOW:\n          if (sp < m->endp && ISWORD(*sp)\n              && (( sp == m->beginp && !( m->eflags & REG_NOTBOL ))\n                  || ( sp > m->offp && !ISWORD(*( sp - 1 )))))\n            { /* yes */\n            }\n          else\n            {\n              return NULL;\n            }\n\n          break;\n\n        case OEOW:\n          if ((( sp == m->endp && !( m->eflags & REG_NOTEOL ))\n               || ( sp < m->endp && *sp == '\\n' && ( m->g->cflags & REG_NEWLINE ))\n               || ( sp < m->endp && !ISWORD(*sp)))\n              && ( sp > m->beginp && ISWORD(*( sp - 1 ))))\n            { /* yes */\n            }\n          else\n            {\n              return NULL;\n            }\n\n          break;\n\n        case O_QUEST:\n          break;\n\n        case OOR1: /* matches null but needs to skip */\n          ss++;\n          s = m->g->strip[ss];\n          do\n            {\n              assert(OP(s) == OOR2);\n              ss += OPND(s);\n            }\n          while ( OP(s = m->g->strip[ss]) != O_CH );\n          /* note that the ss++ gets us past the O_CH */\n          break;\n\n        default: /* have to make a choice */\n          hard = 1;\n          break;\n        }\n    }\n\n  if (!hard)\n    { /* that was it! */\n      if (sp != stop)\n        {\n          return NULL;\n        }\n\n      return sp;\n    }\n\n  ss--; /* adjust for the for's final increment */\n\n  /* the hard stuff */\n  AT(\"hard\", sp, stop, ss, stopst);\n  s = m->g->strip[ss];\n  switch (OP(s))\n    {\n    case OBACK_: /* the vilest depths */\n      i = OPND(s);\n      assert(0 < i && i <= m->g->nsub);\n      if (m->pmatch[i].rm_eo == -1)\n        {\n          return NULL;\n        }\n\n      assert(m->pmatch[i].rm_so != -1);\n      len = m->pmatch[i].rm_eo - m->pmatch[i].rm_so;\n      if (len == 0 && rec++ > MAX_RECURSION)\n        {\n          return NULL;\n        }\n\n      assert(stop - m->beginp >= len);\n      if (sp > stop - len)\n        {\n          return NULL; /* not enough left to match */\n        }\n\n      ssp = m->offp + m->pmatch[i].rm_so;\n      if (memcmp(sp, ssp, len) != 0)\n        {\n          return NULL;\n        }\n\n      while (m->g->strip[ss] != SOP(O_BACK, i))\n        {\n          ss++;\n        }\n      return backref(m, sp + len, stop, ss + 1, stopst, lev, rec);\n\n      break;\n\n    case OQUEST_: /* to null or not */\n      dp = backref(m, sp, stop, ss + 1, stopst, lev, rec);\n      if (dp != NULL)\n        {\n          return dp; /* not */\n        }\n\n      return backref(m, sp, stop, ss + OPND(s) + 1, stopst, lev, rec);\n\n      break;\n\n    case OPLUS_:\n      assert(m->lastpos != NULL);\n      assert(lev + 1 <= m->g->nplus);\n      m->lastpos[lev + 1] = sp;\n      return backref(m, sp, stop, ss + 1, stopst, lev + 1, rec);\n\n      break;\n\n    case O_PLUS:\n      if (sp == m->lastpos[lev]) /* last pass matched null */\n        {\n          return backref(m, sp, stop, ss + 1, stopst, lev - 1, rec);\n        }\n\n      /* try another pass */\n      m->lastpos[lev] = sp;\n      dp = backref(m, sp, stop, ss - OPND(s) + 1, stopst, lev, rec);\n      if (dp == NULL)\n        {\n          return backref(m, sp, stop, ss + 1, stopst, lev - 1, rec);\n        }\n      else\n        {\n          return dp;\n        }\n\n      break;\n\n    case OCH_: /* find the right one, if any */\n      ssub = ss + 1;\n      esub = ss + OPND(s) - 1;\n      assert(OP(m->g->strip[esub]) == OOR1);\n      for (;;)\n        { /* find first matching branch */\n          dp = backref(m, sp, stop, ssub, esub, lev, rec);\n          if (dp != NULL)\n            {\n              return dp;\n            }\n\n          /* that one missed, try next one */\n          if (OP(m->g->strip[esub]) == O_CH)\n            {\n              return NULL; /* there is none */\n            }\n\n          esub++;\n          assert(OP(m->g->strip[esub]) == OOR2);\n          ssub = esub + 1;\n          esub += OPND(m->g->strip[esub]);\n          if (OP(m->g->strip[esub]) == OOR2)\n            {\n              esub--;\n            }\n          else\n            {\n              assert(OP(m->g->strip[esub]) == O_CH);\n            }\n        }\n\n      break;\n\n    case OLPAREN: /* must undo assignment if rest fails */\n      i = OPND(s);\n      assert(0 < i && i <= m->g->nsub);\n      offsave = m->pmatch[i].rm_so;\n      m->pmatch[i].rm_so = sp - m->offp;\n      dp = backref(m, sp, stop, ss + 1, stopst, lev, rec);\n      if (dp != NULL)\n        {\n          return dp;\n        }\n\n      m->pmatch[i].rm_so = offsave;\n      return NULL;\n\n      break;\n\n    case ORPAREN: /* must undo assignment if rest fails */\n      i = OPND(s);\n      assert(0 < i && i <= m->g->nsub);\n      offsave = m->pmatch[i].rm_eo;\n      m->pmatch[i].rm_eo = sp - m->offp;\n      dp = backref(m, sp, stop, ss + 1, stopst, lev, rec);\n      if (dp != NULL)\n        {\n          return dp;\n        }\n\n      m->pmatch[i].rm_eo = offsave;\n      return NULL;\n\n      break;\n\n    default: /* uh oh */\n      assert(nope);\n      break;\n    }\n\n  /* \"can't happen\" */\n  assert(nope);\n  /* NOTREACHED */\n  return NULL;\n}\n\n/*\n * - fast - step through the string at top speed\n */\nstatic const char * /* where tentative match ended, or NULL */\nfast(struct match *m, const char *start, const char *stop, sopno startst,\n     sopno stopst)\n{\n  states st = m->st;\n  states fresh = m->fresh;\n  states tmp = m->tmp;\n  const char *p = start;\n  int c;\n  int lastc; /* previous c */\n  int flagch;\n  int i;\n  const char *coldp; /* last p after which no match was underway */\n\n  if (start == m->offp || ( start == m->beginp && !( m->eflags & REG_NOTBOL )))\n    {\n      c = OUT;\n    }\n  else\n    {\n      c = *( start - 1 );\n    }\n\n  CLEAR(st);\n  SET1(st, startst);\n  st = step(m->g, startst, stopst, st, NOTHING, st);\n  ASSIGN(fresh, st);\n  SP(\"start\", st, *p);\n  coldp = NULL;\n  for (;;)\n    {\n      /* next character */\n      lastc = c;\n      c = ( p == m->endp ) ? OUT : *p;\n      if (EQ(st, fresh))\n        {\n          coldp = p;\n        }\n\n      /* is there an EOL and/or BOL between lastc and c? */\n      flagch = '\\0';\n      i = 0;\n      if (( lastc == '\\n' && m->g->cflags & REG_NEWLINE )\n          || ( lastc == OUT && !( m->eflags & REG_NOTBOL )))\n        {\n          flagch = BOL;\n          i = m->g->nbol;\n        }\n\n      if (( c == '\\n' && m->g->cflags & REG_NEWLINE )\n          || ( c == OUT && !( m->eflags & REG_NOTEOL )))\n        {\n          flagch = ( flagch == BOL ) ? BOLEOL : EOL;\n          i += m->g->neol;\n        }\n\n      if (i != 0)\n        {\n          for (; i > 0; i--)\n            {\n              st = step(m->g, startst, stopst, st, flagch, st);\n            }\n\n          SP(\"boleol\", st, c);\n        }\n\n      /* how about a word boundary? */\n      if (( flagch == BOL || ( lastc != OUT && !ISWORD(lastc)))\n          && ( c != OUT && ISWORD(c)))\n        {\n          flagch = BOW;\n        }\n\n      if (( lastc != OUT && ISWORD(lastc))\n          && ( flagch == EOL || ( c != OUT && !ISWORD(c))))\n        {\n          flagch = EOW;\n        }\n\n      if (flagch == BOW || flagch == EOW)\n        {\n          st = step(m->g, startst, stopst, st, flagch, st);\n          SP(\"boweow\", st, c);\n        }\n\n      /* are we done? */\n      if (ISSET(st, stopst) || p == stop)\n        {\n          break; /* NOTE BREAK OUT */\n        }\n\n      /* no, we must deal with this character */\n      ASSIGN(tmp, st);\n      ASSIGN(st, fresh);\n      assert(c != OUT);\n      st = step(m->g, startst, stopst, tmp, c, st);\n      SP(\"aft\", st, c);\n      assert(EQ(step(m->g, startst, stopst, st, NOTHING, st), st));\n      p++;\n    }\n\n  assert(coldp != NULL);\n  m->coldp = coldp;\n  if (ISSET(st, stopst))\n    {\n      return p + 1;\n    }\n  else\n    {\n      return NULL;\n    }\n}\n\n/*\n * - slow - step through the string more deliberately\n */\nstatic const char * /* where it ended */\nslow(struct match *m, const char *start, const char *stop, sopno startst,\n     sopno stopst)\n{\n  states st = m->st;\n  states empty = m->empty;\n  states tmp = m->tmp;\n  const char *p = start;\n  int c;\n  int lastc; /* previous c */\n  int flagch;\n  int i;\n  const char *matchp; /* last p at which a match ended */\n\n  if (start == m->offp || ( start == m->beginp && !( m->eflags & REG_NOTBOL )))\n    {\n      c = OUT;\n    }\n  else\n    {\n      c = *( start - 1 );\n    }\n\n  AT(\"slow\", start, stop, startst, stopst);\n  CLEAR(st);\n  SET1(st, startst);\n  SP(\"sstart\", st, *p);\n  st = step(m->g, startst, stopst, st, NOTHING, st);\n  matchp = NULL;\n  for (;;)\n    {\n      /* next character */\n      lastc = c;\n      c = ( p == m->endp ) ? OUT : *p;\n\n      /* is there an EOL and/or BOL between lastc and c? */\n      flagch = '\\0';\n      i = 0;\n      if (( lastc == '\\n' && m->g->cflags & REG_NEWLINE )\n          || ( lastc == OUT && !( m->eflags & REG_NOTBOL )))\n        {\n          flagch = BOL;\n          i = m->g->nbol;\n        }\n\n      if (( c == '\\n' && m->g->cflags & REG_NEWLINE )\n          || ( c == OUT && !( m->eflags & REG_NOTEOL )))\n        {\n          flagch = ( flagch == BOL ) ? BOLEOL : EOL;\n          i += m->g->neol;\n        }\n\n      if (i != 0)\n        {\n          for (; i > 0; i--)\n            {\n              st = step(m->g, startst, stopst, st, flagch, st);\n            }\n\n          SP(\"sboleol\", st, c);\n        }\n\n      /* how about a word boundary? */\n      if (( flagch == BOL || ( lastc != OUT && !ISWORD(lastc)))\n          && ( c != OUT && ISWORD(c)))\n        {\n          flagch = BOW;\n        }\n\n      if (( lastc != OUT && ISWORD(lastc))\n          && ( flagch == EOL || ( c != OUT && !ISWORD(c))))\n        {\n          flagch = EOW;\n        }\n\n      if (flagch == BOW || flagch == EOW)\n        {\n          st = step(m->g, startst, stopst, st, flagch, st);\n          SP(\"sboweow\", st, c);\n        }\n\n      /* are we done? */\n      if (ISSET(st, stopst))\n        {\n          matchp = p;\n        }\n\n      if (EQ(st, empty) || p == stop)\n        {\n          break; /* NOTE BREAK OUT */\n        }\n\n      /* no, we must deal with this character */\n      ASSIGN(tmp, st);\n      ASSIGN(st, empty);\n      assert(c != OUT);\n      st = step(m->g, startst, stopst, tmp, c, st);\n      SP(\"saft\", st, c);\n      assert(EQ(step(m->g, startst, stopst, st, NOTHING, st), st));\n      p++;\n    }\n\n  return matchp;\n}\n\n/*\n * - step - map set of states reachable before char to set reachable after\n */\nstatic states\nstep(struct re_guts *g, sopno start,  /* start state within strip */\n     sopno stop,                      /* state after stop state within strip */\n     states bef,                      /* states reachable before */\n     int ch,                          /* character or NONCHAR code */\n     states aft)  /* states already known reachable after */\n{\n  cset *cs;\n  sop s;\n  sopno pc;\n  onestate here; /* note, macros know this name */\n  sopno look;\n  int i;\n\n  for (pc = start, INIT(here, pc); pc != stop; pc++, INC(here))\n    {\n      s = g->strip[pc];\n      switch (OP(s))\n        {\n        case OEND:\n          assert(pc == stop - 1);\n          break;\n\n        case OCHAR:\n          /* only characters can match */\n          assert(!NONCHAR(ch) || ch != (char)OPND(s));\n          if (ch == (char)OPND(s))\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OBOL:\n          if (ch == BOL || ch == BOLEOL)\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OEOL:\n          if (ch == EOL || ch == BOLEOL)\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OBOW:\n          if (ch == BOW)\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OEOW:\n          if (ch == EOW)\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OANY:\n          if (!NONCHAR(ch))\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OANYOF:\n          cs = &g->sets[OPND(s)];\n          if (!NONCHAR(ch) && CHIN(cs, ch))\n            {\n              FWD(aft, bef, 1);\n            }\n\n          break;\n\n        case OBACK_: /* ignored here */\n        case O_BACK:\n          FWD(aft, aft, 1);\n          break;\n\n        case OPLUS_: /* forward, this is just an empty */\n          FWD(aft, aft, 1);\n          break;\n\n        case O_PLUS: /* both forward and back */\n          FWD(aft, aft, 1);\n          i = ISSETBACK(aft, OPND(s));\n          BACK(aft, aft, OPND(s));\n          if (!i && ISSETBACK(aft, OPND(s)))\n            {\n              /* oho, must reconsider loop body */\n              pc -= OPND(s) + 1;\n              INIT(here, pc);\n            }\n\n          break;\n\n        case OQUEST_: /* two branches, both forward */\n          FWD(aft, aft, 1);\n          FWD(aft, aft, OPND(s));\n          break;\n\n        case O_QUEST: /* just an empty */\n          FWD(aft, aft, 1);\n          break;\n\n        case OLPAREN: /* not significant here */\n        case ORPAREN:\n          FWD(aft, aft, 1);\n          break;\n\n        case OCH_: /* mark the first two branches */\n          FWD(aft, aft, 1);\n          assert(OP(g->strip[pc + OPND(s)]) == OOR2);\n          FWD(aft, aft, OPND(s));\n          break;\n\n        case OOR1: /* done a branch, find the O_CH */\n          if (ISSTATEIN(aft, here))\n            {\n              for (look = 1; OP(s = g->strip[pc + look]) != O_CH; look += OPND(s))\n                {\n                  assert(OP(s) == OOR2);\n                }\n\n              FWD(aft, aft, look + 1);\n            }\n\n          break;\n\n        case OOR2: /* propagate OCH_'s marking */\n          FWD(aft, aft, 1);\n          if (OP(g->strip[pc + OPND(s)]) != O_CH)\n            {\n              assert(OP(g->strip[pc + OPND(s)]) == OOR2);\n              FWD(aft, aft, OPND(s));\n            }\n\n          break;\n\n        case O_CH: /* just empty */\n          FWD(aft, aft, 1);\n          break;\n\n        default: /* ooooops... */\n          assert(nope);\n          break;\n        }\n    }\n\n  return aft;\n}\n\n#ifdef REDEBUG\n/*\n * - print - print a set of states\n */\nstatic void\nprint(struct match *m, const char *caption, states st, int ch, FILE *d)\n{\n  struct re_guts *g = m->g;\n  int i;\n  int first = 1;\n\n  if (!( m->eflags & REG_TRACE ))\n    {\n      return;\n    }\n\n  (void)fprintf(d, \"%s\", caption);\n  (void)fprintf(d, \" %s\", pchar(ch));\n  for (i = 0; i < g->nstates; i++)\n    {\n      if (ISSET(st, i))\n        {\n          (void)fprintf(d, \"%s%d\", ( first ) ? \"\\t\" : \", \", i);\n          first = 0;\n        }\n    }\n\n  (void)fprintf(d, \"\\n\");\n}\n\n/*\n * - at - print current situation\n */\nstatic void\nat(struct match *m, const char *title, const char *start, const char *stop,\n   sopno startst, sopno stopst)\n{\n  if (!( m->eflags & REG_TRACE ))\n    {\n      return;\n    }\n\n  (void)printf(\"%s %s-\", title, pchar(*start));\n  (void)printf(\"%s \", pchar(*stop));\n  (void)printf(\"%ld-%ld\\n\", (long)startst, (long)stopst);\n}\n\n# ifndef PCHARDONE\n#  define PCHARDONE /* never again */\nstatic const char *nonchars[]\n  = {\n  \"OUT\", \"BOL\", \"EOL\", \"BOLEOL\", \"NOTHING\", \"BOW\", \"EOW\"\n  };\n#  define PNONCHAR(c) \\\n       (( c ) - OUT < ( sizeof ( nonchars ) / sizeof ( nonchars[0] )) \\\n           ? nonchars[( c ) - OUT] : \"invalid\" )\n\n/*\n * - pchar - make a character printable\n *\n * Is this identical to regchar() over in debug.c?  Well, yes.  But a\n * duplicate here avoids having a debugging-capable regexec.o tied to\n * a matching debug.o, and this is convenient.  It all disappears in\n * the non-debug compilation anyway, so it doesn't matter much.\n */\nstatic const char * /* -> representation */\npchar(int ch)\n{\n  static char pbuf[10];\n\n  if (NONCHAR(ch))\n    {\n      if (ch - OUT < ( sizeof ( nonchars ) / sizeof ( nonchars[0] )))\n        {\n          return nonchars[ch - OUT];\n        }\n\n      return \"invalid\";\n    }\n\n  if (isprint((unsigned char)ch) || ch == ' ')\n    {\n      (void)snprintf(pbuf, sizeof pbuf, \"%c\", ch);\n    }\n  else\n    {\n      (void)snprintf(pbuf, sizeof pbuf, \"\\\\%o\", ch);\n    }\n\n  return pbuf;\n}\n# endif /* ifndef PCHARDONE */\n#endif /* ifdef REDEBUG */\n\n#undef matcher\n#undef fast\n#undef slow\n#undef dissect\n#undef backref\n#undef step\n#undef print\n#undef at\n#undef match\n#undef nope\n"
  },
  {
    "path": "regex/regcomp.c",
    "content": "/*      $OpenBSD: regcomp.c,v 1.44 2022/12/27 17:10:06 jmc Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)regcomp.c   8.5 (Berkeley) 3/20/94\n */\n\n#include \"../include/compat.h\"\n\n#include <sys/types.h>\n#include <stdio.h>\n#include <bsd_string.h>\n#include <ctype.h>\n#include <limits.h>\n#include <bsd_stdlib.h>\n#include <bsd_regex.h>\n#include <bsd_unistd.h>\n\n#include \"utils.h\"\n#include \"bsd_regex2.h\"\n\n#include \"cclass.h\"\n#include \"cname.h\"\n\n/*\n * parse structure, passed up and down to avoid global variables and\n * other clumsinesses\n */\nstruct parse\n{\n  const char *next;     /* next character in RE */\n  const char *end;      /* end of string (-> NUL normally) */\n  int error;            /* has an error been seen? */\n  sop *strip;           /* malloced strip */\n  sopno ssize;          /* malloced strip size (allocated) */\n  sopno slen;           /* malloced strip length (used) */\n  int ncsalloc;         /* number of csets allocated */\n  struct re_guts *g;\n#define NPAREN 10       /* we need to remember () 1-9 for back refs */\n  sopno pbegin[NPAREN]; /* -> ( ([0] unused) */\n  sopno pend[NPAREN];   /* -> ) ([0] unused) */\n};\n\nstatic void p_ere(struct parse *, int);\nstatic void p_ere_exp(struct parse *);\nstatic void p_str(struct parse *);\nstatic void p_bre(struct parse *, int, int);\nstatic int p_simp_re(struct parse *, int);\nstatic int p_count(struct parse *);\nstatic void p_bracket(struct parse *);\nstatic void p_b_term(struct parse *, cset *);\nstatic void p_b_cclass(struct parse *, cset *);\nstatic void p_b_eclass(struct parse *, cset *);\nstatic char p_b_symbol(struct parse *);\nstatic char p_b_coll_elem(struct parse *, int);\nstatic char othercase(int);\nstatic void bothcases(struct parse *, int);\nstatic void ordinary(struct parse *, int);\nstatic void backslash(struct parse *, int);\nstatic void nonnewline(struct parse *);\nstatic void repeat(struct parse *, sopno, int, int);\nstatic void seterr(struct parse *, int);\nstatic cset *allocset(struct parse *);\nstatic void freeset(struct parse *, cset *);\nstatic int freezeset(struct parse *, cset *);\nstatic int firstch(struct parse *, cset *);\nstatic int nch(struct parse *, cset *);\nstatic sopno dupl(struct parse *, sopno, sopno);\nstatic void doemit(struct parse *, sop, size_t);\nstatic void doinsert(struct parse *, sop, size_t, sopno);\nstatic void dofwd(struct parse *, sopno, sop);\nstatic int enlarge(struct parse *, sopno);\nstatic void stripsnug(struct parse *, struct re_guts *);\nstatic void findmust(struct parse *, struct re_guts *);\nstatic sopno pluscount(struct parse *, struct re_guts *);\n\nstatic char nuls[10]; /* place to point scanner in event of error */\n\n/*\n * macros for use with parse structure\n * BEWARE:  these know that the parse structure is named `p' !!!\n */\n#define PEEK() ( *p->next )\n#define PEEK2() ( *( p->next + 1 ))\n#define MORE() ( p->end - p->next > 0 )\n#define MORE2() ( p->end - p->next > 1 )\n#define SEE(c) ( MORE() && PEEK() == ( c ))\n#define SEETWO(a, b) ( MORE2() && PEEK() == ( a ) && PEEK2() == ( b ))\n#define EAT(c) (( SEE(c)) ? ( NEXT(), 1 ) : 0 )\n#define EATTWO(a, b) (( SEETWO(a, b)) ? ( NEXT2(), 1 ) : 0 )\n#define NEXT() ( p->next++ )\n#define NEXT2() ( p->next += 2 )\n#define NEXTn(n) ( p->next += ( n ))\n#define GETNEXT() ( *p->next++ )\n#define SETERROR(e) seterr(p, ( e ))\n\n#define REQUIRE(co, e)                                                        \\\n  do                                                                          \\\n    {                                                                         \\\n      if (!( co ))                                                            \\\n      SETERROR(e);                                                            \\\n    } while ( 0 )\n\n#define EMIT(op, sopnd) doemit(p, (sop)( op ), (size_t)( sopnd ))\n#define INSERT(op, pos) doinsert(p, (sop)( op ), HERE() - ( pos ) + 1, pos)\n#define AHEAD(pos) dofwd(p, pos, HERE() - ( pos ))\n#define ASTERN(sop, pos) EMIT(sop, HERE() - pos)\n#define HERE() ( p->slen )\n#define THERE() ( p->slen - 1 )\n#define THERETHERE() ( p->slen - 2 )\n#define DROP(n) ( p->slen -= ( n ))\n\n#ifndef NDEBUG\nstatic int never = 0; /* for use in asserts; shuts lint up */\n#else  /* ifndef NDEBUG */\n# define never 0 /* some <assert.h>s have bugs too */\n#endif /* ifndef NDEBUG */\n\n/*\n * - regcomp - interface for parser and compilation\n */\nint /* 0 success, otherwise REG_something */\nregcomp(regex_t *preg, const char *pattern, int cflags)\n{\n  struct parse pa;\n  struct re_guts *g;\n  struct parse *p = &pa;\n  int i;\n  size_t len;\n\n#ifdef REDEBUG\n# define GOODFLAGS(f) ( f )\n#else  /* ifdef REDEBUG */\n# define GOODFLAGS(f) (( f ) & ~REG_DUMP )\n#endif /* ifdef REDEBUG */\n\n  cflags = GOODFLAGS(cflags);\n  if (( cflags & REG_EXTENDED ) && ( cflags & REG_NOSPEC ))\n    {\n      return REG_INVARG;\n    }\n\n  if (cflags & REG_PEND)\n    {\n      if (preg->re_endp < pattern)\n        {\n          return REG_INVARG;\n        }\n\n      len = preg->re_endp - pattern;\n    }\n  else\n    {\n      len = strlen((char *)pattern);\n    }\n\n  /* do the mallocs early so failure handling is easy */\n  g = malloc(sizeof ( struct re_guts ));\n  if (g == NULL)\n    {\n      return REG_ESPACE;\n    }\n\n  p->ssize = len / (size_t)2 * (size_t)3 + (size_t)1; /* ugh */\n  p->strip = openbsd_reallocarray(NULL, p->ssize, sizeof ( sop ));\n  p->slen = 0;\n  if (p->strip == NULL)\n    {\n      free(g);\n      return REG_ESPACE;\n    }\n\n  /* set things up */\n  p->g = g;\n  p->next = pattern;\n  p->end = p->next + len;\n  p->error = 0;\n  p->ncsalloc = 0;\n  for (i = 0; i < NPAREN; i++)\n    {\n      p->pbegin[i] = 0;\n      p->pend[i] = 0;\n    }\n\n  g->csetsize = NC;\n  g->sets = NULL;\n  g->setbits = NULL;\n  g->ncsets = 0;\n  g->cflags = cflags;\n  g->iflags = 0;\n  g->nbol = 0;\n  g->neol = 0;\n  g->must = NULL;\n  g->mlen = 0;\n  g->nsub = 0;\n  g->backrefs = 0;\n\n  /* do it */\n  EMIT(OEND, 0);\n  g->firststate = THERE();\n  if (cflags & REG_EXTENDED)\n    {\n      p_ere(p, OUT);\n    }\n  else if (cflags & REG_NOSPEC)\n    {\n      p_str(p);\n    }\n  else\n    {\n      p_bre(p, OUT, OUT);\n    }\n\n  EMIT(OEND, 0);\n  g->laststate = THERE();\n\n  /* tidy up loose ends and fill things in */\n  stripsnug(p, g);\n  findmust(p, g);\n  g->nplus = pluscount(p, g);\n  g->magic = MAGIC2;\n  preg->re_nsub = g->nsub;\n  preg->re_g = g;\n  preg->re_magic = MAGIC1;\n#ifndef REDEBUG\n  /* not debugging, so can't rely on the assert() in regexec() */\n  if (g->iflags & BAD)\n    {\n      SETERROR(REG_ASSERT);\n    }\n\n#endif /* ifndef REDEBUG */\n\n  /* win or lose, we're done */\n  if (p->error != 0) /* lose */\n    {\n      regfree(preg);\n    }\n\n  return p->error;\n}\n\n/*\n * - p_ere - ERE parser top level, concatenation and alternation\n */\nstatic void\np_ere(struct parse *p, int stop)  /* character this ERE should end at */\n{\n  char c;\n  sopno prevback;\n  sopno prevfwd;\n  sopno conc;\n  int first = 1; /* is this the first alternative? */\n\n  for (;;)\n    {\n      /* do a bunch of concatenated expressions */\n      conc = HERE();\n      while (MORE() && ( c = PEEK()) != '|' && c != stop)\n        {\n          p_ere_exp(p);\n        }\n      REQUIRE(HERE() != conc, REG_EMPTY); /* require nonempty */\n\n      if (!EAT('|'))\n        {\n          break; /* NOTE BREAK OUT */\n        }\n\n      if (first)\n        {\n          INSERT(OCH_, conc); /* offset is wrong */\n          prevfwd = conc;\n          prevback = conc;\n          first = 0;\n        }\n\n      ASTERN(OOR1, prevback);\n      prevback = THERE();\n      AHEAD(prevfwd); /* fix previous offset */\n      prevfwd = HERE();\n      EMIT(OOR2, 0); /* offset is very wrong */\n    }\n\n  if (!first)\n    { /* tail-end fixups */\n      AHEAD(prevfwd);\n      ASTERN(O_CH, prevback);\n    }\n\n  assert(!MORE() || SEE(stop));\n}\n\n/*\n * - p_ere_exp - parse one subERE, an atom possibly followed by a repetition op\n */\nstatic void\np_ere_exp(struct parse *p)\n{\n  char c;\n  sopno pos;\n  int count;\n  int count2;\n  sopno subno;\n  int wascaret = 0;\n\n  assert(MORE());   /* caller should have ensured this */\n  c = GETNEXT();\n\n  pos = HERE();\n  switch (c)\n    {\n    case '(':\n      REQUIRE(MORE(), REG_EPAREN);\n      p->g->nsub++;\n      subno = p->g->nsub;\n      if (subno < NPAREN)\n        {\n          p->pbegin[subno] = HERE();\n        }\n\n      EMIT(OLPAREN, subno);\n      if (!SEE(')'))\n        {\n          p_ere(p, ')');\n        }\n\n      if (subno < NPAREN)\n        {\n          p->pend[subno] = HERE();\n          assert(p->pend[subno] != 0);\n        }\n\n      EMIT(ORPAREN, subno);\n      REQUIRE(MORE() && GETNEXT() == ')', REG_EPAREN);\n      break;\n\n    case '^':\n      EMIT(OBOL, 0);\n      p->g->iflags |= USEBOL;\n      p->g->nbol++;\n      wascaret = 1;\n      break;\n\n    case '$':\n      EMIT(OEOL, 0);\n      p->g->iflags |= USEEOL;\n      p->g->neol++;\n      break;\n\n    case '|':\n      SETERROR(REG_EMPTY);\n      break;\n\n    case '*':\n    case '+':\n    case '?':\n      SETERROR(REG_BADRPT);\n      break;\n\n    case '.':\n      if (p->g->cflags & REG_NEWLINE)\n        {\n          nonnewline(p);\n        }\n      else\n        {\n          EMIT(OANY, 0);\n        }\n\n      break;\n\n    case '[':\n      p_bracket(p);\n      break;\n\n    case '\\\\':\n      REQUIRE(MORE(), REG_EESCAPE);\n      c = GETNEXT();\n      backslash(p, c);\n      break;\n\n    case '{': /* okay as ordinary except if digit follows */\n      REQUIRE(!MORE() || !isdigit((uch)PEEK()), REG_BADRPT);\n\n    /* FALLTHROUGH */\n    default:\n      if (p->error != 0)\n        {\n          return;\n        }\n\n      ordinary(p, c);\n      break;\n    }\n\n  if (!MORE())\n    {\n      return;\n    }\n\n  c = PEEK();\n  /* we call { a repetition if followed by a digit */\n  if (!( c == '*' || c == '+' || c == '?'\n         || ( c == '{' && MORE2() && isdigit((uch)PEEK2()))))\n    {\n      return; /* no repetition, we're done */\n    }\n\n  NEXT();\n\n  REQUIRE(!wascaret, REG_BADRPT);\n  switch (c)\n    {\n    case '*': /* implemented as +? */\n      /* this case does not require the (y|) trick, noKLUDGE */\n      INSERT(OPLUS_, pos);\n      ASTERN(O_PLUS, pos);\n      INSERT(OQUEST_, pos);\n      ASTERN(O_QUEST, pos);\n      break;\n\n    case '+':\n      INSERT(OPLUS_, pos);\n      ASTERN(O_PLUS, pos);\n      break;\n\n    case '?':\n      /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */\n      INSERT(OCH_, pos); /* offset slightly wrong */\n      ASTERN(OOR1, pos); /* this one's right */\n      AHEAD(pos);        /* fix the OCH_ */\n      EMIT(OOR2, 0);     /* offset very wrong... */\n      AHEAD(THERE());    /* ...so fix it */\n      ASTERN(O_CH, THERETHERE());\n      break;\n\n    case '{':\n      count = p_count(p);\n      if (EAT(','))\n        {\n          if (isdigit((uch)PEEK()))\n            {\n              count2 = p_count(p);\n              REQUIRE(count <= count2, REG_BADBR);\n            }\n          else /* single number with comma */\n            {\n              count2 = INFINITY;\n            }\n        }\n      else /* just a single number */\n        {\n          count2 = count;\n        }\n\n      repeat(p, pos, count, count2);\n      if (!EAT('}'))\n        { /* error heuristics */\n          while (MORE() && PEEK() != '}')\n            {\n              NEXT();\n            }\n          REQUIRE(MORE(), REG_EBRACE);\n          SETERROR(REG_BADBR);\n        }\n\n      break;\n    }\n\n  if (!MORE())\n    {\n      return;\n    }\n\n  c = PEEK();\n  if (!( c == '*' || c == '+' || c == '?'\n         || ( c == '{' && MORE2() && isdigit((uch)PEEK2()))))\n    {\n      return;\n    }\n\n  SETERROR(REG_BADRPT);\n}\n\n/*\n * - p_str - string (no metacharacters) \"parser\"\n */\nstatic void\np_str(struct parse *p)\n{\n  REQUIRE(MORE(), REG_EMPTY);\n  while (MORE())\n    {\n      ordinary(p, GETNEXT());\n    }\n}\n\n/*\n * - p_bre - BRE parser top level, anchoring and concatenation\n * Giving end1 as OUT essentially eliminates the end1/end2 check.\n *\n * This implementation is a bit of a kludge, in that a trailing $ is first\n * taken as an ordinary character and then revised to be an anchor.  The\n * only undesirable side effect is that '$' gets included as a character\n * category in such cases.  This is fairly harmless; not worth fixing.\n * The amount of lookahead needed to avoid this kludge is excessive.\n */\nstatic void\np_bre(struct parse *p, int end1,  /* first terminating character */\n      int end2)                   /* second terminating character */\n{\n  sopno start = HERE();\n  int first = 1; /* first subexpression? */\n  int wasdollar = 0;\n\n  if (EAT('^'))\n    {\n      EMIT(OBOL, 0);\n      p->g->iflags |= USEBOL;\n      p->g->nbol++;\n    }\n\n  while (MORE() && !SEETWO(end1, end2))\n    {\n      wasdollar = p_simp_re(p, first);\n      first = 0;\n    }\n  if (wasdollar)\n    { /* oops, that was a trailing anchor */\n      DROP(1);\n      EMIT(OEOL, 0);\n      p->g->iflags |= USEEOL;\n      p->g->neol++;\n    }\n\n  REQUIRE(HERE() != start, REG_EMPTY);   /* require nonempty */\n}\n\n/*\n * - p_simp_re - parse a simple RE, an atom possibly followed by a repetition\n */\nstatic int /* was the simple RE an un-backslashed $? */\np_simp_re(struct parse *p,\n          int    starordinary)  /* is a leading * an ordinary character? */\n{\n  int c;\n  int count;\n  int count2;\n  sopno pos;\n  int i;\n  sopno subno;\n\n#define BACKSL ( 1 << CHAR_BIT )\n\n  pos = HERE();  /* repetition op, if any, covers from here */\n\n  assert(MORE());   /* caller should have ensured this */\n  c = GETNEXT();\n  if (c == '\\\\')\n    {\n      REQUIRE(MORE(), REG_EESCAPE);\n      c = BACKSL | GETNEXT();\n    }\n\n  switch (c)\n    {\n    case '.':\n      if (p->g->cflags & REG_NEWLINE)\n        {\n          nonnewline(p);\n        }\n      else\n        {\n          EMIT(OANY, 0);\n        }\n\n      break;\n\n    case '[':\n      p_bracket(p);\n      break;\n\n    case BACKSL | '<':\n      EMIT(OBOW, 0);\n      break;\n\n    case BACKSL | '>':\n      EMIT(OEOW, 0);\n      break;\n\n    case BACKSL | '{':\n      SETERROR(REG_BADRPT);\n      break;\n\n    case BACKSL | '(':\n      p->g->nsub++;\n      subno = p->g->nsub;\n      if (subno < NPAREN)\n        {\n          p->pbegin[subno] = HERE();\n        }\n\n      EMIT(OLPAREN, subno);\n      /* the MORE here is an error heuristic */\n      if (MORE() && !SEETWO('\\\\', ')'))\n        {\n          p_bre(p, '\\\\', ')');\n        }\n\n      if (subno < NPAREN)\n        {\n          p->pend[subno] = HERE();\n          assert(p->pend[subno] != 0);\n        }\n\n      EMIT(ORPAREN, subno);\n      REQUIRE(EATTWO('\\\\', ')'), REG_EPAREN);\n      break;\n\n    case BACKSL | ')': /* should not get here -- must be user */\n    case BACKSL | '}':\n      SETERROR(REG_EPAREN);\n      break;\n\n    case BACKSL | '1':\n    case BACKSL | '2':\n    case BACKSL | '3':\n    case BACKSL | '4':\n    case BACKSL | '5':\n    case BACKSL | '6':\n    case BACKSL | '7':\n    case BACKSL | '8':\n    case BACKSL | '9':\n      i = ( c & ~BACKSL ) - '0';\n      assert(i < NPAREN);\n      if (p->pend[i] != 0)\n        {\n          assert(i <= p->g->nsub);\n          EMIT(OBACK_, i);\n          assert(p->pbegin[i] != 0);\n          assert(OP(p->strip[p->pbegin[i]]) == OLPAREN);\n          assert(OP(p->strip[p->pend[i]]) == ORPAREN);\n          (void)dupl(p, p->pbegin[i] + 1, p->pend[i]);\n          EMIT(O_BACK, i);\n        }\n      else\n        {\n          SETERROR(REG_ESUBREG);\n        }\n\n      p->g->backrefs = 1;\n      break;\n\n    case '*':\n      REQUIRE(starordinary, REG_BADRPT);\n\n    /* FALLTHROUGH */\n    default:\n      if (p->error != 0)\n        {\n          return 0; /* Definitely not $... */\n        }\n\n      ordinary(p, (char)c);\n      break;\n    }\n\n  if (EAT('*'))\n    { /* implemented as +? */\n      /* this case does not require the (y|) trick, noKLUDGE */\n      INSERT(OPLUS_, pos);\n      ASTERN(O_PLUS, pos);\n      INSERT(OQUEST_, pos);\n      ASTERN(O_QUEST, pos);\n    }\n  else if (EATTWO('\\\\', '{'))\n    {\n      count = p_count(p);\n      if (EAT(','))\n        {\n          if (MORE() && isdigit((uch)PEEK()))\n            {\n              count2 = p_count(p);\n              REQUIRE(count <= count2, REG_BADBR);\n            }\n          else /* single number with comma */\n            {\n              count2 = INFINITY;\n            }\n        }\n      else /* just a single number */\n        {\n          count2 = count;\n        }\n\n      repeat(p, pos, count, count2);\n      if (!EATTWO('\\\\', '}'))\n        { /* error heuristics */\n          while (MORE() && !SEETWO('\\\\', '}'))\n            {\n              NEXT();\n            }\n          REQUIRE(MORE(), REG_EBRACE);\n          SETERROR(REG_BADBR);\n        }\n    }\n  else if (c == '$') /* $ (but not \\$) ends it */\n    {\n      return 1;\n    }\n\n  return 0;\n}\n\n/*\n * - p_count - parse a repetition count\n */\nstatic int /* the value */\np_count(struct parse *p)\n{\n  int count = 0;\n  int ndigits = 0;\n\n  while (MORE() && isdigit((uch)PEEK()) && count <= DUPMAX)\n    {\n      count = count * 10 + ( GETNEXT() - '0' );\n      ndigits++;\n    }\n\n  REQUIRE(ndigits > 0 && count <= DUPMAX, REG_BADBR);\n  return count;\n}\n\n/*\n * - p_bracket - parse a bracketed character list\n *\n * Note a significant property of this code:  if the allocset() did SETERROR,\n * no set operations are done.\n */\nstatic void\np_bracket(struct parse *p)\n{\n  cset *cs;\n  int invert = 0;\n\n  /* Dept of Truly Sickening Special-Case Kludges */\n  if (p->end - p->next > 5)\n    {\n      if (strncmp(p->next, \"[:<:]]\", 6) == 0)\n        {\n          EMIT(OBOW, 0);\n          NEXTn(6);\n          return;\n        }\n\n      if (strncmp(p->next, \"[:>:]]\", 6) == 0)\n        {\n          EMIT(OEOW, 0);\n          NEXTn(6);\n          return;\n        }\n    }\n\n  if (( cs = allocset(p)) == NULL)\n    {\n      /* allocset did set error status in p */\n      return;\n    }\n\n  if (EAT('^'))\n    {\n      invert++; /* make note to invert set at end */\n    }\n\n  if (EAT(']'))\n    {\n      CHadd(cs, ']');\n    }\n  else if (EAT('-'))\n    {\n      CHadd(cs, '-');\n    }\n\n  while (MORE() && PEEK() != ']' && !SEETWO('-', ']'))\n    {\n      p_b_term(p, cs);\n    }\n  if (EAT('-'))\n    {\n      CHadd(cs, '-');\n    }\n\n  REQUIRE(MORE() && GETNEXT() == ']', REG_EBRACK);\n\n  if (p->error != 0)\n    { /* don't mess things up further */\n      freeset(p, cs);\n      return;\n    }\n\n  if (p->g->cflags & REG_ICASE)\n    {\n      int i;\n      int ci;\n\n      for (i = p->g->csetsize - 1; i >= 0; i--)\n        {\n          if (CHIN(cs, i) && isalpha(i))\n            {\n              ci = othercase(i);\n              if (ci != i)\n                {\n                  CHadd(cs, ci);\n                }\n            }\n        }\n    }\n\n  if (invert)\n    {\n      int i;\n\n      for (i = p->g->csetsize - 1; i >= 0; i--)\n        {\n          if (CHIN(cs, i))\n            {\n              CHsub(cs, i);\n            }\n          else\n            {\n              CHadd(cs, i);\n            }\n        }\n\n      if (p->g->cflags & REG_NEWLINE)\n        {\n          CHsub(cs, '\\n');\n        }\n    }\n\n  if (nch(p, cs) == 1)\n    { /* optimize singleton sets */\n      ordinary(p, firstch(p, cs));\n      freeset(p, cs);\n    }\n  else\n    {\n      EMIT(OANYOF, freezeset(p, cs));\n    }\n}\n\n/*\n * - p_b_term - parse one term of a bracketed character list\n */\nstatic void\np_b_term(struct parse *p, cset *cs)\n{\n  char c;\n  char start, finish;\n  int i;\n\n  /* classify what we've got */\n  switch (( MORE()) ? PEEK() : '\\0')\n    {\n    case '[':\n      c = ( MORE2()) ? PEEK2() : '\\0';\n      break;\n\n    case '-':\n      SETERROR(REG_ERANGE);\n      return; /* NOTE RETURN */\n\n      break;\n\n    default:\n      c = '\\0';\n      break;\n    }\n\n  switch (c)\n    {\n    case ':': /* character class */\n      NEXT2();\n      REQUIRE(MORE(), REG_EBRACK);\n      c = PEEK();\n      REQUIRE(c != '-' && c != ']', REG_ECTYPE);\n      p_b_cclass(p, cs);\n      REQUIRE(MORE(), REG_EBRACK);\n      REQUIRE(EATTWO(':', ']'), REG_ECTYPE);\n      break;\n\n    case '=': /* equivalence class */\n      NEXT2();\n      REQUIRE(MORE(), REG_EBRACK);\n      c = PEEK();\n      REQUIRE(c != '-' && c != ']', REG_ECOLLATE);\n      p_b_eclass(p, cs);\n      REQUIRE(MORE(), REG_EBRACK);\n      REQUIRE(EATTWO('=', ']'), REG_ECOLLATE);\n      break;\n\n    default: /* symbol, ordinary character, or range */\n             /* xxx revision needed for multichar stuff */\n      start = p_b_symbol(p);\n      if (SEE('-') && MORE2() && PEEK2() != ']')\n        {\n          /* range */\n          NEXT();\n          if (EAT('-'))\n            {\n              finish = '-';\n            }\n          else\n            {\n              finish = p_b_symbol(p);\n            }\n        }\n      else\n        {\n          finish = start;\n        }\n\n      /* xxx what about signed chars here... */\n      REQUIRE(start <= finish, REG_ERANGE);\n      for (i = start; i <= finish; i++)\n        {\n          CHadd(cs, i);\n        }\n\n      break;\n    }\n}\n\n/*\n * - p_b_cclass - parse a character-class name and deal with it\n */\nstatic void\np_b_cclass(struct parse *p, cset *cs)\n{\n  const char *sp = p->next;\n  const struct cclass *cp;\n  size_t len;\n  const char *u;\n  char c;\n\n  while (MORE() && isalpha((uch)PEEK()))\n    {\n      NEXT();\n    }\n  len = p->next - sp;\n  for (cp = cclasses; cp->name != NULL; cp++)\n    {\n      if (strncmp(cp->name, sp, len) == 0 && cp->name[len] == '\\0')\n        {\n          break;\n        }\n    }\n\n  if (cp->name == NULL)\n    {\n      /* oops, didn't find it */\n      SETERROR(REG_ECTYPE);\n      return;\n    }\n\n  u = cp->chars;\n  while (( c = *u++ ) != '\\0')\n    {\n      CHadd(cs, c);\n    }\n}\n\n/*\n * - p_b_eclass - parse an equivalence-class name and deal with it\n *\n * This implementation is incomplete. xxx\n */\nstatic void\np_b_eclass(struct parse *p, cset *cs)\n{\n  char c;\n\n  c = p_b_coll_elem(p, '=');\n  CHadd(cs, c);\n}\n\n/*\n * - p_b_symbol - parse a character or [..]ed multicharacter collating symbol\n */\nstatic char /* value of symbol */\np_b_symbol(struct parse *p)\n{\n  char value;\n\n  REQUIRE(MORE(), REG_EBRACK);\n  if (!EATTWO('[', '.'))\n    {\n      return GETNEXT();\n    }\n\n  /* collating symbol */\n  value = p_b_coll_elem(p, '.');\n  REQUIRE(EATTWO('.', ']'), REG_ECOLLATE);\n  return value;\n}\n\n/*\n * - p_b_coll_elem - parse a collating-element name and look it up\n */\nstatic char                               /* value of collating element */\np_b_coll_elem(struct parse *p, int endc)  /* name ended by endc,']' */\n{\n  const char *sp = p->next;\n  const struct cname *cp;\n  size_t len;\n\n  while (MORE() && !SEETWO(endc, ']'))\n    {\n      NEXT();\n    }\n  if (!MORE())\n    {\n      SETERROR(REG_EBRACK);\n      return 0;\n    }\n\n  len = p->next - sp;\n  for (cp = cnames; cp->name != NULL; cp++)\n    {\n      if (strncmp(cp->name, sp, len) == 0 && strlen(cp->name) == len)\n        {\n          return cp->code; /* known name */\n        }\n    }\n\n  if (len == 1)\n    {\n      return *sp;        /* single character */\n    }\n\n  SETERROR(REG_ECOLLATE);  /* neither */\n  return 0;\n}\n\n/*\n * - othercase - return the case counterpart of an alphabetic\n */\nstatic char /* if no counterpart, return ch */\nothercase(int ch)\n{\n  ch = (uch)ch;\n  assert(isalpha(ch));\n  if (isupper(ch))\n    {\n      return (uch)tolower(ch);\n    }\n  else if (islower(ch))\n    {\n      return (uch)toupper(ch);\n    }\n  else /* peculiar, but could happen */\n    {\n      return ch;\n    }\n}\n\n/*\n * - bothcases - emit a dualcase version of a two-case character\n *\n * Boy, is this implementation ever a kludge...\n */\nstatic void\nbothcases(struct parse *p, int ch)\n{\n  const char *oldnext = p->next;\n  const char *oldend = p->end;\n  char bracket[3];\n\n  ch = (uch)ch;\n  assert(othercase(ch) != ch);   /* p_bracket() would recurse */\n  p->next = bracket;\n  p->end = bracket + 2;\n  bracket[0] = ch;\n  bracket[1] = ']';\n  bracket[2] = '\\0';\n  p_bracket(p);\n  assert(p->next == bracket + 2);\n  p->next = oldnext;\n  p->end = oldend;\n}\n\n/*\n * - ordinary - emit an ordinary character\n */\nstatic void\nordinary(struct parse *p, int ch)\n{\n  if (( p->g->cflags & REG_ICASE ) && isalpha((uch)ch) && othercase(ch) != ch)\n    {\n      bothcases(p, ch);\n    }\n  else\n    {\n      EMIT(OCHAR, (uch)ch);\n    }\n}\n\n/*\n * do something magic with this character, but only if it's extra magic\n */\nstatic void\nbackslash(struct parse *p, int ch)\n{\n  switch (ch)\n    {\n    case '<':\n      EMIT(OBOW, 0);\n      break;\n\n    case '>':\n      EMIT(OEOW, 0);\n      break;\n\n    default:\n      ordinary(p, ch);\n      break;\n    }\n}\n\n/*\n * - nonnewline - emit REG_NEWLINE version of OANY\n *\n * Boy, is this implementation ever a kludge...\n */\nstatic void\nnonnewline(struct parse *p)\n{\n  const char *oldnext = p->next;\n  const char *oldend = p->end;\n  static const char bracket[4] = {\n    '^', '\\n', ']', '\\0'\n  };\n\n  p->next = bracket;\n  p->end = bracket + 3;\n  p_bracket(p);\n  assert(p->next == bracket + 3);\n  p->next = oldnext;\n  p->end = oldend;\n}\n\n/*\n * - repeat - generate code for a bounded repetition, recursively if needed\n */\nstatic void\nrepeat(struct parse *p, sopno start,  /* operand from here to end of strip */\n       int from,                              /* repeated from this number */\n       int to)                 /* to this number of times (maybe INFINITY) */\n{\n  sopno finish = HERE();\n\n#define N 2\n#define INF 3\n#define REP(f, t) (( f ) * 8 + ( t ))\n#define MAP(n) ((( n ) <= 1 ) ? ( n ) : (( n ) == INFINITY ) ? INF : N )\n  sopno copy;\n\n  if (p->error != 0) /* head off possible runaway recursion */\n    {\n      return;\n    }\n\n  assert(from <= to);\n\n  switch (REP(MAP(from), MAP(to)))\n    {\n    case REP(0, 0):        /* must be user doing this */\n      DROP(finish - start); /* drop the operand */\n      break;\n\n    case REP(0, 1):  /* as x{1,1}? */\n    case REP(0, N):  /* as x{1,n}? */\n    case REP(0, INF): /* as x{1,}? */\n      /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */\n      INSERT(OCH_, start); /* offset is wrong... */\n      repeat(p, start + 1, 1, to);\n      ASTERN(OOR1, start);\n      AHEAD(start); /* ... fix it */\n      EMIT(OOR2, 0);\n      AHEAD(THERE());\n      ASTERN(O_CH, THERETHERE());\n      break;\n\n    case REP(1, 1): /* trivial case */\n      /* done */\n      break;\n\n    case REP(1, N): /* as x?x{1,n-1} */\n      /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */\n      INSERT(OCH_, start);\n      ASTERN(OOR1, start);\n      AHEAD(start);\n      EMIT(OOR2, 0);  /* offset very wrong... */\n      AHEAD(THERE()); /* ...so fix it */\n      ASTERN(O_CH, THERETHERE());\n      copy = dupl(p, start + 1, finish + 1);\n      assert(copy == finish + 4);\n      repeat(p, copy, 1, to - 1);\n      break;\n\n    case REP(1, INF): /* as x+ */\n      INSERT(OPLUS_, start);\n      ASTERN(O_PLUS, start);\n      break;\n\n    case REP(N, N): /* as xx{m-1,n-1} */\n      copy = dupl(p, start, finish);\n      repeat(p, copy, from - 1, to - 1);\n      break;\n\n    case REP(N, INF): /* as xx{n-1,INF} */\n      copy = dupl(p, start, finish);\n      repeat(p, copy, from - 1, to);\n      break;\n\n    default:               /* \"can't happen\" */\n      SETERROR(REG_ASSERT); /* just in case */\n      break;\n    }\n}\n\n/*\n * - seterr - set an error condition\n */\nstatic void\nseterr(struct parse *p, int e)\n{\n  if (p->error == 0) /* keep earliest error condition */\n    {\n      p->error = e;\n    }\n\n  p->next = nuls; /* try to bring things to a halt */\n  p->end = nuls;\n}\n\n/*\n * - allocset - allocate a set of characters for []\n */\nstatic cset *\nallocset(struct parse *p)\n{\n  int no = p->g->ncsets++;\n  size_t nc;\n  size_t nbytes;\n  cset *cs;\n  size_t css = (size_t)p->g->csetsize;\n  int i;\n\n  if (no >= p->ncsalloc)\n    { /* need another column of space */\n      void *ptr;\n\n      p->ncsalloc += CHAR_BIT;\n      nc = p->ncsalloc;\n      assert(nc % CHAR_BIT == 0);\n\n      ptr = openbsd_reallocarray(p->g->sets, nc, sizeof ( cset ));\n      if (ptr == NULL)\n        {\n          goto nomem;\n        }\n\n      p->g->sets = ptr;\n\n      ptr = openbsd_reallocarray(p->g->setbits, nc / CHAR_BIT, css);\n      if (ptr == NULL)\n        {\n          goto nomem;\n        }\n\n      nbytes = ( nc / CHAR_BIT ) * css;\n      p->g->setbits = ptr;\n\n      for (i = 0; i < no; i++)\n        {\n          p->g->sets[i].ptr = p->g->setbits + css * ( i / CHAR_BIT );\n        }\n\n      (void)memset((char *)p->g->setbits + ( nbytes - css ), 0, css);\n    }\n\n  /* XXX should not happen */\n  if (p->g->sets == NULL || p->g->setbits == NULL)\n    {\n      goto nomem;\n    }\n\n  cs = &p->g->sets[no];\n  cs->ptr = p->g->setbits + css * (( no ) / CHAR_BIT );\n  cs->mask = 1 << (( no ) % CHAR_BIT );\n  cs->hash = 0;\n\n  return cs;\n\nnomem:\n  free(p->g->sets);\n  p->g->sets = NULL;\n  free(p->g->setbits);\n  p->g->setbits = NULL;\n\n  SETERROR(REG_ESPACE);\n  /* caller's responsibility not to do set ops */\n  return NULL;\n}\n\n/*\n * - freeset - free a now-unused set\n */\nstatic void\nfreeset(struct parse *p, cset *cs)\n{\n  int i;\n  cset *top = &p->g->sets[p->g->ncsets];\n  size_t css = (size_t)p->g->csetsize;\n\n  for (i = 0; i < css; i++)\n    {\n      CHsub(cs, i);\n    }\n\n  if (cs == top - 1) /* recover only the easy case */\n    {\n      p->g->ncsets--;\n    }\n}\n\n/*\n * - freezeset - final processing on a set of characters\n *\n * The main task here is merging identical sets.  This is usually a waste\n * of time (although the hash code minimizes the overhead), but can win\n * big if REG_ICASE is being used.  REG_ICASE, by the way, is why the hash\n * is done using addition rather than xor -- all ASCII [aA] sets xor to\n * the same value!\n */\nstatic int /* set number */\nfreezeset(struct parse *p, cset *cs)\n{\n  uch h = cs->hash;\n  int i;\n  cset *top = &p->g->sets[p->g->ncsets];\n  cset *cs2;\n  size_t css = (size_t)p->g->csetsize;\n\n  /* look for an earlier one which is the same */\n  for (cs2 = &p->g->sets[0]; cs2 < top; cs2++)\n    {\n      if (cs2->hash == h && cs2 != cs)\n        {\n          /* maybe */\n          for (i = 0; i < css; i++)\n            {\n              if (CHIN(cs2, i) != CHIN(cs, i))\n                {\n                  break; /* no */\n                }\n            }\n\n          if (i == css)\n            {\n              break; /* yes */\n            }\n        }\n    }\n\n  if (cs2 < top)\n    { /* found one */\n      freeset(p, cs);\n      cs = cs2;\n    }\n\n  return (int)( cs - p->g->sets );\n}\n\n/*\n * - firstch - return first character in a set (which must have at least one)\n */\nstatic int /* character; there is no \"none\" value */\nfirstch(struct parse *p, cset *cs)\n{\n  int i;\n  size_t css = (size_t)p->g->csetsize;\n\n  for (i = 0; i < css; i++)\n    {\n      if (CHIN(cs, i))\n        {\n          return (char)i;\n        }\n    }\n\n  assert(never);\n  return 0; /* arbitrary */\n}\n\n/*\n * - nch - number of characters in a set\n */\nstatic int\nnch(struct parse *p, cset *cs)\n{\n  int i;\n  size_t css = (size_t)p->g->csetsize;\n  int n = 0;\n\n  for (i = 0; i < css; i++)\n    {\n      if (CHIN(cs, i))\n        {\n          n++;\n        }\n    }\n\n  return n;\n}\n\n/*\n * - dupl - emit a duplicate of a bunch of sops\n */\nstatic sopno                        /* start of duplicate */\ndupl(struct parse *p, sopno start,  /* from here */\n     sopno finish)                  /* to this less one */\n{\n  sopno ret = HERE();\n  sopno len = finish - start;\n\n  assert(finish >= start);\n  if (len == 0)\n    {\n      return ret;\n    }\n\n  if (!enlarge(p, p->ssize + len))  /* this many unexpected additions */\n    {\n      return ret;\n    }\n\n  (void)memcpy(p->strip + p->slen, p->strip + start, len * sizeof ( sop ));\n  p->slen += len;\n  return ret;\n}\n\n/*\n * - doemit - emit a strip operator\n *\n * It might seem better to implement this as a macro with a function as\n * hard-case backup, but it's just too big and messy unless there are\n * some changes to the data structures.  Maybe later.\n */\nstatic void\ndoemit(struct parse *p, sop op, size_t opnd)\n{\n  /* avoid making error situations worse */\n  if (p->error != 0)\n    {\n      return;\n    }\n\n  /* deal with oversize operands (\"can't happen\", more or less) */\n  assert(opnd < 1 << OPSHIFT);\n\n  /* deal with undersized strip */\n  if (p->slen >= p->ssize)\n    {\n      if (!enlarge(p, ( p->ssize + 1 ) / 2 * 3)) /* +50% */\n        {\n          return;\n        }\n    }\n\n  /* finally, it's all reduced to the easy case */\n  p->strip[p->slen++] = SOP(op, opnd);\n}\n\n/*\n * - doinsert - insert a sop into the strip\n */\nstatic void\ndoinsert(struct parse *p, sop op, size_t opnd, sopno pos)\n{\n  sopno sn;\n  sop s;\n  int i;\n\n  /* avoid making error situations worse */\n  if (p->error != 0)\n    {\n      return;\n    }\n\n  sn = HERE();\n  EMIT(op, opnd);  /* do checks, ensure space */\n  assert(HERE() == sn + 1);\n  s = p->strip[sn];\n\n  /* adjust paren pointers */\n  assert(pos > 0);\n  for (i = 1; i < NPAREN; i++)\n    {\n      if (p->pbegin[i] >= pos)\n        {\n          p->pbegin[i]++;\n        }\n\n      if (p->pend[i] >= pos)\n        {\n          p->pend[i]++;\n        }\n    }\n\n  memmove(\n    (char *)&p->strip[pos + 1],\n    (char *)&p->strip[pos],\n    ( HERE() - pos - 1 ) * sizeof ( sop ));\n  p->strip[pos] = s;\n}\n\n/*\n * - dofwd - complete a forward reference\n */\nstatic void\ndofwd(struct parse *p, sopno pos, sop value)\n{\n  /* avoid making error situations worse */\n  if (p->error != 0)\n    {\n      return;\n    }\n\n  assert(value < 1 << OPSHIFT);\n  p->strip[pos] = OP(p->strip[pos]) | value;\n}\n\n/*\n * - enlarge - enlarge the strip\n */\nstatic int\nenlarge(struct parse *p, sopno size)\n{\n  sop *sp;\n\n  if (p->ssize >= size)\n    {\n      return 1;\n    }\n\n  sp = openbsd_reallocarray(p->strip, size, sizeof ( sop ));\n  if (sp == NULL)\n    {\n      SETERROR(REG_ESPACE);\n      return 0;\n    }\n\n  p->strip = sp;\n  p->ssize = size;\n  return 1;\n}\n\n/*\n * - stripsnug - compact the strip\n */\nstatic void\nstripsnug(struct parse *p, struct re_guts *g)\n{\n  g->nstates = p->slen;\n  g->strip = openbsd_reallocarray(p->strip, p->slen, sizeof ( sop ));\n  if (g->strip == NULL)\n    {\n      SETERROR(REG_ESPACE);\n      g->strip = p->strip;\n    }\n}\n\n/*\n * - findmust - fill in must and mlen with longest mandatory literal string\n *\n * This algorithm could do fancy things like analyzing the operands of |\n * for common subsequences.  Someday.  This code is simple and finds most\n * of the interesting cases.\n *\n * Note that must and mlen got initialized during setup.\n */\nstatic void\nfindmust(struct parse *p, struct re_guts *g)\n{\n  sop *scan;\n  sop *start;    /* start initialized in the default case, after that */\n  sop *newstart; /* newstart was initialized in the OCHAR case */\n  sopno newlen;\n  sop s;\n  char *cp;\n  sopno i;\n\n  /* avoid making error situations worse */\n  if (p->error != 0)\n    {\n      return;\n    }\n\n  /* find the longest OCHAR sequence in strip */\n  newlen = 0;\n  scan = g->strip + 1;\n  do\n    {\n      s = *scan++;\n      switch (OP(s))\n        {\n        case OCHAR:    /* sequence member */\n          if (newlen == 0) /* new sequence */\n            {\n              newstart = scan - 1;\n            }\n\n          newlen++;\n          break;\n\n        case OPLUS_: /* things that don't break one */\n        case OLPAREN:\n        case ORPAREN:\n          break;\n\n        case OQUEST_: /* things that must be skipped */\n        case OCH_:\n          scan--;\n          do\n            {\n              scan += OPND(s);\n              s = *scan;\n              /* assert() interferes w debug printouts */\n              if (OP(s) != O_QUEST && OP(s) != O_CH && OP(s) != OOR2)\n                {\n                  g->iflags |= BAD;\n                  return;\n                }\n            }\n          while ( OP(s) != O_QUEST && OP(s) != O_CH );\n\n        /* fallthrough */\n        default: /* things that break a sequence */\n          if (newlen > g->mlen)\n            { /* ends one */\n              start = newstart;\n              g->mlen = newlen;\n            }\n\n          newlen = 0;\n          break;\n        }\n    }\n  while ( OP(s) != OEND );\n\n  if (g->mlen == 0) /* there isn't one */\n    {\n      return;\n    }\n\n  /* turn it into a character string */\n  g->must = malloc((size_t)g->mlen + 1);\n  if (g->must == NULL)\n    { /* argh; just forget it */\n      g->mlen = 0;\n      return;\n    }\n\n  cp = g->must;\n  scan = start;\n  for (i = g->mlen; i > 0; i--)\n    {\n      while (OP(s = *scan++) != OCHAR)\n        {\n          continue;\n        }\n      assert(cp < g->must + g->mlen);\n      *cp++ = (char)OPND(s);\n    }\n\n  assert(cp == g->must + g->mlen);\n  *cp = '\\0'; /* just on general principles */\n}\n\n/*\n * - pluscount - count + nesting\n */\nstatic sopno /* nesting depth */\npluscount(struct parse *p, struct re_guts *g)\n{\n  sop *scan;\n  sop s;\n  sopno plusnest = 0;\n  sopno maxnest = 0;\n\n  if (p->error != 0)\n    {\n      return 0; /* there may not be an OEND */\n    }\n\n  scan = g->strip + 1;\n  do\n    {\n      s = *scan++;\n      switch (OP(s))\n        {\n        case OPLUS_:\n          plusnest++;\n          break;\n\n        case O_PLUS:\n          if (plusnest > maxnest)\n            {\n              maxnest = plusnest;\n            }\n\n          plusnest--;\n          break;\n        }\n    }\n  while ( OP(s) != OEND );\n  if (plusnest != 0)\n    {\n      g->iflags |= BAD;\n    }\n\n  return maxnest;\n}\n"
  },
  {
    "path": "regex/regerror.c",
    "content": "/*      $OpenBSD: regerror.c,v 1.15 2020/12/30 08:56:38 tb Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)regerror.c  8.4 (Berkeley) 3/20/94\n */\n\n#include <sys/types.h>\n#include <stdio.h>\n#include <bsd_string.h>\n#include <ctype.h>\n#include <limits.h>\n#include <bsd_stdlib.h>\n#include <bsd_regex.h>\n#include <bsd_unistd.h>\n\n#include \"utils.h\"\n\nstatic const char *regatoi(const regex_t *, char *, int);\n\nstatic const struct rerr\n{\n  int code;\n  const char *name;\n  const char *explain;\n} rerrs[]\n  = { { REG_NOMATCH,  \"REG_NOMATCH\",  \"regexec() failed to match\"           },\n      { REG_BADPAT,   \"REG_BADPAT\",   \"invalid regular expression\"          },\n      { REG_ECOLLATE, \"REG_ECOLLATE\", \"invalid collating element\"           },\n      { REG_ECTYPE,   \"REG_ECTYPE\",   \"invalid character class\"             },\n      { REG_EESCAPE,  \"REG_EESCAPE\",  \"trailing backslash (\\\\)\"             },\n      { REG_ESUBREG,  \"REG_ESUBREG\",  \"invalid backreference number\"        },\n      { REG_EBRACK,   \"REG_EBRACK\",   \"brackets ([ ]) not balanced\"         },\n      { REG_EPAREN,   \"REG_EPAREN\",   \"parentheses not balanced\"            },\n      { REG_EBRACE,   \"REG_EBRACE\",   \"braces not balanced\"                 },\n      { REG_BADBR,    \"REG_BADBR\",    \"invalid repetition count(s)\"         },\n      { REG_ERANGE,   \"REG_ERANGE\",   \"invalid character range\"             },\n      { REG_ESPACE,   \"REG_ESPACE\",   \"out of memory\"                       },\n      { REG_BADRPT,   \"REG_BADRPT\",   \"repetition-operator operand invalid\" },\n      { REG_EMPTY,    \"REG_EMPTY\",    \"empty (sub)expression\"               },\n      { REG_ASSERT,   \"REG_ASSERT\",   \"\\\"can't happen\\\" -- you found a bug\" },\n      { REG_INVARG,   \"REG_INVARG\",   \"invalid argument to regex routine\"   },\n      { 0,            \"\",             \"*** unknown regexp error code ***\"   } };\n\n/*\n * - regerror - the interface to error numbers\n * = extern size_t regerror(int, const regex_t *, char *, size_t);\n */\nsize_t\nregerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)\n{\n  const struct rerr *r;\n  size_t len;\n  int target = errcode & ~REG_ITOA;\n  const char *s;\n  char convbuf[50];\n\n  if (errcode == REG_ATOI)\n    {\n      s = regatoi(preg, convbuf, sizeof convbuf);\n    }\n  else\n    {\n      for (r = rerrs; r->code != 0; r++)\n        {\n          if (r->code == target)\n            {\n              break;\n            }\n        }\n\n      if (errcode & REG_ITOA)\n        {\n          if (r->code != 0)\n            {\n              assert(strlen(r->name) < sizeof ( convbuf ));\n              (void)openbsd_strlcpy(convbuf, r->name, sizeof convbuf);\n            }\n          else\n            {\n              (void)snprintf(convbuf, sizeof convbuf, \"REG_0x%x\", target);\n            }\n\n          s = convbuf;\n        }\n      else\n        {\n          s = r->explain;\n        }\n    }\n\n  if (errbuf_size != 0)\n    {\n      len = openbsd_strlcpy(errbuf, s, errbuf_size);\n    }\n  else\n    {\n      len = strlen(s);\n    }\n\n  return len + 1;\n}\n\n/*\n * - regatoi - internal routine to implement REG_ATOI\n */\nstatic const char *\nregatoi(const regex_t *preg, char *localbuf, int localbufsize)\n{\n  const struct rerr *r;\n\n  for (r = rerrs; r->code != 0; r++)\n    {\n      if (strcmp(r->name, preg->re_endp) == 0)\n        {\n          break;\n        }\n    }\n\n  if (r->code == 0)\n    {\n      return \"0\";\n    }\n\n  (void)snprintf(localbuf, localbufsize, \"%d\", r->code);\n  return localbuf;\n}\n"
  },
  {
    "path": "regex/regexec.c",
    "content": "/*      $OpenBSD: regexec.c,v 1.14 2018/07/11 12:38:46 martijn Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)regexec.c   8.3 (Berkeley) 3/20/94\n */\n\n/*\n * the outer shell of regexec()\n *\n * This file includes engine.c *twice*, after muchos fiddling with the\n * macros that code uses.  This lets the same code operate on two different\n * representations for state sets.\n */\n\n#include <sys/types.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <limits.h>\n#include <ctype.h>\n#include <bsd_regex.h>\n\n#include \"utils.h\"\n#include \"bsd_regex2.h\"\n\n/* macros for manipulating states, small version */\n#define states long\n#define states1 long /* for later use in regexec() decision */\n#define CLEAR(v) (( v ) = 0 )\n#define SET0(v, n) (( v ) &= ~((unsigned long)1 << ( n )))\n#define SET1(v, n) (( v ) |= (unsigned long)1 << ( n ))\n#define ISSET(v, n) ((( v ) & ((unsigned long)1 << ( n ))) != 0 )\n#define ASSIGN(d, s) (( d ) = ( s ))\n#define EQ(a, b) (( a ) == ( b ))\n#define STATEVARS long dummy /* dummy version */\n#define STATESETUP(m, n)     /* nothing */\n#define STATETEARDOWN(m)     /* nothing */\n#define SETUP(v) (( v ) = 0 )\n#define onestate long\n#define INIT(o, n) (( o ) = (unsigned long)1 << ( n ))\n#define INC(o) (( o ) <<= 1 )\n#define ISSTATEIN(v, o) ((( v ) & ( o )) != 0 )\n/* some abbreviations; note that some of these know variable names! */\n/* do \"if I'm here, I can also be there\" etc without branches */\n#define FWD(dst, src, n) (( dst ) |= ((unsigned long)( src ) & ( here )) << ( n ))\n#define BACK(dst, src, n) (( dst ) |= ((unsigned long)( src ) & ( here )) >> ( n ))\n#define ISSETBACK(v, n) ((( v ) & ((unsigned long)here >> ( n ))) != 0 )\n/* function names */\n#define SNAMES /* engine.c looks after details */\n\n#include \"engine.c\"\n\n/* now undo things */\n#undef states\n#undef CLEAR\n#undef SET0\n#undef SET1\n#undef ISSET\n#undef ASSIGN\n#undef EQ\n#undef STATEVARS\n#undef STATESETUP\n#undef STATETEARDOWN\n#undef SETUP\n#undef onestate\n#undef INIT\n#undef INC\n#undef ISSTATEIN\n#undef FWD\n#undef BACK\n#undef ISSETBACK\n#undef SNAMES\n\n/* macros for manipulating states, large version */\n#define states char *\n#define CLEAR(v) memset(v, 0, m->g->nstates)\n#define SET0(v, n) (( v )[n] = 0 )\n#define SET1(v, n) (( v )[n] = 1 )\n#define ISSET(v, n) (( v )[n] )\n#define ASSIGN(d, s) memcpy(d, s, m->g->nstates)\n#define EQ(a, b) ( memcmp(a, b, m->g->nstates) == 0 )\n\n#define STATEVARS                                                             \\\n  long vn;                                                                    \\\n  char *space\n\n#define STATESETUP(m, nv)                                                     \\\n  {                                                                           \\\n    ( m )->space = openbsd_reallocarray(                                      \\\n      NULL, ( m )->g->nstates, ( nv ) );                                      \\\n    if (( m )->space == NULL)                                                 \\\n    return REG_ESPACE;                                                        \\\n    ( m )->vn = 0;                                                            \\\n  }\n\n#define STATETEARDOWN(m)                                                      \\\n  {                                                                           \\\n    free(( m )->space);                                                       \\\n  }\n\n#define SETUP(v) (( v ) = &m->space[m->vn++ *m->g->nstates] )\n#define onestate long\n#define INIT(o, n) (( o ) = ( n ))\n#define INC(o) (( o )++ )\n#define ISSTATEIN(v, o) (( v )[o] )\n/* some abbreviations; note that some of these know variable names! */\n/* do \"if I'm here, I can also be there\" etc without branches */\n#define FWD(dst, src, n) (( dst )[here + ( n )] |= ( src )[here] )\n#define BACK(dst, src, n) (( dst )[here - ( n )] |= ( src )[here] )\n#define ISSETBACK(v, n) (( v )[here - ( n )] )\n/* function names */\n#define LNAMES /* flag */\n\n#include \"engine.c\"\n\n/*\n * - regexec - interface for matching\n *\n * We put this here so we can exploit knowledge of the state representation\n * when choosing which matcher to call.  Also, by this point the matchers\n * have been prototyped.\n */\nint /* 0 success, REG_NOMATCH failure */\nregexec(const regex_t *preg, const char *string, size_t nmatch,\n        regmatch_t pmatch[], int eflags)\n{\n  struct re_guts *g = preg->re_g;\n\n#ifdef REDEBUG\n# define GOODFLAGS(f) ( f )\n#else  /* ifdef REDEBUG */\n# define GOODFLAGS(f) (( f ) & ( REG_NOTBOL | REG_NOTEOL | REG_STARTEND ))\n#endif /* ifdef REDEBUG */\n\n  if (preg->re_magic != MAGIC1 || g->magic != MAGIC2)\n    {\n      return REG_BADPAT;\n    }\n\n  assert(!( g->iflags & BAD ));\n  if (g->iflags & BAD) /* backstop for no-debug case */\n    {\n      return REG_BADPAT;\n    }\n\n  eflags = GOODFLAGS(eflags);\n\n  if (g->nstates <= CHAR_BIT * sizeof ( states1 ) && !( eflags & REG_LARGE ))\n    {\n      return smatcher(g, string, nmatch, pmatch, eflags);\n    }\n  else\n    {\n      return lmatcher(g, string, nmatch, pmatch, eflags);\n    }\n}\n"
  },
  {
    "path": "regex/regfree.c",
    "content": "/*      $OpenBSD: regfree.c,v 1.11 2015/12/28 22:27:03 mmcc Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)regfree.c   8.3 (Berkeley) 3/20/94\n */\n\n#include <sys/types.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_regex.h>\n#include <limits.h>\n\n#include \"utils.h\"\n#include \"bsd_regex2.h\"\n\n/*\n - regfree - free everything\n */\nvoid\nregfree(regex_t *preg)\n{\n        struct re_guts *g;\n\n        if (preg->re_magic != MAGIC1)           /* oops */\n                return;                         /* nice to complain, but hard */\n\n        g = preg->re_g;\n        if (g == NULL || g->magic != MAGIC2)    /* oops again */\n                return;\n        preg->re_magic = 0;                     /* mark it invalid */\n        g->magic = 0;                           /* mark it invalid */\n\n        free(g->strip);\n        free(g->sets);\n        free(g->setbits);\n        free(g->must);\n        free(g);\n}\n"
  },
  {
    "path": "regex/utils.h",
    "content": "/*      $OpenBSD: utils.h,v 1.4 2003/06/02 20:18:36 millert Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994 Henry Spencer.\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Henry Spencer.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *      @(#)utils.h     8.3 (Berkeley) 3/20/94\n */\n\n/* utility definitions */\n\n#if defined(__managarm__)\n# if !defined(_POSIX2_RE_DUP_MAX)\n#  define _POSIX2_RE_DUP_MAX 255\n# endif\n#endif\n\n#define DUPMAX          _POSIX2_RE_DUP_MAX\n#define INFINITY        (DUPMAX + 1)\n#define NC              (CHAR_MAX - CHAR_MIN + 1)\ntypedef unsigned char   uch;\n\n/* switch off assertions (if not already off) if no REDEBUG */\n#ifndef REDEBUG\n# ifndef NDEBUG\n#  define NDEBUG        /* no assertions please */\n# endif /* ifndef NDEBUG */\n#endif /* ifndef REDEBUG */\n#include <assert.h>\n"
  },
  {
    "path": "scripts/virecover",
    "content": "#!/usr/bin/env perl\n\n# $OpenBSD: recover,v 1.13 2018/09/17 15:41:17 millert Exp $\n# SPDX-License-Identifier: BSD-2-Clause\n# Copyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc.\n# Copyright (c) 2021-2024 Jeffrey H. Johnson\n# Script to (safely) recover OpenBSD vi edit sessions.\n\nuse warnings;\nuse strict;\nuse Fcntl;\n\nmy $recoverdir = $ARGV[0] || \"/var/tmp/vi.recover\";\nmy $sendmail = \"/usr/sbin/sendmail\";\n\ndie \"Sorry, $0 must be run as root\\n\" if $>;\n\n# Make the recovery dir if it does not already exist.\nif (!sysopen(DIR, $recoverdir, O_RDONLY|O_NOFOLLOW) || !stat(DIR)) {\n        die \"Warning! $recoverdir is a symbolic link! (ignoring)\\n\"\n            if -l $recoverdir;\n        mkdir($recoverdir, 01777) || die \"Unable to create $recoverdir: $!\\n\";\n        chmod(01777, $recoverdir);\n        exit(0);\n}\n\n#\n# Sanity check the vi recovery dir\n#\ndie \"Warning! $recoverdir is not a directory! (ignoring)\\n\"\n    unless -d _;\ndie \"$0: can't chdir to $recoverdir: $!\\n\" unless chdir DIR;\nif (! -O _) {\n        warn \"Warning! $recoverdir is not owned by root! (fixing)\\n\";\n        chown(0, 0, \".\");\n}\nif (((stat(_))[2] & 07777) != 01777) {\n        warn \"Warning! $recoverdir is not mode 01777! (fixing)\\n\";\n        chmod(01777, \".\");\n}\n\n# Check editor backup files.\nopendir(RECDIR, \".\") || die \"$0: can't open $recoverdir: $!\\n\";\nforeach my $file (readdir(RECDIR)) {\n        next unless $file =~ /^vi\\./;\n\n        #\n        # Unmodified vi editor backup files either have the\n        # execute bit set or are zero length.  Delete them.\n        # Anything that is not a normal file gets deleted too.\n        #\n        lstat($file) || die \"$0: can't stat $file: $!\\n\";\n        if (-x _ || ! -s _ || ! -f _) {\n                unlink($file) unless -d _;\n        }\n}\n\n#\n# It is possible to get incomplete recovery files if the editor crashes\n# at the right time.\n#\nrewinddir(RECDIR);\nforeach my $file (readdir(RECDIR)) {\n        next unless $file =~ /^recover\\./;\n\n        if (!sysopen(RECFILE, $file, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) {\n            warn \"$0: can't open $file: $!\\n\";\n            next;\n        }\n\n        #\n        # Delete anything that is not a regular file as that is either\n        # filesystem corruption from fsck or an exploit attempt.\n        # Real vi recovery files are created with mode 0600, ignore others.\n        #\n        if (!stat(RECFILE)) {\n                warn \"$0: can't stat $file: $!\\n\";\n                close(RECFILE);\n                next;\n        }\n        if (((stat(_))[2] & 07777) != 0600) {\n                close(RECFILE);\n                next;\n        }\n        my $owner = (stat(_))[4];\n        if (! -f _ || ! -s _) {\n                unlink($file) unless -d _;\n                close(RECFILE);\n                next;\n        }\n\n        #\n        # Slurp in the recover.* file and search for X-vi-recover-path\n        # (which should point to an existing vi.* file).\n        #\n        my @recfile = <RECFILE>;\n        close(RECFILE);\n\n        #\n        # Delete any recovery files that have no (or more than one)\n        # corresponding backup file.\n        #\n        my @backups = grep(m#^X-vi-recover-path:\\s*\\Q$recoverdir\\E/+#, @recfile);\n        if (@backups != 1) {\n                unlink($file);\n                next;\n        }\n\n        #\n        # Make a copy of the backup file path.\n        # We must not modify @backups directly since it contains\n        # references to data in @recfile which we pipe to sendmail.\n        #\n        $backups[0] =~ m#^X-vi-recover-path:\\s*\\Q$recoverdir\\E/+(.*)[\\r\\n]*$#;\n        my $backup = $1;\n\n        #\n        # If backup file is not rooted in the recover dir, ignore it.\n        # If backup file owner doesn't match recovery file owner, ignore it.\n        # If backup file is zero length or not a regular file, remove it.\n        # Else send mail to the user.\n        #\n        if ($backup =~ m#/# || !lstat($backup)) {\n                unlink($file);\n        } elsif ($owner != 0 && (stat(_))[4] != $owner) {\n                unlink($file);\n        } elsif (! -f _ || ! -s _) {\n                unlink($file, $backup);\n        } else {\n                open(SENDMAIL, \"|$sendmail -t\") ||\n                    die \"$0: can't run $sendmail -t: $!\\n\";\n                print SENDMAIL @recfile;\n                close(SENDMAIL);\n        }\n}\nclosedir(RECDIR);\nclose(DIR);\n\nexit(0);\n"
  },
  {
    "path": "scripts/virecover.8",
    "content": ".\\\" $NetBSD: virecover.8,v 1.1 2013/11/22 16:00:45 christos Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-2-Clause\n.\\\"\n.\\\" Copyright (c) 2006 The NetBSD Foundation, Inc.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" This code is derived from software contributed to The NetBSD Foundation\n.\\\" by Jeremy C. Reed.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS\n.\\\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n.\\\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n.\\\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS\n.\\\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n.\\\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n.\\\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n.\\\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n.\\\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n.\\\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n.\\\" POSSIBILITY OF SUCH DAMAGE.\n.\\\"\n.Dd October 9, 2006\n.Dt VIRECOVER 8\n.Os\n.Sh NAME\n.Nm vi.recover\n.Nd report recovered nvi edit sessions\n.Sh SYNOPSIS\n.Pa /usr/libexec/vi.recover\n.Sh DESCRIPTION\nThe\n.Nm\nutility sends emails to users who have\n.Xr nvi 1\nrecovery files.\n.Pp\nThis email gives the name of the file that was\nsaved for recovery and instructions for recovering\nmost, if not all, of the changes to the file.\nThis is done by using the\n.Fl r\noption with\n.Xr nvi 1 .\nSee the\n.Fl r\noption in\n.Xr nvi 1\nfor details.\n.Pp\nIf the backup files have the execute bit set or are zero length,\nthen they have not been modified, so\n.Nm\ndeletes them to clean up.\n.Nm\nalso removes recovery files that are corrupted, zero length,\nor do not have a corresponding backup file.\n.Pp\n.Nm\nis normally run automatically at boot time.\n.Sh FILES\n.Bl -tag -width \"/var/tmp/vi.recover/recover.*\" -compact\n.It Pa /var/tmp/vi.recover/recover.*\n.Xr nvi 1\nrecovery files\n.It Pa /var/tmp/vi.recover/vi.*\n.Xr nvi 1\neditor backup files\n.El\n.Sh SEE ALSO\n.Xr nvi 1\n.Sh AUTHORS\nThis man page was originally written by\n.An Jeremy C. Reed Aq Mt reed@reedmedia.net .\n"
  },
  {
    "path": "vi/getc.c",
    "content": "/*      $OpenBSD: getc.c,v 1.10 2016/01/06 22:28:52 millert Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * Character stream routines --\n *      These routines return the file a character at a time.  There are two\n *      special cases.  First, the end of a line, end of a file, start of a\n *      file and empty lines are returned as special cases, and no character\n *      is returned.  Second, empty lines include lines that have only white\n *      space in them, because the vi search functions don't care about white\n *      space, and this makes it easier for them to be consistent.\n */\n\n/*\n * cs_init --\n *      Initialize character stream routines.\n *\n * PUBLIC: int cs_init(SCR *, VCS *);\n */\nint\ncs_init(SCR *sp, VCS *csp)\n{\n        int isempty;\n\n        if (db_eget(sp, csp->cs_lno, (char **) &csp->cs_bp, &csp->cs_len,\n            &isempty)) {\n                if (isempty)\n                        msgq(sp, M_BERR, \"Empty file\");\n                return (1);\n        }\n        if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) {\n                csp->cs_cno = 0;\n                csp->cs_flags = CS_EMP;\n        } else {\n                csp->cs_flags = 0;\n                csp->cs_ch = csp->cs_bp[csp->cs_cno];\n        }\n        return (0);\n}\n\n/*\n * cs_next --\n *      Retrieve the next character.\n *\n * PUBLIC: int cs_next(SCR *, VCS *);\n */\nint\ncs_next(SCR *sp, VCS *csp)\n{\n        char *p;\n\n        switch (csp->cs_flags) {\n        case CS_EMP:                            /* EMP; get next line. */\n        case CS_EOL:                            /* EOL; get next line. */\n                if (db_get(sp, ++csp->cs_lno, 0, &p, &csp->cs_len)) {\n                        --csp->cs_lno;\n                        csp->cs_flags = CS_EOF;\n                } else {\n                        csp->cs_bp = p;\n                        if (csp->cs_len == 0 ||\n                            v_isempty(csp->cs_bp, csp->cs_len)) {\n                                csp->cs_cno = 0;\n                                csp->cs_flags = CS_EMP;\n                        } else {\n                                csp->cs_flags = 0;\n                                csp->cs_ch = csp->cs_bp[csp->cs_cno = 0];\n                        }\n                }\n                break;\n        case 0:\n                if (csp->cs_cno == csp->cs_len - 1)\n                        csp->cs_flags = CS_EOL;\n                else\n                        csp->cs_ch = csp->cs_bp[++csp->cs_cno];\n                break;\n        case CS_EOF:                            /* EOF. */\n                break;\n        default:\n                abort();\n                /* NOTREACHED */\n        }\n        return (0);\n}\n\n/*\n * cs_fspace --\n *      If on a space, eat forward until something other than a\n *      whitespace character.\n *\n * XXX\n * Semantics of checking the current character were coded for the fword()\n * function -- once the other word routines are converted, they may have\n * to change.\n *\n * PUBLIC: int cs_fspace(SCR *, VCS *);\n */\nint\ncs_fspace(SCR *sp, VCS *csp)\n{\n        if (csp->cs_flags != 0 || !isblank(csp->cs_ch))\n                return (0);\n        for (;;) {\n                if (cs_next(sp, csp))\n                        return (1);\n                if (csp->cs_flags != 0 || !isblank(csp->cs_ch))\n                        break;\n        }\n        return (0);\n}\n\n/*\n * cs_fblank --\n *      Eat forward to the next non-whitespace character.\n *\n * PUBLIC: int cs_fblank(SCR *, VCS *);\n */\nint\ncs_fblank(SCR *sp, VCS *csp)\n{\n        for (;;) {\n                if (cs_next(sp, csp))\n                        return (1);\n                if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP ||\n                    (csp->cs_flags == 0 && isblank(csp->cs_ch)))\n                        continue;\n                break;\n        }\n        return (0);\n}\n\n/*\n * cs_prev --\n *      Retrieve the previous character.\n *\n * PUBLIC: int cs_prev(SCR *, VCS *);\n */\nint\ncs_prev(SCR *sp, VCS *csp)\n{\n        switch (csp->cs_flags) {\n        case CS_EMP:                            /* EMP; get previous line. */\n        case CS_EOL:                            /* EOL; get previous line. */\n                if (csp->cs_lno == 1) {         /* SOF. */\n                        csp->cs_flags = CS_SOF;\n                        break;\n                }\n                if (db_get(sp,                  /* The line should exist. */\n                    --csp->cs_lno, DBG_FATAL, (char **) &csp->cs_bp,\n                    &csp->cs_len)) {\n                        ++csp->cs_lno;\n                        return (1);\n                }\n                if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) {\n                        csp->cs_cno = 0;\n                        csp->cs_flags = CS_EMP;\n                } else {\n                        csp->cs_flags = 0;\n                        csp->cs_cno = csp->cs_len - 1;\n                        csp->cs_ch = csp->cs_bp[csp->cs_cno];\n                }\n                break;\n        case CS_EOF:                            /* EOF: get previous char. */\n        case 0:\n                if (csp->cs_cno == 0)\n                        if (csp->cs_lno == 1)\n                                csp->cs_flags = CS_SOF;\n                        else\n                                csp->cs_flags = CS_EOL;\n                else\n                        csp->cs_ch = csp->cs_bp[--csp->cs_cno];\n                break;\n        case CS_SOF:                            /* SOF. */\n                break;\n        default:\n                abort();\n                /* NOTREACHED */\n        }\n        return (0);\n}\n\n/*\n * cs_bblank --\n *      Eat backward to the next non-whitespace character.\n *\n * PUBLIC: int cs_bblank(SCR *, VCS *);\n */\nint\ncs_bblank(SCR *sp, VCS *csp)\n{\n        for (;;) {\n                if (cs_prev(sp, csp))\n                        return (1);\n                if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP ||\n                    (csp->cs_flags == 0 && isblank(csp->cs_ch)))\n                        continue;\n                break;\n        }\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_at.c",
    "content": "/*      $OpenBSD: v_at.c,v 1.11 2016/05/27 09:18:12 martijn Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_at -- @\n *      Execute a buffer.\n *\n * PUBLIC: int v_at(SCR *, VICMD *);\n */\nint\nv_at(SCR *sp, VICMD *vp)\n{\n        CB *cbp;\n        CHAR_T name;\n        TEXT *tp;\n        size_t len;\n        char nbuf[20];\n\n        /*\n         * !!!\n         * Historically, [@*]<carriage-return> and [@*][@*] executed the most\n         * recently executed buffer in ex mode.  In vi mode, only @@ repeated\n         * the last buffer.  We change historic practice and make @* work from\n         * vi mode as well, it's simpler and more consistent.\n         *\n         * My intent is that *[buffer] will, in the future, pass the buffer to\n         * whatever interpreter is loaded.\n         */\n        name = F_ISSET(vp, VC_BUFFER) ? vp->buffer : '@';\n        if (name == '@' || name == '*') {\n                if (!F_ISSET(sp, SC_AT_SET)) {\n                        ex_emsg(sp, NULL, EXM_NOPREVBUF);\n                        return (1);\n                }\n                name = sp->at_lbuf;\n        }\n        F_SET(sp, SC_AT_SET);\n\n        CBNAME(sp, cbp, name);\n        if (cbp == NULL) {\n                ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF);\n                return (1);\n        }\n\n        /* Save for reuse. */\n        sp->at_lbuf = name;\n\n        /*\n         * The buffer is executed in vi mode, while in vi mode, so simply\n         * push it onto the terminal queue and continue.\n         *\n         * !!!\n         * Historic practice is that if the buffer was cut in line mode,\n         * <newlines> were appended to each line as it was pushed onto\n         * the stack.  If the buffer was cut in character mode, <newlines>\n         * were appended to all lines but the last one.\n         *\n         * XXX\n         * Historic practice is that execution of an @ buffer could be\n         * undone by a single 'u' command, i.e. the changes were grouped\n         * together.  We don't get this right; I'm waiting for the new DB\n         * logging code to be available.\n         */\n        TAILQ_FOREACH_REVERSE(tp, &cbp->textq, _texth, q)\n                if (((F_ISSET(cbp, CB_LMODE) || TAILQ_NEXT(tp, q)) &&\n                    v_event_push(sp, NULL, \"\\n\", 1, 0)) ||\n                    v_event_push(sp, NULL, tp->lb, tp->len, 0))\n                        return (1);\n\n        /*\n         * !!!\n         * If any count was supplied, it applies to the first command in the\n         * at buffer.\n         */\n        if (F_ISSET(vp, VC_C1SET)) {\n                len = snprintf(nbuf, sizeof(nbuf), \"%lu\", vp->count);\n                if (len >= sizeof(nbuf))\n                        len = sizeof(nbuf) - 1;\n                if (v_event_push(sp, NULL, nbuf, len, 0))\n                        return (1);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_ch.c",
    "content": "/*      $OpenBSD: v_ch.c,v 1.10 2016/05/27 09:18:12 martijn Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic void notfound(SCR *, CHAR_T);\nstatic void noprev(SCR *);\n\n/*\n * v_chrepeat -- [count];\n *      Repeat the last F, f, T or t search.\n *\n * PUBLIC: int v_chrepeat(SCR *, VICMD *);\n */\nint\nv_chrepeat(SCR *sp, VICMD *vp)\n{\n        vp->character = VIP(sp)->lastckey;\n\n        switch (VIP(sp)->csearchdir) {\n        case CNOTSET:\n                noprev(sp);\n                return (1);\n        case FSEARCH:\n                return (v_chF(sp, vp));\n        case fSEARCH:\n                return (v_chf(sp, vp));\n        case TSEARCH:\n                return (v_chT(sp, vp));\n        case tSEARCH:\n                return (v_cht(sp, vp));\n        default:\n                abort();\n        }\n        /* NOTREACHED */\n}\n\n/*\n * v_chrrepeat -- [count],\n *      Repeat the last F, f, T or t search in the reverse direction.\n *\n * PUBLIC: int v_chrrepeat(SCR *, VICMD *);\n */\nint\nv_chrrepeat(SCR *sp, VICMD *vp)\n{\n        cdir_t savedir;\n        int rval;\n\n        vp->character = VIP(sp)->lastckey;\n        savedir = VIP(sp)->csearchdir;\n\n        switch (VIP(sp)->csearchdir) {\n        case CNOTSET:\n                noprev(sp);\n                return (1);\n        case FSEARCH:\n                rval = v_chf(sp, vp);\n                break;\n        case fSEARCH:\n                rval = v_chF(sp, vp);\n                break;\n        case TSEARCH:\n                rval = v_cht(sp, vp);\n                break;\n        case tSEARCH:\n                rval = v_chT(sp, vp);\n                break;\n        default:\n                abort();\n        }\n        VIP(sp)->csearchdir = savedir;\n        return (rval);\n}\n\n/*\n * v_cht -- [count]tc\n *      Search forward in the line for the character before the next\n *      occurrence of the specified character.\n *\n * PUBLIC: int v_cht(SCR *, VICMD *);\n */\nint\nv_cht(SCR *sp, VICMD *vp)\n{\n        if (v_chf(sp, vp))\n                return (1);\n\n        /*\n         * v_chf places the cursor on the character, where the 't'\n         * command wants it to its left.  We know this is safe since\n         * we had to move right for v_chf() to have succeeded.\n         */\n        --vp->m_stop.cno;\n\n        /*\n         * Make any necessary correction to the motion decision made\n         * by the v_chf routine.\n         */\n        if (!ISMOTION(vp))\n                vp->m_final = vp->m_stop;\n\n        VIP(sp)->csearchdir = tSEARCH;\n        return (0);\n}\n\n/*\n * v_chf -- [count]fc\n *      Search forward in the line for the next occurrence of the\n *      specified character.\n *\n * PUBLIC: int v_chf(SCR *, VICMD *);\n */\nint\nv_chf(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        unsigned long cnt;\n        int isempty, key;\n        char *endp, *p, *startp;\n\n        /*\n         * !!!\n         * If it's a dot command, it doesn't reset the key for which we're\n         * searching, e.g. in \"df1|f2|.|;\", the ';' searches for a '2'.\n         */\n        key = vp->character;\n        if (!F_ISSET(vp, VC_ISDOT))\n                VIP(sp)->lastckey = key;\n        VIP(sp)->csearchdir = fSEARCH;\n\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (isempty)\n                        goto empty;\n                return (1);\n        }\n\n        if (len == 0) {\nempty:          notfound(sp, key);\n                return (1);\n        }\n\n        endp = (startp = p) + len;\n        p += vp->m_start.cno;\n        for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {\n                while (++p < endp && *p != (char) key);\n                if (p == endp) {\n                        notfound(sp, key);\n                        return (1);\n                }\n        }\n\n        vp->m_stop.cno = p - startp;\n\n        /*\n         * Non-motion commands move to the end of the range.\n         * Delete and yank stay at the start, ignore others.\n         */\n        vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        return (0);\n}\n\n/*\n * v_chT -- [count]Tc\n *      Search backward in the line for the character after the next\n *      occurrence of the specified character.\n *\n * PUBLIC: int v_chT(SCR *, VICMD *);\n */\nint\nv_chT(SCR *sp, VICMD *vp)\n{\n        if (v_chF(sp, vp))\n                return (1);\n\n        /*\n         * v_chF places the cursor on the character, where the 'T'\n         * command wants it to its right.  We know this is safe since\n         * we had to move left for v_chF() to have succeeded.\n         */\n        ++vp->m_stop.cno;\n        vp->m_final = vp->m_stop;\n\n        VIP(sp)->csearchdir = TSEARCH;\n        return (0);\n}\n\n/*\n * v_chF -- [count]Fc\n *      Search backward in the line for the next occurrence of the\n *      specified character.\n *\n * PUBLIC: int v_chF(SCR *, VICMD *);\n */\nint\nv_chF(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        unsigned long cnt;\n        int isempty, key;\n        char *endp, *p;\n\n        /*\n         * !!!\n         * If it's a dot command, it doesn't reset the key for which\n         * we're searching, e.g. in \"df1|f2|.|;\", the ';' searches\n         * for a '2'.\n         */\n        key = vp->character;\n        if (!F_ISSET(vp, VC_ISDOT))\n                VIP(sp)->lastckey = key;\n        VIP(sp)->csearchdir = FSEARCH;\n\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (isempty)\n                        goto empty;\n                return (1);\n        }\n\n        if (len == 0) {\nempty:          notfound(sp, key);\n                return (1);\n        }\n\n        endp = p - 1;\n        p += vp->m_start.cno;\n        for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {\n                while (--p > endp && *p != (char) key);\n                if (p == endp) {\n                        notfound(sp, key);\n                        return (1);\n                }\n        }\n\n        vp->m_stop.cno = (p - endp) - 1;\n\n        /*\n         * All commands move to the end of the range.  Motion commands\n         * adjust the starting point to the character before the current\n         * one.\n         */\n        vp->m_final = vp->m_stop;\n        if (ISMOTION(vp))\n                --vp->m_start.cno;\n        return (0);\n}\n\nstatic void\nnoprev(SCR *sp)\n{\n        msgq(sp, M_BERR, \"No previous F, f, T or t search\");\n}\n\nstatic void\nnotfound(SCR *sp, CHAR_T ch)\n{\n        msgq(sp, M_BERR, \"%s not found\", KEY_NAME(sp, ch));\n}\n"
  },
  {
    "path": "vi/v_cmd.c",
    "content": "/*      $OpenBSD: v_cmd.c,v 1.5 2016/03/13 18:30:43 martijn Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * This array maps keystrokes to vi command functions.  It is known\n * in ex/ex_usage.c that it takes four columns to name a vi character.\n */\n\nVIKEYS const vikeys [MAXVIKEY + 1] = {\n/* 000 NUL -- The code in vi.c expects key 0 to be undefined. */\n        {NULL, 0, NULL, NULL},\n/* 001  ^A */\n        {v_searchw,     V_ABS|V_CNT|V_MOVE|V_KEYW|VM_CUTREQ|VM_RCM_SET,\n            \"[count]^A\",\n            \"^A search forward for cursor word\"},\n/* 002  ^B */\n        {v_pageup,      V_CNT|VM_RCM_SET,\n            \"[count]^B\",\n            \"^B scroll up by screens\"},\n/* 003  ^C */\n        {NULL,          0,\n            \"^C\",\n            \"^C interrupt an operation (e.g. read, write, search)\"},\n/* 004  ^D */\n        {v_hpagedown,   V_CNT|VM_RCM_SET,\n            \"[count]^D\",\n            \"^D scroll down by half screens (setting count)\"},\n/* 005  ^E */\n        {v_linedown,    V_CNT,\n            \"[count]^E\",\n            \"^E scroll down by lines\"},\n/* 006  ^F */\n        {v_pagedown,    V_CNT|VM_RCM_SET,\n            \"[count]^F\",\n            \"^F scroll down by screens\"},\n/* 007  ^G */\n        {v_status,      0,\n            \"^G\",\n            \"^G file status\"},\n/* 010  ^H */\n        {v_left,        V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]^H\",\n            \"^H move left by characters\"},\n/* 011  ^I */\n        {NULL, 0, NULL, NULL},\n/* 012  ^J */\n        {v_down,        V_CNT|V_MOVE|VM_LMODE|VM_RCM,\n            \"[count]^J\",\n            \"^J move down by lines\"},\n/* 013  ^K */\n        {NULL, 0, NULL, NULL},\n/* 014  ^L */\n        {v_redraw,      0,\n            \"^L\",\n            \"^L redraw screen\"},\n/* 015  ^M */\n        {v_cr,          V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,\n            \"[count]^M\",\n            \"^M move down by lines (to first non-blank)\"},\n/* 016  ^N */\n        {v_down,        V_CNT|V_MOVE|VM_LMODE|VM_RCM,\n            \"[count]^N\",\n            \"^N move down by lines\"},\n/* 017  ^O */\n        {NULL, 0, NULL, NULL},\n/* 020  ^P */\n        {v_up,          V_CNT|V_MOVE|VM_LMODE|VM_RCM,\n            \"[count]^P\",\n            \"^P move up by lines\"},\n/* 021  ^Q -- same as ^V if not used for hardware flow control. */\n        {NULL, 0, NULL, NULL},\n/* 022  ^R */\n        {v_redraw,      0,\n            \"^R\",\n            \"^R redraw screen\"},\n/* 023  ^S -- not available, used for hardware flow control. */\n        {NULL, 0, NULL, NULL},\n/* 024  ^T */\n        {v_tagpop,      V_ABS|VM_RCM_SET,\n            \"^T\",\n            \"^T tag pop\"},\n/* 025  ^U */\n        {v_hpageup,     V_CNT|VM_RCM_SET,\n            \"[count]^U\",\n            \"^U half page up (set count)\"},\n/* 026  ^V */\n        {NULL,          0,\n            \"^V\",\n            \"^V input a literal character\"},\n/* 027  ^W */\n        {v_screen,      0,\n            \"^W\",\n            \"^W move to next screen\"},\n/* 030  ^X */\n        {NULL, 0, NULL, NULL},\n/* 031  ^Y */\n        {v_lineup,      V_CNT,\n            \"[count]^Y\",\n            \"^Y page up by lines\"},\n/* 032  ^Z */\n        {v_suspend,     V_SECURE,\n            \"^Z\",\n            \"^Z suspend editor\"},\n/* 033  ^[ */\n        {NULL,          0,\n            \"^[ <escape>\",\n            \"^[ <escape> exit input mode, cancel partial commands\"},\n/* 034  ^\\ */\n        {v_exmode,      0,\n            \"^\\\\\",\n            \"^\\\\ switch to ex mode\"},\n/* 035  ^] */\n        {v_tagpush,     V_ABS|V_KEYW|VM_RCM_SET,\n            \"^]\",\n            \"^] tag push cursor word\"},\n/* 036  ^^ */\n        {v_switch,      0,\n            \"^^\",\n            \"^^ switch to previous file\"},\n/* 037  ^_ */\n        {NULL, 0, NULL, NULL},\n/* 040 ' ' */\n        {v_right,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]' '\",\n            \"   <space> move right by columns\"},\n/* 041   ! */\n        {v_filter,      V_CNT|V_DOT|V_MOTION|V_SECURE|VM_RCM_SET,\n            \"[count]![count]motion command(s)\",\n            \" ! filter through command(s) to motion\"},\n/* 042   \" */\n        {NULL, 0, NULL, NULL},\n/* 043   # */\n        {v_increment,   V_CHAR|V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]# +|-|#\",\n            \" # number increment/decrement\"},\n/* 044   $ */\n        {v_dollar,      V_CNT|V_MOVE|VM_RCM_SETLAST,\n            \" [count]$\",\n            \" $ move to last column\"},\n/* 045   % */\n        {v_match,       V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"%\",\n            \" % move to match\"},\n/* 046   & */\n        {v_again,       0,\n            \"&\",\n            \" & repeat substitution\"},\n/* 047   ' */\n        {v_fmark,       V_ABS_L|V_CHAR|V_MOVE|VM_LMODE|VM_RCM_SET,\n            \"'['a-z]\",\n            \" ' move to mark (to first non-blank)\"},\n/* 050   ( */\n        {v_sentenceb,   V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"[count](\",\n            \" ( move back sentence\"},\n/* 051   ) */\n        {v_sentencef,   V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"[count])\",\n            \" ) move forward sentence\"},\n/* 052   * */\n        {NULL, 0, NULL, NULL},\n/* 053   + */\n        {v_down,        V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,\n            \"[count]+\",\n            \" + move down by lines (to first non-blank)\"},\n/* 054   , */\n        {v_chrrepeat,   V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count],\",\n            \" , reverse last F, f, T or t search\"},\n/* 055   - */\n        {v_up,          V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,\n            \"[count]-\",\n            \" - move up by lines (to first non-blank)\"},\n/* 056   . */\n        {NULL,          0,\n            \".\",\n            \" . repeat the last command\"},\n/* 057   / */\n        {v_searchf,     V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"/RE[/ offset]\",\n            \" / search forward\"},\n/* 060   0 */\n        {v_zero,        V_MOVE|VM_RCM_SET,\n            \"0\",\n            \" 0 move to first character\"},\n/* 061   1 */\n        {NULL, 0, NULL, NULL},\n/* 062   2 */\n        {NULL, 0, NULL, NULL},\n/* 063   3 */\n        {NULL, 0, NULL, NULL},\n/* 064   4 */\n        {NULL, 0, NULL, NULL},\n/* 065   5 */\n        {NULL, 0, NULL, NULL},\n/* 066   6 */\n        {NULL, 0, NULL, NULL},\n/* 067   7 */\n        {NULL, 0, NULL, NULL},\n/* 070   8 */\n        {NULL, 0, NULL, NULL},\n/* 071   9 */\n        {NULL, 0, NULL, NULL},\n/* 072   : */\n        {v_ex,          0,\n            \":command [| command] ...\",\n            \" : ex command\"},\n/* 073   ; */\n        {v_chrepeat,    V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count];\",\n            \" ; repeat last F, f, T or t search\"},\n/* 074   < */\n        {v_shiftl,      V_CNT|V_DOT|V_MOTION|VM_RCM_SET,\n            \"[count]<[count]motion\",\n            \" < shift lines left to motion\"},\n/* 075   = */\n        {NULL, 0, NULL, NULL},\n/* 076   > */\n        {v_shiftr,      V_CNT|V_DOT|V_MOTION|VM_RCM_SET,\n            \"[count]>[count]motion\",\n            \" > shift lines right to motion\"},\n/* 077   ? */\n        {v_searchb,     V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"?RE[? offset]\",\n            \" ? search backward\"},\n/* 100   @ */\n        {v_at,          V_CNT|V_RBUF|VM_RCM_SET,\n            \"@buffer\",\n            \" @ execute buffer\"},\n/* 101   A */\n        {v_iA,          V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]A\",\n            \" A append to the line\"},\n/* 102   B */\n        {v_wordB,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]B\",\n            \" B move back bigword\"},\n/* 103   C */\n        {NULL,          0,\n            \"[buffer][count]C\",\n            \" C change to end-of-line\"},\n/* 104   D */\n        {NULL,          0,\n            \"[buffer]D\",\n            \" D delete to end-of-line\"},\n/* 105   E */\n        {v_wordE,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]E\",\n            \" E move to end of bigword\"},\n/* 106   F */\n        {v_chF,         V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]F character\",\n            \" F character in line backward search\"},\n/* 107   G */\n        {v_lgoto,       V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,\n            \"[count]G\",\n            \" G move to line\"},\n/* 110   H */\n        {v_home,        V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB,\n            \"[count]H\",\n            \" H move to count lines from screen top\"},\n/* 111   I */\n        {v_iI,          V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]I\",\n            \" I insert before first nonblank\"},\n/* 112   J */\n        {v_join,        V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]J\",\n            \" J join lines\"},\n/* 113   K */\n        {NULL, 0, NULL, NULL},\n/* 114   L */\n        {v_bottom,      V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB,\n            \"[count]L\",\n            \" L move to screen bottom\"},\n/* 115   M */\n        {v_middle,      V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB,\n            \"M\",\n            \" M move to screen middle\"},\n/* 116   N */\n        {v_searchN,     V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"n\",\n            \" N reverse last search\"},\n/* 117   O */\n        {v_iO,          V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]O\",\n            \" O insert above line\"},\n/* 120   P */\n        {v_Put,         V_CNT|V_DOT|V_OBUF|VM_RCM_SET,\n            \"[buffer]P\",\n            \" P insert before cursor from buffer\"},\n/* 121   Q */\n        {v_exmode,      0,\n            \"Q\",\n            \" Q switch to ex mode\"},\n/* 122   R */\n        {v_Replace,     V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]R\",\n            \" R replace characters\"},\n/* 123   S */\n        {NULL,          0,\n            \"[buffer][count]S\",\n            \" S substitute for the line(s)\"},\n/* 124   T */\n        {v_chT,         V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]T character\",\n            \" T before character in line backward search\"},\n/* 125   U */\n        {v_Undo,        VM_RCM_SET,\n            \"U\",\n            \" U restore the current line\"},\n/* 126   V */\n        {NULL, 0, NULL, NULL},\n/* 127   W */\n        {v_wordW,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]W\",\n            \" W move to next bigword\"},\n/* 130   X */\n        {v_Xchar,       V_CNT|V_DOT|V_OBUF|VM_RCM_SET,\n            \"[buffer][count]X\",\n            \" X delete character before cursor\"},\n/* 131   Y */\n        {NULL,          0,\n            \"[buffer][count]Y\",\n            \" Y copy line\"},\n/* 132   Z */\n        {v_zexit,       0,\n            \"ZZ\",\n            \"ZZ save file and exit\"},\n/* 133   [ */\n        {v_sectionb,    V_ABS|V_CNT|V_MOVE|VM_RCM_SET,\n            \"[[\",\n            \"[[ move back section\"},\n/* 134   \\ */\n        {NULL, 0, NULL, NULL},\n/* 135   ] */\n        {v_sectionf,    V_ABS|V_CNT|V_MOVE|VM_RCM_SET,\n            \"]]\",\n            \"]] move forward section\"},\n/* 136   ^ */\n        /*\n         * DON'T set the VM_RCM_SETFNB flag, the function has to do the work\n         * anyway, in case it's a motion component.  DO set VM_RCM_SET, so\n         * that any motion that's part of a command is preserved.\n         */\n        {v_first,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"^\",\n            \" ^ move to first non-blank\"},\n/* 137   _ */\n        /*\n         * Needs both to set the VM_RCM_SETFNB flag, and to do the work\n         * in the function, in case it's a delete.\n         */\n        {v_cfirst,      V_CNT|V_MOVE|VM_RCM_SETFNB,\n            \"_\",\n            \" _ move to first non-blank\"},\n/* 140   ` */\n        {v_bmark,       V_ABS_C|V_CHAR|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"`[`a-z]\",\n            \" ` move to mark\"},\n/* 141   a */\n        {v_ia,          V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]a\",\n            \" a append after cursor\"},\n/* 142   b */\n        {v_wordb,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]b\",\n            \" b move back word\"},\n/* 143   c */\n        {v_change,      V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET,\n            \"[buffer][count]c[count]motion\",\n            \" c change to motion\"},\n/* 144   d */\n        {v_delete,      V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET,\n            \"[buffer][count]d[count]motion\",\n            \" d delete to motion\"},\n/* 145   e */\n        {v_worde,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]e\",\n            \" e move to end of word\"},\n/* 146   f */\n        {v_chf,         V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]f character\",\n            \" f character in line forward search\"},\n/* 147   g */\n        {NULL, 0, NULL, NULL},\n/* 150   h */\n        {v_left,        V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]h\",\n            \" h move left by columns\"},\n/* 151   i */\n        {v_ii,          V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]i\",\n            \" i insert before cursor\"},\n/* 152   j */\n        {v_down,        V_CNT|V_MOVE|VM_LMODE|VM_RCM,\n            \"[count]j\",\n            \" j move down by lines\"},\n/* 153   k */\n        {v_up,          V_CNT|V_MOVE|VM_LMODE|VM_RCM,\n            \"[count]k\",\n            \" k move up by lines\"},\n/* 154   l */\n        {v_right,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]l\",\n            \" l move right by columns\"},\n/* 155   m */\n        {v_mark,        V_CHAR,\n            \"m[a-z]\",\n            \" m set mark\"},\n/* 156   n */\n        {v_searchn,     V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"n\",\n            \" n repeat last search\"},\n/* 157   o */\n        {v_io,          V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]o\",\n            \" o append after line\"},\n/* 160   p */\n        {v_put,         V_CNT|V_DOT|V_OBUF|VM_RCM_SET,\n            \"[buffer]p\",\n            \" p insert after cursor from buffer\"},\n/* 161   q */\n        {NULL, 0, NULL, NULL},\n/* 162   r */\n        {v_replace,     V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]r character\",\n            \" r replace character\"},\n/* 163   s */\n        {v_subst,       V_CNT|V_DOT|V_OBUF|VM_RCM_SET,\n            \"[buffer][count]s\",\n            \" s substitute character\"},\n/* 164   t */\n        {v_cht,         V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]t character\",\n            \" t before character in line forward search\"},\n/* 165   u */\n        /*\n         * DON'T set the V_DOT flag, it' more complicated than that.\n         * See vi/vi.c for details.\n         */\n        {v_undo,        VM_RCM_SET,\n            \"u\",\n            \" u undo last change\"},\n/* 166   v */\n        {NULL, 0, NULL, NULL},\n/* 167   w */\n        {v_wordw,       V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]w\",\n            \" w move to next word\"},\n/* 170   x */\n        {v_xchar,       V_CNT|V_DOT|V_OBUF|VM_RCM_SET,\n            \"[buffer][count]x\",\n            \" x delete character\"},\n/* 171   y */\n        {v_yank,        V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET,\n            \"[buffer][count]y[count]motion\",\n            \" y copy text to motion into a cut buffer\"},\n/* 172   z */\n        /*\n         * DON'T set the V_CHAR flag, the char isn't required,\n         * so it's handled specially in getcmd().\n         */\n        {v_z,           V_ABS_L|V_CNT|VM_RCM_SETFNB,\n            \"[line]z[window_size][-|.|+|^|<CR>]\",\n            \" z reposition the screen\"},\n/* 173   { */\n        {v_paragraphb,  V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"[count]{\",\n            \" { move back paragraph\"},\n/* 174   | */\n        {v_ncol,        V_CNT|V_MOVE|VM_RCM_SET,\n            \"[count]|\",\n            \" | move to column\"},\n/* 175   } */\n        {v_paragraphf,  V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,\n            \"[count]}\",\n            \" } move forward paragraph\"},\n/* 176   ~ */\n        {v_ulcase,      V_CNT|V_DOT|VM_RCM_SET,\n            \"[count]~\",\n            \" ~ reverse case\"},\n};\n"
  },
  {
    "path": "vi/v_delete.c",
    "content": "/*      $OpenBSD: v_delete.c,v 1.8 2014/11/12 04:28:41 bentley Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_delete -- [buffer][count]d[count]motion\n *             [buffer][count]D\n *      Delete a range of text.\n *\n * PUBLIC: int v_delete(SCR *, VICMD *);\n */\nint\nv_delete(SCR *sp, VICMD *vp)\n{\n        recno_t nlines = 0;\n        size_t len;\n        int lmode;\n\n        lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0;\n\n        /* Yank the lines. */\n        if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_stop,\n            lmode | (F_ISSET(vp, VM_CUTREQ) ? CUT_NUMREQ : CUT_NUMOPT)))\n                return (1);\n\n        /* Delete the lines. */\n        if (del(sp, &vp->m_start, &vp->m_stop, lmode))\n                return (1);\n\n        /*\n         * Check for deletion of the entire file.  Try to check a close\n         * by line so we don't go to the end of the file unnecessarily.\n         */\n        if (!db_exist(sp, vp->m_final.lno + 1)) {\n                if (db_last(sp, &nlines))\n                        return (1);\n                if (nlines == 0) {\n                        vp->m_final.lno = 1;\n                        vp->m_final.cno = 0;\n                        return (0);\n                }\n        }\n\n        /*\n         * One special correction, in case we've deleted the current line or\n         * character.  We check it here instead of checking in every command\n         * that can be a motion component.\n         */\n        if (db_get(sp, vp->m_final.lno, 0, NULL, &len)) {\n                if (db_get(sp, nlines, DBG_FATAL, NULL, &len))\n                        return (1);\n                vp->m_final.lno = nlines;\n        }\n\n        /*\n         * !!!\n         * Cursor movements, other than those caused by a line mode command\n         * moving to another line, historically reset the relative position.\n         *\n         * This currently matches the check made in v_yank(), I'm hoping that\n         * they should be consistent...\n         */\n        if (!F_ISSET(vp, VM_LMODE)) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SET);\n\n                /* Make sure the set cursor position exists. */\n                if (vp->m_final.cno >= len)\n                        vp->m_final.cno = len ? len - 1 : 0;\n        }\n\n        /*\n         * !!!\n         * The \"dd\" command moved to the first non-blank; \"d<motion>\"\n         * didn't.\n         */\n        if (F_ISSET(vp, VM_LDOUBLE)) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SETFNB);\n        }\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_ex.c",
    "content": "/*      $OpenBSD: v_ex.c,v 1.13 2016/01/06 22:28:52 millert Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic int v_ecl(SCR *);\nstatic int v_ecl_init(SCR *);\nstatic int v_ecl_log(SCR *, TEXT *);\nstatic int v_ex_done(SCR *, VICMD *);\nstatic int v_exec_ex(SCR *, VICMD *, EXCMD *);\n\n/*\n * v_again -- &\n *      Repeat the previous substitution.\n *\n * PUBLIC: int v_again(SCR *, VICMD *);\n */\nint\nv_again(SCR *sp, VICMD *vp)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_SUBAGAIN, 2, vp->m_start.lno, vp->m_start.lno, 1, ap);\n        ex_cadd(&cmd, &a, \"\", 1);\n\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_exmode -- Q\n *      Switch the editor into EX mode.\n *\n * PUBLIC: int v_exmode(SCR *, VICMD *);\n */\nint\nv_exmode(SCR *sp, VICMD *vp)\n{\n        GS *gp;\n\n        gp = sp->gp;\n\n        /* Try and switch screens -- the screen may not permit it. */\n        if (gp->scr_screen(sp, SC_EX)) {\n                msgq(sp, M_ERR,\n                    \"The Q command requires the ex terminal interface\");\n                return (1);\n        }\n        (void)gp->scr_attr(sp, SA_ALTERNATE, 0);\n\n        /* Save the current cursor position. */\n        sp->frp->lno = sp->lno;\n        sp->frp->cno = sp->cno;\n        F_SET(sp->frp, FR_CURSORSET);\n\n        /* Switch to ex mode. */\n        F_CLR(sp, SC_VI | SC_SCR_VI);\n        F_SET(sp, SC_EX);\n\n        /* Move out of the vi screen. */\n        (void)ex_puts(sp, \"\\n\");\n\n        return (0);\n}\n\n/*\n * v_join -- [count]J\n *      Join lines together.\n *\n * PUBLIC: int v_join(SCR *, VICMD *);\n */\nint\nv_join(SCR *sp, VICMD *vp)\n{\n        EXCMD cmd;\n        int lno;\n\n        /*\n         * YASC.\n         * The general rule is that '#J' joins # lines, counting the current\n         * line.  However, 'J' and '1J' are the same as '2J', i.e. join the\n         * current and next lines.  This doesn't map well into the ex command\n         * (which takes two line numbers), so we handle it here.  Note that\n         * we never test for EOF -- historically going past the end of file\n         * worked just fine.\n         */\n        lno = vp->m_start.lno + 1;\n        if (F_ISSET(vp, VC_C1SET) && vp->count > 2)\n                lno = vp->m_start.lno + (vp->count - 1);\n\n        ex_cinit(&cmd, C_JOIN, 2, vp->m_start.lno, lno, 0, NULL);\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_shiftl -- [count]<motion\n *      Shift lines left.\n *\n * PUBLIC: int v_shiftl(SCR *, VICMD *);\n */\nint\nv_shiftl(SCR *sp, VICMD *vp)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_SHIFTL, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap);\n        ex_cadd(&cmd, &a, \"<\", 1);\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_shiftr -- [count]>motion\n *      Shift lines right.\n *\n * PUBLIC: int v_shiftr(SCR *, VICMD *);\n */\nint\nv_shiftr(SCR *sp, VICMD *vp)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_SHIFTR, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap);\n        ex_cadd(&cmd, &a, \">\", 1);\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_suspend -- ^Z\n *      Suspend vi.\n *\n * PUBLIC: int v_suspend(SCR *, VICMD *);\n */\nint\nv_suspend(SCR *sp, VICMD *vp)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_STOP, 0, OOBLNO, OOBLNO, 0, ap);\n        ex_cadd(&cmd, &a, \"suspend\", sizeof(\"suspend\") - 1);\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_switch -- ^^\n *      Switch to the previous file.\n *\n * PUBLIC: int v_switch(SCR *, VICMD *);\n */\nint\nv_switch(SCR *sp, VICMD *vp)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n        char *name;\n\n        /*\n         * Try the alternate file name, then the previous file\n         * name.  Use the real name, not the user's current name.\n         */\n        if (sp->alt_name == NULL) {\n                msgq(sp, M_ERR, \"No previous file to edit\");\n                return (1);\n        }\n        if ((name = strdup(sp->alt_name)) == NULL) {\n                msgq(sp, M_SYSERR, NULL);\n                return (1);\n        }\n\n        /* If autowrite is set, write out the file. */\n        if (file_m1(sp, 0, FS_ALL)) {\n                free(name);\n                return (1);\n        }\n\n        ex_cinit(&cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0, ap);\n        ex_cadd(&cmd, &a, name, strlen(name));\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_tagpush -- ^[\n *      Do a tag search on the cursor keyword.\n *\n * PUBLIC: int v_tagpush(SCR *, VICMD *);\n */\nint\nv_tagpush(SCR *sp, VICMD *vp)\n{\n        ARGS *ap[2], a;\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_TAG, 0, OOBLNO, 0, 0, ap);\n        ex_cadd(&cmd, &a, VIP(sp)->keyw, strlen(VIP(sp)->keyw));\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_tagpop -- ^T\n *      Pop the tags stack.\n *\n * PUBLIC: int v_tagpop(SCR *, VICMD *);\n */\nint\nv_tagpop(SCR *sp, VICMD *vp)\n{\n        EXCMD cmd;\n\n        ex_cinit(&cmd, C_TAGPOP, 0, OOBLNO, 0, 0, NULL);\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_filter -- [count]!motion command(s)\n *      Run range through shell commands, replacing text.\n *\n * PUBLIC: int v_filter(SCR *, VICMD *);\n */\nint\nv_filter(SCR *sp, VICMD *vp)\n{\n        EXCMD cmd;\n        TEXT *tp;\n\n        /*\n         * !!!\n         * Historical vi permitted \"!!\" in an empty file, and it's handled\n         * as a special case in the ex_bang routine.  Don't modify this setup\n         * without understanding that one.  In particular, note that we're\n         * manipulating the ex argument structures behind ex's back.\n         *\n         * !!!\n         * Historical vi did not permit the '!' command to be associated with\n         * a non-line oriented motion command, in general, although it did\n         * with search commands.  So, !f; and !w would fail, but !/;<CR>\n         * would succeed, even if they all moved to the same location in the\n         * current line.  I don't see any reason to disallow '!' using any of\n         * the possible motion commands.\n         *\n         * !!!\n         * Historical vi ran the last bang command if N or n was used as the\n         * search motion.\n         */\n        if (F_ISSET(vp, VC_ISDOT) ||\n            ISCMD(vp->rkp, 'N') || ISCMD(vp->rkp, 'n')) {\n                ex_cinit(&cmd, C_BANG,\n                    2, vp->m_start.lno, vp->m_stop.lno, 0, NULL);\n                EXP(sp)->argsoff = 0;                   /* XXX */\n\n                if (argv_exp1(sp, &cmd, \"!\", 1, 1))\n                        return (1);\n                cmd.argc = EXP(sp)->argsoff;            /* XXX */\n                cmd.argv = EXP(sp)->args;               /* XXX */\n                return (v_exec_ex(sp, vp, &cmd));\n        }\n\n        /* Get the command from the user. */\n        if (v_tcmd(sp, vp,\n            '!', TXT_BS | TXT_CR | TXT_ESCAPE | TXT_FILEC | TXT_PROMPT))\n                return (1);\n\n        /*\n         * Check to see if the user changed their mind.\n         *\n         * !!!\n         * Entering <escape> on an empty line was historically an error,\n         * this implementation doesn't bother.\n         */\n        tp = TAILQ_FIRST(&sp->tiq);\n        if (tp->term != TERM_OK) {\n                vp->m_final.lno = sp->lno;\n                vp->m_final.cno = sp->cno;\n                return (0);\n        }\n\n        /* Home the cursor. */\n        vs_home(sp);\n\n        ex_cinit(&cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL);\n        EXP(sp)->argsoff = 0;                   /* XXX */\n\n        if (argv_exp1(sp, &cmd, tp->lb + 1, tp->len - 1, 1))\n                return (1);\n        cmd.argc = EXP(sp)->argsoff;            /* XXX */\n        cmd.argv = EXP(sp)->args;               /* XXX */\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_event_exec --\n *      Execute some command(s) based on an event.\n *\n * PUBLIC: int v_event_exec(SCR *, VICMD *);\n */\nint\nv_event_exec(SCR *sp, VICMD *vp)\n{\n        EXCMD cmd;\n\n        switch (vp->ev.e_event) {\n        case E_QUIT:\n                ex_cinit(&cmd, C_QUIT, 0, OOBLNO, OOBLNO, 0, NULL);\n                break;\n        case E_WRITE:\n                ex_cinit(&cmd, C_WRITE, 0, OOBLNO, OOBLNO, 0, NULL);\n                break;\n        default:\n                abort();\n        }\n        return (v_exec_ex(sp, vp, &cmd));\n}\n\n/*\n * v_exec_ex --\n *      Execute an ex command.\n */\nstatic int\nv_exec_ex(SCR *sp, VICMD *vp, EXCMD *exp)\n{\n        int rval;\n\n        rval = exp->cmd->fn(sp, exp);\n        return (v_ex_done(sp, vp) || rval);\n}\n\n/*\n * v_ex -- :\n *      Execute a colon command line.\n *\n * PUBLIC: int v_ex(SCR *, VICMD *);\n */\nint\nv_ex(SCR *sp, VICMD *vp)\n{\n        GS *gp;\n        TEXT *tp;\n        int do_cedit, do_resolution, ifcontinue;\n\n        gp = sp->gp;\n\n        /*\n         * !!!\n         * If we put out more than a single line of messages, or ex trashes\n         * the screen, the user may continue entering ex commands.  We find\n         * this out when we do the screen/message resolution.  We can't enter\n         * completely into ex mode however, because the user can elect to\n         * return into vi mode by entering any key, i.e. we have to be in raw\n         * mode.\n         */\n        for (do_cedit = do_resolution = 0;;) {\n                /*\n                 * !!!\n                 * There may already be an ex command waiting to run.  If\n                 * so, we continue with it.\n                 */\n                if (!EXCMD_RUNNING(gp)) {\n                        /* Get a command. */\n                        if (v_tcmd(sp, vp, ':',\n                            TXT_BS | TXT_CEDIT | TXT_FILEC | TXT_PROMPT))\n                                return (1);\n                        tp = TAILQ_FIRST(&sp->tiq);\n\n                        /*\n                         * If the user entered a single <esc>, they want to\n                         * edit their colon command history.  If they already\n                         * entered some text, move it into the edit history.\n                         */\n                        if (tp->term == TERM_CEDIT) {\n                                if (tp->len > 1 && v_ecl_log(sp, tp))\n                                        return (1);\n                                do_cedit = 1;\n                                break;\n                        }\n\n                        /* If the user changed their mind, return. */\n                        if (tp->term != TERM_OK)\n                                break;\n\n                        /* Log the command. */\n                        if (O_STR(sp, O_CEDIT) != NULL && v_ecl_log(sp, tp))\n                                return (1);\n\n                        /* Push a command on the command stack. */\n                        if (ex_run_str(sp, NULL, tp->lb, tp->len, 0, 1))\n                                return (1);\n                }\n\n                /* Home the cursor. */\n                vs_home(sp);\n\n                /*\n                 * !!!\n                 * If the editor wrote the screen behind curses back, put out\n                 * a <newline> so that we don't overwrite the user's command\n                 * with its output or the next want-to-continue? message.  This\n                 * doesn't belong here, but I can't find another place to put\n                 * it.  See, we resolved the output from the last ex command,\n                 * and the user entered another one.  This is the only place\n                 * where we have control before the ex command writes output.\n                 * We could get control in vs_msg(), but we have no way to know\n                 * if command didn't put out any output when we try and resolve\n                 * this command.  This fixes a bug where combinations of ex\n                 * commands, e.g. \":set<CR>:!date<CR>:set\" didn't look right.\n                 */\n                if (F_ISSET(sp, SC_SCR_EXWROTE))\n                        (void)putchar('\\n');\n\n                /* Call the ex parser. */\n                (void)ex_cmd(sp);\n\n                /* Flush ex messages. */\n                (void)ex_fflush(sp);\n\n                /* Resolve any messages. */\n                if (vs_ex_resolve(sp, &ifcontinue))\n                        return (1);\n\n                /*\n                 * Continue or return.  If continuing, make sure that we\n                 * eventually do resolution.\n                 */\n                if (!ifcontinue)\n                        break;\n                do_resolution = 1;\n\n                /* If we're continuing, it's a new command. */\n                ++sp->ccnt;\n        }\n\n        /*\n         * If the user previously continued an ex command, we have to do\n         * resolution to clean up the screen.  Don't wait, we already did\n         * that.\n         */\n        if (do_resolution) {\n                F_SET(sp, SC_EX_WAIT_NO);\n                if (vs_ex_resolve(sp, &ifcontinue))\n                        return (1);\n        }\n\n        /* Cleanup from the ex command. */\n        if (v_ex_done(sp, vp))\n                return (1);\n\n        /* The user may want to edit their colon command history. */\n        if (do_cedit)\n                return (v_ecl(sp));\n\n        return (0);\n}\n\n/*\n * v_ex_done --\n *      Cleanup from an ex command.\n */\nstatic int\nv_ex_done(SCR *sp, VICMD *vp)\n{\n        size_t len;\n\n        /*\n         * The only cursor modifications are real, however, the underlying\n         * line may have changed; don't trust anything.  This code has been\n         * a remarkably fertile place for bugs.  Do a reality check on a\n         * cursor value, and make sure it's okay.  If necessary, change it.\n         * Ex keeps track of the line number, but it cares less about the\n         * column and it may have disappeared.\n         *\n         * Don't trust ANYTHING.\n         *\n         * XXX\n         * Ex will soon have to start handling the column correctly; see\n         * the POSIX 1003.2 standard.\n         */\n        if (db_eget(sp, sp->lno, NULL, &len, NULL)) {\n                sp->lno = 1;\n                sp->cno = 0;\n        } else if (sp->cno >= len)\n                sp->cno = len ? len - 1 : 0;\n\n        vp->m_final.lno = sp->lno;\n        vp->m_final.cno = sp->cno;\n\n        /*\n         * Don't re-adjust the cursor after executing an ex command,\n         * and ex movements are permanent.\n         */\n        F_CLR(vp, VM_RCM_MASK);\n        F_SET(vp, VM_RCM_SET);\n\n        return (0);\n}\n\n/*\n * v_ecl --\n *      Start an edit window on the colon command-line commands.\n */\nstatic int\nv_ecl(SCR *sp)\n{\n        GS *gp;\n        SCR *new;\n\n        /* Initialize the screen, if necessary. */\n        gp = sp->gp;\n        if (gp->ccl_sp == NULL && v_ecl_init(sp))\n                return (1);\n\n        /* Get a new screen. */\n        if (screen_init(gp, sp, &new))\n                return (1);\n        if (vs_split(sp, new, 1)) {\n                (void)screen_end(new);\n                return (1);\n        }\n\n        /* Attach to the screen. */\n        new->ep = gp->ccl_sp->ep;\n        ++new->ep->refcnt;\n\n        new->frp = gp->ccl_sp->frp;\n        new->frp->flags = sp->frp->flags;\n\n        /* Move the cursor to the end. */\n        (void)db_last(new, &new->lno);\n        if (new->lno == 0)\n                new->lno = 1;\n\n        /* Remember the originating window. */\n        sp->ccl_parent = sp;\n\n        /* It's a special window. */\n        F_SET(new, SC_COMEDIT);\n\n        /* Set up the switch. */\n        sp->nextdisp = new;\n        F_SET(sp, SC_SSWITCH);\n        return (0);\n}\n\n/*\n * v_ecl_exec --\n *      Execute a command from a colon command-line window.\n *\n * PUBLIC: int v_ecl_exec(SCR *);\n */\nint\nv_ecl_exec(SCR *sp)\n{\n        size_t len;\n        char *p;\n\n        if (db_get(sp, sp->lno, 0, &p, &len) && sp->lno == 1) {\n                v_emsg(sp, NULL, VIM_EMPTY);\n                return (1);\n        }\n        if (len == 0) {\n                msgq(sp, M_BERR, \"No ex command to execute\");\n                return (1);\n        }\n\n        /* Push the command on the command stack. */\n        if (ex_run_str(sp, NULL, p, len, 0, 0))\n                return (1);\n\n        /* Set up the switch. */\n        sp->nextdisp = sp->ccl_parent;\n        F_SET(sp, SC_EXIT);\n        return (0);\n}\n\n/*\n * v_ecl_log --\n *      Log a command into the colon command-line log file.\n */\nstatic int\nv_ecl_log(SCR *sp, TEXT *tp)\n{\n        EXF *save_ep;\n        recno_t lno;\n        int rval;\n\n        /* Initialize the screen, if necessary. */\n        if (sp->gp->ccl_sp == NULL && v_ecl_init(sp))\n                return (1);\n\n        /*\n         * Don't log colon command window commands into the colon command\n         * window...\n         */\n        if (sp->ep == sp->gp->ccl_sp->ep)\n                return (0);\n\n        /*\n         * XXX\n         * Swap the current EXF with the colon command file EXF.  This\n         * isn't pretty, but too many routines \"know\" that sp->ep points\n         * to the current EXF.\n         */\n        save_ep = sp->ep;\n        sp->ep = sp->gp->ccl_sp->ep;\n        if (db_last(sp, &lno)) {\n                sp->ep = save_ep;\n                return (1);\n        }\n        rval = db_append(sp, 0, lno, tp->lb, tp->len);\n        sp->ep = save_ep;\n        return (rval);\n}\n\n/*\n * v_ecl_init --\n *      Initialize the colon command-line log file.\n */\nstatic int\nv_ecl_init(SCR *sp)\n{\n        FREF *frp;\n        GS *gp;\n\n        gp = sp->gp;\n\n        /* Get a temporary file. */\n        if ((frp = file_add(sp, NULL)) == NULL)\n                return (1);\n\n        /*\n         * XXX\n         * Create a screen -- the file initialization code wants one.\n         */\n        if (screen_init(gp, sp, &gp->ccl_sp))\n                return (1);\n        if (file_init(gp->ccl_sp, frp, NULL, 0)) {\n                (void)screen_end(gp->ccl_sp);\n                gp->ccl_sp = 0;\n                return (1);\n        }\n\n        /* The underlying file isn't recoverable. */\n        F_CLR(gp->ccl_sp->ep, F_RCV_ON);\n\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_increment.c",
    "content": "/*      $OpenBSD: v_increment.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic char * const fmt[] = {\n#define DEC     0\n        \"%ld\",\n#define SDEC    1\n        \"%+ld\",\n#define HEXC    2\n        \"0X%0*lX\",\n#define HEXL    3\n        \"0x%0*lx\",\n#define OCTAL   4\n        \"%#0*lo\",\n};\n\nstatic void inc_err(SCR *, enum nresult);\n\n/*\n * v_increment -- [count]#[#+-]\n *      Increment/decrement a keyword number.\n *\n * PUBLIC: int v_increment(SCR *, VICMD *);\n */\nint\nv_increment(SCR *sp, VICMD *vp)\n{\n        enum nresult nret;\n        unsigned long ulval;\n        long change, ltmp, lval;\n        size_t beg, blen, end, len, nlen, wlen;\n        int base, isempty, rval;\n        char *bp, *ntype, *p, *t, nbuf[100];\n\n        /* Validate the operator. */\n        if (vp->character == '#')\n                vp->character = '+';\n        if (vp->character != '+' && vp->character != '-') {\n                v_emsg(sp, vp->kp->usage, VIM_USAGE);\n                return (1);\n        }\n\n        /* If new value set, save it off, but it has to fit in a long. */\n        if (F_ISSET(vp, VC_C1SET)) {\n                if (vp->count > LONG_MAX) {\n                        inc_err(sp, NUM_OVER);\n                        return (1);\n                }\n                change = vp->count;\n        } else\n                change = 1;\n\n        /* Get the line. */\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (isempty)\n                        goto nonum;\n                return (1);\n        }\n\n        /*\n         * Skip any leading space before the number.  Getting a cursor word\n         * implies moving the cursor to its beginning, if we moved, refresh\n         * now.\n         */\n        for (beg = vp->m_start.cno; beg < len && isspace(p[beg]); ++beg);\n        if (beg >= len)\n                goto nonum;\n        if (beg != vp->m_start.cno) {\n                sp->cno = beg;\n                (void)vs_refresh(sp, 0);\n        }\n\n#undef  ishex\n#define ishex(c)        (isdigit(c) || strchr(\"abcdefABCDEF\", (c)))\n#undef  isoctal\n#define isoctal(c)      (isdigit(c) && (c) != '8' && (c) != '9')\n\n        /*\n         * Look for 0[Xx], or leading + or - signs, guess at the base.\n         * The character after that must be a number.  Wlen is set to\n         * the remaining characters in the line that could be part of\n         * the number.\n         */\n        wlen = len - beg;\n        if (p[beg] == '0' && wlen > 2 &&\n            (p[beg + 1] == 'X' || p[beg + 1] == 'x')) {\n                base = 16;\n                end = beg + 2;\n                if (!ishex(p[end]))\n                        goto decimal;\n                ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL];\n        } else if (p[beg] == '0' && wlen > 1) {\n                base = 8;\n                end = beg + 1;\n                if (!isoctal(p[end]))\n                        goto decimal;\n                ntype = fmt[OCTAL];\n        } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) {\n                base = 10;\n                end = beg + 1;\n                ntype = fmt[SDEC];\n                if (!isdigit(p[end]))\n                        goto nonum;\n        } else {\ndecimal:        base = 10;\n                end = beg;\n                ntype = fmt[DEC];\n                if (!isdigit(p[end])) {\nnonum:                  msgq(sp, M_ERR, \"Cursor not in a number\");\n                        return (1);\n                }\n        }\n\n        /* Find the end of the word, possibly correcting the base. */\n        while (++end < len) {\n                switch (base) {\n                case 8:\n                        if (isoctal(p[end]))\n                                continue;\n                        if (p[end] == '8' || p[end] == '9') {\n                                base = 10;\n                                ntype = fmt[DEC];\n                                continue;\n                        }\n                        break;\n                case 10:\n                        if (isdigit(p[end]))\n                                continue;\n                        break;\n                case 16:\n                        if (ishex(p[end]))\n                                continue;\n                        break;\n                default:\n                        abort();\n                        /* NOTREACHED */\n                }\n                break;\n        }\n        wlen = (end - beg);\n\n        /*\n         * XXX\n         * If the line was at the end of the buffer, we have to copy it\n         * so we can guarantee that it's NULL-terminated.  We make the\n         * buffer big enough to fit the line changes as well, and only\n         * allocate once.\n         */\n        GET_SPACE_RET(sp, bp, blen, len + 50);\n        if (bp == NULL) {\n                nret = NUM_UNDER;\n                goto err;\n        }\n        if (end == len) {\n                memmove(bp, &p[beg], wlen);\n                bp[wlen] = '\\0';\n                t = bp;\n        } else\n                t = &p[beg];\n\n        /*\n         * Octal or hex deal in unsigned longs, everything else is done\n         * in signed longs.\n         */\n        if (base == 10) {\n                if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK)\n                        goto err;\n                ltmp = vp->character == '-' ? -change : change;\n                if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) {\n                        nret = NUM_OVER;\n                        goto err;\n                }\n                if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) {\n                        nret = NUM_UNDER;\n                        goto err;\n                }\n                lval += ltmp;\n                /* If we cross 0, signed numbers lose their sign. */\n                if (lval == 0 && ntype == fmt[SDEC])\n                        ntype = fmt[DEC];\n                nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval);\n                if (nlen >= sizeof(nbuf))\n                        nlen = sizeof(nbuf) - 1;\n        } else {\n                if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK)\n                        goto err;\n                if (vp->character == '+') {\n                        if (!NPFITS(ULONG_MAX, ulval, change)) {\n                                nret = NUM_OVER;\n                                goto err;\n                        }\n                        ulval += change;\n                } else {\n                        if (ulval < change) {\n                                nret = NUM_UNDER;\n                                goto err;\n                        }\n                        ulval -= change;\n                }\n\n                /* Correct for literal \"0[Xx]\" in format. */\n                if (base == 16)\n                        wlen -= 2;\n\n                nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval);\n                if (nlen >= sizeof(nbuf))\n                        nlen = sizeof(nbuf) - 1;\n        }\n\n        /* Build the new line. */\n        if (bp == NULL)\n        {\n            nret = NUM_UNDER;\n            goto err;\n        }\n        memmove(bp, p, beg);\n        memmove(bp + beg, nbuf, nlen);\n        memmove(bp + beg + nlen, p + end, len - beg - (end - beg));\n        len = beg + nlen + (len - beg - (end - beg));\n\n        nret = NUM_OK;\n        (void)nret;\n        rval = db_set(sp, vp->m_start.lno, bp, len);\n\n        if (0) {\nerr:            rval = 1;\n                inc_err(sp, nret);\n        }\n        if (bp != NULL)\n                FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n\nstatic void\ninc_err(SCR *sp, enum nresult nret)\n{\n        switch (nret) {\n        case NUM_ERR:\n                break;\n        case NUM_OK:\n                abort();\n                /* NOREACHED */\n        case NUM_OVER:\n                msgq(sp, M_ERR, \"Resulting number too large\");\n                break;\n        case NUM_UNDER:\n                msgq(sp, M_ERR, \"Resulting number too small\");\n                break;\n        }\n}\n"
  },
  {
    "path": "vi/v_init.c",
    "content": "/*      $OpenBSD: v_init.c,v 1.8 2017/04/18 01:45:35 deraadt Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_screen_copy --\n *      Copy vi screen.\n *\n * PUBLIC: int v_screen_copy(SCR *, SCR *);\n */\nint\nv_screen_copy(SCR *orig, SCR *sp)\n{\n        VI_PRIVATE *ovip, *nvip;\n\n        /* Create the private vi structure. */\n        CALLOC_RET(orig, nvip, 1, sizeof(VI_PRIVATE));\n        sp->vi_private = nvip;\n\n        /* Invalidate the line size cache. */\n        VI_SCR_CFLUSH(nvip);\n\n        if (orig == NULL) {\n                nvip->csearchdir = CNOTSET;\n        } else {\n                ovip = VIP(orig);\n\n                /* User can replay the last input, but nothing else. */\n                if (ovip->rep_len != 0) {\n                        MALLOC_RET(orig, nvip->rep, ovip->rep_len);\n                        memmove(nvip->rep, ovip->rep, ovip->rep_len);\n                        nvip->rep_len = ovip->rep_len;\n                }\n\n                /* Copy the paragraph/section information. */\n                if (ovip->ps != NULL && (nvip->ps =\n                    v_strdup(sp, ovip->ps, strlen(ovip->ps))) == NULL)\n                        return (1);\n\n                nvip->lastckey = ovip->lastckey;\n                nvip->csearchdir = ovip->csearchdir;\n\n                nvip->srows = ovip->srows;\n        }\n        return (0);\n}\n\n/*\n * v_screen_end --\n *      End a vi screen.\n *\n * PUBLIC: int v_screen_end(SCR *);\n */\nint\nv_screen_end(SCR *sp)\n{\n        VI_PRIVATE *vip;\n\n        if ((vip = VIP(sp)) == NULL)\n                return (0);\n        free(vip->keyw);\n        free(vip->rep);\n        free(vip->ps);\n        free(HMAP);\n        free(vip);\n        sp->vi_private = NULL;\n\n        return (0);\n}\n\n/*\n * v_optchange --\n *      Handle change of options for vi.\n *\n * PUBLIC: int v_optchange(SCR *, int, char *, unsigned long *);\n */\nint\nv_optchange(SCR *sp, int offset, char *str, unsigned long *valp)\n{\n        switch (offset) {\n        case O_PARAGRAPHS:\n                return (v_buildps(sp, str, O_STR(sp, O_SECTIONS)));\n        case O_SECTIONS:\n                return (v_buildps(sp, O_STR(sp, O_PARAGRAPHS), str));\n        case O_WINDOW:\n                return (vs_crel(sp, *valp));\n        }\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_itxt.c",
    "content": "/*      $OpenBSD: v_itxt.c,v 1.8 2014/11/12 04:28:41 bentley Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * !!!\n * Repeated input in the historic vi is mostly wrong and this isn't very\n * backward compatible.  For example, if the user entered \"3Aab\\ncd\" in\n * the historic vi, the \"ab\" was repeated 3 times, and the \"\\ncd\" was then\n * appended to the result.  There was also a hack which I don't remember\n * right now, where \"3o\" would open 3 lines and then let the user fill them\n * in, to make screen movements on 300 baud modems more tolerable.  I don't\n * think it's going to be missed.\n *\n * !!!\n * There's a problem with the way that we do logging for change commands with\n * implied motions (e.g. A, I, O, cc, etc.).  Since the main vi loop logs the\n * starting cursor position before the change command \"moves\" the cursor, the\n * cursor position to which we return on undo will be where the user entered\n * the change command, not the start of the change.  Several of the following\n * routines re-log the cursor to make this work correctly.  Historic vi tried\n * to do the same thing, and mostly got it right.  (The only spectacular way\n * it fails is if the user entered 'o' from anywhere but the last character of\n * the line, the undo returned the cursor to the start of the line.  If the\n * user was on the last character of the line, the cursor returned to that\n * position.)  We also check for mapped keys waiting, i.e. if we're in the\n * middle of a map, don't bother logging the cursor.\n */\n#define LOG_CORRECT {                                                   \\\n        if (!MAPPED_KEYS_WAITING(sp))                                   \\\n                (void)log_cursor(sp);                                   \\\n}\n\nstatic u_int32_t set_txt_std(SCR *, VICMD *, u_int32_t);\n\n/*\n * v_iA -- [count]A\n *      Append text to the end of the line.\n *\n * PUBLIC: int v_iA(SCR *, VICMD *);\n */\nint\nv_iA(SCR *sp, VICMD *vp)\n{\n        size_t len;\n\n        if (!db_get(sp, vp->m_start.lno, 0, NULL, &len))\n                sp->cno = len == 0 ? 0 : len - 1;\n\n        LOG_CORRECT;\n\n        return (v_ia(sp, vp));\n}\n\n/*\n * v_ia -- [count]a\n *         [count]A\n *      Append text to the cursor position.\n *\n * PUBLIC: int v_ia(SCR *, VICMD *);\n */\nint\nv_ia(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        u_int32_t flags;\n        int isempty;\n        char *p;\n\n        flags = set_txt_std(sp, vp, 0);\n        sp->showmode = SM_APPEND;\n        sp->lno = vp->m_start.lno;\n\n        /* Move the cursor one column to the right and repaint the screen. */\n        if (db_eget(sp, sp->lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n                LF_SET(TXT_APPENDEOL);\n        } else if (len) {\n                if (len == sp->cno + 1) {\n                        sp->cno = len;\n                        LF_SET(TXT_APPENDEOL);\n                } else\n                        ++sp->cno;\n        } else\n                LF_SET(TXT_APPENDEOL);\n\n        return (v_txt(sp, vp, NULL, p, len,\n            0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));\n}\n\n/*\n * v_iI -- [count]I\n *      Insert text at the first nonblank.\n *\n * PUBLIC: int v_iI(SCR *, VICMD *);\n */\nint\nv_iI(SCR *sp, VICMD *vp)\n{\n        sp->cno = 0;\n        if (nonblank(sp, vp->m_start.lno, &sp->cno))\n                return (1);\n\n        LOG_CORRECT;\n\n        return (v_ii(sp, vp));\n}\n\n/*\n * v_ii -- [count]i\n *         [count]I\n *      Insert text at the cursor position.\n *\n * PUBLIC: int v_ii(SCR *, VICMD *);\n */\nint\nv_ii(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        u_int32_t flags;\n        int isempty;\n        char *p;\n\n        flags = set_txt_std(sp, vp, 0);\n        sp->showmode = SM_INSERT;\n        sp->lno = vp->m_start.lno;\n\n        if (db_eget(sp, sp->lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n        }\n\n        if (len == 0)\n                LF_SET(TXT_APPENDEOL);\n        return (v_txt(sp, vp, NULL, p, len,\n            0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));\n}\n\nenum which { o_cmd, O_cmd };\nstatic int io(SCR *, VICMD *, enum which);\n\n/*\n * v_iO -- [count]O\n *      Insert text above this line.\n *\n * PUBLIC: int v_iO(SCR *, VICMD *);\n */\nint\nv_iO(SCR *sp, VICMD *vp)\n{\n        return (io(sp, vp, O_cmd));\n}\n\n/*\n * v_io -- [count]o\n *      Insert text after this line.\n *\n * PUBLIC: int v_io(SCR *, VICMD *);\n */\nint\nv_io(SCR *sp, VICMD *vp)\n{\n        return (io(sp, vp, o_cmd));\n}\n\nstatic int\nio(SCR *sp, VICMD *vp, enum which cmd)\n{\n        recno_t ai_line, lno;\n        size_t len;\n        u_int32_t flags;\n        char *p;\n\n        flags = set_txt_std(sp, vp, TXT_ADDNEWLINE | TXT_APPENDEOL);\n        sp->showmode = SM_INSERT;\n\n        if (sp->lno == 1) {\n                if (db_last(sp, &lno))\n                        return (1);\n                if (lno != 0)\n                        goto insert;\n                p = NULL;\n                len = 0;\n                ai_line = OOBLNO;\n        } else {\ninsert:         p = \"\";\n                sp->cno = 0;\n                LOG_CORRECT;\n\n                if (cmd == O_cmd) {\n                        if (db_insert(sp, sp->lno, p, 0))\n                                return (1);\n                        if (db_get(sp, sp->lno, DBG_FATAL, &p, &len))\n                                return (1);\n                        ai_line = sp->lno + 1;\n                } else {\n                        if (db_append(sp, 1, sp->lno, p, 0))\n                                return (1);\n                        if (db_get(sp, ++sp->lno, DBG_FATAL, &p, &len))\n                                return (1);\n                        ai_line = sp->lno - 1;\n                }\n        }\n        return (v_txt(sp, vp, NULL, p, len,\n            0, ai_line, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));\n}\n\n/*\n * v_change -- [buffer][count]c[count]motion\n *             [buffer][count]C\n *             [buffer][count]S\n *      Change command.\n *\n * PUBLIC: int v_change(SCR *, VICMD *);\n */\nint\nv_change(SCR *sp, VICMD *vp)\n{\n        size_t blen, len;\n        u_int32_t flags;\n        int isempty, lmode, rval;\n        char *bp, *p;\n\n        /*\n         * 'c' can be combined with motion commands that set the resulting\n         * cursor position, i.e. \"cG\".  Clear the VM_RCM flags and make the\n         * resulting cursor position stick, inserting text has its own rules\n         * for cursor positioning.\n         */\n        F_CLR(vp, VM_RCM_MASK);\n        F_SET(vp, VM_RCM_SET);\n\n        /*\n         * Find out if the file is empty, it's easier to handle it as a\n         * special case.\n         */\n        if (vp->m_start.lno == vp->m_stop.lno &&\n            db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                return (v_ia(sp, vp));\n        }\n\n        flags = set_txt_std(sp, vp, 0);\n        sp->showmode = SM_CHANGE;\n\n        /*\n         * Move the cursor to the start of the change.  Note, if autoindent\n         * is turned on, the cc command in line mode changes from the first\n         * *non-blank* character of the line, not the first character.  And,\n         * to make it just a bit more exciting, the initial space is handled\n         * as auto-indent characters.\n         */\n        lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0;\n        if (lmode) {\n                vp->m_start.cno = 0;\n                if (O_ISSET(sp, O_AUTOINDENT)) {\n                        if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno))\n                                return (1);\n                        LF_SET(TXT_AICHARS);\n                }\n        }\n        sp->lno = vp->m_start.lno;\n        sp->cno = vp->m_start.cno;\n\n        LOG_CORRECT;\n\n        /*\n         * If not in line mode and changing within a single line, copy the\n         * text and overwrite it.\n         */\n        if (!lmode && vp->m_start.lno == vp->m_stop.lno) {\n                /*\n                 * !!!\n                 * Historic practice, c did not cut into the numeric buffers,\n                 * only the unnamed one.\n                 */\n                if (cut(sp,\n                    F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n                    &vp->m_start, &vp->m_stop, lmode))\n                        return (1);\n                if (len == 0)\n                        LF_SET(TXT_APPENDEOL);\n                LF_SET(TXT_EMARK | TXT_OVERWRITE);\n                return (v_txt(sp, vp, &vp->m_stop, p, len,\n                    0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));\n        }\n\n        /*\n         * It's trickier if in line mode or changing over multiple lines.  If\n         * we're in line mode delete all of the lines and insert a replacement\n         * line which the user edits.  If there was leading whitespace in the\n         * first line being changed, we copy it and use it as the replacement.\n         * If we're not in line mode, we delete the text and start inserting.\n         *\n         * !!!\n         * Copy the text.  Historic practice, c did not cut into the numeric\n         * buffers, only the unnamed one.\n         */\n        if (cut(sp,\n            F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_stop, lmode))\n                return (1);\n\n        /* If replacing entire lines and there's leading text. */\n        if (lmode && vp->m_start.cno) {\n                /*\n                 * Get a copy of the first line changed, and copy out the\n                 * leading text.\n                 */\n                if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len))\n                        return (1);\n                GET_SPACE_RET(sp, bp, blen, vp->m_start.cno);\n                memmove(bp, p, vp->m_start.cno);\n        } else\n                bp = NULL;\n\n        /* Delete the text. */\n        if (del(sp, &vp->m_start, &vp->m_stop, lmode))\n                return (1);\n\n        /* If replacing entire lines, insert a replacement line. */\n        if (lmode) {\n                if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno))\n                        return (1);\n                sp->lno = vp->m_start.lno;\n                len = sp->cno = vp->m_start.cno;\n        }\n\n        /* Get the line we're editing. */\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n        }\n\n        /* Check to see if we're appending to the line. */\n        if (vp->m_start.cno >= len)\n                LF_SET(TXT_APPENDEOL);\n\n        rval = v_txt(sp, vp, NULL, p, len,\n            0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags);\n\n        if (bp != NULL)\n                FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n\n/*\n * v_Replace -- [count]R\n *      Overwrite multiple characters.\n *\n * PUBLIC: int v_Replace(SCR *, VICMD *);\n */\nint\nv_Replace(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        u_int32_t flags;\n        int isempty;\n        char *p;\n\n        flags = set_txt_std(sp, vp, 0);\n        sp->showmode = SM_REPLACE;\n\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n                LF_SET(TXT_APPENDEOL);\n        } else {\n                if (len == 0)\n                        LF_SET(TXT_APPENDEOL);\n                LF_SET(TXT_OVERWRITE | TXT_REPLACE);\n        }\n        vp->m_stop.lno = vp->m_start.lno;\n        vp->m_stop.cno = len ? len - 1 : 0;\n\n        return (v_txt(sp, vp, &vp->m_stop, p, len,\n            0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));\n}\n\n/*\n * v_subst -- [buffer][count]s\n *      Substitute characters.\n *\n * PUBLIC: int v_subst(SCR *, VICMD *);\n */\nint\nv_subst(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        u_int32_t flags;\n        int isempty;\n        char *p;\n\n        flags = set_txt_std(sp, vp, 0);\n        sp->showmode = SM_CHANGE;\n\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n                LF_SET(TXT_APPENDEOL);\n        } else {\n                if (len == 0)\n                        LF_SET(TXT_APPENDEOL);\n                LF_SET(TXT_EMARK | TXT_OVERWRITE);\n        }\n\n        vp->m_stop.lno = vp->m_start.lno;\n        vp->m_stop.cno =\n            vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0);\n        if (vp->m_stop.cno > len - 1)\n                vp->m_stop.cno = len - 1;\n\n        if (p != NULL && cut(sp,\n            F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_stop, 0))\n                return (1);\n\n        return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, 1, flags));\n}\n\n/*\n * set_txt_std --\n *      Initialize text processing flags.\n */\nstatic u_int32_t\nset_txt_std(SCR *sp, VICMD *vp, u_int32_t flags)\n{\n        LF_SET(TXT_CNTRLT |\n            TXT_ESCAPE | TXT_MAPINPUT | TXT_RECORD | TXT_RESOLVE);\n\n        if (F_ISSET(vp, VC_ISDOT))\n                LF_SET(TXT_REPLAY);\n\n        if (O_ISSET(sp, O_ALTWERASE))\n                LF_SET(TXT_ALTWERASE);\n        if (O_ISSET(sp, O_AUTOINDENT))\n                LF_SET(TXT_AUTOINDENT);\n        if (O_ISSET(sp, O_BEAUTIFY))\n                LF_SET(TXT_BEAUTIFY);\n        if (O_ISSET(sp, O_SHOWMATCH))\n                LF_SET(TXT_SHOWMATCH);\n        if (F_ISSET(sp, SC_SCRIPT))\n                LF_SET(TXT_CR);\n        if (O_ISSET(sp, O_TTYWERASE))\n                LF_SET(TXT_TTYWERASE);\n\n        /*\n         * !!!\n         * Mapped keys were sometimes unaffected by the wrapmargin option\n         * in the historic 4BSD vi.  Consider the following commands, where\n         * each is executed on an empty line, in an 80 column screen, with\n         * the wrapmargin value set to 60.\n         *\n         *      aABC DEF <ESC>....\n         *      :map K aABC DEF ^V<ESC><CR>KKKKK\n         *      :map K 5aABC DEF ^V<ESC><CR>K\n         *\n         * The first and second commands are affected by wrapmargin.  The\n         * third is not.  (If the inserted text is itself longer than the\n         * wrapmargin value, i.e. if the \"ABC DEF \" string is replaced by\n         * something that's longer than 60 columns from the beginning of\n         * the line, the first two commands behave as before, but the third\n         * command gets fairly strange.)  The problem is that people wrote\n         * macros that depended on the third command NOT being affected by\n         * wrapmargin, as in this gem which centers lines:\n         *\n         *      map #c $mq81a ^V^[81^V^V|D`qld0:s/  / /g^V^M$p\n         *\n         * For compatibility reasons, we try and make it all work here.  I\n         * offer no hope that this is right, but it's probably pretty close.\n         *\n         * XXX\n         * Once I work my courage up, this is all gonna go away.  It's too\n         * evil to survive.\n         */\n        if ((O_ISSET(sp, O_WRAPLEN) || O_ISSET(sp, O_WRAPMARGIN)) &&\n            (!MAPPED_KEYS_WAITING(sp) || !F_ISSET(vp, VC_C1SET)))\n                LF_SET(TXT_WRAPMARGIN);\n        return (flags);\n}\n"
  },
  {
    "path": "vi/v_left.c",
    "content": "/*      $OpenBSD: v_left.c,v 1.7 2022/12/26 19:16:04 jmc Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_left -- [count]^H, [count]h\n *      Move left by columns.\n *\n * PUBLIC: int v_left(SCR *, VICMD *);\n */\nint\nv_left(SCR *sp, VICMD *vp)\n{\n        recno_t cnt;\n\n        /*\n         * !!!\n         * The ^H and h commands always failed in the first column.\n         */\n        if (vp->m_start.cno == 0) {\n                v_sol(sp);\n                return (1);\n        }\n\n        /* Find the end of the range. */\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        if (vp->m_start.cno > cnt)\n                vp->m_stop.cno = vp->m_start.cno - cnt;\n        else\n                vp->m_stop.cno = 0;\n\n        /*\n         * All commands move to the end of the range.  Motion commands\n         * adjust the starting point to the character before the current\n         * one.\n         */\n        if (ISMOTION(vp))\n                --vp->m_start.cno;\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_cfirst -- [count]_\n *      Move to the first non-blank character in a line.\n *\n * PUBLIC: int v_cfirst(SCR *, VICMD *);\n */\nint\nv_cfirst(SCR *sp, VICMD *vp)\n{\n        recno_t cnt, lno;\n\n        /*\n         * !!!\n         * If the _ is a motion component, it makes the command a line motion\n         * e.g. \"d_\" deletes the line.  It also means that the cursor doesn't\n         * move.\n         *\n         * The _ command never failed in the first column.\n         */\n        if (ISMOTION(vp))\n                F_SET(vp, VM_LMODE);\n        /*\n         * !!!\n         * Historically a specified count makes _ move down count - 1\n         * rows, so, \"3_\" is the same as \"2j_\".\n         */\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        if (cnt != 1) {\n                --vp->count;\n                return (v_down(sp, vp));\n        }\n\n        /*\n         * Move to the first non-blank.\n         *\n         * Can't just use RCM_SET_FNB, in case _ is used as the motion\n         * component of another command.\n         */\n        vp->m_stop.cno = 0;\n        if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))\n                return (1);\n\n        /*\n         * !!!\n         * The _ command has to fail if the file is empty and we're doing\n         * a delete.  If deleting line 1, and 0 is the first nonblank,\n         * make the check.\n         */\n        if (vp->m_stop.lno == 1 &&\n            vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) {\n                if (db_last(sp, &lno))\n                        return (1);\n                if (lno == 0) {\n                        v_sol(sp);\n                        return (1);\n                }\n        }\n\n        /*\n         * Delete and non-motion commands move to the end of the range,\n         * yank stays at the start.  Ignore others.\n         */\n        vp->m_final =\n            ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop;\n        return (0);\n}\n\n/*\n * v_first -- ^\n *      Move to the first non-blank character in this line.\n *\n * PUBLIC: int v_first(SCR *, VICMD *);\n */\nint\nv_first(SCR *sp, VICMD *vp)\n{\n        /*\n         * !!!\n         * Yielding to none in our quest for compatibility with every\n         * historical blemish of vi, no matter how strange it might be,\n         * we permit the user to enter a count and then ignore it.\n         */\n\n        /*\n         * Move to the first non-blank.\n         *\n         * Can't just use RCM_SET_FNB, in case ^ is used as the motion\n         * component of another command.\n         */\n        vp->m_stop.cno = 0;\n        if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))\n                return (1);\n\n        /*\n         * !!!\n         * The ^ command succeeded if used as a command when the cursor was\n         * on the first non-blank in the line, but failed if used as a motion\n         * component in the same situation.\n         */\n        if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) {\n                v_sol(sp);\n                return (1);\n        }\n\n        /*\n         * If moving right, non-motion commands move to the end of the range.\n         * Delete and yank stay at the start.  Motion commands adjust the\n         * ending point to the character before the current ending character.\n         *\n         * If moving left, all commands move to the end of the range.  Motion\n         * commands adjust the starting point to the character before the\n         * current starting character.\n         */\n        if (vp->m_start.cno < vp->m_stop.cno)\n                if (ISMOTION(vp)) {\n                        --vp->m_stop.cno;\n                        vp->m_final = vp->m_start;\n                } else\n                        vp->m_final = vp->m_stop;\n        else {\n                if (ISMOTION(vp))\n                        --vp->m_start.cno;\n                vp->m_final = vp->m_stop;\n        }\n        return (0);\n}\n\n/*\n * v_ncol -- [count]|\n *      Move to column count or the first column on this line.  If the\n *      requested column is past EOL, move to EOL.  The nasty part is\n *      that we have to know character column widths to make this work.\n *\n * PUBLIC: int v_ncol(SCR *, VICMD *);\n */\nint\nv_ncol(SCR *sp, VICMD *vp)\n{\n        if (F_ISSET(vp, VC_C1SET) && vp->count > 1) {\n                --vp->count;\n                vp->m_stop.cno =\n                    vs_colpos(sp, vp->m_start.lno, (size_t)vp->count);\n                /*\n                 * !!!\n                 * The | command succeeded if used as a command and the cursor\n                 * didn't move, but failed if used as a motion component in the\n                 * same situation.\n                 */\n                if (ISMOTION(vp) && vp->m_stop.cno == vp->m_start.cno) {\n                        v_nomove(sp);\n                        return (1);\n                }\n        } else {\n                /*\n                 * !!!\n                 * The | command succeeded if used as a command in column 0\n                 * without a count, but failed if used as a motion component\n                 * in the same situation.\n                 */\n                if (ISMOTION(vp) && vp->m_start.cno == 0) {\n                        v_sol(sp);\n                        return (1);\n                }\n                vp->m_stop.cno = 0;\n        }\n\n        /*\n         * If moving right, non-motion commands move to the end of the range.\n         * Delete and yank stay at the start.  Motion commands adjust the\n         * ending point to the character before the current ending character.\n         *\n         * If moving left, all commands move to the end of the range.  Motion\n         * commands adjust the starting point to the character before the\n         * current starting character.\n         */\n        if (vp->m_start.cno < vp->m_stop.cno)\n                if (ISMOTION(vp)) {\n                        --vp->m_stop.cno;\n                        vp->m_final = vp->m_start;\n                } else\n                        vp->m_final = vp->m_stop;\n        else {\n                if (ISMOTION(vp))\n                        --vp->m_start.cno;\n                vp->m_final = vp->m_stop;\n        }\n        return (0);\n}\n\n/*\n * v_zero -- 0\n *      Move to the first column on this line.\n *\n * PUBLIC: int v_zero(SCR *, VICMD *);\n */\nint\nv_zero(SCR *sp, VICMD *vp)\n{\n        /*\n         * !!!\n         * The 0 command succeeded if used as a command in the first column\n         * but failed if used as a motion component in the same situation.\n         */\n        if (ISMOTION(vp) && vp->m_start.cno == 0) {\n                v_sol(sp);\n                return (1);\n        }\n\n        /*\n         * All commands move to the end of the range.  Motion commands\n         * adjust the starting point to the character before the current\n         * one.\n         */\n        vp->m_stop.cno = 0;\n        if (ISMOTION(vp))\n                --vp->m_start.cno;\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_mark.c",
    "content": "/*      $OpenBSD: v_mark.c,v 1.11 2022/12/26 19:16:04 jmc Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <bsd_stdlib.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_mark -- m[a-z]\n *      Set a mark.\n *\n * PUBLIC: int v_mark(SCR *, VICMD *);\n */\nint\nv_mark(SCR *sp, VICMD *vp)\n{\n        return (mark_set(sp, vp->character, &vp->m_start, 1));\n}\n\nenum which {BQMARK, FQMARK};\nstatic int mark(SCR *, VICMD *, enum which);\n\n/*\n * v_bmark -- `['`a-z]\n *      Move to a mark.\n *\n * Moves to a mark, setting both row and column.\n *\n * !!!\n * Although not commonly known, the \"'`\" and \"'`\" forms are historically\n * valid.  The behavior is determined by the first character, so \"`'\" is\n * the same as \"``\".  Remember this fact -- you'll be amazed at how many\n * people don't know it and will be delighted that you are able to tell\n * them.\n *\n * PUBLIC: int v_bmark(SCR *, VICMD *);\n */\nint\nv_bmark(SCR *sp, VICMD *vp)\n{\n        return (mark(sp, vp, BQMARK));\n}\n\n/*\n * v_fmark -- '['`a-z]\n *      Move to a mark.\n *\n * Move to the first nonblank character of the line containing the mark.\n *\n * PUBLIC: int v_fmark(SCR *, VICMD *);\n */\nint\nv_fmark(SCR *sp, VICMD *vp)\n{\n        return (mark(sp, vp, FQMARK));\n}\n\n/*\n * mark --\n *      Mark commands.\n */\nstatic int\nmark(SCR *sp, VICMD *vp, enum which cmd)\n{\n        MARK m;\n        size_t len;\n\n        if (mark_get(sp, vp->character, &vp->m_stop, M_BERR))\n                return (1);\n\n        /*\n         * !!!\n         * Historically, BQMARKS for character positions that no longer\n         * existed acted as FQMARKS.\n         *\n         * FQMARKS move to the first non-blank.\n         */\n        switch (cmd) {\n        case BQMARK:\n                if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len))\n                        return (1);\n                if (vp->m_stop.cno < len ||\n                    (vp->m_stop.cno == len && len == 0))\n                        break;\n\n                if (ISMOTION(vp))\n                        F_SET(vp, VM_LMODE);\n                cmd = FQMARK;\n                /* FALLTHROUGH */\n        case FQMARK:\n                vp->m_stop.cno = 0;\n                if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))\n                        return (1);\n                break;\n        default:\n                abort();\n        }\n\n        /* Non-motion commands move to the end of the range. */\n        if (!ISMOTION(vp)) {\n                vp->m_final = vp->m_stop;\n                return (0);\n        }\n\n        /*\n         * !!!\n         * If a motion component to a BQMARK, the cursor has to move.\n         */\n        if (cmd == BQMARK &&\n            vp->m_stop.lno == vp->m_start.lno &&\n            vp->m_stop.cno == vp->m_start.cno) {\n                v_nomove(sp);\n                return (1);\n        }\n\n        /*\n         * If the motion is in the reverse direction, switch the start and\n         * stop MARK's so that it's in a forward direction.  (There's no\n         * reason for this other than to make the tests below easier.  The\n         * code in vi.c:vi() would have done the switch.)  Both forward\n         * and backward motions can happen for any kind of search command.\n         */\n        if (vp->m_start.lno > vp->m_stop.lno ||\n            (vp->m_start.lno == vp->m_stop.lno &&\n            vp->m_start.cno > vp->m_stop.cno)) {\n                m = vp->m_start;\n                vp->m_start = vp->m_stop;\n                vp->m_stop = m;\n        }\n\n        /*\n         * Yank cursor motion, when associated with marks as motion commands,\n         * historically behaved as follows:\n         *\n         * ` motion                     ' motion\n         *              Line change?            Line change?\n         *              Y       N               Y       N\n         *            --------------          ---------------\n         * FORWARD:  |  NM      NM            | NM      NM\n         *           |                        |\n         * BACKWARD: |  M       M             | M       NM(1)\n         *\n         * where NM means the cursor didn't move, and M means the cursor\n         * moved to the mark.\n         *\n         * As the cursor was usually moved for yank commands associated\n         * with backward motions, this implementation regularizes it by\n         * changing the NM at position (1) to be an M.  This makes mark\n         * motions match search motions, which is probably A Good Thing.\n         *\n         * Delete cursor motion was always to the start of the text region,\n         * regardless.  Ignore other motion commands.\n         */\n        vp->m_final = vp->m_start;\n\n        /*\n         * Forward marks are always line oriented, and it's set in the\n         * vcmd.c table.\n         */\n        if (cmd == FQMARK)\n                return (0);\n\n        /*\n         * BQMARK'S moving backward and starting at column 0, and ones moving\n         * forward and ending at column 0 are corrected to the last column of\n         * the previous line.  Otherwise, adjust the starting/ending point to\n         * the character before the current one (this is safe because we know\n         * the search had to move to succeed).\n         *\n         * Mark motions become line mode operations if they start at the first\n         * nonblank and end at column 0 of another line.\n         */\n        if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) {\n                if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len))\n                        return (1);\n                vp->m_stop.cno = len ? len - 1 : 0;\n                len = 0;\n                if (nonblank(sp, vp->m_start.lno, &len))\n                        return (1);\n                if (vp->m_start.cno <= len)\n                        F_SET(vp, VM_LMODE);\n        } else\n                --vp->m_stop.cno;\n\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_match.c",
    "content": "/*      $OpenBSD: v_match.c,v 1.9 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_match -- %\n *      Search to matching character.\n *\n * PUBLIC: int v_match(SCR *, VICMD *);\n */\nint\nv_match(SCR *sp, VICMD *vp)\n{\n        VCS cs;\n        MARK *mp;\n        size_t cno, len, off;\n        int cnt, isempty, matchc, startc, (*gc)(SCR *, VCS *);\n        char *p;\n\n        /*\n         * !!!\n         * Historic practice; ignore the count.\n         *\n         * !!!\n         * Historical practice was to search for the initial character in the\n         * forward direction only.\n         */\n        if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                if (isempty)\n                        goto nomatch;\n                return (1);\n        }\n        for (off = vp->m_start.cno;; ++off) {\n                if (off >= len) {\nnomatch:                msgq(sp, M_BERR, \"No match character on this line\");\n                        return (1);\n                }\n                switch (startc = p[off]) {\n                case '(':\n                        matchc = ')';\n                        gc = cs_next;\n                        break;\n                case ')':\n                        matchc = '(';\n                        gc = cs_prev;\n                        break;\n                case '[':\n                        matchc = ']';\n                        gc = cs_next;\n                        break;\n                case ']':\n                        matchc = '[';\n                        gc = cs_prev;\n                        break;\n                case '{':\n                        matchc = '}';\n                        gc = cs_next;\n                        break;\n                case '}':\n                        matchc = '{';\n                        gc = cs_prev;\n                        break;\n                case '<':\n                        matchc = '>';\n                        gc = cs_next;\n                        break;\n                case '>':\n                        matchc = '<';\n                        gc = cs_prev;\n                        break;\n                default:\n                        continue;\n                }\n                break;\n        }\n\n        cs.cs_lno = vp->m_start.lno;\n        cs.cs_cno = off;\n        if (cs_init(sp, &cs))\n                return (1);\n        for (cnt = 1;;) {\n                if (gc(sp, &cs))\n                        return (1);\n                if (cs.cs_flags != 0) {\n                        if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF)\n                                break;\n                        continue;\n                }\n                if (cs.cs_ch == startc)\n                        ++cnt;\n                else if (cs.cs_ch == matchc && --cnt == 0)\n                        break;\n        }\n        if (cnt) {\n                msgq(sp, M_BERR, \"Matching character not found\");\n                return (1);\n        }\n\n        vp->m_stop.lno = cs.cs_lno;\n        vp->m_stop.cno = cs.cs_cno;\n\n        /*\n         * If moving right, non-motion commands move to the end of the range.\n         * Delete and yank stay at the start.\n         *\n         * If moving left, all commands move to the end of the range.\n         *\n         * !!!\n         * Don't correct for leftward movement -- historic vi deleted the\n         * starting cursor position when deleting to a match.\n         */\n        if (vp->m_start.lno < vp->m_stop.lno ||\n            (vp->m_start.lno == vp->m_stop.lno &&\n            vp->m_start.cno < vp->m_stop.cno))\n                vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        else\n                vp->m_final = vp->m_stop;\n\n        /*\n         * !!!\n         * If the motion is across lines, and the earliest cursor position\n         * is at or before any non-blank characters in the line, i.e. the\n         * movement is cutting all of the line's text, and the later cursor\n         * position has nothing other than whitespace characters between it\n         * and the end of its line, the buffer is in line mode.\n         */\n        if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno)\n                return (0);\n        mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop;\n        if (mp->cno != 0) {\n                cno = 0;\n                if (nonblank(sp, mp->lno, &cno))\n                        return (1);\n                if (cno < mp->cno)\n                        return (0);\n        }\n        mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start;\n        if (db_get(sp, mp->lno, DBG_FATAL, &p, &len))\n                return (1);\n        for (p += mp->cno + 1, len -= mp->cno; --len; ++p)\n                if (!isblank(*p))\n                        return (0);\n        F_SET(vp, VM_LMODE);\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_paragraph.c",
    "content": "/*      $OpenBSD: v_paragraph.c,v 1.10 2023/09/07 11:17:32 tobhe Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n#define INTEXT_CHECK {                                                  \\\n        if (len == 0 || v_isempty(p, len)) {                            \\\n                if (!--cnt)                                             \\\n                        goto found;                                     \\\n                pstate = P_INBLANK;                                     \\\n        }                                                               \\\n        /*                                                              \\\n         * !!!                                                          \\\n         * Historic documentation (USD:15-11, 4.2) said that formfeed   \\\n         * characters (^L) in the first column delimited paragraphs.    \\\n         * The historic vi code mentions formfeed characters, but never \\\n         * implements them.  It seems reasonable, do it.                \\\n         */                                                             \\\n        if (p[0] == '\\014') {                                           \\\n                if (!--cnt)                                             \\\n                        goto found;                                     \\\n                if (pstate == P_INTEXT && !--cnt)                       \\\n                        goto found;                                     \\\n                continue;                                               \\\n        }                                                               \\\n        if (p[0] != '.' || len < 2)                                     \\\n                continue;                                               \\\n        for (lp = VIP(sp)->ps; *lp != '\\0'; lp += 2)                    \\\n                if (lp[0] == p[1] &&                                    \\\n                    ((lp[1] == ' ' && len == 2) || (lp[1] == p[2]))) {  \\\n                    if (!--cnt)                                         \\\n                        goto found;                                     \\\n                    if (pstate == P_INTEXT && !--cnt)                   \\\n                        goto found;                                     \\\n                }                                                       \\\n}\n\n/*\n * v_paragraphf -- [count]}\n *      Move forward count paragraphs.\n *\n * Paragraphs are empty lines after text, formfeed characters, or values\n * from the paragraph or section options.\n *\n * PUBLIC: int v_paragraphf(SCR *, VICMD *);\n */\nint\nv_paragraphf(SCR *sp, VICMD *vp)\n{\n        enum { P_INTEXT, P_INBLANK } pstate;\n        size_t lastlen, len;\n        recno_t cnt, lastlno, lno;\n        int isempty;\n        char *p, *lp;\n\n        /*\n         * !!!\n         * If the starting cursor position is at or before any non-blank\n         * characters in the line, i.e. the movement is cutting all of the\n         * line's text, the buffer is in line mode.  It's a lot easier to\n         * check here, because we know that the end is going to be the start\n         * or end of a line.\n         *\n         * This was historical practice in vi, with a single exception.  If\n         * the paragraph movement was from the start of the last line to EOF,\n         * then all the characters were deleted from the last line, but the\n         * line itself remained.  If somebody complains, don't pause, don't\n         * hesitate, just hit them.\n         */\n\n        if (ISMOTION(vp)) {\n                if (vp->m_start.cno == 0)\n                        F_SET(vp, VM_LMODE);\n                else {\n                        vp->m_stop = vp->m_start;\n                        vp->m_stop.cno = 0;\n                        if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))\n                                return (1);\n                        if (vp->m_start.cno <= vp->m_stop.cno)\n                                F_SET(vp, VM_LMODE);\n                }\n        }\n\n        /* Figure out what state we're currently in. */\n        lno = vp->m_start.lno;\n        if (db_get(sp, lno, 0, &p, &len))\n                goto eof;\n\n        /*\n         * If we start in text, we want to switch states\n         * (2 * N - 1) times, in non-text, (2 * N) times.\n         */\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        cnt *= 2;\n        if (len == 0 || v_isempty(p, len))\n                pstate = P_INBLANK;\n        else {\n                --cnt;\n                pstate = P_INTEXT;\n        }\n\n        for (;;) {\n                lastlno = lno;\n                lastlen = len;\n                if (db_get(sp, ++lno, 0, &p, &len))\n                        goto eof;\n                switch (pstate) {\n                case P_INTEXT:\n                        INTEXT_CHECK;\n                        break;\n                case P_INBLANK:\n                        if (len == 0 || v_isempty(p, len))\n                                break;\n                        if (--cnt) {\n                                pstate = P_INTEXT;\n                                break;\n                        }\n\n                        /*\n                         * !!!\n                         * Non-motion commands move to the end of the range,\n                         * delete and yank stay at the start.  Ignore others.\n                         * Adjust the end of the range for motion commands;\n                         * historically, a motion component was to the end of\n                         * the previous line, whereas the movement command was\n                         * to the start of the new \"paragraph\".\n                         */\n\nfound:                  if (ISMOTION(vp)) {\n                                vp->m_stop.lno = lastlno;\n                                vp->m_stop.cno = lastlen ? lastlen - 1 : 0;\n                                vp->m_final = vp->m_start;\n                        } else {\n                                vp->m_stop.lno = lno;\n                                vp->m_stop.cno = 0;\n                                vp->m_final = vp->m_stop;\n                        }\n                        return (0);\n                default:\n                        abort();\n                }\n        }\n\n        /*\n         * !!!\n         * Adjust end of the range for motion commands; EOF is a movement\n         * sink.  The } command historically moved to the end of the last\n         * line, not the beginning, from any position before the end of the\n         * last line.  It also historically worked on empty files, so we\n         * have to make it okay.\n         */\n\neof:    if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) {\n                if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {\n                        if (!isempty)\n                                return (1);\n                        vp->m_start.cno = 0;\n                        return (0);\n                }\n                if (vp->m_start.cno == (len ? len - 1 : 0)) {\n                        v_eof(sp, NULL);\n                        return (1);\n                }\n        }\n\n        /*\n         * !!!\n         * Non-motion commands move to the end of the range, delete\n         * and yank stay at the start.  Ignore others.\n         *\n         * If deleting the line (which happens if deleting to EOF), then\n         * cursor movement is to the first nonblank.\n         */\n\n        if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SETFNB);\n        }\n        vp->m_stop.lno = lno - 1;\n        vp->m_stop.cno = len ? len - 1 : 0;\n        vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        return (0);\n}\n\n/*\n * v_paragraphb -- [count]{\n *      Move backward count paragraphs.\n *\n * PUBLIC: int v_paragraphb(SCR *, VICMD *);\n */\nint\nv_paragraphb(SCR *sp, VICMD *vp)\n{\n        enum { P_INTEXT, P_INBLANK } pstate;\n        size_t len;\n        recno_t cnt, lno;\n        char *p, *lp;\n\n        /*\n         * !!!\n         * Check for SOF.  The historic vi didn't complain if users hit SOF\n         * repeatedly, unless it was part of a motion command.  There is no\n         * question but that Emerson's editor of choice was vi.\n         *\n         * The { command historically moved to the beginning of the first\n         * line if invoked on the first line.\n         *\n         * !!!\n         * If the starting cursor position is in the first column (backward\n         * paragraph movements did NOT historically pay attention to non-blank\n         * characters) i.e. the movement is cutting the entire line, the buffer\n         * is in line mode.  Cuts from the beginning of the line also did not\n         * cut the current line, but started at the previous EOL.\n         *\n         * Correct for a left motion component while we're thinking about it.\n         */\n\n        lno = vp->m_start.lno;\n\n        if (ISMOTION(vp)) {\n                if (vp->m_start.cno == 0) {\n                        if (vp->m_start.lno == 1) {\n                                v_sof(sp, &vp->m_start);\n                                return (1);\n                        } else\n                                --vp->m_start.lno;\n                        F_SET(vp, VM_LMODE);\n                } else\n                        --vp->m_start.cno;\n        }\n\n        if (vp->m_start.lno <= 1)\n                goto sof;\n\n        /* Figure out what state we're currently in. */\n        if (db_get(sp, lno, 0, &p, &len))\n                goto sof;\n\n        /*\n         * If we start in text, we want to switch states\n         * (2 * N - 1) times, in non-text, (2 * N) times.\n         */\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        cnt *= 2;\n        if (len == 0 || v_isempty(p, len))\n                pstate = P_INBLANK;\n        else {\n                --cnt;\n                pstate = P_INTEXT;\n\n                /*\n                 * !!!\n                 * If the starting cursor is past the first column,\n                 * the current line is checked for a paragraph.\n                 */\n\n                if (vp->m_start.cno > 0)\n                        ++lno;\n        }\n\n        for (;;) {\n                if (db_get(sp, --lno, 0, &p, &len))\n                        goto sof;\n                switch (pstate) {\n                case P_INTEXT:\n                        INTEXT_CHECK;\n                        break;\n                case P_INBLANK:\n                        if (len != 0 && !v_isempty(p, len)) {\n                                if (!--cnt)\n                                        goto found;\n                                pstate = P_INTEXT;\n                        }\n                        break;\n                default:\n                        abort();\n                }\n        }\n\n        /* SOF is a movement sink. */\nsof:    lno = 1;\n\nfound:  vp->m_stop.lno = lno;\n        vp->m_stop.cno = 0;\n\n        /*\n         * All commands move to the end of the range.  (We already\n         * adjusted the start of the range for motion commands).\n         */\n\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_buildps --\n *      Build the paragraph command search pattern.\n *\n * PUBLIC: int v_buildps(SCR *, char *, char *);\n */\nint\nv_buildps(SCR *sp, char *p_p, char *s_p)\n{\n        VI_PRIVATE *vip;\n        size_t p_len, s_len;\n        char *p;\n\n        /*\n         * The vi paragraph command searches for either a paragraph or\n         * section option macro.\n         */\n\n        p_len = p_p == NULL ? 0 : strlen(p_p);\n        s_len = s_p == NULL ? 0 : strlen(s_p);\n\n        if (p_len == 0 && s_len == 0)\n                return (0);\n\n        MALLOC_RET(sp, p, p_len + s_len + 1);\n\n        vip = VIP(sp);\n        free(vip->ps);\n\n        if (p_p != NULL)\n                memmove(p, p_p, p_len + 1);\n        if (s_p != NULL)\n                memmove(p + p_len, s_p, s_len + 1);\n        vip->ps = p;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_put.c",
    "content": "/*      $OpenBSD: v_put.c,v 1.9 2025/08/23 21:02:10 millert Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic void     inc_buf(SCR *, VICMD *);\n\n/*\n * v_Put -- [buffer]P\n *      Insert the contents of the buffer before the cursor.\n *\n * PUBLIC: int v_Put(SCR *, VICMD *);\n */\nint\nv_Put(SCR *sp, VICMD *vp)\n{\n        unsigned long cnt;\n\n        if (F_ISSET(vp, VC_ISDOT))\n                inc_buf(sp, vp);\n\n        /*\n         * !!!\n         * Historic vi did not support a count with the 'p' and 'P'\n         * commands.  It's useful, so we do.\n         */\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        if (put(sp, NULL, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_final, 0, cnt))\n                return (1);\n\n        return (0);\n}\n\n/*\n * v_put -- [buffer]p\n *      Insert the contents of the buffer after the cursor.\n *\n * PUBLIC: int v_put(SCR *, VICMD *);\n */\nint\nv_put(SCR *sp, VICMD *vp)\n{\n        unsigned long cnt;\n\n        if (F_ISSET(vp, VC_ISDOT))\n                inc_buf(sp, vp);\n\n        /*\n         * !!!\n         * Historic vi did not support a count with the 'p' and 'P'\n         * commands.  It's useful, so we do.\n         */\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        if (put(sp, NULL, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_final, 1, cnt))\n                return (1);\n\n        return (0);\n}\n\n/*\n * !!!\n * Historical whackadoo.  The dot command `puts' the numbered buffer\n * after the last one put.  For example, `\"4p.' would put buffer #4\n * and buffer #5.  If the user continued to enter '.', the #9 buffer\n * would be repeatedly output.  This was not documented, and is a bit\n * tricky to reconstruct.  Historical versions of vi also dropped the\n * contents of the default buffer after each put, so after `\"4p' the\n * default buffer would be empty.  This makes no sense to me, so we\n * don't bother.  Don't assume sequential order of numeric characters.\n *\n * And, if that weren't exciting enough, failed commands don't normally\n * set the dot command.  Well, boys and girls, an exception is that\n * the buffer increment gets done regardless of the success of the put.\n */\nstatic void\ninc_buf(SCR *sp, VICMD *vp)\n{\n        CHAR_T v;\n\n        switch (vp->buffer) {\n        case '1':\n                v = '2';\n                break;\n        case '2':\n                v = '3';\n                break;\n        case '3':\n                v = '4';\n                break;\n        case '4':\n                v = '5';\n                break;\n        case '5':\n                v = '6';\n                break;\n        case '6':\n                v = '7';\n                break;\n        case '7':\n                v = '8';\n                break;\n        case '8':\n                v = '9';\n                break;\n        default:\n                return;\n        }\n        VIP(sp)->sdot.buffer = vp->buffer = v;\n}\n"
  },
  {
    "path": "vi/v_redraw.c",
    "content": "/*      $OpenBSD: v_redraw.c,v 1.6 2014/11/12 04:28:41 bentley Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_redraw -- ^L, ^R\n *      Redraw the screen.\n *\n * PUBLIC: int v_redraw(SCR *, VICMD *);\n */\n\nint\nv_redraw(SCR *sp, VICMD *vp)\n{\n        F_SET(sp, SC_SCR_REFORMAT);\n        return (sp->gp->scr_refresh(sp, 1));\n}\n"
  },
  {
    "path": "vi/v_replace.c",
    "content": "/*      $OpenBSD: v_replace.c,v 1.9 2016/01/06 22:28:52 millert Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_replace -- [count]r<char>\n *\n * !!!\n * The r command in historic vi was almost beautiful in its badness.  For\n * example, \"r<erase>\" and \"r<word erase>\" beeped the terminal and deleted\n * a single character.  \"Nr<carriage return>\", where N was greater than 1,\n * inserted a single carriage return.  \"r<escape>\" did cancel the command,\n * but \"r<literal><escape>\" erased a single character.  To enter a literal\n * <literal> character, it required three <literal> characters after the\n * command.  This may not be right, but at least it's not insane.\n *\n * PUBLIC: int v_replace(SCR *, VICMD *);\n */\n\nint\nv_replace(SCR *sp, VICMD *vp)\n{\n        EVENT ev;\n        VI_PRIVATE *vip;\n        TEXT *tp;\n        size_t blen, len;\n        unsigned long cnt;\n        int quote, rval;\n        char *bp, *p;\n\n        vip = VIP(sp);\n\n        /*\n         * If the line doesn't exist, or it's empty, replacement isn't\n         * allowed.  It's not hard to implement, but:\n         *\n         *      1: It's historic practice (vi beeped before the replacement\n         *         character was even entered).\n         *      2: For consistency, this change would require that the more\n         *         general case, \"Nr\", when the user is < N characters from\n         *         the end of the line, also work, which would be a bit odd.\n         *      3: Replacing with a <newline> has somewhat odd semantics.\n         */\n        if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len))\n                return (1);\n        if (len == 0) {\n                msgq(sp, M_BERR, \"No characters to replace\");\n                return (1);\n        }\n\n        /*\n         * Figure out how many characters to be replace.  For no particular\n         * reason (other than that the semantics of replacing the newline\n         * are confusing) only permit the replacement of the characters in\n         * the current line.  I suppose we could append replacement characters\n         * to the line, but I see no compelling reason to do so.  Check this\n         * before we get the character to match historic practice, where Nr\n         * failed immediately if there were less than N characters from the\n         * cursor to the end of the line.\n         */\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        vp->m_stop.lno = vp->m_start.lno;\n        vp->m_stop.cno = vp->m_start.cno + cnt - 1;\n        if (vp->m_stop.cno > len - 1) {\n                v_eol(sp, &vp->m_start);\n                return (1);\n        }\n\n        /*\n         * If it's not a repeat, reset the current mode and get a replacement\n         * character.\n         */\n        quote = 0;\n        if (!F_ISSET(vp, VC_ISDOT)) {\n                sp->showmode = SM_REPLACE;\n                if (vs_refresh(sp, 0))\n                        return (1);\nnext:           if (v_event_get(sp, &ev, 0, 0))\n                        return (1);\n\n                switch (ev.e_event) {\n                case E_CHARACTER:\n                        /*\n                         * <literal_next> means escape the next character.\n                         * <escape> means they changed their minds.\n                         */\n                        if (!quote) {\n                                if (ev.e_value == K_VLNEXT) {\n                                        quote = 1;\n                                        goto next;\n                                }\n                                if (ev.e_value == K_ESCAPE)\n                                        return (0);\n                        }\n                        vip->rlast = ev.e_c;\n                        vip->rvalue = ev.e_value;\n                        break;\n                case E_ERR:\n                case E_EOF:\n                        F_SET(sp, SC_EXIT_FORCE);\n                        return (1);\n                case E_INTERRUPT:\n                        /* <interrupt> means they changed their minds. */\n                        return (0);\n                case E_WRESIZE:\n                        /* <resize> interrupts the input mode. */\n                        sp->gp->scr_imctrl(sp, IMCTRL_OFF);\n                        v_emsg(sp, NULL, VIM_WRESIZE);\n                        return (0);\n                case E_REPAINT:\n                        if (vs_repaint(sp, &ev))\n                                return (1);\n                        goto next;\n                default:\n                        v_event_err(sp, &ev);\n                        return (0);\n                }\n        }\n\n        /* Copy the line. */\n        GET_SPACE_RET(sp, bp, blen, len);\n        memmove(bp, p, len);\n        p = bp;\n\n        /*\n         * Versions of nvi before 1.57 created N new lines when they replaced\n         * N characters with <carriage-return> or <newline> characters.  This\n         * is different from the historic vi, which replaced N characters with\n         * a single new line.  Users complained, so we match historic practice.\n         */\n        if ((!quote && vip->rvalue == K_CR) || vip->rvalue == K_NL) {\n                /* Set return line. */\n                vp->m_stop.lno = vp->m_start.lno + 1;\n                vp->m_stop.cno = 0;\n\n                /* The first part of the current line. */\n                if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno))\n                        goto err_ret;\n\n                /*\n                 * The rest of the current line.  And, of course, now it gets\n                 * tricky.  If there are characters left in the line and if\n                 * the autoindent edit option is set, white space after the\n                 * replaced character is discarded, autoindent is applied, and\n                 * the cursor moves to the last indent character.\n                 */\n                p += vp->m_start.cno + cnt;\n                len -= vp->m_start.cno + cnt;\n                if (len != 0 && O_ISSET(sp, O_AUTOINDENT))\n                        for (; len && isblank(*p); --len, ++p);\n\n                if ((tp = text_init(sp, p, len, len)) == NULL)\n                        goto err_ret;\n\n                if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) {\n                        if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp))\n                                goto err_ret;\n                        vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0;\n                } else\n                        vp->m_stop.cno = 0;\n\n                vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0;\n                if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len))\nerr_ret:                rval = 1;\n                else {\n                        text_free(tp);\n                        rval = 0;\n                }\n        } else {\n                memset(bp + vp->m_start.cno, vip->rlast, cnt);\n                rval = db_set(sp, vp->m_start.lno, bp, len);\n        }\n        FREE_SPACE(sp, bp, blen);\n\n        vp->m_final = vp->m_stop;\n        return (rval);\n}\n"
  },
  {
    "path": "vi/v_right.c",
    "content": "/*      $OpenBSD: v_right.c,v 1.6 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_right -- [count]' ', [count]l\n *      Move right by columns.\n *\n * PUBLIC: int v_right(SCR *, VICMD *);\n */\nint\nv_right(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        int isempty;\n\n        if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) {\n                if (isempty)\n                        goto eol;\n                return (1);\n        }\n\n        /* It's always illegal to move right on empty lines. */\n        if (len == 0) {\neol:            v_eol(sp, NULL);\n                return (1);\n        }\n\n        /*\n         * Non-motion commands move to the end of the range.  Delete and\n         * yank stay at the start.  Ignore others.  Adjust the end of the\n         * range for motion commands.\n         *\n         * !!!\n         * Historically, \"[cdsy]l\" worked at the end of a line.  Also,\n         * EOL is a count sink.\n         */\n        vp->m_stop.cno = vp->m_start.cno +\n            (F_ISSET(vp, VC_C1SET) ? vp->count : 1);\n        if (vp->m_start.cno == len - 1 && !ISMOTION(vp)) {\n                v_eol(sp, NULL);\n                return (1);\n        }\n        if (vp->m_stop.cno >= len) {\n                vp->m_stop.cno = len - 1;\n                vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        } else if (ISMOTION(vp)) {\n                --vp->m_stop.cno;\n                vp->m_final = vp->m_start;\n        } else\n                vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_dollar -- [count]$\n *      Move to the last column.\n *\n * PUBLIC: int v_dollar(SCR *, VICMD *);\n */\nint\nv_dollar(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        int isempty;\n\n        /*\n         * !!!\n         * A count moves down count - 1 rows, so, \"3$\" is the same as \"2j$\".\n         */\n        if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) {\n                /*\n                 * !!!\n                 * Historically, if the $ is a motion, and deleting from\n                 * at or before the first non-blank of the line, it's a\n                 * line motion, and the line motion flag is set.\n                 */\n                vp->m_stop.cno = 0;\n                if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno))\n                        return (1);\n                if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno)\n                        F_SET(vp, VM_LMODE);\n\n                --vp->count;\n                if (v_down(sp, vp))\n                        return (1);\n        }\n\n        /*\n         * !!!\n         * Historically, it was illegal to use $ as a motion command on\n         * an empty line.  Unfortunately, even though C was historically\n         * aliased to c$, it (and not c$) was special cased to work on\n         * empty lines.  Since we alias C to c$ too, we have a problem.\n         * To fix it, we let c$ go through, on the assumption that it's\n         * not a problem for it to work.\n         */\n        if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) {\n                if (!isempty)\n                        return (1);\n                len = 0;\n        }\n\n        if (len == 0) {\n                if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) {\n                        v_eol(sp, NULL);\n                        return (1);\n                }\n                return (0);\n        }\n\n        /*\n         * Non-motion commands move to the end of the range.  Delete\n         * and yank stay at the start.  Ignore others.\n         */\n        vp->m_stop.cno = len ? len - 1 : 0;\n        vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_screen.c",
    "content": "/*      $OpenBSD: v_screen.c,v 1.9 2016/01/06 22:28:52 millert Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_screen -- ^W\n *      Switch screens.\n *\n * PUBLIC: int v_screen(SCR *, VICMD *);\n */\nint\nv_screen(SCR *sp, VICMD *vp)\n{\n        /*\n         * You can't leave a colon command-line edit window -- it's not that\n         * it won't work, but it gets real weird, real fast when you execute\n         * a colon command out of a window that was forked from a window that's\n         * now backgrounded...  You get the idea.\n         */\n        if (F_ISSET(sp, SC_COMEDIT)) {\n                msgq(sp, M_ERR,\n                    \"Enter <CR> to execute a command, :q to exit\");\n                return (1);\n        }\n\n        /*\n         * Try for the next lower screen, or, go back to the first\n         * screen on the stack.\n         */\n        if (TAILQ_NEXT(sp, q))\n                sp->nextdisp = TAILQ_NEXT(sp, q);\n        else if (TAILQ_FIRST(&sp->gp->dq) == sp) {\n                msgq(sp, M_ERR, \"No other screen to switch to\");\n                return (1);\n        } else\n                sp->nextdisp = TAILQ_FIRST(&sp->gp->dq);\n\n        F_SET(sp->nextdisp, SC_STATUS);\n        F_SET(sp, SC_SSWITCH | SC_STATUS);\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_scroll.c",
    "content": "/*      $OpenBSD: v_scroll.c,v 1.10 2015/01/16 06:40:14 deraadt Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\nstatic void goto_adjust(VICMD *);\n\n/*\n * The historic vi had a problem in that all movements were by physical\n * lines, not by logical, or screen lines.  Arguments can be made that this\n * is the right thing to do.  For example, single line movements, such as\n * 'j' or 'k', should probably work on physical lines.  Commands like \"dj\",\n * or \"j.\", where '.' is a change command, make more sense for physical lines\n * than they do for logical lines.\n *\n * These arguments, however, don't apply to scrolling commands like ^D and\n * ^F -- if the window is fairly small, using physical lines can result in\n * a half-page scroll repainting the entire screen, which is not what the\n * user wanted.  Second, if the line is larger than the screen, using physical\n * lines can make it impossible to display parts of the line -- there aren't\n * any commands that don't display the beginning of the line in historic vi,\n * and if both the beginning and end of the line can't be on the screen at\n * the same time, you lose.  This is even worse in the case of the H, L, and\n * M commands -- for large lines, they may all refer to the same line and\n * will result in no movement at all.\n *\n * Another issue is that page and half-page scrolling commands historically\n * moved to the first non-blank character in the new line.  If the line is\n * approximately the same size as the screen, this loses because the cursor\n * before and after a ^D, may refer to the same location on the screen.  In\n * this implementation, scrolling commands set the cursor to the first non-\n * blank character if the line changes because of the scroll.  Otherwise,\n * the cursor is left alone.\n *\n * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the\n * cursor positioning commands (H, L, M) commands using logical lines, not\n * physical.\n */\n\n/*\n * v_lgoto -- [count]G\n *      Go to first non-blank character of the line count, the last line\n *      of the file by default.\n *\n * PUBLIC: int v_lgoto(SCR *, VICMD *);\n */\nint\nv_lgoto(SCR *sp, VICMD *vp)\n{\n        recno_t nlines;\n\n        if (F_ISSET(vp, VC_C1SET)) {\n                if (!db_exist(sp, vp->count)) {\n                        /*\n                         * !!!\n                         * Historically, 1G was legal in an empty file.\n                         */\n                        if (vp->count == 1) {\n                                if (db_last(sp, &nlines))\n                                        return (1);\n                                if (nlines == 0)\n                                        return (0);\n                        }\n                        v_eof(sp, &vp->m_start);\n                        return (1);\n                }\n                vp->m_stop.lno = vp->count;\n        } else {\n                if (db_last(sp, &nlines))\n                        return (1);\n                vp->m_stop.lno = nlines ? nlines : 1;\n        }\n        goto_adjust(vp);\n        return (0);\n}\n\n/*\n * v_home -- [count]H\n *      Move to the first non-blank character of the logical line\n *      count - 1 from the top of the screen, 0 by default.\n *\n * PUBLIC: int v_home(SCR *, VICMD *);\n */\nint\nv_home(SCR *sp, VICMD *vp)\n{\n        if (vs_sm_position(sp, &vp->m_stop,\n            F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP))\n                return (1);\n        goto_adjust(vp);\n        return (0);\n}\n\n/*\n * v_middle -- M\n *      Move to the first non-blank character of the logical line\n *      in the middle of the screen.\n *\n * PUBLIC: int v_middle(SCR *, VICMD *);\n */\nint\nv_middle(SCR *sp, VICMD *vp)\n{\n        /*\n         * Yielding to none in our quest for compatibility with every\n         * historical blemish of vi, no matter how strange it might be,\n         * we permit the user to enter a count and then ignore it.\n         */\n        if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE))\n                return (1);\n        goto_adjust(vp);\n        return (0);\n}\n\n/*\n * v_bottom -- [count]L\n *      Move to the first non-blank character of the logical line\n *      count - 1 from the bottom of the screen, 0 by default.\n *\n * PUBLIC: int v_bottom(SCR *, VICMD *);\n */\nint\nv_bottom(SCR *sp, VICMD *vp)\n{\n        if (vs_sm_position(sp, &vp->m_stop,\n            F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM))\n                return (1);\n        goto_adjust(vp);\n        return (0);\n}\n\nstatic void\ngoto_adjust(VICMD *vp)\n{\n        /* Guess that it's the end of the range. */\n        vp->m_final = vp->m_stop;\n\n        /*\n         * Non-motion commands move the cursor to the end of the range, and\n         * then to the NEXT nonblank of the line.  Historic vi always moved\n         * to the first nonblank in the line; since the H, M, and L commands\n         * are logical motions in this implementation, we do the next nonblank\n         * so that it looks approximately the same to the user.  To make this\n         * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table.\n         *\n         * If it's a motion, it's more complicated.  The best possible solution\n         * is probably to display the first nonblank of the line the cursor\n         * will eventually rest on.  This is tricky, particularly given that if\n         * the associated command is a delete, we don't yet know what line that\n         * will be.  So, we clear the VM_RCM_SETNNB flag, and set the first\n         * nonblank flag (VM_RCM_SETFNB).  Note, if the lines are sufficiently\n         * long, this can cause the cursor to warp out of the screen.  It's too\n         * hard to fix.\n         *\n         * XXX\n         * The G command is always first nonblank, so it's okay to reset it.\n         */\n        if (ISMOTION(vp)) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SETFNB);\n        } else\n                return;\n\n        /*\n         * If moving backward in the file, delete and yank move to the end\n         * of the range, unless the line didn't change, in which case yank\n         * doesn't move.  If moving forward in the file, delete and yank\n         * stay at the start of the range.  Ignore others.\n         */\n        if (vp->m_stop.lno < vp->m_start.lno ||\n            (vp->m_stop.lno == vp->m_start.lno &&\n            vp->m_stop.cno < vp->m_start.cno)) {\n                if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno)\n                        vp->m_final = vp->m_start;\n        } else\n                vp->m_final = vp->m_start;\n}\n\n/*\n * v_up -- [count]^P, [count]k, [count]-\n *      Move up by lines.\n *\n * PUBLIC: int v_up(SCR *, VICMD *);\n */\nint\nv_up(SCR *sp, VICMD *vp)\n{\n        recno_t lno;\n\n        lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        if (vp->m_start.lno <= lno) {\n                v_sof(sp, &vp->m_start);\n                return (1);\n        }\n        vp->m_stop.lno = vp->m_start.lno - lno;\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_cr -- [count]^M\n *      In a script window, send the line to the shell.\n *      In a regular window, move down by lines.\n *\n * PUBLIC: int v_cr(SCR *, VICMD *);\n */\nint\nv_cr(SCR *sp, VICMD *vp)\n{\n        /* If it's a colon command-line edit window, it's an ex command. */\n        if (F_ISSET(sp, SC_COMEDIT))\n                return (v_ecl_exec(sp));\n\n        /* If it's a script window, exec the line. */\n        if (F_ISSET(sp, SC_SCRIPT))\n                return (sscr_exec(sp, vp->m_start.lno));\n\n        /* Otherwise, it's the same as v_down(). */\n        return (v_down(sp, vp));\n}\n\n/*\n * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+\n *      Move down by lines.\n *\n * PUBLIC: int v_down(SCR *, VICMD *);\n */\nint\nv_down(SCR *sp, VICMD *vp)\n{\n        recno_t lno;\n\n        lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1);\n        if (!db_exist(sp, lno)) {\n                v_eof(sp, &vp->m_start);\n                return (1);\n        }\n        vp->m_stop.lno = lno;\n        vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        return (0);\n}\n\n/*\n * v_hpageup -- [count]^U\n *      Page up half screens.\n *\n * PUBLIC: int v_hpageup(SCR *, VICMD *);\n */\nint\nv_hpageup(SCR *sp, VICMD *vp)\n{\n        /*\n         * Half screens always succeed unless already at SOF.\n         *\n         * !!!\n         * Half screens set the scroll value, even if the command\n         * ultimately failed, in historic vi.  Probably a don't care.\n         */\n        if (F_ISSET(vp, VC_C1SET))\n                sp->defscroll = vp->count;\n        if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U))\n                return (1);\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_hpagedown -- [count]^D\n *      Page down half screens.\n *\n * PUBLIC: int v_hpagedown(SCR *, VICMD *);\n */\nint\nv_hpagedown(SCR *sp, VICMD *vp)\n{\n        /*\n         * Half screens always succeed unless already at EOF.\n         *\n         * !!!\n         * Half screens set the scroll value, even if the command\n         * ultimately failed, in historic vi.  Probably a don't care.\n         */\n        if (F_ISSET(vp, VC_C1SET))\n                sp->defscroll = vp->count;\n        if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D))\n                return (1);\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_pagedown -- [count]^F\n *      Page down full screens.\n * !!!\n * Historic vi did not move to the EOF if the screen couldn't move, i.e.\n * if EOF was already displayed on the screen.  This implementation does\n * move to EOF in that case, making ^F more like the historic ^D.\n *\n * PUBLIC: int v_pagedown(SCR *, VICMD *);\n */\nint\nv_pagedown(SCR *sp, VICMD *vp)\n{\n        recno_t offset;\n\n        /*\n         * !!!\n         * The calculation in IEEE Std 1003.2-1992 (POSIX) is:\n         *\n         *      top_line = top_line + count * (window - 2);\n         *\n         * which was historically wrong.  The correct one is:\n         *\n         *      top_line = top_line + count * window - 2;\n         *\n         * i.e. the two line \"overlap\" was only subtracted once.  Which\n         * makes no sense, but then again, an overlap makes no sense for\n         * any screen but the \"next\" one anyway.  We do it the historical\n         * way as there's no good reason to change it.\n         *\n         * If the screen has been split, use the smaller of the current\n         * window size and the window option value.\n         *\n         * It possible for this calculation to be less than 1; move at\n         * least one line.\n         */\n        offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ?\n            MINIMUM(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW));\n        offset = offset <= 2 ? 1 : offset - 2;\n        if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F))\n                return (1);\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_pageup -- [count]^B\n *      Page up full screens.\n *\n * !!!\n * Historic vi did not move to the SOF if the screen couldn't move, i.e.\n * if SOF was already displayed on the screen.  This implementation does\n * move to SOF in that case, making ^B more like the historic ^U.\n *\n * PUBLIC: int v_pageup(SCR *, VICMD *);\n */\nint\nv_pageup(SCR *sp, VICMD *vp)\n{\n        recno_t offset;\n\n        /*\n         * !!!\n         * The calculation in IEEE Std 1003.2-1992 (POSIX) is:\n         *\n         *      top_line = top_line - count * (window - 2);\n         *\n         * which was historically wrong.  The correct one is:\n         *\n         *      top_line = (top_line - count * window) + 2;\n         *\n         * A simpler expression is that, as with ^F, we scroll exactly:\n         *\n         *      count * window - 2\n         *\n         * lines.\n         *\n         * Bizarre.  As with ^F, an overlap makes no sense for anything\n         * but the first screen.  We do it the historical way as there's\n         * no good reason to change it.\n         *\n         * If the screen has been split, use the smaller of the current\n         * window size and the window option value.\n         *\n         * It possible for this calculation to be less than 1; move at\n         * least one line.\n         */\n        offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ?\n            MINIMUM(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW));\n        offset = offset <= 2 ? 1 : offset - 2;\n        if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B))\n                return (1);\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_lineup -- [count]^Y\n *      Page up by lines.\n *\n * PUBLIC: int v_lineup(SCR *, VICMD *);\n */\nint\nv_lineup(SCR *sp, VICMD *vp)\n{\n        /*\n         * The cursor moves down, staying with its original line, unless it\n         * reaches the bottom of the screen.\n         */\n        if (vs_sm_scroll(sp,\n            &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y))\n                return (1);\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_linedown -- [count]^E\n *      Page down by lines.\n *\n * PUBLIC: int v_linedown(SCR *, VICMD *);\n */\nint\nv_linedown(SCR *sp, VICMD *vp)\n{\n        /*\n         * The cursor moves up, staying with its original line, unless it\n         * reaches the top of the screen.\n         */\n        if (vs_sm_scroll(sp,\n            &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E))\n                return (1);\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_search.c",
    "content": "/*      $OpenBSD: v_search.c,v 1.14 2016/01/06 22:28:52 millert Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic int v_exaddr(SCR *, VICMD *, dir_t);\nstatic int v_search(SCR *, VICMD *, char *, size_t, unsigned int, dir_t);\n\n/*\n * v_srch -- [count]?RE[? offset]\n *      Ex address search backward.\n *\n * PUBLIC: int v_searchb(SCR *, VICMD *);\n */\nint\nv_searchb(SCR *sp, VICMD *vp)\n{\n        return (v_exaddr(sp, vp, BACKWARD));\n}\n\n/*\n * v_searchf -- [count]/RE[/ offset]\n *      Ex address search forward.\n *\n * PUBLIC: int v_searchf(SCR *, VICMD *);\n */\nint\nv_searchf(SCR *sp, VICMD *vp)\n{\n        return (v_exaddr(sp, vp, FORWARD));\n}\n\n/*\n * v_exaddr --\n *      Do a vi search (which is really an ex address).\n */\nstatic int\nv_exaddr(SCR *sp, VICMD *vp, dir_t dir)\n{\n        static EXCMDLIST fake = { \"search\", NULL, 0, NULL, NULL, NULL };\n        EXCMD *cmdp;\n        GS *gp;\n        TEXT *tp;\n        recno_t s_lno;\n        size_t len, s_cno, tlen;\n        int err, nb, type;\n        char *cmd, *t, buf[20];\n\n        /*\n         * !!!\n         * If using the search command as a motion, any addressing components\n         * are lost, i.e. y/ptrn/+2, when repeated, is the same as y/ptrn/.\n         */\n        if (F_ISSET(vp, VC_ISDOT))\n                return (v_search(sp, vp,\n                    NULL, 0, SEARCH_PARSE | SEARCH_MSG | SEARCH_SET, dir));\n\n        /* Get the search pattern. */\n        if (v_tcmd(sp, vp, dir == BACKWARD ? CH_BSEARCH : CH_FSEARCH,\n            TXT_BS | TXT_CR | TXT_ESCAPE | TXT_PROMPT |\n            (O_ISSET(sp, O_SEARCHINCR) ? TXT_SEARCHINCR : 0)))\n                return (1);\n\n        tp = TAILQ_FIRST(&sp->tiq);\n\n        /* If the user backspaced over the prompt, do nothing. */\n        if (tp->term == TERM_BS)\n                return (1);\n\n        /*\n         * If the user was doing an incremental search, then we've already\n         * updated the cursor and moved to the right location.  Return the\n         * correct values, we're done.\n         */\n        if (tp->term == TERM_SEARCH) {\n                vp->m_stop.lno = sp->lno;\n                vp->m_stop.cno = sp->cno;\n                if (ISMOTION(vp))\n                        return (v_correct(sp, vp, 0));\n                vp->m_final = vp->m_stop;\n                return (0);\n        }\n\n        /*\n         * If the user entered <escape> or <carriage-return>, the length is\n         * 1 and the right thing will happen, i.e. the prompt will be used\n         * as a command character.\n         *\n         * Build a fake ex command structure.\n         */\n        gp = sp->gp;\n        gp->excmd.cp = tp->lb;\n        gp->excmd.clen = tp->len;\n        F_INIT(&gp->excmd, E_VISEARCH);\n\n        /*\n         * XXX\n         * Warn if the search wraps.  This is a pretty special case, but it's\n         * nice feature that wasn't in the original implementations of ex/vi.\n         * (It was added at some point to System V's version.)  This message\n         * is only displayed if there are no keys in the queue. The problem is\n         * the command is going to succeed, and the message is informational,\n         * not an error.  If a macro displays it repeatedly, e.g., the pattern\n         * only occurs once in the file and wrapscan is set, you lose big.  For\n         * example, if the macro does something like:\n         *\n         *      :map K /pattern/^MjK\n         *\n         * Each search will display the message, but the following \"/pattern/\"\n         * will immediately overwrite it, with strange results.  The System V\n         * vi displays the \"wrapped\" message multiple times, but because it's\n         * overwritten each time, it's not as noticeable.  As we don't discard\n         * messages, it's a real problem for us.\n         */\n        if (!KEYS_WAITING(sp))\n                F_SET(&gp->excmd, E_SEARCH_WMSG);\n\n        /* Save the current line/column. */\n        s_lno = sp->lno;\n        s_cno = sp->cno;\n\n        /*\n         * !!!\n         * Historically, vi / and ? commands were full-blown ex addresses,\n         * including ';' delimiters, trailing <blank>'s, multiple search\n         * strings (separated by semi-colons) and, finally, full-blown z\n         * commands after the / and ? search strings.  (If the search was\n         * being used as a motion, the trailing z command was ignored.\n         * Also, we do some argument checking on the z command, to be sure\n         * that it's not some other random command.) For multiple search\n         * strings, leading <blank>'s at the second and subsequent strings\n         * were eaten as well.  This has some (unintended?) side-effects:\n         * the command /ptrn/;3 is legal and results in moving to line 3.\n         * I suppose you could use it to optionally move to line 3...\n         *\n         * !!!\n         * Historically, if any part of the search command failed, the cursor\n         * remained unmodified (even if ; was used).  We have to play games\n         * because the underlying ex parser thinks we're modifying the cursor\n         * as we go, but I think we're compatible with historic practice.\n         *\n         * !!!\n         * Historically, the command \"/STRING/;   \" failed, apparently it\n         * confused the parser.  We're not that compatible.\n         */\n        cmdp = &gp->excmd;\n        if (ex_range(sp, cmdp, &err))\n                return (1);\n\n        /*\n         * Remember where any remaining command information is, and clean\n         * up the fake ex command.\n         */\n        cmd = cmdp->cp;\n        len = cmdp->clen;\n        gp->excmd.clen = 0;\n\n        if (err)\n                goto err2;\n\n        /* Copy out the new cursor position and make sure it's okay. */\n        switch (cmdp->addrcnt) {\n        case 1:\n                vp->m_stop = cmdp->addr1;\n                break;\n        case 2:\n                vp->m_stop = cmdp->addr2;\n                break;\n        }\n        if (!db_exist(sp, vp->m_stop.lno)) {\n                ex_badaddr(sp, &fake,\n                    vp->m_stop.lno == 0 ? A_ZERO : A_EOF, NUM_OK);\n                goto err2;\n        }\n\n        /*\n         * !!!\n         * Historic practice is that a trailing 'z' was ignored if it was a\n         * motion command.  Should probably be an error, but not worth the\n         * effort.\n         */\n        if (ISMOTION(vp))\n                return (v_correct(sp, vp, F_ISSET(cmdp, E_DELTA)));\n\n        /*\n         * !!!\n         * Historically, if it wasn't a motion command, a delta in the search\n         * pattern turns it into a first nonblank movement.\n         */\n        nb = F_ISSET(cmdp, E_DELTA);\n\n        /* Check for the 'z' command. */\n        if (len != 0) {\n                if (*cmd != 'z')\n                        goto err1;\n\n                /* No blanks, just like the z command. */\n                for (t = cmd + 1, tlen = len - 1; tlen > 0; ++t, --tlen)\n                        if (!isdigit(*t))\n                                break;\n                if (tlen &&\n                    (*t == '-' || *t == '.' || *t == '+' || *t == '^')) {\n                        ++t;\n                        --tlen;\n                        type = 1;\n                } else\n                        type = 0;\n                if (tlen)\n                        goto err1;\n\n                /* The z command will do the nonblank for us. */\n                nb = 0;\n\n                /* Default to z+. */\n                if (!type &&\n                    v_event_push(sp, NULL, \"+\", 1, CH_NOMAP | CH_QUOTED))\n                        return (1);\n\n                /* Push the user's command. */\n                if (v_event_push(sp, NULL, cmd, len, CH_NOMAP | CH_QUOTED))\n                        return (1);\n\n                /* Push line number so get correct z display. */\n                tlen = snprintf(buf,\n                    sizeof(buf), \"%lu\", (unsigned long)vp->m_stop.lno);\n                if (tlen >= sizeof(buf))\n                        tlen = sizeof(buf) - 1;\n                if (v_event_push(sp, NULL, buf, tlen, CH_NOMAP | CH_QUOTED))\n                        return (1);\n\n                /* Don't refresh until after 'z' happens. */\n                F_SET(VIP(sp), VIP_S_REFRESH);\n        }\n\n        /* Non-motion commands move to the end of the range. */\n        vp->m_final = vp->m_stop;\n        if (nb) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SETFNB);\n        }\n        return (0);\n\nerr1:   msgq(sp, M_ERR,\n            \"Characters after search string, line offset and/or z command\");\nerr2:   vp->m_final.lno = s_lno;\n        vp->m_final.cno = s_cno;\n        return (1);\n}\n\n/*\n * v_searchN -- N\n *      Reverse last search.\n *\n * PUBLIC: int v_searchN(SCR *, VICMD *);\n */\nint\nv_searchN(SCR *sp, VICMD *vp)\n{\n        dir_t dir;\n\n        switch (sp->searchdir) {\n        case BACKWARD:\n                dir = FORWARD;\n                break;\n        case FORWARD:\n                dir = BACKWARD;\n                break;\n        default:\n                dir = sp->searchdir;\n                break;\n        }\n        return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, dir));\n}\n\n/*\n * v_searchn -- n\n *      Repeat last search.\n *\n * PUBLIC: int v_searchn(SCR *, VICMD *);\n */\nint\nv_searchn(SCR *sp, VICMD *vp)\n{\n        return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, sp->searchdir));\n}\n\n/*\n * v_searchw -- [count]^A\n *      Search for the word under the cursor.\n *\n * PUBLIC: int v_searchw(SCR *, VICMD *);\n */\nint\nv_searchw(SCR *sp, VICMD *vp)\n{\n        size_t blen, len;\n        int rval;\n        char *bp;\n\n        len = VIP(sp)->klen + sizeof(RE_WSTART) + sizeof(RE_WSTOP);\n        GET_SPACE_RET(sp, bp, blen, len);\n        len = snprintf(bp, blen, \"%s%s%s\", RE_WSTART, VIP(sp)->keyw, RE_WSTOP);\n        if (len >= blen)\n                len = blen - 1;\n\n        rval = v_search(sp, vp, bp, len, SEARCH_SET, FORWARD);\n\n        FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n\n/*\n * v_search --\n *      The search commands.\n */\nstatic int\nv_search(SCR *sp, VICMD *vp, char *ptrn, size_t plen, unsigned int flags, dir_t dir)\n{\n        /* Display messages. */\n        LF_SET(SEARCH_MSG);\n\n        /* If it's a motion search, offset past end-of-line is okay. */\n        if (ISMOTION(vp))\n                LF_SET(SEARCH_EOL);\n\n        /*\n         * XXX\n         * Warn if the search wraps.  See the comment above, in v_exaddr().\n         */\n        if (!KEYS_WAITING(sp))\n                LF_SET(SEARCH_WMSG);\n\n        switch (dir) {\n        case BACKWARD:\n                if (b_search(sp,\n                    &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags))\n                        return (1);\n                break;\n        case FORWARD:\n                if (f_search(sp,\n                    &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags))\n                        return (1);\n                break;\n        case NOTSET:\n                msgq(sp, M_ERR, \"No previous search pattern\");\n                return (1);\n        default:\n                abort();\n        }\n\n        /* Correct motion commands, otherwise, simply move to the location. */\n        if (ISMOTION(vp)) {\n                if (v_correct(sp, vp, 0))\n                        return(1);\n        } else\n                vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_correct --\n *      Handle command with a search as the motion.\n *\n * !!!\n * Historically, commands didn't affect the line searched to/from if the\n * motion command was a search and the final position was the start/end\n * of the line.  There were some special cases and vi was not consistent;\n * it was fairly easy to confuse it.  For example, given the two lines:\n *\n *      abcdefghi\n *      ABCDEFGHI\n *\n * placing the cursor on the 'A' and doing y?$ would so confuse it that 'h'\n * 'k' and put would no longer work correctly.  In any case, we try to do\n * the right thing, but it's not going to exactly match historic practice.\n *\n * PUBLIC: int v_correct(SCR *, VICMD *, int);\n */\nint\nv_correct(SCR *sp, VICMD *vp, int isdelta)\n{\n        MARK m;\n        size_t len;\n\n        /*\n         * !!!\n         * We may have wrapped if wrapscan was set, and we may have returned\n         * to the position where the cursor started.  Historic vi didn't cope\n         * with this well.  Yank wouldn't beep, but the first put after the\n         * yank would move the cursor right one column (without adding any\n         * text) and the second would put a copy of the current line.  The\n         * change and delete commands would beep, but would leave the cursor\n         * on the colon command line.  I believe that there are macros that\n         * depend on delete, at least, failing.  For now, commands that use\n         * search as a motion component fail when the search returns to the\n         * original cursor position.\n         */\n        if (vp->m_start.lno == vp->m_stop.lno &&\n            vp->m_start.cno == vp->m_stop.cno) {\n                msgq(sp, M_BERR, \"Search wrapped to original position\");\n                return (1);\n        }\n\n        /*\n         * !!!\n         * Searches become line mode operations if there was a delta specified\n         * to the search pattern.\n         */\n        if (isdelta)\n                F_SET(vp, VM_LMODE);\n\n        /*\n         * If the motion is in the reverse direction, switch the start and\n         * stop MARK's so that it's in a forward direction.  (There's no\n         * reason for this other than to make the tests below easier.  The\n         * code in vi.c:vi() would have done the switch.)  Both forward\n         * and backward motions can happen for any kind of search command\n         * because of the wrapscan option.\n         */\n        if (vp->m_start.lno > vp->m_stop.lno ||\n            (vp->m_start.lno == vp->m_stop.lno &&\n            vp->m_start.cno > vp->m_stop.cno)) {\n                m = vp->m_start;\n                vp->m_start = vp->m_stop;\n                vp->m_stop = m;\n        }\n\n        /*\n         * BACKWARD:\n         *      Delete and yank commands move to the end of the range.\n         *      Ignore others.\n         *\n         * FORWARD:\n         *      Delete and yank commands don't move.  Ignore others.\n         */\n        vp->m_final = vp->m_start;\n\n        /*\n         * !!!\n         * Delta'd searches don't correct based on column positions.\n         */\n        if (isdelta)\n                return (0);\n\n        /*\n         * !!!\n         * Backward searches starting at column 0, and forward searches ending\n         * at column 0 are corrected to the last column of the previous line.\n         * Otherwise, adjust the starting/ending point to the character before\n         * the current one (this is safe because we know the search had to move\n         * to succeed).\n         *\n         * Searches become line mode operations if they start at the first\n         * nonblank and end at column 0 of another line.\n         */\n        if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) {\n                if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len))\n                        return (1);\n                vp->m_stop.cno = len ? len - 1 : 0;\n                len = 0;\n                if (nonblank(sp, vp->m_start.lno, &len))\n                        return (1);\n                if (vp->m_start.cno <= len)\n                        F_SET(vp, VM_LMODE);\n        } else\n                --vp->m_stop.cno;\n\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_section.c",
    "content": "/*      $OpenBSD: v_section.c,v 1.7 2014/11/12 04:28:41 bentley Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * !!!\n * In historic vi, the section commands ignored empty lines, unlike the\n * paragraph commands, which was probably okay.  However, they also moved\n * to the start of the last line when there where no more sections instead\n * of the end of the last line like the paragraph commands.  I've changed\n * the latter behavior to match the paragraph commands.\n *\n * In historic vi, a section was defined as the first character(s) of the\n * line matching, which could be followed by anything.  This implementation\n * follows that historic practice.\n *\n * !!!\n * The historic vi documentation (USD:15-10) claimed:\n *      The section commands interpret a preceding count as a different\n *      window size in which to redraw the screen at the new location,\n *      and this window size is the base size for newly drawn windows\n *      until another size is specified.  This is very useful if you are\n *      on a slow terminal ...\n *\n * I can't get the 4BSD vi to do this, it just beeps at me.  For now, a\n * count to the section commands simply repeats the command.\n */\n\n/*\n * v_sectionf -- [count]]]\n *      Move forward count sections/functions.\n *\n * !!!\n * Using ]] as a motion command was a bit special, historically.  It could\n * match } as well as the usual { and section values.  If it matched a { or\n * a section, it did NOT include the matched line.  If it matched a }, it\n * did include the line.  No clue why.\n *\n * PUBLIC: int v_sectionf(SCR *, VICMD *);\n */\nint\nv_sectionf(SCR *sp, VICMD *vp)\n{\n        recno_t cnt, lno;\n        size_t len;\n        char *p, *list, *lp;\n\n        /* Get the macro list. */\n        if ((list = O_STR(sp, O_SECTIONS)) == NULL)\n                return (1);\n\n        /*\n         * !!!\n         * If the starting cursor position is at or before any non-blank\n         * characters in the line, i.e. the movement is cutting all of the\n         * line's text, the buffer is in line mode.  It's a lot easier to\n         * check here, because we know that the end is going to be the start\n         * or end of a line.\n         */\n        if (ISMOTION(vp)) {\n                if (vp->m_start.cno == 0)\n                        F_SET(vp, VM_LMODE);\n                else {\n                        vp->m_stop = vp->m_start;\n                        vp->m_stop.cno = 0;\n                        if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))\n                                return (1);\n                        if (vp->m_start.cno <= vp->m_stop.cno)\n                                F_SET(vp, VM_LMODE);\n                }\n        }\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) {\n                if (len == 0)\n                        continue;\n                if (p[0] == '{' || (ISMOTION(vp) && p[0] == '}')) {\n                        if (!--cnt) {\n                                if (p[0] == '{')\n                                        goto adjust1;\n                                goto adjust2;\n                        }\n                        continue;\n                }\n                /*\n                 * !!!\n                 * Historic documentation (USD:15-11, 4.2) said that formfeed\n                 * characters (^L) in the first column delimited sections.\n                 * The historic code mentions formfeed characters, but never\n                 * implements them.  Seems reasonable, do it.\n                 */\n                if (p[0] == '\\014') {\n                        if (!--cnt)\n                                goto adjust1;\n                        continue;\n                }\n                if (p[0] != '.' || len < 2)\n                        continue;\n                for (lp = list; *lp != '\\0'; lp += 2 * sizeof(*lp))\n                        if (lp[0] == p[1] &&\n                            ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) &&\n                            !--cnt) {\n                                /*\n                                 * !!!\n                                 * If not cutting this line, adjust to the end\n                                 * of the previous one.  Otherwise, position to\n                                 * column 0.\n                                 */\nadjust1:                        if (ISMOTION(vp))\n                                        goto ret1;\n\nadjust2:                        vp->m_stop.lno = lno;\n                                vp->m_stop.cno = 0;\n                                goto ret2;\n                        }\n        }\n\n        /* If moving forward, reached EOF, check to see if we started there. */\n        if (vp->m_start.lno == lno - 1) {\n                v_eof(sp, NULL);\n                return (1);\n        }\n\nret1:   if (db_get(sp, --lno, DBG_FATAL, NULL, &len))\n                return (1);\n        vp->m_stop.lno = lno;\n        vp->m_stop.cno = len ? len - 1 : 0;\n\n        /*\n         * Non-motion commands go to the end of the range.  Delete and\n         * yank stay at the start of the range.  Ignore others.\n         */\nret2:   if (ISMOTION(vp)) {\n                vp->m_final = vp->m_start;\n                if (F_ISSET(vp, VM_LMODE))\n                        vp->m_final.cno = 0;\n        } else\n                vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_sectionb -- [count][[\n *      Move backward count sections/functions.\n *\n * PUBLIC: int v_sectionb(SCR *, VICMD *);\n */\nint\nv_sectionb(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        recno_t cnt, lno;\n        char *p, *list, *lp;\n\n        /* An empty file or starting from line 1 is always illegal. */\n        if (vp->m_start.lno <= 1) {\n                v_sof(sp, NULL);\n                return (1);\n        }\n\n        /* Get the macro list. */\n        if ((list = O_STR(sp, O_SECTIONS)) == NULL)\n                return (1);\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        for (lno = vp->m_start.lno; !db_get(sp, --lno, 0, &p, &len);) {\n                if (len == 0)\n                        continue;\n                if (p[0] == '{') {\n                        if (!--cnt)\n                                goto adjust1;\n                        continue;\n                }\n                /*\n                 * !!!\n                 * Historic documentation (USD:15-11, 4.2) said that formfeed\n                 * characters (^L) in the first column delimited sections.\n                 * The historic code mentions formfeed characters, but never\n                 * implements them.  Seems reasonable, do it.\n                 */\n                if (p[0] == '\\014') {\n                        if (!--cnt)\n                                goto adjust1;\n                        continue;\n                }\n                if (p[0] != '.' || len < 2)\n                        continue;\n                for (lp = list; *lp != '\\0'; lp += 2 * sizeof(*lp))\n                        if (lp[0] == p[1] &&\n                            ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) &&\n                            !--cnt) {\nadjust1:                        vp->m_stop.lno = lno;\n                                vp->m_stop.cno = 0;\n                                goto ret1;\n                        }\n        }\n\n        /*\n         * If moving backward, reached SOF, which is a movement sink.\n         * We already checked for starting there.\n         */\n        vp->m_stop.lno = 1;\n        vp->m_stop.cno = 0;\n\n        /*\n         * All commands move to the end of the range.\n         *\n         * !!!\n         * Historic practice is the section cut was in line mode if it started\n         * from column 0 and was in the backward direction.  Otherwise, left\n         * motion commands adjust the starting point to the character before\n         * the current one.  What makes this worse is that if it cut to line\n         * mode it also went to the first non-<blank>.\n         */\nret1:   if (vp->m_start.cno == 0) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SETFNB);\n\n                --vp->m_start.lno;\n                F_SET(vp, VM_LMODE);\n        } else\n                --vp->m_start.cno;\n\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_sentence.c",
    "content": "/*      $OpenBSD: v_sentence.c,v 1.8 2022/12/26 19:16:04 jmc Exp $  */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * !!!\n * In historic vi, a sentence was delimited by a '.', '?' or '!' character\n * followed by TWO spaces or a newline.  One or more empty lines was also\n * treated as a separate sentence.  The Berkeley documentation for historical\n * vi states that any number of ')', ']', '\"' and '\\'' characters can be\n * between the delimiter character and the spaces or end of line, however,\n * the historical implementation did not handle additional '\"' characters.\n * We follow the documentation here, not the implementation.\n *\n * Once again, historical vi didn't do sentence movements associated with\n * counts consistently, mostly in the presence of lines containing only\n * white-space characters.\n *\n * This implementation also permits a single tab to delimit sentences, and\n * treats lines containing only white-space characters as empty lines.\n * Finally, tabs are eaten (along with spaces) when skipping to the start\n * of the text following a \"sentence\".\n */\n\n/*\n * v_sentencef -- [count])\n *      Move forward count sentences.\n *\n * PUBLIC: int v_sentencef(SCR *, VICMD *);\n */\nint\nv_sentencef(SCR *sp, VICMD *vp)\n{\n        enum { BLANK, NONE, PERIOD } state;\n        VCS cs;\n        size_t len;\n        unsigned long cnt;\n\n        cs.cs_lno = vp->m_start.lno;\n        cs.cs_cno = vp->m_start.cno;\n        if (cs_init(sp, &cs))\n                return (1);\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n\n        /*\n         * !!!\n         * If in white-space, the next start of sentence counts as one.\n         * This may not handle \"  .  \" correctly, but it's real unclear\n         * what correctly means in that case.\n         */\n        if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && isblank(cs.cs_ch))) {\n                if (cs_fblank(sp, &cs))\n                        return (1);\n                if (--cnt == 0) {\n                        if (vp->m_start.lno != cs.cs_lno ||\n                            vp->m_start.cno != cs.cs_cno)\n                                goto okret;\n                        return (1);\n                }\n        }\n\n        for (state = NONE;;) {\n                if (cs_next(sp, &cs))\n                        return (1);\n                if (cs.cs_flags == CS_EOF)\n                        break;\n                if (cs.cs_flags == CS_EOL) {\n                        if ((state == PERIOD || state == BLANK) && --cnt == 0) {\n                                if (cs_next(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == 0 &&\n                                    isblank(cs.cs_ch) && cs_fblank(sp, &cs))\n                                        return (1);\n                                goto okret;\n                        }\n                        state = NONE;\n                        continue;\n                }\n                if (cs.cs_flags == CS_EMP) {    /* An EMP is two sentences. */\n                        if (--cnt == 0)\n                                goto okret;\n                        if (cs_fblank(sp, &cs))\n                                return (1);\n                        if (--cnt == 0)\n                                goto okret;\n                        state = NONE;\n                        continue;\n                }\n                switch (cs.cs_ch) {\n                case '.':\n                case '?':\n                case '!':\n                        state = PERIOD;\n                        break;\n                case ')':\n                case ']':\n                case '\"':\n                case '\\'':\n                        if (state != PERIOD)\n                                state = NONE;\n                        break;\n                case '\\t':\n                        if (state == PERIOD)\n                                state = BLANK;\n                        /* FALLTHROUGH */\n                case ' ':\n                        if (state == PERIOD) {\n                                state = BLANK;\n                                break;\n                        }\n                        if (state == BLANK && --cnt == 0) {\n                                if (cs_fblank(sp, &cs))\n                                        return (1);\n                                goto okret;\n                        }\n                        /* FALLTHROUGH */\n                default:\n                        state = NONE;\n                        break;\n                }\n        }\n\n        /* EOF is a movement sink, but it's an error not to have moved. */\n        if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) {\n                v_eof(sp, NULL);\n                return (1);\n        }\n\nokret:  vp->m_stop.lno = cs.cs_lno;\n        vp->m_stop.cno = cs.cs_cno;\n\n        /*\n         * !!!\n         * Historic, uh, features, yeah, that's right, call 'em features.\n         * If the starting and ending cursor positions are at the first\n         * column in their lines, i.e. the movement is cutting entire lines,\n         * the buffer is in line mode, and the ending position is the last\n         * character of the previous line.  Note check to make sure that\n         * it's not within a single line.\n         *\n         * Non-motion commands move to the end of the range.  Delete and\n         * yank stay at the start.  Ignore others.  Adjust the end of the\n         * range for motion commands.\n         */\n        if (ISMOTION(vp)) {\n                if (vp->m_start.cno == 0 &&\n                    (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {\n                        if (vp->m_start.lno < vp->m_stop.lno) {\n                                if (db_get(sp,\n                                    --vp->m_stop.lno, DBG_FATAL, NULL, &len))\n                                        return (1);\n                                vp->m_stop.cno = len ? len - 1 : 0;\n                        }\n                        F_SET(vp, VM_LMODE);\n                } else\n                        --vp->m_stop.cno;\n                vp->m_final = vp->m_start;\n        } else\n                vp->m_final = vp->m_stop;\n        return (0);\n}\n\n/*\n * v_sentenceb -- [count](\n *      Move backward count sentences.\n *\n * PUBLIC: int v_sentenceb(SCR *, VICMD *);\n */\nint\nv_sentenceb(SCR *sp, VICMD *vp)\n{\n        VCS cs;\n        recno_t slno;\n        size_t len, scno;\n        unsigned long cnt;\n        int last;\n\n        /*\n         * !!!\n         * Historic vi permitted the user to hit SOF repeatedly.\n         */\n        if (vp->m_start.lno == 1 && vp->m_start.cno == 0)\n                return (0);\n\n        cs.cs_lno = vp->m_start.lno;\n        cs.cs_cno = vp->m_start.cno;\n        if (cs_init(sp, &cs))\n                return (1);\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n\n        /*\n         * !!!\n         * In empty lines, skip to the previous non-white-space character.\n         * If in text, skip to the previous white-space character.  Believe\n         * it or not, in the paragraph:\n         *      ab cd.\n         *      AB CD.\n         * if the cursor is on the 'A' or 'B', ( moves to the 'a'.  If it\n         * is on the ' ', 'C' or 'D', it moves to the 'A'.  Yes, Virginia,\n         * Berkeley was once a major center of drug activity.\n         */\n        if (cs.cs_flags == CS_EMP) {\n                if (cs_bblank(sp, &cs))\n                        return (1);\n                for (;;) {\n                        if (cs_prev(sp, &cs))\n                                return (1);\n                        if (cs.cs_flags != CS_EOL)\n                                break;\n                }\n        } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch))\n                for (;;) {\n                        if (cs_prev(sp, &cs))\n                                return (1);\n                        if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                break;\n                }\n\n        for (last = 0;;) {\n                if (cs_prev(sp, &cs))\n                        return (1);\n                if (cs.cs_flags == CS_SOF)      /* SOF is a movement sink. */\n                        break;\n                if (cs.cs_flags == CS_EOL) {\n                        last = 1;\n                        continue;\n                }\n                if (cs.cs_flags == CS_EMP) {\n                        if (--cnt == 0)\n                                goto ret;\n                        if (cs_bblank(sp, &cs))\n                                return (1);\n                        last = 0;\n                        continue;\n                }\n                switch (cs.cs_ch) {\n                case '.':\n                case '?':\n                case '!':\n                        if (!last || --cnt != 0) {\n                                last = 0;\n                                continue;\n                        }\n\nret:                    slno = cs.cs_lno;\n                        scno = cs.cs_cno;\n\n                        /*\n                         * Move to the start of the sentence, skipping blanks\n                         * and special characters.\n                         */\n                        do {\n                                if (cs_next(sp, &cs))\n                                        return (1);\n                        } while (!cs.cs_flags &&\n                            (cs.cs_ch == ')' || cs.cs_ch == ']' ||\n                            cs.cs_ch == '\"' || cs.cs_ch == '\\''));\n                        if ((cs.cs_flags || isblank(cs.cs_ch)) &&\n                            cs_fblank(sp, &cs))\n                                return (1);\n\n                        /*\n                         * If it was \".  xyz\", with the cursor on the 'x', or\n                         * \"end.  \", with the cursor in the spaces, or the\n                         * beginning of a sentence preceded by an empty line,\n                         * we can end up where we started.  Fix it.\n                         */\n                        if (vp->m_start.lno != cs.cs_lno ||\n                            vp->m_start.cno > cs.cs_cno)\n                                goto okret;\n\n                        /*\n                         * Well, if an empty line preceded possible blanks\n                         * and the sentence, it could be a real sentence.\n                         */\n                        for (;;) {\n                                if (cs_prev(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_EOL)\n                                        continue;\n                                if (cs.cs_flags == 0 && isblank(cs.cs_ch))\n                                        continue;\n                                break;\n                        }\n                        if (cs.cs_flags == CS_EMP)\n                                goto okret;\n\n                        /* But it wasn't; try again. */\n                        ++cnt;\n                        cs.cs_lno = slno;\n                        cs.cs_cno = scno;\n                        last = 0;\n                        break;\n                case '\\t':\n                        last = 1;\n                        break;\n                default:\n                        last =\n                            cs.cs_flags == CS_EOL || isblank(cs.cs_ch) ||\n                            cs.cs_ch == ')' || cs.cs_ch == ']' ||\n                            cs.cs_ch == '\"' || cs.cs_ch == '\\'' ? 1 : 0;\n                }\n        }\n\nokret:  vp->m_stop.lno = cs.cs_lno;\n        vp->m_stop.cno = cs.cs_cno;\n\n        /*\n         * !!!\n         * If the starting and stopping cursor positions are at the first\n         * columns in the line, i.e. the movement is cutting an entire line,\n         * the buffer is in line mode, and the starting position is the last\n         * character of the previous line.\n         *\n         * All commands move to the end of the range.  Adjust the start of\n         * the range for motion commands.\n         */\n        if (ISMOTION(vp)) {\n                if (vp->m_start.cno == 0 &&\n                    (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {\n                        if (db_get(sp,\n                            --vp->m_start.lno, DBG_FATAL, NULL, &len))\n                                return (1);\n                        vp->m_start.cno = len ? len - 1 : 0;\n                        F_SET(vp, VM_LMODE);\n                } else\n                        --vp->m_start.cno;\n        }\n        vp->m_final = vp->m_stop;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_status.c",
    "content": "/*      $OpenBSD: v_status.c,v 1.6 2014/11/12 04:28:41 bentley Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_status -- ^G\n *      Show the file status.\n *\n * PUBLIC: int v_status(SCR *, VICMD *);\n */\nint\nv_status(SCR *sp, VICMD *vp)\n{\n        (void)msgq_status(sp, vp->m_start.lno, MSTAT_SHOWLAST);\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_txt.c",
    "content": "/*      $OpenBSD: v_txt.c,v 1.36 2022/12/26 19:16:01 jmc Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/queue.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))\n\nstatic int       txt_abbrev(SCR *, TEXT *, CHAR_T *, int, int *, int *);\nstatic void      txt_ai_resolve(SCR *, TEXT *, int *);\nstatic TEXT     *txt_backup(SCR *, TEXTH *, TEXT *, u_int32_t *);\nstatic int       txt_dent(SCR *, TEXT *, int, int);\nstatic int       txt_emark(SCR *, TEXT *, size_t);\nstatic void      txt_err(SCR *, TEXTH *);\nstatic int       txt_fc(SCR *, TEXT *, int *);\nstatic int       txt_fc_col(SCR *, int, ARGS **);\nstatic int       txt_hex(SCR *, TEXT *);\nstatic int       txt_insch(SCR *, TEXT *, CHAR_T *, unsigned int);\nstatic int       txt_isrch(SCR *, VICMD *, TEXT *, u_int8_t *);\nstatic int       txt_map_end(SCR *);\nstatic int       txt_map_init(SCR *);\nstatic int       txt_margin(SCR *, TEXT *, TEXT *, int *, u_int32_t);\nstatic void      txt_nomorech(SCR *);\nstatic void      txt_Rresolve(SCR *, TEXTH *, TEXT *, const size_t);\nstatic int       txt_resolve(SCR *, TEXTH *, u_int32_t);\nstatic int       txt_showmatch(SCR *, TEXT *);\nstatic void      txt_unmap(SCR *, TEXT *, u_int32_t *);\n\n/* Cursor character (space is hard to track on the screen). */\n#if defined(DEBUG) && 0\n# undef CH_CURSOR\n# define CH_CURSOR      '+'\n#endif /* if defined(DEBUG) && 0 */\n\n/*\n * v_tcmd --\n *      Fill a buffer from the terminal for vi.\n *\n * PUBLIC: int v_tcmd(SCR *, VICMD *, CHAR_T, unsigned int);\n */\nint\nv_tcmd(SCR *sp, VICMD *vp, CHAR_T prompt, unsigned int flags)\n{\n        /* Normally, we end up where we started. */\n        vp->m_final.lno = sp->lno;\n        vp->m_final.cno = sp->cno;\n\n        /* Initialize the map. */\n        if (txt_map_init(sp))\n                return (1);\n\n        /* Move to the last line. */\n        sp->lno = TMAP[0].lno;\n        sp->cno = 0;\n\n        /* Don't update the modeline for now. */\n        F_SET(sp, SC_TINPUT_INFO);\n\n        /* Set the input flags. */\n        LF_SET(TXT_APPENDEOL |\n            TXT_CR | TXT_ESCAPE | TXT_INFOLINE | TXT_MAPINPUT);\n        if (O_ISSET(sp, O_ALTWERASE))\n                LF_SET(TXT_ALTWERASE);\n        if (O_ISSET(sp, O_TTYWERASE))\n                LF_SET(TXT_TTYWERASE);\n\n        /* Do the input thing. */\n        if (v_txt(sp, vp, NULL, NULL, 0, prompt, 0, 1, flags))\n                return (1);\n\n        /* Re-enable the modeline updates. */\n        F_CLR(sp, SC_TINPUT_INFO);\n\n        /* Clean up the map. */\n        if (txt_map_end(sp))\n                return (1);\n\n        if (IS_ONELINE(sp))\n                F_SET(sp, SC_SCR_REDRAW);       /* XXX */\n\n        /* Set the cursor to the resulting position. */\n        sp->lno = vp->m_final.lno;\n        sp->cno = vp->m_final.cno;\n\n        return (0);\n}\n\n/*\n * txt_map_init\n *      Initialize the screen map for colon command-line input.\n */\nstatic int\ntxt_map_init(SCR *sp)\n{\n        SMAP *esmp;\n        VI_PRIVATE *vip;\n\n        vip = VIP(sp);\n        if (!IS_ONELINE(sp)) {\n                /*\n                 * Fake like the user is doing input on the last line of the\n                 * screen.  This makes all of the scrolling work correctly,\n                 * and allows us the use of the vi text editing routines, not\n                 * to mention practically infinite length ex commands.\n                 *\n                 * Save the current location.\n                 */\n                vip->sv_tm_lno    = TMAP->lno;\n                vip->sv_tm_soff   = TMAP->soff;\n                vip->sv_tm_coff   = TMAP->coff;\n                vip->sv_t_maxrows = sp->t_maxrows;\n                vip->sv_t_minrows = sp->t_minrows;\n                vip->sv_t_rows    = sp->t_rows;\n\n                /*\n                 * If it's a small screen, TMAP may be small for the screen.\n                 * Fix it, filling in fake lines as we go.\n                 */\n                if (IS_SMALL(sp))\n                        for (esmp =\n                            HMAP + (sp->t_maxrows - 1); TMAP < esmp; ++TMAP) {\n                                TMAP[1].lno  = TMAP[0].lno + 1;\n                                TMAP[1].coff = HMAP->coff;\n                                TMAP[1].soff = 1;\n                        }\n\n                /* Build the fake entry. */\n                TMAP[1].lno  = TMAP[0].lno + 1;\n                TMAP[1].soff = 1;\n                TMAP[1].coff = 0;\n                SMAP_FLUSH(&TMAP[1]);\n                ++TMAP;\n\n                /* Reset the screen information. */\n                sp->t_rows = sp->t_minrows = ++sp->t_maxrows;\n        }\n        return (0);\n}\n\n/*\n * txt_map_end\n *      Reset the screen map for colon command-line input.\n */\nstatic int\ntxt_map_end(SCR *sp)\n{\n        VI_PRIVATE *vip;\n        size_t cnt;\n\n        vip = VIP(sp);\n        if (!IS_ONELINE(sp)) {\n                /* Restore the screen information. */\n                sp->t_rows    = vip->sv_t_rows;\n                sp->t_minrows = vip->sv_t_minrows;\n                sp->t_maxrows = vip->sv_t_maxrows;\n\n                /*\n                 * If it's a small screen, TMAP may be wrong.  Clear any\n                 * lines that might have been overwritten.\n                 */\n                if (IS_SMALL(sp)) {\n                        for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {\n                                (void)sp->gp->scr_move(sp, cnt, 0);\n                                (void)sp->gp->scr_clrtoeol(sp);\n                        }\n                        TMAP = HMAP + (sp->t_rows - 1);\n                } else\n                        --TMAP;\n\n                /*\n                 * The map may be wrong if the user entered more than one\n                 * (logical) line.  Fix it.  If the user entered a whole\n                 * screen, this will be slow, but we probably don't care.\n                 */\n                if (!O_ISSET(sp, O_LEFTRIGHT))\n                        while (vip->sv_tm_lno != TMAP->lno ||\n                            vip->sv_tm_soff != TMAP->soff)\n                                if (vs_sm_1down(sp))\n                                        return (1);\n        }\n\n        /*\n         * Invalidate the cursor and the line size cache, the line never\n         * really existed.  This fixes bugs where the user searches for\n         * the last line on the screen + 1 and the refresh routine thinks\n         * that's where we just were.\n         */\n        VI_SCR_CFLUSH(vip);\n        F_SET(vip, VIP_CUR_INVALID);\n\n        return (0);\n}\n\n/*\n * If doing input mapping on the colon command line, may need to unmap\n * based on the command.\n */\n#define UNMAP_TST                                                       \\\n        FL_ISSET(ec_flags, EC_MAPINPUT) && LF_ISSET(TXT_INFOLINE)\n\n/*\n * Internally, we maintain tp->lno and tp->cno, externally, everyone uses\n * sp->lno and sp->cno.  Make them consistent as necessary.\n */\n#define UPDATE_POSITION(sp, tp) {                                       \\\n        (sp)->lno = (tp)->lno;                                          \\\n        (sp)->cno = (tp)->cno;                                          \\\n}\n\n/*\n * v_txt --\n *      Vi text input.\n *\n * PUBLIC: int v_txt(SCR *, VICMD *, MARK *,\n * PUBLIC:    const char *, size_t, CHAR_T, recno_t, unsigned long, u_int32_t);\n */\nint\nv_txt(SCR *sp, VICMD *vp, MARK *tm, const char *lp, size_t len,\n    CHAR_T prompt, recno_t ai_line, unsigned long rcount, u_int32_t flags)\n{\n        EVENT ev, *evp = NULL;  /* Current event. */\n        EVENT fc;               /* File name completion event. */\n        GS *gp;\n        TEXT *ntp, *tp;         /* Input text structures. */\n        TEXT ait;               /* Autoindent text structure. */\n        TEXT wmt;               /* Wrapmargin text structure. */\n        TEXTH *tiqh;\n        VI_PRIVATE *vip;\n        abb_t abb;              /* State of abbreviation checks. */\n        carat_t carat;          /* State of the \"[^0]^D\" sequences. */\n        quote_t quote;          /* State of quotation. */\n        size_t owrite, insert;  /* Temporary copies of TEXT fields. */\n        size_t margin;          /* Wrapmargin value. */\n        size_t rcol;            /* 0-N: insert offset in the replay buffer. */\n        size_t tcol;            /* Temporary column. */\n        u_int32_t ec_flags;     /* Input mapping flags. */\n#define IS_RESTART      0x01    /* Reset the incremental search. */\n#define IS_RUNNING      0x02    /* Incremental search turned on. */\n        u_int8_t is_flags;\n        int abcnt, ab_turnoff;  /* Abbreviation character count, switch. */\n        int filec_redraw;       /* Redraw after the file completion routine. */\n        int hexcnt;             /* Hex character count. */\n        int showmatch;          /* Showmatch set on this character. */\n        int wm_set, wm_skip;    /* Wrapmargin happened, blank skip flags. */\n        int max, tmp;\n        int nochange;\n        char *p;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n\n        /*\n         * Set the input flag, so tabs get displayed correctly\n         * and everyone knows that the text buffer is in use.\n         */\n        F_SET(sp, SC_TINPUT);\n\n        /*\n         * Get one TEXT structure with some initial buffer space, reusing\n         * the last one if it's big enough.  (All TEXT bookkeeping fields\n         * default to 0 -- text_init() handles this.)  If changing a line,\n         * copy it into the TEXT buffer.\n         */\n        tiqh = &sp->tiq;\n        if (!TAILQ_EMPTY(tiqh)) {\n                tp = TAILQ_FIRST(tiqh);\n                if (TAILQ_NEXT(tp, q) || tp->lb_len < len + 32) {\n                        text_lfree(tiqh);\n                        goto newtp;\n                }\n                tp->ai = tp->insert = tp->offset = tp->owrite = 0;\n                if (lp != NULL) {\n                        tp->len = len;\n                        memmove(tp->lb, lp, len);\n                } else\n                        tp->len = 0;\n        } else {\nnewtp:          if ((tp = text_init(sp, lp, len, len + 32)) == NULL)\n                        return (1);\n                TAILQ_INSERT_HEAD(tiqh, tp, q);\n        }\n\n        /* Set default termination condition. */\n        tp->term = TERM_OK;\n\n        /* Set the starting line, column. */\n        tp->lno = sp->lno;\n        tp->cno = sp->cno;\n\n        /*\n         * Set the insert and overwrite counts.  If overwriting characters,\n         * do insertion afterward.  If not overwriting characters, assume\n         * doing insertion.  If change is to a mark, emphasize it with an\n         * CH_ENDMARK character.\n         */\n        if (len) {\n                if (LF_ISSET(TXT_OVERWRITE)) {\n                        tp->owrite = (tm->cno - tp->cno) + 1;\n                        tp->insert = (len - tm->cno) - 1;\n                } else\n                        tp->insert = len - tp->cno;\n\n                if (LF_ISSET(TXT_EMARK) && txt_emark(sp, tp, tm->cno))\n                        return (1);\n        }\n\n        /*\n         * Many of the special cases in text input are to handle autoindent\n         * support.  Somebody decided that it would be a good idea if \"^^D\"\n         * and \"0^D\" deleted all of the autoindented characters.  In an editor\n         * that takes single character input from the user, this beggars the\n         * imagination.  Note also, \"^^D\" resets the next lines' autoindent,\n         * but \"0^D\" doesn't.\n         *\n         * We assume that autoindent only happens on empty lines, so insert\n         * and overwrite will be zero.  If doing autoindent, figure out how\n         * much indentation we need and fill it in.  Update input column and\n         * screen cursor as necessary.\n         */\n        if (LF_ISSET(TXT_AUTOINDENT) && ai_line != OOBLNO) {\n                if (v_txt_auto(sp, ai_line, NULL, 0, tp))\n                        return (1);\n                tp->cno = tp->ai;\n        } else {\n                /*\n                 * The cc and S commands have a special feature -- leading\n                 * <blank> characters are handled as autoindent characters.\n                 * Beauty!\n                 */\n                if (LF_ISSET(TXT_AICHARS)) {\n                        tp->offset = 0;\n                        tp->ai = tp->cno;\n                } else\n                        tp->offset = tp->cno;\n        }\n\n        /* If getting a command buffer from the user, there may be a prompt. */\n        if (LF_ISSET(TXT_PROMPT)) {\n                tp->lb[tp->cno++] = prompt;\n                ++tp->len;\n                ++tp->offset;\n        }\n\n        /*\n         * If appending after the end-of-line, add a space into the buffer\n         * and move the cursor right.  This space is inserted, i.e. pushed\n         * along, and then deleted when the line is resolved.  Assumes that\n         * the cursor is already positioned at the end of the line.  This\n         * avoids the nastiness of having the cursor reside on a magical\n         * column, i.e. a column that doesn't really exist.  The only down\n         * side is that we may wrap lines or scroll the screen before it's\n         * strictly necessary.  Not a big deal.\n         */\n        if (LF_ISSET(TXT_APPENDEOL)) {\n                tp->lb[tp->cno] = CH_CURSOR;\n                ++tp->len;\n                ++tp->insert;\n                (void)vs_change(sp, tp->lno, LINE_RESET);\n        }\n\n        /*\n         * Historic practice is that the wrapmargin value was a distance\n         * from the RIGHT-HAND margin, not the left.  It's more useful to\n         * us as a distance from the left-hand margin, i.e. the same as\n         * the wraplen value.  The wrapmargin option is historic practice.\n         * Nvi added the wraplen option so that it would be possible to\n         * edit files with consistent margins without knowing the number of\n         * columns in the window.\n         *\n         * XXX\n         * Setting margin causes a significant performance hit.  Normally\n         * we don't update the screen if there are keys waiting, but we\n         * have to if margin is set, otherwise the screen routines don't\n         * know where the cursor is.\n         *\n         * !!!\n         * Abbreviated keys were affected by the wrapmargin option in the\n         * historic 4BSD vi.  Mapped keys were usually, but sometimes not.\n         * See the comment in vi/v_text():set_txt_std for more information.\n         *\n         * !!!\n         * One more special case.  If an inserted <blank> character causes\n         * wrapmargin to split the line, the next user entered character is\n         * discarded if it's a <space> character.\n         */\n        wm_set = wm_skip = 0;\n        if (LF_ISSET(TXT_WRAPMARGIN))\n                if ((margin = O_VAL(sp, O_WRAPMARGIN)) != 0)\n                        margin = sp->cols - margin;\n                else\n                        margin = O_VAL(sp, O_WRAPLEN);\n        else\n                margin = 0;\n\n        /* Initialize abbreviation checks. */\n        abcnt = ab_turnoff = 0;\n        abb = F_ISSET(gp, G_ABBREV) &&\n              LF_ISSET(TXT_MAPINPUT) ? AB_INWORD : AB_NOTSET;\n\n        /*\n         * Set up the dot command.  Dot commands are done by saving the actual\n         * characters and then reevaluating them so that things like wrapmargin\n         * can change between the insert and the replay.\n         *\n         * !!!\n         * Historically, vi did not remap or reabbreviate replayed input.  (It\n         * did beep at you if you changed an abbreviation and then replayed the\n         * input.  We're not that compatible.)  We don't have to do anything to\n         * avoid remapping, as we're not getting characters from the terminal\n         * routines.  Turn the abbreviation check off.\n         *\n         * XXX\n         * It would be nice if we could swallow backspaces and such, but it's\n         * not all that easy to do.  What we can do is turn off the common\n         * error messages during the replay.  Otherwise, when the user enters\n         * an illegal command, e.g., \"Ia<erase><erase><erase><erase>b<escape>\",\n         * and then does a '.', they get a list of error messages after command\n         * completion.\n         */\n        rcol = 0;\n        if (LF_ISSET(TXT_REPLAY)) {\n                abb = AB_NOTSET;\n                LF_CLR(TXT_RECORD);\n        }\n\n        /* Other text input mode setup. */\n        quote = Q_NOTSET;\n        carat = C_NOTSET;\n        nochange = 0;\n        FL_INIT(is_flags,\n            LF_ISSET(TXT_SEARCHINCR) ? IS_RESTART | IS_RUNNING : 0);\n        filec_redraw = hexcnt = showmatch = 0;\n\n        /* Initialize input flags. */\n        ec_flags = LF_ISSET(TXT_MAPINPUT) ? EC_MAPINPUT : 0;\n\n        /* Refresh the screen. */\n        UPDATE_POSITION(sp, tp);\n        if (vs_refresh(sp, 1))\n                return (1);\n\n        /* If it's dot, just do it now. */\n        if (F_ISSET(vp, VC_ISDOT))\n                goto replay;\n\n        /* Get an event. */\n        evp = &ev;\nnext:   if (v_event_get(sp, evp, 0, ec_flags))\n                return (1);\n\n        /*\n         * If file completion overwrote part of the screen and nothing else has\n         * been displayed, clean up.  We don't do this as part of the normal\n         * message resolution because we know the user is on the colon command\n         * line and there's no reason to enter explicit characters to continue.\n         */\n        if (filec_redraw && !F_ISSET(sp, SC_SCR_EXWROTE)) {\n                filec_redraw = 0;\n\n                fc.e_event = E_REPAINT;\n                fc.e_flno = vip->totalcount >=\n                    sp->rows ? 1 : sp->rows - vip->totalcount;\n                fc.e_tlno = sp->rows;\n                vip->linecount = vip->lcontinue = vip->totalcount = 0;\n                (void)vs_repaint(sp, &fc);\n                (void)vs_refresh(sp, 1);\n        }\n\n        /* Deal with all non-character events. */\n        switch (evp->e_event) {\n        case E_CHARACTER:\n                break;\n        case E_ERR:\n        case E_EOF:\n                F_SET(sp, SC_EXIT_FORCE);\n                return (1);\n        case E_REPAINT:\n                if (vs_repaint(sp, &ev))\n                        return (1);\n                goto next;\n        case E_WRESIZE:\n                /* <resize> interrupts the input mode. */\n                sp->gp->scr_imctrl(sp, IMCTRL_OFF);\n                v_emsg(sp, NULL, VIM_WRESIZE);\n        /* FALLTHROUGH */\n        default:\n                if (evp->e_event != E_INTERRUPT && evp->e_event != E_WRESIZE)\n                        v_event_err(sp, evp);\n                /*\n                 * !!!\n                 * Historically, <interrupt> exited the user from text input\n                 * mode or cancelled a colon command, and returned to command\n                 * mode.  It also beeped the terminal, but that seemed a bit\n                 * excessive.\n                 */\n\n                /*\n                 * If we are recording, morph into <escape> key so that\n                 * we can repeat the command safely: there is no way to\n                 * invalidate the repetition of an instance of a command,\n                 * which would be the alternative possibility.\n                 *\n                 * If we are not recording (most likely on the command line),\n                 * simply discard the input and return to command mode\n                 * so that an INTERRUPT doesn't become for example a file\n                 * completion request. -aymeric\n                 */\n                if (LF_ISSET(TXT_RECORD)) {\n                    evp->e_event = E_CHARACTER;\n                    evp->e_c = 033;\n                    evp->e_flags = 0;\n                    evp->e_value = K_ESCAPE;\n                    break;\n                } else {\n                    tp->term = TERM_ESC;\n                    goto k_escape;\n                }\n        }\n\n        /*\n         * !!!\n         * If the first character of the input is a NULL, replay the previous\n         * input.  (Historically, it's okay to replay non-existent input.)\n         * This was not documented as far as I know, and is a great test of vi\n         * clones.\n         */\n        if (LF_ISSET(TXT_RECORD) && rcol == 0 && evp->e_c == '\\0') {\n                if (vip->rep == NULL)\n                        goto done;\n\n                abb = AB_NOTSET;\n                LF_CLR(TXT_RECORD);\n                LF_SET(TXT_REPLAY);\n                goto replay;\n        }\n\n        /*\n         * File name completion and colon command-line editing.   We don't\n         * have enough meta characters, so we expect people to overload\n         * them.  If the two characters are the same, then we do file name\n         * completion if the cursor is past the first column, and do colon\n         * command-line editing if it's not.\n         */\n        if (quote == Q_NOTSET) {\n                int L__cedit, L__filec;\n\n                L__cedit = L__filec = 0;\n                if (LF_ISSET(TXT_CEDIT) && O_STR(sp, O_CEDIT) != NULL &&\n                    O_STR(sp, O_CEDIT)[0] == evp->e_c)\n                        L__cedit = 1;\n                if (LF_ISSET(TXT_FILEC) && O_STR(sp, O_FILEC) != NULL &&\n                    O_STR(sp, O_FILEC)[0] == evp->e_c)\n                        L__filec = 1;\n                if (L__cedit == 1 && (L__filec == 0 || tp->cno == tp->offset)) {\n                        tp->term = TERM_CEDIT;\n                        goto k_escape;\n                }\n                if (L__filec == 1) {\n                        if (txt_fc(sp, tp, &filec_redraw))\n                                goto err;\n                        goto resolve;\n                }\n        }\n\n        /* Abbreviation overflow check.  See comment in txt_abbrev(). */\n#define MAX_ABBREVIATION_EXPANSION      256\n        if (F_ISSET(&evp->e_ch, CH_ABBREVIATED)) {\n                if (++abcnt > MAX_ABBREVIATION_EXPANSION) {\n                        if (v_event_flush(sp, CH_ABBREVIATED))\n                                msgq(sp, M_ERR,\n                \"Abbreviation exceeded expansion limit: characters discarded\");\n                        abcnt = 0;\n                        if (LF_ISSET(TXT_REPLAY))\n                                goto done;\n                        goto resolve;\n                }\n        } else\n                abcnt = 0;\n\n        /* Check to see if the character fits into the replay buffers. */\n        if (LF_ISSET(TXT_RECORD)) {\n                BINC_GOTO(sp, vip->rep,\n                    vip->rep_len, (rcol + 1) * sizeof(EVENT));\n                vip->rep[rcol++] = *evp;\n        }\n\nreplay: if (LF_ISSET(TXT_REPLAY))\n                evp = vip->rep + rcol++;\n\n        /* Wrapmargin check for leading space. */\n        if (wm_skip) {\n                wm_skip = 0;\n                if (evp->e_c == ' ')\n                        goto resolve;\n        }\n\n        /* If quoted by someone else, simply insert the character. */\n        if (F_ISSET(&evp->e_ch, CH_QUOTED))\n                goto insq_ch;\n\n        /*\n         * !!!\n         * If this character was quoted by a K_VLNEXT, replace the placeholder\n         * (a carat) with the new character.  We've already adjusted the cursor\n         * because it has to appear on top of the placeholder character.\n         * Historic practice.\n         *\n         * Skip tests for abbreviations; \":ab xa XA\" followed by \"ixa^V<space>\"\n         * doesn't perform an abbreviation.  Special case, ^V^J (not ^V^M) is\n         * the same as ^J, historically.\n         */\n        if (quote == Q_VTHIS) {\n                FL_CLR(ec_flags, EC_QUOTED);\n                if (LF_ISSET(TXT_MAPINPUT))\n                        FL_SET(ec_flags, EC_MAPINPUT);\n\n                if (evp->e_value != K_NL) {\n                        quote = Q_NOTSET;\n                        goto insl_ch;\n                }\n                quote = Q_NOTSET;\n        }\n\n        /*\n         * !!!\n         * Translate \"<CH_HEX>[isxdigit()]*\" to a character with a hex value:\n         * this test delimits the value by any non-hex character.  Offset by\n         * one, we use 0 to mean that we've found <CH_HEX>.\n         */\n        if (hexcnt > 1 && !isxdigit(evp->e_c)) {\n                hexcnt = 0;\n                if (txt_hex(sp, tp))\n                        goto err;\n        }\n\n        switch (evp->e_value) {\n        case K_CR:                              /* Carriage return. */\n        case K_NL:                              /* New line. */\n                /* Return in script windows and the command line. */\nk_cr:           if (LF_ISSET(TXT_CR)) {\n                        /*\n                         * If this was a map, we may have not displayed\n                         * the line.  Display it, just in case.\n                         *\n                         * If a script window and not the colon line,\n                         * push a <cr> so it gets executed.\n                         */\n                        if (LF_ISSET(TXT_INFOLINE)) {\n                                if (vs_change(sp, tp->lno, LINE_RESET))\n                                        goto err;\n                        } else if (F_ISSET(sp, SC_SCRIPT))\n                                (void)v_event_push(sp, NULL, \"\\r\", 1, CH_NOMAP);\n\n                        /* Set term condition: if empty. */\n                        if (tp->cno <= tp->offset)\n                                tp->term = TERM_CR;\n                        /*\n                         * Set term condition: if searching incrementally and\n                         * the user entered a pattern, return a completed\n                         * search, regardless if the entire pattern was found.\n                         */\n                        if (FL_ISSET(is_flags, IS_RUNNING) &&\n                            tp->cno >= tp->offset + 1)\n                                tp->term = TERM_SEARCH;\n\n                        goto k_escape;\n                }\n\n#define LINE_RESOLVE {                                                  \\\n                /*                                                      \\\n                 * Handle abbreviations.  If there was one, discard the \\\n                 * replay characters.                                   \\\n                 */                                                     \\\n                if (abb == AB_INWORD &&                                 \\\n                    !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) {   \\\n                        if (txt_abbrev(sp, tp, &evp->e_c,               \\\n                            LF_ISSET(TXT_INFOLINE), &tmp,               \\\n                            &ab_turnoff))                               \\\n                                goto err;                               \\\n                        if (tmp) {                                      \\\n                                if (LF_ISSET(TXT_RECORD))               \\\n                                        rcol -= tmp + 1;                \\\n                                goto resolve;                           \\\n                        }                                               \\\n                }                                                       \\\n                if (abb != AB_NOTSET)                                   \\\n                        abb = AB_NOTWORD;                               \\\n                if (UNMAP_TST)                                          \\\n                        txt_unmap(sp, tp, &ec_flags);                   \\\n                /*                                                      \\\n                 * Delete any appended cursor.  It's possible to get in \\\n                 * situations where TXT_APPENDEOL is set but tp->insert \\\n                 * is 0 when using the R command and all the characters \\\n                 * are tp->owrite characters.                           \\\n                 */                                                     \\\n                if (LF_ISSET(TXT_APPENDEOL) && tp->insert > 0) {        \\\n                        --tp->len;                                      \\\n                        --tp->insert;                                   \\\n                }                                                       \\\n}\n                LINE_RESOLVE;\n\n                /*\n                 * Save the current line information for restoration in\n                 * txt_backup(), and set the line final length.\n                 */\n                tp->sv_len = tp->len;\n                tp->sv_cno = tp->cno;\n                tp->len = tp->cno;\n\n                /* Update the old line. */\n                if (vs_change(sp, tp->lno, LINE_RESET))\n                        goto err;\n\n                /*\n                 * Historic practice, when the autoindent edit option was set,\n                 * was to delete <blank> characters following the inserted\n                 * newline.  This affected the 'R', 'c', and 's' commands; 'c'\n                 * and 's' retained the insert characters only, 'R' moved the\n                 * overwrite and insert characters into the next TEXT structure.\n                 * We keep track of the number of characters erased for the 'R'\n                 * command so that the final resolution of the line is correct.\n                 */\n                tp->R_erase = 0;\n                owrite = tp->owrite;\n                insert = tp->insert;\n                if (LF_ISSET(TXT_REPLACE) && owrite != 0) {\n                        for (p = tp->lb + tp->cno; owrite > 0 && isblank(*p);\n                            ++p, --owrite, ++tp->R_erase);\n                        if (owrite == 0)\n                                for (; insert > 0 && isblank(*p);\n                                    ++p, ++tp->R_erase, --insert);\n                } else {\n                        p = tp->lb + tp->cno + owrite;\n                        if (O_ISSET(sp, O_AUTOINDENT))\n                                for (; insert > 0 &&\n                                    isblank(*p); ++p, --insert);\n                        owrite = 0;\n                }\n\n                /*\n                 * !!!\n                 * Create a new line and insert the new TEXT into the queue.\n                 * DON'T insert until the old line has been updated, or the\n                 * inserted line count in line.c:db_get() will be wrong.\n                 */\n                if ((ntp = text_init(sp, p,\n                    insert + owrite, insert + owrite + 32)) == NULL)\n                        goto err;\n                TAILQ_INSERT_TAIL(&sp->tiq, ntp, q);\n\n                /* Set up bookkeeping for the new line. */\n                ntp->insert = insert;\n                ntp->owrite = owrite;\n                ntp->lno    = tp->lno + 1;\n\n                /*\n                 * Reset the autoindent line value.  0^D keeps the autoindent\n                 * line from changing, ^D changes the level, even if there were\n                 * no characters in the old line.  Note, if using the current\n                 * tp structure, use the cursor as the length, the autoindent\n                 * characters may have been erased.\n                 */\n                if (LF_ISSET(TXT_AUTOINDENT)) {\n                        if (nochange) {\n                                nochange = 0;\n                                if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp))\n                                        goto err;\n                                FREE_SPACE(sp, ait.lb, ait.lb_len);\n                        } else\n                                if (v_txt_auto(sp, OOBLNO, tp, tp->cno, ntp))\n                                        goto err;\n                        carat = C_NOTSET;\n                }\n\n                /* Reset the cursor. */\n                ntp->cno = ntp->ai;\n\n                /*\n                 * If we're here because wrapmargin was set and we've broken a\n                 * line, there may be additional information (i.e. the start of\n                 * a line) in the wmt structure.\n                 */\n                if (wm_set) {\n                        if (wmt.offset != 0 ||\n                            wmt.owrite != 0 || wmt.insert != 0) {\n#define WMTSPACE        wmt.offset + wmt.owrite + wmt.insert\n                                BINC_GOTO(sp, ntp->lb,\n                                    ntp->lb_len, ntp->len + WMTSPACE + 32);\n                                memmove(ntp->lb + ntp->cno, wmt.lb, WMTSPACE);\n                                ntp->len   += WMTSPACE;\n                                ntp->cno   += wmt.offset;\n                                ntp->owrite = wmt.owrite;\n                                ntp->insert = wmt.insert;\n                        }\n                        wm_set = 0;\n                }\n\n                /* New lines are TXT_APPENDEOL. */\n                if (ntp->owrite == 0 && ntp->insert == 0) {\n                        BINC_GOTO(sp, ntp->lb, ntp->lb_len, ntp->len + 1);\n                        LF_SET(TXT_APPENDEOL);\n                        ntp->lb[ntp->cno] = CH_CURSOR;\n                        ++ntp->insert;\n                        ++ntp->len;\n                }\n\n                /* Swap old and new TEXT's, and update the new line. */\n                tp = ntp;\n                if (vs_change(sp, tp->lno, LINE_INSERT))\n                        goto err;\n\n                goto resolve;\n        case K_ESCAPE:                          /* Escape. */\n                if (!LF_ISSET(TXT_ESCAPE))\n                        goto ins_ch;\n\n                /* If we have a count, start replaying the input. */\n                if (rcount > 1) {\n                        --rcount;\n\n                        rcol = 0;\n                        abb = AB_NOTSET;\n                        LF_CLR(TXT_RECORD);\n                        LF_SET(TXT_REPLAY);\n\n                        /*\n                         * Some commands (e.g. 'o') need a <newline> for each\n                         * repetition.\n                         */\n                        if (LF_ISSET(TXT_ADDNEWLINE))\n                                goto k_cr;\n\n                        /*\n                         * The R command turns into the 'a' command after the\n                         * first repetition.\n                         */\n                        if (LF_ISSET(TXT_REPLACE)) {\n                                tp->insert = tp->owrite;\n                                tp->owrite = 0;\n                                LF_CLR(TXT_REPLACE);\n                        }\n                        goto replay;\n                }\n\n                /* Set term condition: if empty. */\n                if (tp->cno <= tp->offset)\n                        tp->term = TERM_ESC;\n                /*\n                 * Set term condition: if searching incrementally and the user\n                 * entered a pattern, return a completed search, regardless if\n                 * the entire pattern was found.\n                 */\n                if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1)\n                        tp->term = TERM_SEARCH;\n\nk_escape:       LINE_RESOLVE;\n\n                /*\n                 * Clean up for the 'R' command, restoring overwrite\n                 * characters, and making them into insert characters.\n                 */\n                if (LF_ISSET(TXT_REPLACE))\n                        txt_Rresolve(sp, &sp->tiq, tp, len);\n\n                /*\n                 * If there are any overwrite characters, copy down\n                 * any insert characters, and decrement the length.\n                 */\n                if (tp->owrite) {\n                        if (tp->insert)\n                                memmove(tp->lb + tp->cno,\n                                    tp->lb + tp->cno + tp->owrite, tp->insert);\n                        tp->len -= tp->owrite;\n                }\n\n                /*\n                 * Optionally resolve the lines into the file.  If not\n                 * resolving the lines into the file, end the line with\n                 * a nul.  If the line is empty, then set the length to\n                 * 0, the termination condition has already been set.\n                 *\n                 * XXX\n                 * This is wrong, should pass back a length.\n                 */\n                if (LF_ISSET(TXT_RESOLVE)) {\n                        if (txt_resolve(sp, &sp->tiq, flags))\n                                goto err;\n                } else {\n                        BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1);\n                        tp->lb[tp->len] = '\\0';\n                }\n\n                /*\n                 * Set the return cursor position to rest on the last\n                 * inserted character.\n                 */\n                if (tp->cno != 0)\n                        --tp->cno;\n\n                /* Update the last line. */\n                if (vs_change(sp, tp->lno, LINE_RESET))\n                        return (1);\n                goto done;\n        case K_CARAT:                   /* Delete autoindent chars. */\n                if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT))\n                        carat = C_CARATSET;\n                goto ins_ch;\n        case K_ZERO:                    /* Delete autoindent chars. */\n                if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT))\n                        carat = C_ZEROSET;\n                goto ins_ch;\n        case K_CNTRLD:                  /* Delete autoindent char. */\n                /*\n                 * If in the first column or no characters to erase, ignore\n                 * the ^D (this matches historic practice).  If not doing\n                 * autoindent or already inserted non-ai characters, it's a\n                 * literal.  The latter test is done in the switch, as the\n                 * CARAT forms are N + 1, not N.\n                 */\n                if (!LF_ISSET(TXT_AUTOINDENT))\n                        goto ins_ch;\n                if (tp->cno == 0)\n                        goto resolve;\n\n                switch (carat) {\n                case C_CARATSET:        /* ^^D */\n                        if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1)\n                                goto ins_ch;\n\n                        /* Save the ai string for later. */\n                        ait.lb     = NULL;\n                        ait.lb_len = 0;\n                        BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai);\n                        memmove(ait.lb, tp->lb, tp->ai);\n                        ait.ai = ait.len = tp->ai;\n\n                        carat = C_NOTSET;\n                        nochange = 0;\n                        goto leftmargin;\n                case C_ZEROSET:         /* 0^D */\n                        if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1)\n                                goto ins_ch;\n\n                        carat = C_NOTSET;\nleftmargin:             tp->lb[tp->cno - 1] = ' ';\n                        tp->owrite += tp->cno - tp->offset;\n                        tp->ai = 0;\n                        tp->cno = tp->offset;\n                        break;\n                case C_NOTSET:          /* ^D */\n                        if (tp->ai == 0 || tp->cno > tp->ai + tp->offset)\n                                goto ins_ch;\n\n                        (void)txt_dent(sp, tp, O_SHIFTWIDTH, 0);\n                        break;\n                default:\n                        abort();\n                }\n                break;\n        case K_VERASE:                  /* Erase the last character. */\n                /* If can erase over the prompt, return. */\n                if (tp->cno <= tp->offset && LF_ISSET(TXT_BS)) {\n                        tp->term = TERM_BS;\n                        goto done;\n                }\n\n                /*\n                 * If at the beginning of the line, try and drop back to a\n                 * previously inserted line.\n                 */\n                if (tp->cno == 0) {\n                        if ((ntp =\n                            txt_backup(sp, &sp->tiq, tp, &flags)) == NULL)\n                                goto err;\n                        tp = ntp;\n                        break;\n                }\n\n                /* If nothing to erase, bell the user. */\n                if (tp->cno <= tp->offset) {\n                        if (!LF_ISSET(TXT_REPLAY))\n                                txt_nomorech(sp);\n                        break;\n                }\n\n                /* Drop back one character. */\n                --tp->cno;\n\n                /*\n                 * Historically, vi didn't replace the erased characters with\n                 * <blank>s, presumably because it's easier to fix a minor\n                 * typing mistake and continue on if the previous letters are\n                 * already there.  This is a problem for incremental searching,\n                 * because the user can no longer tell where they are in the\n                 * colon command line because the cursor is at the last search\n                 * point in the screen.  So, if incrementally searching, erase\n                 * the erased characters from the screen.\n                 */\n                if (FL_ISSET(is_flags, IS_RUNNING) || O_ISSET(sp, O_BSERASE))\n                        tp->lb[tp->cno] = ' ';\n\n                /*\n                 * Increment overwrite, decrement ai if deleted.\n                 *\n                 * !!!\n                 * Historic vi did not permit users to use erase characters\n                 * to delete autoindent characters.  We do.  Eat hot death,\n                 * POSIX.\n                 */\n                ++tp->owrite;\n                if (tp->cno < tp->ai)\n                        --tp->ai;\n\n                /* Reset if we deleted an incremental search character. */\n                if (FL_ISSET(is_flags, IS_RUNNING))\n                        FL_SET(is_flags, IS_RESTART);\n                break;\n        case K_VWERASE:                 /* Skip back one word. */\n                /*\n                 * If at the beginning of the line, try and drop back to a\n                 * previously inserted line.\n                 */\n                if (tp->cno == 0) {\n                        if ((ntp =\n                            txt_backup(sp, &sp->tiq, tp, &flags)) == NULL)\n                                goto err;\n                        tp = ntp;\n                }\n\n                /*\n                 * If at offset, nothing to erase so bell the user.\n                 */\n                if (tp->cno <= tp->offset) {\n                        if (!LF_ISSET(TXT_REPLAY))\n                                txt_nomorech(sp);\n                        break;\n                }\n\n                /*\n                 * The first werase goes back to any autoindent column and the\n                 * second werase goes back to the offset.\n                 *\n                 * !!!\n                 * Historic vi did not permit users to use erase characters to\n                 * delete autoindent characters.\n                 */\n                if (tp->ai && tp->cno > tp->ai)\n                        max = tp->ai;\n                else {\n                        tp->ai = 0;\n                        max = tp->offset;\n                }\n\n                /* Skip over trailing space characters. */\n                while (tp->cno > max && isblank(tp->lb[tp->cno - 1])) {\n                        --tp->cno;\n                        ++tp->owrite;\n                }\n                if (tp->cno == max)\n                        break;\n                /*\n                 * There are three types of word erase found on UNIX systems.\n                 * They can be identified by how the string /a/b/c is treated\n                 * -- as 1, 3, or 6 words.  Historic vi had two classes of\n                 * characters, and strings were delimited by them and\n                 * <blank>'s, so, 6 words.  The historic tty interface used\n                 * <blank>'s to delimit strings, so, 1 word.  The algorithm\n                 * offered in the 4.4BSD tty interface (as stty altwerase)\n                 * treats it as 3 words -- there are two classes of\n                 * characters, and strings are delimited by them and\n                 * <blank>'s.  The difference is that the type of the first\n                 * erased character erased is ignored, which is exactly right\n                 * when erasing pathname components.  The edit options\n                 * TXT_ALTWERASE and TXT_TTYWERASE specify the 4.4BSD tty\n                 * interface and the historic tty driver behavior,\n                 * respectively, and the default is the same as the historic\n                 * vi behavior.\n                 *\n                 * Overwrite erased characters if doing incremental search;\n                 * see comment above.\n                 */\n                if (LF_ISSET(TXT_TTYWERASE))\n                        while (tp->cno > max) {\n                                if (isblank(tp->lb[tp->cno - 1]))\n                                        break;\n                                --tp->cno;\n                                ++tp->owrite;\n                                if (FL_ISSET(is_flags, IS_RUNNING))\n                                        tp->lb[tp->cno] = ' ';\n                        }\n                else {\n                        if (LF_ISSET(TXT_ALTWERASE)) {\n                                --tp->cno;\n                                ++tp->owrite;\n                                if (FL_ISSET(is_flags, IS_RUNNING))\n                                        tp->lb[tp->cno] = ' ';\n                        }\n                        if (tp->cno > max)\n                                tmp = inword(tp->lb[tp->cno - 1]);\n                        while (tp->cno > max) {\n                                if (tmp != inword(tp->lb[tp->cno - 1])\n                                       || isblank(tp->lb[tp->cno - 1]))\n                                        break;\n                                --tp->cno;\n                                ++tp->owrite;\n                                if (FL_ISSET(is_flags, IS_RUNNING))\n                                        tp->lb[tp->cno] = ' ';\n                        }\n                }\n\n                /* Reset if we deleted an incremental search character. */\n                if (FL_ISSET(is_flags, IS_RUNNING))\n                      FL_SET(is_flags, IS_RESTART);\n                break;\n        case K_VKILL:                   /* Restart this line. */\n                /*\n                 * !!!\n                 * If at the beginning of the line, try and drop back to a\n                 * previously inserted line.  Historic vi did not permit\n                 * users to go back to previous lines.\n                 */\n                if (tp->cno == 0) {\n                        if ((ntp =\n                            txt_backup(sp, &sp->tiq, tp, &flags)) == NULL)\n                                goto err;\n                        tp = ntp;\n                }\n\n                /* If at offset, nothing to erase so bell the user. */\n                if (tp->cno <= tp->offset) {\n                        if (!LF_ISSET(TXT_REPLAY))\n                                txt_nomorech(sp);\n                        break;\n                }\n\n                /*\n                 * First kill goes back to any autoindent and second kill goes\n                 * back to the offset.\n                 *\n                 * !!!\n                 * Historic vi did not permit users to use erase characters to\n                 * delete autoindent characters.\n                 */\n                if (tp->ai && tp->cno > tp->ai)\n                        max = tp->ai;\n                else {\n                        tp->ai = 0;\n                        max = tp->offset;\n                }\n                tp->owrite += tp->cno - max;\n\n                /*\n                 * Overwrite erased characters if doing incremental search;\n                 * see comment above.\n                 */\n                if (FL_ISSET(is_flags, IS_RUNNING))\n                        do {\n                                tp->lb[--tp->cno] = ' ';\n                        } while (tp->cno > max);\n                else\n                        tp->cno = max;\n\n                /* Reset if we deleted an incremental search character. */\n                if (FL_ISSET(is_flags, IS_RUNNING))\n                        FL_SET(is_flags, IS_RESTART);\n                break;\n        case K_CNTRLT:                  /* Add autoindent characters. */\n                if (!LF_ISSET(TXT_CNTRLT))\n                        goto ins_ch;\n                if (txt_dent(sp, tp, O_SHIFTWIDTH, 1))\n                        goto err;\n                goto ebuf_chk;\n        case K_RIGHTBRACE:\n        case K_RIGHTPAREN:\n                if (LF_ISSET(TXT_SHOWMATCH))\n                        showmatch = 1;\n                goto ins_ch;\n        case K_VLNEXT:                  /* Quote next character. */\n                evp->e_c = '^';\n                quote = Q_VNEXT;\n                /*\n                 * Turn on the quote flag so that the underlying routines\n                 * quote the next character where it's possible. Turn off\n                 * the input mapbiting flag so that we don't remap the next\n                 * character.\n                 */\n                FL_SET(ec_flags, EC_QUOTED);\n                FL_CLR(ec_flags, EC_MAPINPUT);\n\n                /*\n                 * !!!\n                 * Skip the tests for abbreviations, so \":ab xa XA\",\n                 * \"ixa^V<space>\" doesn't perform the abbreviation.\n                 */\n                goto insl_ch;\n        case K_HEXCHAR:\n                hexcnt = 1;\n                goto insq_ch;\n        case K_TAB:\n                if (sp->showmode != SM_COMMAND && quote != Q_VTHIS &&\n                    O_ISSET(sp, O_EXPANDTAB)) {\n                        if (txt_dent(sp, tp, O_TABSTOP, 1))\n                                goto err;\n                        goto ebuf_chk;\n                }\n                goto insq_ch;\n        default:                        /* Insert the character. */\nins_ch:         /*\n                 * Historically, vi eliminated nul's out of hand.  If the\n                 * beautify option was set, it also deleted any unknown\n                 * ASCII value less than space (040) and the del character\n                 * (0177), except for tabs.  Unknown is a key word here.\n                 * Most vi documentation claims that it deleted everything\n                 * but <tab>, <nl> and <ff>, as that's what the original\n                 * 4BSD documentation said.  This is obviously wrong,\n                 * however, as <esc> would be included in that list.  What\n                 * we do is eliminate any unquoted, iscntrl() character that\n                 * wasn't a replay and wasn't handled specially, except\n                 * <tab> or <ff>.\n                 */\n                if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(evp->e_c) &&\n                    evp->e_value != K_FORMFEED && evp->e_value != K_TAB) {\n                        msgq(sp, M_BERR,\n                            \"Illegal character; quote to enter\");\n                        if (LF_ISSET(TXT_REPLAY))\n                                goto done;\n                        break;\n                }\n\ninsq_ch:        /*\n                 * If entering a non-word character after a word, check for\n                 * abbreviations.  If there was one, discard replay characters.\n                 * If entering a blank character, check for unmap commands,\n                 * as well.\n                 */\n                if (!inword(evp->e_c)) {\n                        if (abb == AB_INWORD &&\n                            !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) {\n                                if (txt_abbrev(sp, tp, &evp->e_c,\n                                    LF_ISSET(TXT_INFOLINE), &tmp, &ab_turnoff))\n                                        goto err;\n                                if (tmp) {\n                                        if (LF_ISSET(TXT_RECORD))\n                                                rcol -= tmp + 1;\n                                        goto resolve;\n                                }\n                        }\n                        if (isblank(evp->e_c) && UNMAP_TST)\n                                txt_unmap(sp, tp, &ec_flags);\n                }\n                if (abb != AB_NOTSET)\n                        abb = inword(evp->e_c) ? AB_INWORD : AB_NOTWORD;\n\ninsl_ch:        if (txt_insch(sp, tp, &evp->e_c, flags))\n                        goto err;\n\n                /*\n                 * If we're using K_VLNEXT to quote the next character, then\n                 * we want the cursor to position itself on the ^ placeholder\n                 * we're displaying, to match historic practice.\n                 */\n                if (quote == Q_VNEXT) {\n                        --tp->cno;\n                        ++tp->owrite;\n                }\n\n                /*\n                 * !!!\n                 * Translate \"<CH_HEX>[isxdigit()]*\" to a character with\n                 * a hex value: this test delimits the value by the max\n                 * number of hex bytes.  Offset by one, we use 0 to mean\n                 * that we've found <CH_HEX>.\n                 */\n                if (hexcnt != 0 && hexcnt++ == sizeof(CHAR_T) * 2 + 1) {\n                        hexcnt = 0;\n                        if (txt_hex(sp, tp))\n                                goto err;\n                }\n\n                /*\n                 * Check to see if we've crossed the margin.\n                 *\n                 * !!!\n                 * In the historic vi, the wrapmargin value was figured out\n                 * using the display widths of the characters, i.e. <tab>\n                 * characters were counted as two characters if the list edit\n                 * option is set, but as the tabstop edit option number of\n                 * characters otherwise.  That's what the vs_column() function\n                 * gives us, so we use it.\n                 */\n                if (margin != 0) {\n                        if (vs_column(sp, &tcol))\n                                goto err;\n                        if (tcol >= margin) {\n                                if (txt_margin(sp, tp, &wmt, &tmp, flags))\n                                        goto err;\n                                if (tmp) {\n                                        if (isblank(evp->e_c))\n                                                wm_skip = 1;\n                                        wm_set = 1;\n                                        goto k_cr;\n                                }\n                        }\n                }\n\n                /*\n                 * If we've reached the end of the buffer, then we need to\n                 * switch into insert mode.  This happens when there's a\n                 * change to a mark and the user puts in more characters than\n                 * the length of the motion.\n                 */\nebuf_chk:       if (tp->cno >= tp->len) {\n                        BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1);\n                        LF_SET(TXT_APPENDEOL);\n\n                        tp->lb[tp->cno] = CH_CURSOR;\n                        ++tp->insert;\n                        ++tp->len;\n                }\n\n                /* Step the quote state forward. */\n                if (quote != Q_NOTSET) {\n                        if (quote == Q_VNEXT)\n                                quote = Q_VTHIS;\n                }\n                break;\n        }\n\n#ifdef DEBUG\n        if (tp->cno + tp->insert + tp->owrite != tp->len) {\n                msgq(sp, M_ERR,\n                    \"len %u != cno: %u ai: %u insert %u overwrite %u\",\n                    tp->len, tp->cno, tp->ai, tp->insert, tp->owrite);\n                if (LF_ISSET(TXT_REPLAY))\n                        goto done;\n                tp->len = tp->cno + tp->insert + tp->owrite;\n        }\n#endif /* ifdef DEBUG */\n\nresolve:/*\n         * 1: If we don't need to know where the cursor really is and we're\n         *    replaying text, keep going.\n         */\n        if (margin == 0 && LF_ISSET(TXT_REPLAY))\n                goto replay;\n\n        /*\n         * 2: Reset the line.  Don't bother unless we're about to wait on\n         *    a character or we need to know where the cursor really is.\n         *    We have to do this before showing matching characters so the\n         *    user can see what they're matching.\n         */\n        if ((margin != 0 || !KEYS_WAITING(sp)) &&\n            vs_change(sp, tp->lno, LINE_RESET))\n                return (1);\n\n        /*\n         * 3: If there aren't keys waiting, display the matching character.\n         *    We have to do this before resolving any messages, otherwise\n         *    the error message from a missing match won't appear correctly.\n         */\n        if (showmatch) {\n                if (!KEYS_WAITING(sp) && txt_showmatch(sp, tp))\n                        return (1);\n                showmatch = 0;\n        }\n\n        /*\n         * 4: If there have been messages and we're not editing on the colon\n         *    command line or doing file name completion, resolve them.\n         */\n        if ((vip->totalcount != 0 || F_ISSET(gp, G_BELLSCHED)) &&\n            !F_ISSET(sp, SC_TINPUT_INFO) && !filec_redraw &&\n            vs_resolve(sp, NULL, 0))\n                return (1);\n\n        /*\n         * 5: Refresh the screen if we're about to wait on a character or we\n         *    need to know where the cursor really is.\n         */\n        if (margin != 0 || !KEYS_WAITING(sp)) {\n                UPDATE_POSITION(sp, tp);\n                if (vs_refresh(sp, margin != 0))\n                        return (1);\n        }\n\n        /* 6: Proceed with the incremental search. */\n        if (FL_ISSET(is_flags, IS_RUNNING) && txt_isrch(sp, vp, tp, &is_flags))\n                return (1);\n\n        /* 7: Next character... */\n        if (LF_ISSET(TXT_REPLAY))\n                goto replay;\n        goto next;\n\ndone:   /* Leave input mode. */\n        F_CLR(sp, SC_TINPUT);\n\n        /* If recording for playback, save it. */\n        if (LF_ISSET(TXT_RECORD))\n                vip->rep_cnt = rcol;\n\n        /*\n         * If not working on the colon command line, set the final cursor\n         * position.\n         */\n        if (!F_ISSET(sp, SC_TINPUT_INFO)) {\n                vp->m_final.lno = tp->lno;\n                vp->m_final.cno = tp->cno;\n        }\n        return (0);\n\nerr:\nalloc_err:\n        F_CLR(sp, SC_TINPUT);\n        txt_err(sp, &sp->tiq);\n        return (1);\n}\n\n/*\n * txt_abbrev --\n *      Handle abbreviations.\n */\nstatic int\ntxt_abbrev(SCR *sp, TEXT *tp, CHAR_T *pushcp, int isinfoline, int *didsubp,\n    int *turnoffp)\n{\n        CHAR_T ch, *p;\n        SEQ *qp;\n        size_t len, off;\n\n        /* Check to make sure we're not at the start of an append. */\n        *didsubp = 0;\n        if (tp->cno == tp->offset)\n                return (0);\n\n        /*\n         * Find the start of the \"word\".\n         *\n         * !!!\n         * We match historic practice, which, as far as I can tell, had an\n         * off-by-one error.  The way this worked was that when the inserted\n         * text switched from a \"word\" character to a non-word character,\n         * vi would check for possible abbreviations.  It would then take the\n         * type (i.e. word/non-word) of the character entered TWO characters\n         * ago, and move backward in the text until reaching a character that\n         * was not that type, or the beginning of the insert, the line, or\n         * the file.  For example, in the string \"abc<space>\", when the <space>\n         * character triggered the abbreviation check, the type of the 'b'\n         * character was used for moving through the string.  Maybe there's a\n         * reason for not using the first (i.e. 'c') character, but I can't\n         * think of one.\n         *\n         * Terminate at the beginning of the insert or the character after the\n         * offset character -- both can be tested for using tp->offset.\n         */\n        off = tp->cno - 1;                      /* Previous character. */\n        p = tp->lb + off;\n        len = 1;                                /* One character test. */\n        if (off == tp->offset || isblank(p[-1]))\n                goto search;\n        if (inword(p[-1]))                      /* Move backward to change. */\n                for (;;) {\n                        --off; --p; ++len;\n                        if (off == tp->offset || !inword(p[-1]))\n                                break;\n                }\n        else\n                for (;;) {\n                        --off; --p; ++len;\n                        if (off == tp->offset ||\n                            inword(p[-1]) || isblank(p[-1]))\n                                break;\n                }\n\n        /*\n         * !!!\n         * Historic vi exploded abbreviations on the command line.  This has\n         * obvious problems in that unabbreviating the string can be extremely\n         * tricky, particularly if the string has, say, an embedded escape\n         * character.  Personally, I think it's a stunningly bad idea.  Other\n         * examples of problems this caused in historic vi are:\n         *      :ab foo bar\n         *      :ab foo baz\n         * results in \"bar\" being abbreviated to \"baz\", which wasn't what the\n         * user had in mind at all.  Also, the commands:\n         *      :ab foo bar\n         *      :unab foo<space>\n         * resulted in an error message that \"bar\" wasn't mapped.  Finally,\n         * since the string was already exploded by the time the unabbreviate\n         * command got it, all it knew was that an abbreviation had occurred.\n         * Cleverly, it checked the replacement string for its unabbreviation\n         * match, which meant that the commands:\n         *      :ab foo1 bar\n         *      :ab foo2 bar\n         *      :unab foo2\n         * unabbreviate \"foo1\", and the commands:\n         *      :ab foo bar\n         *      :ab bar baz\n         * unabbreviate \"foo\"!\n         *\n         * Anyway, people neglected to first ask my opinion before they wrote\n         * macros that depend on this stuff, so, we make this work as follows:\n         *\n         * When checking for an abbreviation on the command line, if we get a\n         * string which is <blank> terminated and which starts at the beginning\n         * of the line, we check to see it is the abbreviate or unabbreviate\n         * commands.  If it is, turn abbreviations off and return as if no\n         * abbreviation was found.  Note also, minor trickiness, so that if\n         * the user erases the line and starts another command, we turn the\n         * abbreviations back on.\n         *\n         * This makes the layering look like a Nachos Supreme.\n         */\nsearch: if (isinfoline) {\n                if (off == tp->ai || off == tp->offset)\n                        if (ex_is_abbrev(p, len)) {\n                                *turnoffp = 1;\n                                return (0);\n                        } else\n                                *turnoffp = 0;\n                else\n                        if (*turnoffp)\n                                return (0);\n        }\n\n        /* Check for any abbreviations. */\n        if ((qp = seq_find(sp, NULL, NULL, p, len, SEQ_ABBREV, NULL)) == NULL)\n                return (0);\n\n        /*\n         * Push the abbreviation onto the tty stack.  Historically, characters\n         * resulting from an abbreviation expansion were themselves subject to\n         * map expansions, O_SHOWMATCH matching etc.  This means the expanded\n         * characters will be re-tested for abbreviations.  It's difficult to\n         * know what historic practice in this case was, since abbreviations\n         * were applied to :colon command lines, so entering abbreviations that\n         * looped was tricky, although possible.  In addition, obvious loops\n         * didn't work as expected.  (The command ':ab a b|ab b c|ab c a' will\n         * silently only implement and/or display the last abbreviation.)\n         *\n         * This implementation doesn't recover well from such abbreviations.\n         * The main input loop counts abbreviated characters, and, when it\n         * reaches a limit, discards any abbreviated characters on the queue.\n         * It's difficult to back up to the original position, as the replay\n         * queue would have to be adjusted, and the line state when an initial\n         * abbreviated character was received would have to be saved.\n         */\n        ch = *pushcp;\n        if (v_event_push(sp, NULL, &ch, 1, CH_ABBREVIATED))\n                return (1);\n        if (v_event_push(sp, NULL, qp->output, qp->olen, CH_ABBREVIATED))\n                return (1);\n\n        /*\n         * If the size of the abbreviation is larger than or equal to the size\n         * of the original text, move to the start of the replaced characters,\n         * and add their length to the overwrite count.\n         *\n         * If the abbreviation is smaller than the original text, we have to\n         * delete the additional overwrite characters and copy down any insert\n         * characters.\n         */\n        tp->cno -= len;\n        if (qp->olen >= len)\n                tp->owrite += len;\n        else {\n                if (tp->insert)\n                        memmove(tp->lb + tp->cno + qp->olen,\n                            tp->lb + tp->cno + tp->owrite + len, tp->insert);\n                tp->owrite += qp->olen;\n                tp->len -= len - qp->olen;\n        }\n\n        /*\n         * We return the length of the abbreviated characters.  This is so\n         * the calling routine can replace the replay characters with the\n         * abbreviation.  This means that subsequent '.' commands will produce\n         * the same text, regardless of intervening :[un]abbreviate commands.\n         * This is historic practice.\n         */\n        *didsubp = len;\n        return (0);\n}\n\n/*\n * txt_unmap --\n *      Handle the unmap command.\n */\nstatic void\ntxt_unmap(SCR *sp, TEXT *tp, u_int32_t *ec_flagsp)\n{\n        size_t len, off;\n        char *p;\n\n        /* Find the beginning of this \"word\". */\n        for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) {\n                if (isblank(*p)) {\n                        ++p;\n                        break;\n                }\n                ++len;\n                if (off == tp->ai || off == tp->offset)\n                        break;\n        }\n\n        /*\n         * !!!\n         * Historic vi exploded input mappings on the command line.  See the\n         * txt_abbrev() routine for an explanation of the problems inherent\n         * in this.\n         *\n         * We make this work as follows:  If we get a string which is <blank>\n         * terminated and which starts at the beginning of the line, we check\n         * to see it is the unmap command.  If it is, we return that the input\n         * mapping should be turned off.  Note also, minor trickiness, so that\n         * if the user erases the line and starts another command, we go ahead\n         * an turn mapping back on.\n         */\n        if ((off == tp->ai || off == tp->offset) && ex_is_unmap(p, len))\n                FL_CLR(*ec_flagsp, EC_MAPINPUT);\n        else\n                FL_SET(*ec_flagsp, EC_MAPINPUT);\n}\n\n/*\n * txt_ai_resolve --\n *      When a line is resolved by <esc>, review autoindent characters.\n */\nstatic void\ntxt_ai_resolve(SCR *sp, TEXT *tp, int *changedp)\n{\n        unsigned long ts;\n        int del;\n        size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs;\n        char *p;\n\n        *changedp = 0;\n\n        /*\n         * If the line is empty, has an offset, or no autoindent\n         * characters, we're done.\n         */\n        if (!tp->len || tp->offset || !tp->ai)\n                return;\n\n        /*\n         * If the length is less than or equal to the autoindent\n         * characters, delete them.\n         */\n        if (tp->len <= tp->ai) {\n                tp->ai = tp->cno = tp->len = 0;\n                return;\n        }\n\n        /*\n         * The autoindent characters plus any leading <blank> characters\n         * in the line are resolved into the minimum number of characters.\n         * Historic practice.\n         */\n        ts = O_VAL(sp, O_TABSTOP);\n\n        /* Figure out the last <blank> screen column. */\n        for (p = tp->lb, scno = 0, len = tp->len,\n            spaces = tab_after_sp = 0; len-- && isblank(*p); ++p)\n                if (*p == '\\t') {\n                        if (spaces)\n                                tab_after_sp = 1;\n                        scno += COL_OFF(scno, ts);\n                } else {\n                        ++spaces;\n                        ++scno;\n                }\n\n        /*\n         * If there are no spaces, or no tabs after spaces and less than\n         * ts spaces, it's already minimal.\n         * Keep analysing if expandtab is set.\n         */\n        if ((!spaces || (!tab_after_sp && spaces < ts)) &&\n            !O_ISSET(sp, O_EXPANDTAB))\n                return;\n\n        /* Count up spaces/tabs needed to get to the target. */\n        cno = 0;\n        tabs = 0;\n        if (!O_ISSET(sp, O_EXPANDTAB)) {\n                for (; cno + COL_OFF(cno, ts) <= scno; ++tabs)\n                        cno += COL_OFF(cno, ts);\n        }\n        spaces = scno - cno;\n\n        /*\n         * Figure out how many characters we're dropping -- if we're not\n         * dropping any, it's already minimal, we're done.\n         */\n        old = p - tp->lb;\n        new = spaces + tabs;\n        if (old == new)\n                return;\n\n        /* Shift the rest of the characters down, adjust the counts. */\n        del = old - new;\n        memmove(p - del, p, tp->len - old);\n        tp->len -= del;\n        tp->cno -= del;\n\n        /* Fill in space/tab characters. */\n        for (p = tp->lb; tabs--;)\n                *p++ = '\\t';\n        while (spaces--)\n                *p++ = ' ';\n        *changedp = 1;\n}\n\n/*\n * v_txt_auto --\n *      Handle autoindent.  If aitp isn't NULL, use it, otherwise,\n *      retrieve the line.\n *\n * PUBLIC: int v_txt_auto(SCR *, recno_t, TEXT *, size_t, TEXT *);\n */\nint\nv_txt_auto(SCR *sp, recno_t lno, TEXT *aitp, size_t len, TEXT *tp)\n{\n        size_t nlen;\n        char *p, *t;\n\n        if (aitp == NULL) {\n                /*\n                 * If the ex append command is executed with an address of 0,\n                 * it's possible to get here with a line number of 0.  Return\n                 * an indent of 0.\n                 */\n                if (lno == 0) {\n                        tp->ai = 0;\n                        return (0);\n                }\n                if (db_get(sp, lno, DBG_FATAL, &t, &len))\n                        return (1);\n        } else\n                t = aitp->lb;\n\n        /* Count whitespace characters. */\n        for (p = t; len > 0; ++p, --len)\n                if (!isblank(*p))\n                        break;\n\n        /* Set count, check for no indentation. */\n        if ((nlen = (p - t)) == 0)\n                return (0);\n\n        /* Make sure the buffer's big enough. */\n        BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen);\n\n        /* Copy the buffer's current contents up. */\n        if (tp->len != 0)\n                memmove(tp->lb + nlen, tp->lb, tp->len);\n        tp->len += nlen;\n\n        /* Copy the indentation into the new buffer. */\n        memmove(tp->lb, t, nlen);\n\n        /* Set the autoindent count. */\n        tp->ai = nlen;\n        return (0);\n}\n\n/*\n * txt_backup --\n *      Back up to the previously edited line.\n */\nstatic TEXT *\ntxt_backup(SCR *sp, TEXTH *tiqh, TEXT *tp, u_int32_t *flagsp)\n{\n        TEXT *ntp;\n\n        /* Get a handle on the previous TEXT structure. */\n        if ((ntp = TAILQ_PREV(tp, _texth, q)) == NULL) {\n                if (!FL_ISSET(*flagsp, TXT_REPLAY))\n                        msgq(sp, M_BERR,\n                            \"Already at the beginning of the insert\");\n                return (tp);\n        }\n\n        /* Bookkeeping. */\n        ntp->len = ntp->sv_len;\n\n        /* Handle appending to the line. */\n        if (ntp->owrite == 0 && ntp->insert == 0) {\n                ntp->lb[ntp->len] = CH_CURSOR;\n                ++ntp->insert;\n                ++ntp->len;\n                FL_SET(*flagsp, TXT_APPENDEOL);\n        } else\n                FL_CLR(*flagsp, TXT_APPENDEOL);\n\n        /* Release the current TEXT. */\n        TAILQ_REMOVE(tiqh, tp, q);\n        text_free(tp);\n\n        /* Update the old line on the screen. */\n        if (vs_change(sp, ntp->lno + 1, LINE_DELETE))\n                return (NULL);\n\n        /* Return the new/current TEXT. */\n        return (ntp);\n}\n\n/*\n * Text indentation is truly strange.  ^T and ^D do movements to the next or\n * previous shiftwidth value, i.e. for a 1-based numbering, with shiftwidth=3,\n * ^T moves a cursor on the 7th, 8th or 9th column to the 10th column, and ^D\n * moves it back.\n *\n * !!!\n * The ^T and ^D characters in historical vi had special meaning only when they\n * were the first characters entered after entering text input mode.  As normal\n * erase characters couldn't erase autoindent characters (^T in this case), it\n * meant that inserting text into previously existing text was strange -- ^T\n * only worked if it was the first keystroke(s), and then could only be erased\n * using ^D.  This implementation treats ^T specially anywhere it occurs in the\n * input, and permits the standard erase characters to erase the characters it\n * inserts.\n *\n * !!!\n * A fun test is to try:\n *      :se sw=4 ai list\n *      i<CR>^Tx<CR>^Tx<CR>^Tx<CR>^Dx<CR>^Dx<CR>^Dx<esc>\n * Historic vi loses some of the '$' marks on the line ends, but otherwise gets\n * it right.\n *\n * XXX\n * Technically, txt_dent should be part of the screen interface, as it requires\n * knowledge of character sizes, including <space>s, on the screen.  It's here\n * because it's a complicated little beast, and I didn't want to shove it down\n * into the screen.  It's probable that KEY_LEN will call into the screen once\n * there are screens with different character representations.\n *\n * txt_dent --\n *      Handle ^T indents, ^D outdents.\n *\n * If anything changes here, check the ex version to see if it needs similar\n * changes.\n */\nstatic int\ntxt_dent(SCR *sp, TEXT *tp, int swopt, int isindent)\n{\n        CHAR_T ch;\n        unsigned long sw, ts;\n        size_t cno, current, spaces, target, tabs;\n        int ai_reset;\n\n        ts = O_VAL(sp, O_TABSTOP);\n        sw = O_VAL(sp, swopt);\n\n        /*\n         * Since we don't know what precedes the character(s) being inserted\n         * (or deleted), the preceding whitespace characters must be resolved.\n         * An example is a <tab>, which doesn't need a full shiftwidth number\n         * of columns because it's preceded by <space>s.  This is easy to get\n         * if the user sets shiftwidth to a value less than tabstop (or worse,\n         * something for which tabstop isn't a multiple) and then uses ^T to\n         * indent, and ^D to outdent.\n         *\n         * Figure out the current and target screen columns.  In the historic\n         * vi, the autoindent column was NOT determined using display widths\n         * of characters as was the wrapmargin column.  For that reason, we\n         * can't use the vs_column() function, but have to calculate it here.\n         * This is slow, but it's normally only on the first few characters of\n         * a line.\n         */\n        for (current = cno = 0; cno < tp->cno; ++cno)\n                current += tp->lb[cno] == '\\t' ?\n                    COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]);\n\n        target = current;\n        if (isindent)\n                target += COL_OFF(target, sw);\n        else {\n                --target;\n                target -= target % sw;\n        }\n\n        /*\n         * The AI characters will be turned into overwrite characters if the\n         * cursor immediately follows them.  We test both the cursor position\n         * and the indent flag because there's no single test.  (^T can only\n         * be detected by the cursor position, and while we know that the test\n         * is always true for ^D, the cursor can be in more than one place, as\n         * \"0^D\" and \"^D\" are different.)\n         */\n        ai_reset = !isindent || tp->cno == tp->ai + tp->offset;\n\n        /*\n         * Back up over any previous <blank> characters, changing them into\n         * overwrite characters (including any ai characters).  Then figure\n         * out the current screen column.\n         */\n        for (; tp->cno > tp->offset &&\n            (tp->lb[tp->cno - 1] == ' ' || tp->lb[tp->cno - 1] == '\\t');\n            --tp->cno, ++tp->owrite);\n        for (current = cno = 0; cno < tp->cno; ++cno)\n                current += tp->lb[cno] == '\\t' ?\n                    COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]);\n\n        /*\n         * If we didn't move up to or past the target, it's because there\n         * weren't enough characters to delete, e.g. the first character\n         * of the line was a tp->offset character, and the user entered\n         * ^D to move to the beginning of a line.  An example of this is:\n         *\n         *      :set ai sw=4<cr>i<space>a<esc>i^T^D\n         *\n         * Otherwise, count up the total spaces/tabs needed to get from the\n         * beginning of the line (or the last non-<blank> character) to the\n         * target.\n         */\n        if (current >= target)\n                spaces = tabs = 0;\n        else {\n                cno = current;\n                tabs = 0;\n                if (!O_ISSET(sp, O_EXPANDTAB)) {\n                        for (; cno + COL_OFF(cno, ts) <= target; ++tabs)\n                                cno += COL_OFF(cno, ts);\n                        }\n                spaces = target - cno;\n        }\n\n        /* If we overwrote ai characters, reset the ai count. */\n        if (ai_reset)\n                tp->ai = tabs + spaces;\n\n        /*\n         * Call txt_insch() to insert each character, so that we get the\n         * correct effect when we add a <tab> to replace N <spaces>.\n         */\n        for (ch = '\\t'; tabs > 0; --tabs)\n                (void)txt_insch(sp, tp, &ch, 0);\n        for (ch = ' '; spaces > 0; --spaces)\n                (void)txt_insch(sp, tp, &ch, 0);\n        return (0);\n}\n\n/*\n * txt_fc --\n *      File name completion.\n */\nstatic int\ntxt_fc(SCR *sp, TEXT *tp, int *redrawp)\n{\n        struct stat sb;\n        ARGS **argv;\n        CHAR_T s_ch;\n        EXCMD cmd;\n        size_t indx, len, nlen, off;\n        int argc, trydir;\n        char *p, *t;\n\n        trydir = 0;\n        *redrawp = 0;\n\n        /*\n         * Find the beginning of this \"word\" -- if we're at the beginning\n         * of the line, it's a special case.\n         */\n        if (tp->cno == 1) {\n                len = 0;\n                p = tp->lb;\n        } else\nretry:          for (len = 0,\n                    off = tp->cno - 1, p = tp->lb + off;; --off, --p) {\n                        if (isblank(*p)) {\n                                ++p;\n                                break;\n                        }\n                        ++len;\n                        if (off == tp->ai || off == tp->offset)\n                                break;\n                }\n\n        /*\n         * Get enough space for a wildcard character.\n         *\n         * XXX\n         * This won't work for \"foo\\\", since the \\ will escape the expansion\n         * character.  I'm not sure if that's a bug or not...\n         */\n        off = p - tp->lb;\n        BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1);\n        p = tp->lb + off;\n\n        s_ch = p[len];\n        p[len] = '*';\n\n        /* Build an ex command, and call the ex expansion routines. */\n        ex_cinit(&cmd, 0, 0, OOBLNO, OOBLNO, 0, NULL);\n        if (argv_init(sp, &cmd))\n                return (1);\n        if (argv_exp2(sp, &cmd, p, len + 1)) {\n                p[len] = s_ch;\n                return (0);\n        }\n        argc = cmd.argc;\n        argv = cmd.argv;\n\n        p[len] = s_ch;\n\n        switch (argc) {\n        case 0:                         /* No matches. */\n                if (!trydir)\n                        (void)sp->gp->scr_bell(sp);\n                return (0);\n        case 1:                         /* One match. */\n                /* If something changed, do the exchange. */\n                nlen = strlen(cmd.argv[0]->bp);\n                if (len != nlen || memcmp(cmd.argv[0]->bp, p, len))\n                        break;\n\n                /* If haven't done a directory test, do it now. */\n                if (!trydir &&\n                    !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) {\n                        p += len;\n                        goto isdir;\n                }\n\n                /* If nothing changed, period, ring the bell. */\n                if (!trydir)\n                        (void)sp->gp->scr_bell(sp);\n                return (0);\n        default:                        /* Multiple matches. */\n                *redrawp = 1;\n                if (txt_fc_col(sp, argc, argv))\n                        return (1);\n\n                /* Find the length of the shortest match. */\n                for (nlen = cmd.argv[0]->len; --argc > 0;) {\n                        if (cmd.argv[argc]->len < nlen)\n                                nlen = cmd.argv[argc]->len;\n                        for (indx = 0; indx < nlen &&\n                            cmd.argv[argc]->bp[indx] == cmd.argv[0]->bp[indx];\n                            ++indx);\n                        nlen = indx;\n                }\n                break;\n        }\n\n        /* Overwrite the expanded text first. */\n        for (t = cmd.argv[0]->bp; len > 0 && nlen > 0; --len, --nlen)\n                *p++ = *t++;\n\n        /* If lost text, make the remaining old text overwrite characters. */\n        if (len) {\n                tp->cno -= len;\n                tp->owrite += len;\n        }\n\n        /* Overwrite any overwrite characters next. */\n        for (; nlen > 0 && tp->owrite > 0; --nlen, --tp->owrite, ++tp->cno)\n                *p++ = *t++;\n\n        /* Shift remaining text up, and move the cursor to the end. */\n        if (nlen) {\n                off = p - tp->lb;\n                BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen);\n                p = tp->lb + off;\n\n                tp->cno += nlen;\n                tp->len += nlen;\n\n                if (tp->insert != 0)\n                        (void)memmove(p + nlen, p, tp->insert);\n                while (nlen--)\n                        *p++ = *t++;\n        }\n\n        /* If a single match and it's a directory, retry it. */\n        if (argc == 1 && !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) {\nisdir:          if (tp->owrite == 0) {\n                        off = p - tp->lb;\n                        BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1);\n                        p = tp->lb + off;\n                        if (tp->insert != 0)\n                                (void)memmove(p + 1, p, tp->insert);\n                        ++tp->len;\n                } else\n                        --tp->owrite;\n\n                ++tp->cno;\n                *p++ = '/';\n\n                trydir = 1;\n                goto retry;\n        }\n        return (0);\n}\n\n/*\n * txt_fc_col --\n *      Display file names for file name completion.\n */\nstatic int\ntxt_fc_col(SCR *sp, int argc, ARGS **argv)\n{\n        ARGS **av;\n        CHAR_T *p;\n        GS *gp;\n        size_t base, cnt, col, colwidth, numrows, numcols, prefix, row;\n        int nf = 0;\n        int ac, reset;\n\n        gp = sp->gp;\n\n        /* Trim any directory prefix common to all of the files. */\n        if ((p = strrchr(argv[0]->bp, '/')) == NULL)\n                prefix = 0;\n        else {\n                prefix = (p - argv[0]->bp) + 1;\n                for (ac = argc - 1, av = argv + 1; ac > 0; --ac, ++av)\n                        if (av[0]->len < prefix ||\n                            memcmp(av[0]->bp, argv[0]->bp, prefix)) {\n                                prefix = 0;\n                                break;\n                        }\n        }\n\n        /*\n         * Figure out the column width for the longest name.  Output is done on\n         * 6 character \"tab\" boundaries for no particular reason.  (Since we\n         * don't output tab characters, we ignore the terminal's tab settings.)\n         * Ignore the user's tab setting because we have no idea how reasonable\n         * it is.\n         */\n        for (ac = argc, av = argv, colwidth = 0; ac > 0; --ac, ++av) {\n                for (col = 0, p = av[0]->bp + prefix; *p != '\\0'; ++p)\n                        col += KEY_LEN(sp, *p);\n                if (col > colwidth)\n                        colwidth = col;\n        }\n        colwidth += COL_OFF(colwidth, 6);\n\n        /*\n         * Writing to the bottom line of the screen is always turned off when\n         * SC_TINPUT_INFO is set.  Turn it back on, we know what we're doing.\n         */\n        if (F_ISSET(sp, SC_TINPUT_INFO)) {\n                reset = 1;\n                F_CLR(sp, SC_TINPUT_INFO);\n        } else\n                reset = 0;\n\n#define CHK_INTR                                                        \\\n        if (F_ISSET(gp, G_INTERRUPTED))                                 \\\n                goto intr;\n\n        /* If the largest file name is too large, just print them. */\n        if (colwidth > sp->cols) {\n                for (ac = argc, av = argv; ac > 0; --ac, ++av) {\n                        p = msg_print(sp, av[0]->bp + prefix, &nf);\n                        (void)ex_printf(sp, \"%s\\n\", p);\n                        if (F_ISSET(gp, G_INTERRUPTED))\n                                break;\n                }\n                if (nf)\n                        FREE_SPACE(sp, (char *) p, 0);\n                CHK_INTR;\n        } else {\n                /* Figure out the number of columns. */\n                numcols = (sp->cols - 1) / colwidth;\n                if (argc > numcols) {\n                        numrows = argc / numcols;\n                        if (argc % numcols)\n                                ++numrows;\n                } else\n                        numrows = 1;\n\n                /* Display the files in sorted order. */\n                for (row = 0; row < numrows; ++row) {\n                        for (base = row, col = 0; col < numcols; ++col) {\n                                p = msg_print(sp, argv[base]->bp + prefix, &nf);\n                                cnt = ex_printf(sp, \"%s\", p);\n                                if (nf)\n                                        FREE_SPACE(sp, (char *) p, 0);\n                                CHK_INTR;\n                                if ((base += numrows) >= argc)\n                                        break;\n                                (void)ex_printf(sp,\n                                    \"%*s\", (int)(colwidth - cnt), \"\");\n                                CHK_INTR;\n                        }\n                        (void)ex_puts(sp, \"\\n\");\n                        CHK_INTR;\n                }\n                (void)ex_puts(sp, \"\\n\");\n                CHK_INTR;\n        }\n        (void)ex_fflush(sp);\n\n        if (0) {\nintr:           F_CLR(gp, G_INTERRUPTED);\n        }\n        if (reset)\n                F_SET(sp, SC_TINPUT_INFO);\n\n        return (0);\n}\n\n/*\n * txt_emark --\n *      Set the end mark on the line.\n */\nstatic int\ntxt_emark(SCR *sp, TEXT *tp, size_t cno)\n{\n        CHAR_T ch, *kp;\n        size_t chlen, nlen, olen;\n        char *p;\n\n        ch = CH_ENDMARK;\n\n        /*\n         * The end mark may not be the same size as the current character.\n         * Don't let the line shift.\n         */\n        nlen = KEY_LEN(sp, ch);\n        if (tp->lb[cno] == '\\t')\n                (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen);\n        else\n                olen = KEY_LEN(sp, tp->lb[cno]);\n\n        /*\n         * If the line got longer, well, it's weird, but it's easy.  If\n         * it's the same length, it's easy.  If it got shorter, we have\n         * to fix it up.\n         */\n        if (olen > nlen) {\n                BINC_RET(sp, tp->lb, tp->lb_len, tp->len + olen);\n                chlen = olen - nlen;\n                if (tp->insert != 0)\n                        memmove(tp->lb + cno + 1 + chlen,\n                            tp->lb + cno + 1, tp->insert);\n\n                tp->len += chlen;\n                tp->owrite += chlen;\n                p = tp->lb + cno;\n                if (tp->lb[cno] == '\\t')\n                        for (cno += chlen; chlen--;)\n                                *p++ = ' ';\n                else\n                        for (kp = KEY_NAME(sp, tp->lb[cno]),\n                            cno += chlen; chlen--;)\n                                *p++ = *kp++;\n        }\n        tp->lb[cno] = ch;\n        return (vs_change(sp, tp->lno, LINE_RESET));\n}\n\n/*\n * txt_err --\n *      Handle an error during input processing.\n */\nstatic void\ntxt_err(SCR *sp, TEXTH *tiqh)\n{\n        recno_t lno;\n\n        /*\n         * The problem with input processing is that the cursor is at an\n         * indeterminate position since some input may have been lost due\n         * to a malloc error.  So, try to go back to the place from which\n         * the cursor started, knowing that it may no longer be available.\n         *\n         * We depend on at least one line number being set in the text\n         * chain.\n         */\n        for (lno = TAILQ_FIRST(tiqh)->lno;\n            !db_exist(sp, lno) && lno > 0; --lno);\n\n        sp->lno = lno == 0 ? 1 : lno;\n        sp->cno = 0;\n\n        /* Redraw the screen, just in case. */\n        F_SET(sp, SC_SCR_REDRAW);\n}\n\n/*\n * txt_hex --\n *      Let the user insert any character value they want.\n *\n * !!!\n * This is an extension.  The pattern \"^X[0-9a-fA-F]*\" is a way\n * for the user to specify a character value which their keyboard\n * may not be able to enter.\n */\nstatic int\ntxt_hex(SCR *sp, TEXT *tp)\n{\n        CHAR_T savec;\n        size_t len, off;\n        unsigned long value;\n        char *p, *wp;\n\n        /*\n         * NULL-terminate the string.  Since NULL isn't a legal hex value,\n         * this should be okay, and lets us use a local routine, which\n         * presumably understands the character set, to convert the value.\n         */\n        savec = tp->lb[tp->cno];\n        tp->lb[tp->cno] = 0;\n\n        /* Find the previous CH_HEX character. */\n        for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off, ++len) {\n                if (*p == CH_HEX) {\n                        wp = p + 1;\n                        break;\n                }\n                /* Not on this line?  Shouldn't happen. */\n                if (off == tp->ai || off == tp->offset)\n                        goto nothex;\n        }\n\n        /* If length of 0, then it wasn't a hex value. */\n        if (len == 0)\n                goto nothex;\n\n        /* Get the value. */\n        errno = 0;\n        value = strtol(wp, NULL, 16);\n        if (errno || value > MAX_CHAR_T) {\nnothex:         tp->lb[tp->cno] = savec;\n                return (0);\n        }\n\n        /* Restore the original character. */\n        tp->lb[tp->cno] = savec;\n\n        /* Adjust the bookkeeping. */\n        tp->cno -= len;\n        tp->len -= len;\n        tp->lb[tp->cno - 1] = value;\n\n        /* Copy down any overwrite characters. */\n        if (tp->owrite)\n                memmove(tp->lb + tp->cno, tp->lb + tp->cno + len, tp->owrite);\n\n        /* Copy down any insert characters. */\n        if (tp->insert)\n                memmove(tp->lb + tp->cno + tp->owrite,\n                    tp->lb + tp->cno + tp->owrite + len, tp->insert);\n\n        return (0);\n}\n\n/*\n * txt_insch --\n *\n * !!!\n * Historic vi did a special screen optimization for tab characters.  As an\n * example, for the keystrokes \"iabcd<esc>0C<tab>\", the tab overwrote the\n * rest of the string when it was displayed.\n *\n * Because early versions of this implementation redisplayed the entire line\n * on each keystroke, the \"bcd\" was pushed to the right as it ignored that\n * the user had \"promised\" to change the rest of the characters.  However,\n * the historic vi implementation had an even worse bug: given the keystrokes\n * \"iabcd<esc>0R<tab><esc>\", the \"bcd\" disappears, and magically reappears\n * on the second <esc> key.\n *\n * POSIX 1003.2 requires (will require) that this be fixed, specifying that\n * vi overwrite characters the user has committed to changing, on the basis\n * of the screen space they require, but that it not overwrite other characters.\n */\nstatic int\ntxt_insch(SCR *sp, TEXT *tp, CHAR_T *chp, unsigned int flags)\n{\n        CHAR_T *kp, savech;\n        size_t chlen, cno, copydown, olen, nlen;\n        char *p;\n\n        /*\n         * The 'R' command does one-for-one replacement, because there's\n         * no way to know how many characters the user intends to replace.\n         */\n        if (LF_ISSET(TXT_REPLACE)) {\n                if (tp->owrite) {\n                        --tp->owrite;\n                        tp->lb[tp->cno++] = *chp;\n                        return (0);\n                }\n        } else if (tp->owrite) {                /* Overwrite a character. */\n                cno = tp->cno;\n\n                /*\n                 * If the old or new characters are tabs, then the length of the\n                 * display depends on the character position in the display.  We\n                 * don't even try to handle this here, just ask the screen.\n                 */\n                if (*chp == '\\t') {\n                        savech = tp->lb[cno];\n                        tp->lb[cno] = '\\t';\n                        (void)vs_columns(sp, tp->lb, tp->lno, &cno, &nlen);\n                        tp->lb[cno] = savech;\n                } else\n                        nlen = KEY_LEN(sp, *chp);\n\n                /*\n                 * Eat overwrite characters until we run out of them or we've\n                 * handled the length of the new character.  If we only eat\n                 * part of an overwrite character, break it into its component\n                 * elements and display the remaining components.\n                 */\n                for (copydown = 0; nlen != 0 && tp->owrite != 0;) {\n                        --tp->owrite;\n\n                        if (tp->lb[cno] == '\\t')\n                                (void)vs_columns(sp,\n                                    tp->lb, tp->lno, &cno, &olen);\n                        else\n                                olen = KEY_LEN(sp, tp->lb[cno]);\n\n                        if (olen == nlen) {\n                                nlen = 0;\n                                break;\n                        }\n                        if (olen < nlen) {\n                                ++copydown;\n                                nlen -= olen;\n                        } else {\n                                BINC_RET(sp,\n                                    tp->lb, tp->lb_len, tp->len + olen);\n                                chlen = olen - nlen;\n                                memmove(tp->lb + cno + 1 + chlen,\n                                    tp->lb + cno + 1, tp->owrite + tp->insert);\n\n                                tp->len += chlen;\n                                tp->owrite += chlen;\n                                if (tp->lb[cno] == '\\t')\n                                        for (p = tp->lb + cno + 1; chlen--;)\n                                                *p++ = ' ';\n                                else\n                                        for (kp =\n                                            KEY_NAME(sp, tp->lb[cno]) + nlen,\n                                            p = tp->lb + cno + 1; chlen--;)\n                                                *p++ = *kp++;\n                                nlen = 0;\n                                break;\n                        }\n                }\n\n                /*\n                 * If had to erase several characters, we adjust the total\n                 * count, and if there are any characters left, shift them\n                 * into position.\n                 */\n                if (copydown != 0 && (tp->len -= copydown) != 0)\n                        memmove(tp->lb + cno, tp->lb + cno + copydown,\n                            tp->owrite + tp->insert + copydown);\n\n                /* If we had enough overwrite characters, we're done. */\n                if (nlen == 0) {\n                        tp->lb[tp->cno++] = *chp;\n                        return (0);\n                }\n        }\n\n        /* Check to see if the character fits into the input buffer. */\n        BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1);\n\n        ++tp->len;\n        if (tp->insert) {                       /* Insert a character. */\n                if (tp->insert == 1)\n                        tp->lb[tp->cno + 1] = tp->lb[tp->cno];\n                else\n                        memmove(tp->lb + tp->cno + 1,\n                            tp->lb + tp->cno, tp->owrite + tp->insert);\n        }\n        tp->lb[tp->cno++] = *chp;\n        return (0);\n}\n\n/*\n * txt_isrch --\n *      Do an incremental search.\n */\nstatic int\ntxt_isrch(SCR *sp, VICMD *vp, TEXT *tp, u_int8_t *is_flagsp)\n{\n        MARK start;\n        recno_t lno;\n        unsigned int sf;\n\n        /* If it's a one-line screen, we don't do incrementals. */\n        if (IS_ONELINE(sp)) {\n                FL_CLR(*is_flagsp, IS_RUNNING);\n                return (0);\n        }\n\n        /*\n         * If the user erases back to the beginning of the buffer, there's\n         * nothing to search for.  Reset the cursor to the starting point.\n         */\n        if (tp->cno <= 1) {\n                vp->m_final = vp->m_start;\n                return (0);\n        }\n\n        /*\n         * If it's an RE quote character, and not quoted, ignore it until\n         * we get another character.\n         */\n        if (tp->lb[tp->cno - 1] == '\\\\' &&\n            (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\\\'))\n                return (0);\n\n        /*\n         * If it's a magic shell character, and not quoted, reset the cursor\n         * to the starting point.\n         */\n        if (strchr(O_STR(sp, O_SHELLMETA), tp->lb[tp->cno - 1]) != NULL &&\n            (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\\\'))\n                vp->m_final = vp->m_start;\n\n        /*\n         * If we see the search pattern termination character, then quit doing\n         * an incremental search.  There may be more, e.g., \":/foo/;/bar/\",\n         * and we can't handle that incrementally.  Also, reset the cursor to\n         * the original location, the ex search routines don't know anything\n         * about incremental searches.\n         */\n        if (tp->lb[0] == tp->lb[tp->cno - 1] &&\n            (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\\\')) {\n                vp->m_final = vp->m_start;\n                FL_CLR(*is_flagsp, IS_RUNNING);\n                return (0);\n        }\n\n        /*\n         * Remember the input line and discard the special input map,\n         * but don't overwrite the input line on the screen.\n         */\n        lno = tp->lno;\n        F_SET(VIP(sp), VIP_S_MODELINE);\n        F_CLR(sp, SC_TINPUT | SC_TINPUT_INFO);\n        if (txt_map_end(sp))\n                return (1);\n\n        /*\n         * Specify a starting point and search.  If we find a match, move to\n         * it and refresh the screen.  If we didn't find the match, then we\n         * beep the screen.  When searching from the original cursor position,\n         * we have to move the cursor, otherwise, we don't want to move the\n         * cursor in case the text at the current position continues to match.\n         */\n        if (FL_ISSET(*is_flagsp, IS_RESTART)) {\n                start = vp->m_start;\n                sf = SEARCH_SET;\n        } else {\n                start = vp->m_final;\n                sf = SEARCH_INCR | SEARCH_SET;\n        }\n\n        if (tp->lb[0] == '/' ?\n            !f_search(sp,\n            &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf) :\n            !b_search(sp,\n            &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf)) {\n                sp->lno = vp->m_final.lno;\n                sp->cno = vp->m_final.cno;\n                FL_CLR(*is_flagsp, IS_RESTART);\n\n                if (!KEYS_WAITING(sp) && vs_refresh(sp, 0))\n                        return (1);\n        } else\n                FL_SET(*is_flagsp, IS_RESTART);\n\n        /* Reinstantiate the special input map. */\n        if (txt_map_init(sp))\n                return (1);\n        F_CLR(VIP(sp), VIP_S_MODELINE);\n        F_SET(sp, SC_TINPUT | SC_TINPUT_INFO);\n\n        /* Reset the line number of the input line. */\n        tp->lno = TMAP[0].lno;\n\n        /*\n         * If the colon command-line moved, i.e. the screen scrolled,\n         * refresh the input line.\n         *\n         * XXX\n         * We shouldn't be calling vs_line, here -- we need dirty bits\n         * on entries in the SMAP array.\n         */\n        if (lno != TMAP[0].lno) {\n                if (vs_line(sp, &TMAP[0], NULL, NULL))\n                        return (1);\n                (void)sp->gp->scr_refresh(sp, 0);\n        }\n        return (0);\n}\n\n/*\n * txt_resolve --\n *      Resolve the input text chain into the file.\n */\nstatic int\ntxt_resolve(SCR *sp, TEXTH *tiqh, u_int32_t flags)\n{\n        TEXT *tp;\n        recno_t lno;\n        int changed;\n\n        /*\n         * The first line replaces a current line, and all subsequent lines\n         * are appended into the file.  Resolve autoindented characters for\n         * each line before committing it.  If the latter causes the line to\n         * change, we have to redisplay it, otherwise the information cached\n         * about the line will be wrong.\n         */\n        tp = TAILQ_FIRST(tiqh);\n\n        if (LF_ISSET(TXT_AUTOINDENT))\n                txt_ai_resolve(sp, tp, &changed);\n        else\n                changed = 0;\n        if (db_set(sp, tp->lno, tp->lb, tp->len) ||\n            (changed && vs_change(sp, tp->lno, LINE_RESET)))\n                return (1);\n\n        for (lno = tp->lno; (tp = TAILQ_NEXT(tp, q)); ++lno) {\n                if (LF_ISSET(TXT_AUTOINDENT))\n                        txt_ai_resolve(sp, tp, &changed);\n                else\n                        changed = 0;\n                if (db_append(sp, 0, lno, tp->lb, tp->len) ||\n                    (changed && vs_change(sp, tp->lno, LINE_RESET)))\n                        return (1);\n        }\n\n        /*\n         * Clear the input flag, the look-aside buffer is no longer valid.\n         * Has to be done as part of text resolution, or upon return we'll\n         * be looking at incorrect data.\n         */\n        F_CLR(sp, SC_TINPUT);\n\n        return (0);\n}\n\n/*\n * txt_showmatch --\n *      Show a character match.\n *\n * !!!\n * Historic vi tried to display matches even in the :colon command line.\n * I think not.\n */\nstatic int\ntxt_showmatch(SCR *sp, TEXT *tp)\n{\n        VCS cs;\n        MARK m;\n        int cnt, endc, startc;\n\n        /*\n         * Do a refresh first, in case we haven't done one in awhile,\n         * so the user can see what we're complaining about.\n         */\n        UPDATE_POSITION(sp, tp);\n        if (vs_refresh(sp, 1))\n                return (1);\n\n        /*\n         * We don't display the match if it's not on the screen.  Find\n         * out what the first character on the screen is.\n         */\n        if (vs_sm_position(sp, &m, 0, P_TOP))\n                return (1);\n\n        /* Initialize the getc() interface. */\n        cs.cs_lno = tp->lno;\n        cs.cs_cno = tp->cno - 1;\n        if (cs_init(sp, &cs))\n                return (1);\n        startc = (endc = cs.cs_ch)  == ')' ? '(' : '{';\n\n        /* Search for the match. */\n        for (cnt = 1;;) {\n                if (cs_prev(sp, &cs))\n                        return (1);\n                if (cs.cs_flags != 0) {\n                        if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) {\n                                msgq(sp, M_BERR,\n                                    \"Unmatched %s\", KEY_NAME(sp, endc));\n                                return (0);\n                        }\n                        continue;\n                }\n                if (cs.cs_ch == endc)\n                        ++cnt;\n                else if (cs.cs_ch == startc && --cnt == 0)\n                        break;\n        }\n\n        /* If the match is on the screen, move to it. */\n        if (cs.cs_lno < m.lno || (cs.cs_lno == m.lno && cs.cs_cno < m.cno))\n                return (0);\n        sp->lno = cs.cs_lno;\n        sp->cno = cs.cs_cno;\n        if (vs_refresh(sp, 1))\n                return (1);\n\n        /* Wait for timeout or character arrival. */\n        return (v_event_get(sp,\n            NULL, O_VAL(sp, O_MATCHTIME) * 100, EC_TIMEOUT));\n}\n\n/*\n * txt_margin --\n *      Handle margin wrap.\n */\nstatic int\ntxt_margin(SCR *sp, TEXT *tp, TEXT *wmtp, int *didbreak, u_int32_t flags)\n{\n        size_t len, off;\n        char *p;\n\n        (void)sp;\n        (void)tp;\n        (void)wmtp;\n        (void)didbreak;\n        (void)flags;\n\n        /* Find the nearest previous blank. */\n        for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --off, --p, ++len) {\n                if (isblank(*p))\n                        break;\n\n                /*\n                 * If reach the start of the line, there's nowhere to break.\n                 *\n                 * !!!\n                 * Historic vi belled each time a character was entered after\n                 * crossing the margin until a space was entered which could\n                 * be used to break the line.  I don't as it tends to wake the\n                 * cats.\n                 */\n                if (off == tp->ai || off == tp->offset) {\n                        *didbreak = 0;\n                        return (0);\n                }\n        }\n\n        /*\n         * Store saved information about the rest of the line in the\n         * wrapmargin TEXT structure.\n         *\n         * !!!\n         * The offset field holds the length of the current characters\n         * that the user entered, but which are getting split to the new\n         * line -- it's going to be used to set the cursor value when we\n         * move to the new line.\n         */\n        wmtp->lb = p + 1;\n        wmtp->offset = len;\n        wmtp->insert = LF_ISSET(TXT_APPENDEOL) ?  tp->insert - 1 : tp->insert;\n        wmtp->owrite = tp->owrite;\n\n        /* Correct current bookkeeping information. */\n        tp->cno -= len;\n        if (LF_ISSET(TXT_APPENDEOL)) {\n                tp->len -= len + tp->owrite + (tp->insert - 1);\n                tp->insert = 1;\n        } else {\n                tp->len -= len + tp->owrite + tp->insert;\n                tp->insert = 0;\n        }\n        tp->owrite = 0;\n\n        /*\n         * !!!\n         * Delete any trailing whitespace from the current line.\n         */\n        for (;; --p, --off) {\n                if (!isblank(*p))\n                        break;\n                --tp->cno;\n                --tp->len;\n                if (off == tp->ai || off == tp->offset)\n                        break;\n        }\n        *didbreak = 1;\n        return (0);\n}\n\n/*\n * txt_Rresolve --\n *      Resolve the input line for the 'R' command.\n */\nstatic void\ntxt_Rresolve(SCR *sp, TEXTH *tiqh, TEXT *tp, const size_t orig_len)\n{\n        TEXT *ttp;\n        size_t input_len, retain;\n        char *p;\n\n        /*\n         * Check to make sure that the cursor hasn't moved beyond\n         * the end of the line.\n         */\n        if (tp->owrite == 0)\n                return;\n\n        /*\n         * Calculate how many characters the user has entered,\n         * plus the blanks erased by <carriage-return>/<newline>s.\n         */\n        input_len = 0;\n        TAILQ_FOREACH(ttp, tiqh, q) {\n                input_len += ttp == tp ? tp->cno : ttp->len + ttp->R_erase;\n        }\n\n        /*\n         * If the user has entered less characters than the original line\n         * was long, restore any overwritable characters to the original\n         * characters.  These characters are entered as \"insert characters\",\n         * because they're after the cursor and we don't want to lose them.\n         * (This is okay because the R command has no insert characters.)\n         * We set owrite to 0 so that the insert characters don't get copied\n         * to somewhere else, which means that the line and the length have\n         * to be adjusted here as well.\n         *\n         * We have to retrieve the original line because the original pinned\n         * page has long since been discarded.  If it doesn't exist, that's\n         * okay, the user just extended the file.\n         */\n        if (input_len < orig_len) {\n                retain = MINIMUM(tp->owrite, orig_len - input_len);\n                if (db_get(sp,\n                    TAILQ_FIRST(tiqh)->lno, DBG_FATAL | DBG_NOCACHE, &p, NULL))\n                        return;\n                memcpy(tp->lb + tp->cno, p + input_len, retain);\n                tp->len -= tp->owrite - retain;\n                tp->owrite = 0;\n                tp->insert += retain;\n        }\n}\n\n/*\n * txt_nomorech --\n *      No more characters message.\n */\nstatic void\ntxt_nomorech(SCR *sp)\n{\n        msgq(sp, M_BERR, \"No more characters to erase\");\n}\n"
  },
  {
    "path": "vi/v_ulcase.c",
    "content": "/*      $OpenBSD: v_ulcase.c,v 1.10 2016/05/27 09:18:12 martijn Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic int ulcase(SCR *, recno_t, CHAR_T *, size_t, size_t, size_t);\n\n/*\n * v_ulcase -- [count]~\n *      Toggle upper & lower case letters.\n *\n * !!!\n * Historic vi didn't permit ~ to cross newline boundaries.  I can\n * think of no reason why it shouldn't, which at least lets the user\n * auto-repeat through a paragraph.\n *\n * !!!\n * In historic vi, the count was ignored.  It would have been better\n * if there had been an associated motion, but it's too late to make\n * that the default now.\n *\n * PUBLIC: int v_ulcase(SCR *, VICMD *);\n */\nint\nv_ulcase(SCR *sp, VICMD *vp)\n{\n        recno_t lno;\n        size_t cno, lcnt, len;\n        unsigned long cnt;\n        char *p;\n\n        lno = vp->m_start.lno;\n        cno = vp->m_start.cno;\n\n        for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt > 0; cno = 0) {\n                /* SOF is an error, EOF is an infinite count sink. */\n                if (db_get(sp, lno, 0, &p, &len)) {\n                        if (lno == 1) {\n                                v_emsg(sp, NULL, VIM_EMPTY);\n                                return (1);\n                        }\n                        --lno;\n                        break;\n                }\n\n                /* Empty lines decrement the count by one. */\n                if (len == 0) {\n                        --cnt;\n                        vp->m_final.cno = 0;\n                        continue;\n                }\n\n                if (cno + cnt >= len) {\n                        lcnt = len - 1;\n                        cnt -= len - cno;\n\n                        vp->m_final.cno = len - 1;\n                } else {\n                        lcnt = cno + cnt - 1;\n                        cnt = 0;\n\n                        vp->m_final.cno = lcnt + 1;\n                }\n\n                if (ulcase(sp, lno, p, len, cno, lcnt))\n                        return (1);\n\n                if (cnt > 0)\n                        ++lno;\n        }\n\n        vp->m_final.lno = lno;\n        return (0);\n}\n\n/*\n * v_mulcase -- [count]~[count]motion\n *      Toggle upper & lower case letters over a range.\n *\n * PUBLIC: int v_mulcase(SCR *, VICMD *);\n */\nint\nv_mulcase(SCR *sp, VICMD *vp)\n{\n        CHAR_T *p;\n        size_t len;\n        recno_t lno;\n\n        for (lno = vp->m_start.lno;;) {\n                if (db_get(sp, lno, DBG_FATAL, (char **) &p, &len))\n                        return (1);\n                if (len != 0 && ulcase(sp, lno, p, len,\n                    lno == vp->m_start.lno ? vp->m_start.cno : 0,\n                    !F_ISSET(vp, VM_LMODE) &&\n                    lno == vp->m_stop.lno ? vp->m_stop.cno : len))\n                        return (1);\n\n                if (++lno > vp->m_stop.lno)\n                        break;\n        }\n\n        /*\n         * XXX\n         * I didn't create a new motion command when I added motion semantics\n         * for ~.  While that's the correct way to do it, that choice would\n         * have required changes all over the vi directory for little gain.\n         * Instead, we pretend it's a yank command.  Note, this means that we\n         * follow the cursor motion rules for yank commands, but that seems\n         * reasonable to me.\n         */\n        return (0);\n}\n\n/*\n * ulcase --\n *      Change part of a line's case.\n */\nstatic int\nulcase(SCR *sp, recno_t lno, CHAR_T *lp, size_t len, size_t scno, size_t ecno)\n{\n        size_t blen;\n        int change, rval;\n        CHAR_T ch, *p, *t;\n        char *bp;\n\n        GET_SPACE_RET(sp, bp, blen, len);\n        memmove(bp, lp, len);\n\n        change = rval = 0;\n        for (p = bp + scno, t = bp + ecno + 1; p < t; ++p) {\n                ch = *(unsigned char *)p;\n                if (islower(ch)) {\n                        *p = toupper(ch);\n                        change = 1;\n                } else if (isupper(ch)) {\n                        *p = tolower(ch);\n                        change = 1;\n                }\n        }\n\n        if (change && db_set(sp, lno, bp, len))\n                rval = 1;\n\n        FREE_SPACE(sp, bp, blen);\n        return (rval);\n}\n"
  },
  {
    "path": "vi/v_undo.c",
    "content": "/*      $OpenBSD: v_undo.c,v 1.6 2014/11/12 04:28:41 bentley Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_Undo -- U\n *      Undo changes to this line.\n *\n * PUBLIC: int v_Undo(SCR *, VICMD *);\n */\nint\nv_Undo(SCR *sp, VICMD *vp)\n{\n        /*\n         * Historically, U reset the cursor to the first column in the line\n         * (not the first non-blank).  This seems a bit non-intuitive, but,\n         * considering that we may have undone multiple changes, anything\n         * else (including the cursor position stored in the logging records)\n         * is going to appear random.\n         */\n        vp->m_final.cno = 0;\n\n        /*\n         * !!!\n         * Set up the flags so that an immediately subsequent 'u' will roll\n         * forward, instead of backward.  In historic vi, a 'u' following a\n         * 'U' redid all of the changes to the line.  Given that the user has\n         * explicitly discarded those changes by entering 'U', it seems likely\n         * that the user wants something between the original and end forms of\n         * the line, so starting to replay the changes seems the best way to\n         * get to there.\n         */\n        F_SET(sp->ep, F_UNDO);\n        sp->ep->lundo = BACKWARD;\n\n        return (log_setline(sp));\n}\n\n/*\n * v_undo -- u\n *      Undo the last change.\n *\n * PUBLIC: int v_undo(SCR *, VICMD *);\n */\nint\nv_undo(SCR *sp, VICMD *vp)\n{\n        EXF *ep;\n\n        /* Set the command count. */\n        VIP(sp)->u_ccnt = sp->ccnt;\n\n        /*\n         * !!!\n         * In historic vi, 'u' toggled between \"undo\" and \"redo\", i.e. 'u'\n         * undid the last undo.  However, if there has been a change since\n         * the last undo/redo, we always do an undo.  To make this work when\n         * the user can undo multiple operations, we leave the old semantic\n         * unchanged, but make '.' after a 'u' do another undo/redo operation.\n         * This has two problems.\n         *\n         * The first is that 'u' didn't set '.' in historic vi.  So, if a\n         * user made a change, realized it was in the wrong place, does a\n         * 'u' to undo it, moves to the right place and then does '.', the\n         * change was reapplied.  To make this work, we only apply the '.'\n         * to the undo command if it's the command immediately following an\n         * undo command.  See vi/vi.c:getcmd() for the details.\n         *\n         * The second is that the traditional way to view the numbered cut\n         * buffers in vi was to enter the commands \"1pu.u.u.u. which will\n         * no longer work because the '.' immediately follows the 'u' command.\n         * Since we provide a much better method of viewing buffers, and\n         * nobody can think of a better way of adding in multiple undo, this\n         * remains broken.\n         *\n         * !!!\n         * There is change to historic practice for the final cursor position\n         * in this implementation.  In historic vi, if an undo was isolated to\n         * a single line, the cursor moved to the start of the change, and\n         * then, subsequent 'u' commands would not move it again. (It has been\n         * pointed out that users used multiple undo commands to get the cursor\n         * to the start of the changed text.)  Nvi toggles between the cursor\n         * position before and after the change was made.  One final issue is\n         * that historic vi only did this if the user had not moved off of the\n         * line before entering the undo command; otherwise, vi would move the\n         * cursor to the most attractive position on the changed line.\n         *\n         * It would be difficult to match historic practice in this area. You\n         * not only have to know that the changes were isolated to one line,\n         * but whether it was the first or second undo command as well.  And,\n         * to completely match historic practice, we'd have to track users line\n         * changes, too.  This isn't worth the effort.\n         */\n        ep = sp->ep;\n        if (!F_ISSET(ep, F_UNDO)) {\n                F_SET(ep, F_UNDO);\n                ep->lundo = BACKWARD;\n        } else if (!F_ISSET(vp, VC_ISDOT))\n                ep->lundo = ep->lundo == BACKWARD ? FORWARD : BACKWARD;\n\n        switch (ep->lundo) {\n        case BACKWARD:\n                return (log_backward(sp, &vp->m_final));\n        case FORWARD:\n                return (log_forward(sp, &vp->m_final));\n        default:\n                abort();\n        }\n        /* NOTREACHED */\n}\n"
  },
  {
    "path": "vi/v_util.c",
    "content": "/*      $OpenBSD: v_util.c,v 1.8 2016/01/06 22:28:52 millert Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_eof --\n *      Vi end-of-file error.\n *\n * PUBLIC: void v_eof(SCR *, MARK *);\n */\nvoid\nv_eof(SCR *sp, MARK *mp)\n{\n        recno_t lno;\n\n        if (mp == NULL)\n                v_emsg(sp, NULL, VIM_EOF);\n        else {\n                if (db_last(sp, &lno))\n                        return;\n                if (mp->lno >= lno)\n                        v_emsg(sp, NULL, VIM_EOF);\n                else\n                        msgq(sp, M_BERR, \"Movement past the end-of-file\");\n        }\n}\n\n/*\n * v_eol --\n *      Vi end-of-line error.\n *\n * PUBLIC: void v_eol(SCR *, MARK *);\n */\nvoid\nv_eol(SCR *sp, MARK *mp)\n{\n        size_t len;\n\n        if (mp == NULL)\n                v_emsg(sp, NULL, VIM_EOL);\n        else {\n                if (db_get(sp, mp->lno, DBG_FATAL, NULL, &len))\n                        return;\n                if (mp->cno == len - 1)\n                        v_emsg(sp, NULL, VIM_EOL);\n                else\n                        msgq(sp, M_BERR, \"Movement past the end-of-line\");\n        }\n}\n\n/*\n * v_nomove --\n *      Vi no cursor movement error.\n *\n * PUBLIC: void v_nomove(SCR *);\n */\nvoid\nv_nomove(SCR *sp)\n{\n        msgq(sp, M_BERR, \"No cursor movement made\");\n}\n\n/*\n * v_sof --\n *      Vi start-of-file error.\n *\n * PUBLIC: void v_sof(SCR *, MARK *);\n */\nvoid\nv_sof(SCR *sp, MARK *mp)\n{\n        if (mp == NULL || mp->lno == 1)\n                msgq(sp, M_BERR, \"Already at the beginning of the file\");\n        else\n                msgq(sp, M_BERR, \"Movement past the beginning of the file\");\n}\n\n/*\n * v_sol --\n *      Vi start-of-line error.\n *\n * PUBLIC: void v_sol(SCR *);\n */\nvoid\nv_sol(SCR *sp)\n{\n        msgq(sp, M_BERR, \"Already at the first column\");\n}\n\n/*\n * v_isempty --\n *      Return if the line contains nothing but white-space characters.\n *\n * PUBLIC: int v_isempty(char *, size_t);\n */\nint\nv_isempty(char *p, size_t len)\n{\n        for (; len--; ++p)\n                if (!isblank(*p))\n                        return (0);\n        return (1);\n}\n\n/*\n * v_emsg --\n *      Display a few common vi messages.\n *\n * PUBLIC: void v_emsg(SCR *, char *, vim_t);\n */\nvoid\nv_emsg(SCR *sp, char *p, vim_t which)\n{\n        switch (which) {\n        case VIM_COMBUF:\n                msgq(sp, M_ERR,\n                    \"Buffers should be specified before the command\");\n                break;\n        case VIM_EMPTY:\n                msgq(sp, M_BERR, \"The file is empty\");\n                break;\n        case VIM_EOF:\n                msgq(sp, M_BERR, \"Already at end-of-file\");\n                break;\n        case VIM_EOL:\n                msgq(sp, M_BERR, \"Already at end-of-line\");\n                break;\n        case VIM_NOCOM:\n        case VIM_NOCOM_B:\n                msgq(sp,\n                    which == VIM_NOCOM_B ? M_BERR : M_ERR,\n                    \"%s isn't a vi command\", p);\n                break;\n        case VIM_WRESIZE:\n                msgq(sp, M_ERR, \"Window resize interrupted text input mode\");\n                break;\n        case VIM_USAGE:\n                msgq(sp, M_ERR, \"Usage: %s\", p);\n                break;\n        }\n}\n"
  },
  {
    "path": "vi/v_word.c",
    "content": "/*      $OpenBSD: v_word.c,v 1.7 2014/11/12 04:28:41 bentley Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * There are two types of \"words\".  Bigwords are easy -- groups of anything\n * delimited by whitespace.  Normal words are trickier.  They are either a\n * group of characters, numbers and underscores, or a group of anything but,\n * delimited by whitespace.  When for a word, if you're in whitespace, it's\n * easy, just remove the whitespace and go to the beginning or end of the\n * word.  Otherwise, figure out if the next character is in a different group.\n * If it is, go to the beginning or end of that group, otherwise, go to the\n * beginning or end of the current group.  The historic version of vi didn't\n * get this right, so, for example, there were cases where \"4e\" was not the\n * same as \"eeee\" -- in particular, single character words, and commands that\n * began in whitespace were almost always handled incorrectly.  To get it right\n * you have to resolve the cursor after each search so that the look-ahead to\n * figure out what type of \"word\" the cursor is in will be correct.\n *\n * Empty lines, and lines that consist of only white-space characters count\n * as a single word, and the beginning and end of the file counts as an\n * infinite number of words.\n *\n * Movements associated with commands are different than movement commands.\n * For example, in \"abc  def\", with the cursor on the 'a', \"cw\" is from\n * 'a' to 'c', while \"w\" is from 'a' to 'd'.  In general, trailing white\n * space is discarded from the change movement.  Another example is that,\n * in the same string, a \"cw\" on any white space character replaces that\n * single character, and nothing else.  Ain't nothin' in here that's easy.\n *\n * One historic note -- in the original vi, the 'w', 'W' and 'B' commands\n * would treat groups of empty lines as individual words, i.e. the command\n * would move the cursor to each new empty line.  The 'e' and 'E' commands\n * would treat groups of empty lines as a single word, i.e. the first use\n * would move past the group of lines.  The 'b' command would just beep at\n * you, or, if you did it from the start of the line as part of a motion\n * command, go absolutely nuts.  If the lines contained only white-space\n * characters, the 'w' and 'W' commands would just beep at you, and the 'B',\n * 'b', 'E' and 'e' commands would treat the group as a single word, and\n * the 'B' and 'b' commands will treat the lines as individual words.  This\n * implementation treats all of these cases as a single white-space word.\n */\n\nenum which {BIGWORD, LITTLEWORD};\n\nstatic int bword(SCR *, VICMD *, enum which);\nstatic int eword(SCR *, VICMD *, enum which);\nstatic int fword(SCR *, VICMD *, enum which);\n\n/*\n * v_wordW -- [count]W\n *      Move forward a bigword at a time.\n *\n * PUBLIC: int v_wordW(SCR *, VICMD *);\n */\nint\nv_wordW(SCR *sp, VICMD *vp)\n{\n        return (fword(sp, vp, BIGWORD));\n}\n\n/*\n * v_wordw -- [count]w\n *      Move forward a word at a time.\n *\n * PUBLIC: int v_wordw(SCR *, VICMD *);\n */\nint\nv_wordw(SCR *sp, VICMD *vp)\n{\n        return (fword(sp, vp, LITTLEWORD));\n}\n\n/*\n * fword --\n *      Move forward by words.\n */\nstatic int\nfword(SCR *sp, VICMD *vp, enum which type)\n{\n        enum { INWORD, NOTWORD } state;\n        VCS cs;\n        unsigned long cnt;\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        cs.cs_lno = vp->m_start.lno;\n        cs.cs_cno = vp->m_start.cno;\n        if (cs_init(sp, &cs))\n                return (1);\n\n        /*\n         * If in white-space:\n         *      If the count is 1, and it's a change command, we're done.\n         *      Else, move to the first non-white-space character, which\n         *      counts as a single word move.  If it's a motion command,\n         *      don't move off the end of the line.\n         */\n        if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && isblank(cs.cs_ch))) {\n                if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) {\n                        if (ISCMD(vp->rkp, 'c'))\n                                return (0);\n                        if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) {\n                                if (cs_fspace(sp, &cs))\n                                        return (1);\n                                goto ret;\n                        }\n                }\n                if (cs_fblank(sp, &cs))\n                        return (1);\n                --cnt;\n        }\n\n        /*\n         * Cyclically move to the next word -- this involves skipping\n         * over word characters and then any trailing non-word characters.\n         * Note, for the 'w' command, the definition of a word keeps\n         * switching.\n         */\n        if (type == BIGWORD)\n                while (cnt--) {\n                        for (;;) {\n                                if (cs_next(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_EOF)\n                                        goto ret;\n                                if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                        break;\n                        }\n                        /*\n                         * If a motion command and we're at the end of the\n                         * last word, we're done.  Delete and yank eat any\n                         * trailing blanks, but we don't move off the end\n                         * of the line regardless.\n                         */\n                        if (cnt == 0 && ISMOTION(vp)) {\n                                if ((ISCMD(vp->rkp, 'd') ||\n                                    ISCMD(vp->rkp, 'y')) &&\n                                    cs_fspace(sp, &cs))\n                                        return (1);\n                                break;\n                        }\n\n                        /* Eat whitespace characters. */\n                        if (cs_fblank(sp, &cs))\n                                return (1);\n                        if (cs.cs_flags == CS_EOF)\n                                goto ret;\n                }\n        else\n                while (cnt--) {\n                        state = cs.cs_flags == 0 &&\n                            inword(cs.cs_ch) ? INWORD : NOTWORD;\n                        for (;;) {\n                                if (cs_next(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_EOF)\n                                        goto ret;\n                                if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                        break;\n                                if (state == INWORD) {\n                                        if (!inword(cs.cs_ch))\n                                                break;\n                                } else\n                                        if (inword(cs.cs_ch))\n                                                break;\n                        }\n                        /* See comment above. */\n                        if (cnt == 0 && ISMOTION(vp)) {\n                                if ((ISCMD(vp->rkp, 'd') ||\n                                    ISCMD(vp->rkp, 'y')) &&\n                                    cs_fspace(sp, &cs))\n                                        return (1);\n                                break;\n                        }\n\n                        /* Eat whitespace characters. */\n                        if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                if (cs_fblank(sp, &cs))\n                                        return (1);\n                        if (cs.cs_flags == CS_EOF)\n                                goto ret;\n                }\n\n        /*\n         * If we didn't move, we must be at EOF.\n         *\n         * !!!\n         * That's okay for motion commands, however.\n         */\nret:    if (!ISMOTION(vp) &&\n            cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {\n                v_eof(sp, &vp->m_start);\n                return (1);\n        }\n\n        /* Adjust the end of the range for motion commands. */\n        vp->m_stop.lno = cs.cs_lno;\n        vp->m_stop.cno = cs.cs_cno;\n        if (ISMOTION(vp) && cs.cs_flags == 0)\n                --vp->m_stop.cno;\n\n        /*\n         * Non-motion commands move to the end of the range.  Delete\n         * and yank stay at the start, ignore others.\n         */\n        vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        return (0);\n}\n\n/*\n * v_wordE -- [count]E\n *      Move forward to the end of the bigword.\n *\n * PUBLIC: int v_wordE(SCR *, VICMD *);\n */\nint\nv_wordE(SCR *sp, VICMD *vp)\n{\n        return (eword(sp, vp, BIGWORD));\n}\n\n/*\n * v_worde -- [count]e\n *      Move forward to the end of the word.\n *\n * PUBLIC: int v_worde(SCR *, VICMD *);\n */\nint\nv_worde(SCR *sp, VICMD *vp)\n{\n        return (eword(sp, vp, LITTLEWORD));\n}\n\n/*\n * eword --\n *      Move forward to the end of the word.\n */\nstatic int\neword(SCR *sp, VICMD *vp, enum which type)\n{\n        enum { INWORD, NOTWORD } state;\n        VCS cs;\n        unsigned long cnt;\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        cs.cs_lno = vp->m_start.lno;\n        cs.cs_cno = vp->m_start.cno;\n        if (cs_init(sp, &cs))\n                return (1);\n\n        /*\n         * !!!\n         * If in whitespace, or the next character is whitespace, move past\n         * it.  (This doesn't count as a word move.)  Stay at the character\n         * past the current one, it sets word \"state\" for the 'e' command.\n         */\n        if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) {\n                if (cs_next(sp, &cs))\n                        return (1);\n                if (cs.cs_flags == 0 && !isblank(cs.cs_ch))\n                        goto start;\n        }\n        if (cs_fblank(sp, &cs))\n                return (1);\n\n        /*\n         * Cyclically move to the next word -- this involves skipping\n         * over word characters and then any trailing non-word characters.\n         * Note, for the 'e' command, the definition of a word keeps\n         * switching.\n         */\nstart:  if (type == BIGWORD)\n                while (cnt--) {\n                        for (;;) {\n                                if (cs_next(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_EOF)\n                                        goto ret;\n                                if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                        break;\n                        }\n                        /*\n                         * When we reach the start of the word after the last\n                         * word, we're done.  If we changed state, back up one\n                         * to the end of the previous word.\n                         */\n                        if (cnt == 0) {\n                                if (cs.cs_flags == 0 && cs_prev(sp, &cs))\n                                        return (1);\n                                break;\n                        }\n\n                        /* Eat whitespace characters. */\n                        if (cs_fblank(sp, &cs))\n                                return (1);\n                        if (cs.cs_flags == CS_EOF)\n                                goto ret;\n                }\n        else\n                while (cnt--) {\n                        state = cs.cs_flags == 0 &&\n                            inword(cs.cs_ch) ? INWORD : NOTWORD;\n                        for (;;) {\n                                if (cs_next(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_EOF)\n                                        goto ret;\n                                if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                        break;\n                                if (state == INWORD) {\n                                        if (!inword(cs.cs_ch))\n                                                break;\n                                } else\n                                        if (inword(cs.cs_ch))\n                                                break;\n                        }\n                        /* See comment above. */\n                        if (cnt == 0) {\n                                if (cs.cs_flags == 0 && cs_prev(sp, &cs))\n                                        return (1);\n                                break;\n                        }\n\n                        /* Eat whitespace characters. */\n                        if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                if (cs_fblank(sp, &cs))\n                                        return (1);\n                        if (cs.cs_flags == CS_EOF)\n                                goto ret;\n                }\n\n        /*\n         * If we didn't move, we must be at EOF.\n         *\n         * !!!\n         * That's okay for motion commands, however.\n         */\nret:    if (!ISMOTION(vp) &&\n            cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {\n                v_eof(sp, &vp->m_start);\n                return (1);\n        }\n\n        /* Set the end of the range for motion commands. */\n        vp->m_stop.lno = cs.cs_lno;\n        vp->m_stop.cno = cs.cs_cno;\n\n        /*\n         * Non-motion commands move to the end of the range.\n         * Delete and yank stay at the start, ignore others.\n         */\n        vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;\n        return (0);\n}\n\n/*\n * v_WordB -- [count]B\n *      Move backward a bigword at a time.\n *\n * PUBLIC: int v_wordB(SCR *, VICMD *);\n */\nint\nv_wordB(SCR *sp, VICMD *vp)\n{\n        return (bword(sp, vp, BIGWORD));\n}\n\n/*\n * v_wordb -- [count]b\n *      Move backward a word at a time.\n *\n * PUBLIC: int v_wordb(SCR *, VICMD *);\n */\nint\nv_wordb(SCR *sp, VICMD *vp)\n{\n        return (bword(sp, vp, LITTLEWORD));\n}\n\n/*\n * bword --\n *      Move backward by words.\n */\nstatic int\nbword(SCR *sp, VICMD *vp, enum which type)\n{\n        enum { INWORD, NOTWORD } state;\n        VCS cs;\n        unsigned long cnt;\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        cs.cs_lno = vp->m_start.lno;\n        cs.cs_cno = vp->m_start.cno;\n        if (cs_init(sp, &cs))\n                return (1);\n\n        /*\n         * !!!\n         * If in whitespace, or the previous character is whitespace, move\n         * past it.  (This doesn't count as a word move.)  Stay at the\n         * character before the current one, it sets word \"state\" for the\n         * 'b' command.\n         */\n        if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) {\n                if (cs_prev(sp, &cs))\n                        return (1);\n                if (cs.cs_flags == 0 && !isblank(cs.cs_ch))\n                        goto start;\n        }\n        if (cs_bblank(sp, &cs))\n                return (1);\n\n        /*\n         * Cyclically move to the beginning of the previous word -- this\n         * involves skipping over word characters and then any trailing\n         * non-word characters.  Note, for the 'b' command, the definition\n         * of a word keeps switching.\n         */\nstart:  if (type == BIGWORD)\n                while (cnt--) {\n                        for (;;) {\n                                if (cs_prev(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_SOF)\n                                        goto ret;\n                                if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                        break;\n                        }\n                        /*\n                         * When we reach the end of the word before the last\n                         * word, we're done.  If we changed state, move forward\n                         * one to the end of the next word.\n                         */\n                        if (cnt == 0) {\n                                if (cs.cs_flags == 0 && cs_next(sp, &cs))\n                                        return (1);\n                                break;\n                        }\n\n                        /* Eat whitespace characters. */\n                        if (cs_bblank(sp, &cs))\n                                return (1);\n                        if (cs.cs_flags == CS_SOF)\n                                goto ret;\n                }\n        else\n                while (cnt--) {\n                        state = cs.cs_flags == 0 &&\n                            inword(cs.cs_ch) ? INWORD : NOTWORD;\n                        for (;;) {\n                                if (cs_prev(sp, &cs))\n                                        return (1);\n                                if (cs.cs_flags == CS_SOF)\n                                        goto ret;\n                                if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                        break;\n                                if (state == INWORD) {\n                                        if (!inword(cs.cs_ch))\n                                                break;\n                                } else\n                                        if (inword(cs.cs_ch))\n                                                break;\n                        }\n                        /* See comment above. */\n                        if (cnt == 0) {\n                                if (cs.cs_flags == 0 && cs_next(sp, &cs))\n                                        return (1);\n                                break;\n                        }\n\n                        /* Eat whitespace characters. */\n                        if (cs.cs_flags != 0 || isblank(cs.cs_ch))\n                                if (cs_bblank(sp, &cs))\n                                        return (1);\n                        if (cs.cs_flags == CS_SOF)\n                                goto ret;\n                }\n\n        /* If we didn't move, we must be at SOF. */\nret:    if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {\n                v_sof(sp, &vp->m_start);\n                return (1);\n        }\n\n        /* Set the end of the range for motion commands. */\n        vp->m_stop.lno = cs.cs_lno;\n        vp->m_stop.cno = cs.cs_cno;\n\n        /*\n         * All commands move to the end of the range.  Motion commands\n         * adjust the starting point to the character before the current\n         * one.\n         *\n         * !!!\n         * The historic vi didn't get this right -- the `yb' command yanked\n         * the right stuff and even updated the cursor value, but the cursor\n         * was not actually updated on the screen.\n         */\n        vp->m_final = vp->m_stop;\n        if (ISMOTION(vp))\n                --vp->m_start.cno;\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_xchar.c",
    "content": "/*      $OpenBSD: v_xchar.c,v 1.8 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_xchar -- [buffer] [count]x\n *      Deletes the character(s) on which the cursor sits.\n *\n * PUBLIC: int v_xchar(SCR *, VICMD *);\n */\nint\nv_xchar(SCR *sp, VICMD *vp)\n{\n        size_t len;\n        int isempty;\n\n        if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) {\n                if (isempty)\n                        goto nodel;\n                return (1);\n        }\n        if (len == 0) {\nnodel:          msgq(sp, M_BERR, \"No characters to delete\");\n                return (1);\n        }\n\n        /*\n         * Delete from the cursor toward the end of line, w/o moving the\n         * cursor.\n         *\n         * !!!\n         * Note, \"2x\" at EOL isn't the same as \"xx\" because the left movement\n         * of the cursor as part of the 'x' command isn't taken into account.\n         * Historically correct.\n         */\n        if (F_ISSET(vp, VC_C1SET))\n                vp->m_stop.cno += vp->count - 1;\n        if (vp->m_stop.cno >= len - 1) {\n                vp->m_stop.cno = len - 1;\n                vp->m_final.cno = vp->m_start.cno ? vp->m_start.cno - 1 : 0;\n        } else\n                vp->m_final.cno = vp->m_start.cno;\n\n        if (cut(sp,\n            F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_stop, 0))\n                return (1);\n        return (del(sp, &vp->m_start, &vp->m_stop, 0));\n}\n\n/*\n * v_Xchar -- [buffer] [count]X\n *      Deletes the character(s) immediately before the current cursor\n *      position.\n *\n * PUBLIC: int v_Xchar(SCR *, VICMD *);\n */\nint\nv_Xchar(SCR *sp, VICMD *vp)\n{\n        unsigned long cnt;\n\n        if (vp->m_start.cno == 0) {\n                v_sol(sp);\n                return (1);\n        }\n\n        cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;\n        if (cnt >= vp->m_start.cno)\n                vp->m_start.cno = 0;\n        else\n                vp->m_start.cno -= cnt;\n        --vp->m_stop.cno;\n        vp->m_final.cno = vp->m_start.cno;\n\n        if (cut(sp,\n            F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,\n            &vp->m_start, &vp->m_stop, 0))\n                return (1);\n        return (del(sp, &vp->m_start, &vp->m_stop, 0));\n}\n"
  },
  {
    "path": "vi/v_yank.c",
    "content": "/*      $OpenBSD: v_yank.c,v 1.7 2014/11/12 04:28:41 bentley Exp $      */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_yank -- [buffer][count]y[count][motion]\n *           [buffer][count]Y\n *      Yank text (or lines of text) into a cut buffer.\n *\n * !!!\n * Historic vi moved the cursor to the from MARK if it was before the current\n * cursor and on a different line, e.g., \"yk\" moves the cursor but \"yj\" and\n * \"yl\" do not.  Unfortunately, it's too late to change this now.  Matching\n * the historic semantics isn't easy.  The line number was always changed and\n * column movement was usually relative.  However, \"y'a\" moved the cursor to\n * the first non-blank of the line marked by a, while \"y`a\" moved the cursor\n * to the line and column marked by a.  Hopefully, the motion component code\n * got it right...   Unlike delete, we make no adjustments here.\n *\n * PUBLIC: int v_yank(SCR *, VICMD *);\n */\nint\nv_yank(SCR *sp, VICMD *vp)\n{\n        size_t len;\n\n        if (cut(sp,\n            F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start,\n            &vp->m_stop, F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0))\n                return (1);\n        sp->rptlines[L_YANKED] += (vp->m_stop.lno - vp->m_start.lno) + 1;\n\n        /*\n         * One special correction, in case we've deleted the current line or\n         * character.  We check it here instead of checking in every command\n         * that can be a motion component.\n         */\n        if (db_get(sp, vp->m_final.lno, DBG_FATAL, NULL, &len))\n                return (1);\n\n        /*\n         * !!!\n         * Cursor movements, other than those caused by a line mode command\n         * moving to another line, historically reset the relative position.\n         *\n         * This currently matches the check made in v_delete(), I'm hoping\n         * that they should be consistent...\n         */\n        if (!F_ISSET(vp, VM_LMODE)) {\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(vp, VM_RCM_SET);\n\n                /* Make sure the set cursor position exists. */\n                if (vp->m_final.cno >= len)\n                        vp->m_final.cno = len ? len - 1 : 0;\n        }\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_z.c",
    "content": "/*      $OpenBSD: v_z.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_z -- [count]z[count][-.+^<CR>]\n *      Move the screen.\n *\n * PUBLIC: int v_z(SCR *, VICMD *);\n */\nint\nv_z(SCR *sp, VICMD *vp)\n{\n        recno_t lno;\n        unsigned int value;\n\n        /*\n         * The first count is the line to use.  If the value doesn't\n         * exist, use the last line.\n         */\n        if (F_ISSET(vp, VC_C1SET)) {\n                lno = vp->count;\n                if (!db_exist(sp, lno) && db_last(sp, &lno))\n                        return (1);\n        } else\n                lno = vp->m_start.lno;\n\n        /* Set default return cursor line. */\n        vp->m_final.lno = lno;\n        vp->m_final.cno = vp->m_start.cno;\n\n        /*\n         * The second count is the displayed window size, i.e. the 'z' command\n         * is another way to get artificially small windows.  Note, you can't\n         * grow beyond the size of the window.\n         *\n         * !!!\n         * A window size of 0 was historically allowed, and simply ignored.\n         * This could be much more simply done by modifying the value of the\n         * O_WINDOW option, but that's not how it worked historically.\n         */\n        if (F_ISSET(vp, VC_C2SET) && vp->count2 != 0) {\n                if (vp->count2 > O_VAL(sp, O_WINDOW))\n                        vp->count2 = O_VAL(sp, O_WINDOW);\n                if (vs_crel(sp, vp->count2))\n                        return (1);\n        }\n\n        switch (vp->character) {\n        case '-':               /* Put the line at the bottom. */\n                if (vs_sm_fill(sp, lno, P_BOTTOM))\n                        return (1);\n                break;\n        case '.':               /* Put the line in the middle. */\n                if (vs_sm_fill(sp, lno, P_MIDDLE))\n                        return (1);\n                break;\n        case '+':\n                /*\n                 * If the user specified a line number, put that line at the\n                 * top and move the cursor to it.  Otherwise, scroll forward\n                 * a screen from the current screen.\n                 */\n                if (F_ISSET(vp, VC_C1SET)) {\n                        if (vs_sm_fill(sp, lno, P_TOP))\n                                return (1);\n                        if (vs_sm_position(sp, &vp->m_final, 0, P_TOP))\n                                return (1);\n                } else\n                        if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_PLUS))\n                                return (1);\n                break;\n        case '^':\n                /*\n                 * If the user specified a line number, put that line at the\n                 * bottom, move the cursor to it, and then display the screen\n                 * before that one.  Otherwise, scroll backward a screen from\n                 * the current screen.\n                 *\n                 * !!!\n                 * Note, we match the off-by-one characteristics of historic\n                 * vi, here.\n                 */\n                if (F_ISSET(vp, VC_C1SET)) {\n                        if (vs_sm_fill(sp, lno, P_BOTTOM))\n                                return (1);\n                        if (vs_sm_position(sp, &vp->m_final, 0, P_TOP))\n                                return (1);\n                        if (vs_sm_fill(sp, vp->m_final.lno, P_BOTTOM))\n                                return (1);\n                } else\n                        if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_CARAT))\n                                return (1);\n                break;\n        default:                /* Put the line at the top for <cr>. */\n                value = KEY_VAL(sp, vp->character);\n                if (value != K_CR && value != K_NL) {\n                        v_emsg(sp, vp->kp->usage, VIM_USAGE);\n                        return (1);\n                }\n                if (vs_sm_fill(sp, lno, P_TOP))\n                        return (1);\n                break;\n        }\n        return (0);\n}\n\n/*\n * vs_crel --\n *      Change the relative size of the current screen.\n *\n * PUBLIC: int vs_crel(SCR *, long);\n */\nint\nvs_crel(SCR *sp, long count)\n{\n        if (sp == NULL)\n                return (0);\n        sp->t_minrows = sp->t_rows = count;\n        if (sp->t_rows > sp->rows - 1)\n                sp->t_minrows = sp->t_rows = sp->rows - 1;\n        if (HMAP != NULL)\n                TMAP = HMAP + (sp->t_rows - 1);\n        F_SET(sp, SC_SCR_REDRAW);\n        return (0);\n}\n"
  },
  {
    "path": "vi/v_zexit.c",
    "content": "/*      $OpenBSD: v_zexit.c,v 1.6 2014/11/12 04:28:41 bentley Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * v_zexit -- ZZ\n *      Save the file and exit.\n *\n * PUBLIC: int v_zexit(SCR *, VICMD *);\n */\nint\nv_zexit(SCR *sp, VICMD *vp)\n{\n        (void)sp;\n        (void)vp;\n\n        /* Write back any modifications. */\n        if (F_ISSET(sp->ep, F_MODIFIED) &&\n            file_write(sp, NULL, NULL, NULL, FS_ALL))\n                return (1);\n\n        /* Check to make sure it's not a temporary file. */\n        if (file_m3(sp, 0))\n                return (1);\n\n        /* Check for more files to edit. */\n        if (ex_ncheck(sp, 0))\n                return (1);\n\n        F_SET(sp, SC_EXIT);\n        return (0);\n}\n"
  },
  {
    "path": "vi/vi.c",
    "content": "/*      $OpenBSD: vi.c,v 1.23 2022/02/20 19:45:51 tb Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\ntypedef enum {\n        GC_ERR, GC_ERR_NOFLUSH, GC_EVENT, GC_FATAL, GC_INTERRUPT, GC_OK\n} gcret_t;\n\nstatic VIKEYS const\n               *v_alias(SCR *, VICMD *, VIKEYS const *);\nstatic gcret_t  v_cmd(SCR *, VICMD *, VICMD *, VICMD *, int *, int *);\nstatic int      v_count(SCR *, CHAR_T, unsigned long *);\nstatic void     v_dtoh(SCR *);\nstatic int      v_init(SCR *);\nstatic gcret_t  v_key(SCR *, int, EVENT *, u_int32_t);\nstatic int      v_keyword(SCR *);\nstatic int      v_motion(SCR *, VICMD *, VICMD *, int *);\n\n#if defined(DEBUG) && defined(COMLOG)\nstatic void     v_comlog(SCR *, VICMD *);\n#endif /* if defined(DEBUG) && defined(COMLOG) */\n\n/*\n * Side-effect:\n *      The dot structure can be set by the underlying vi functions,\n *      see v_Put() and v_put().\n */\n#define DOT             (&VIP(sp)->sdot)\n#define DOTMOTION       (&VIP(sp)->sdotmotion)\n\n/*\n * vi --\n *      Main vi command loop.\n *\n * PUBLIC: int vi(SCR **);\n */\nint\nvi(SCR **spp)\n{\n        GS *gp;\n        MARK abs;\n        SCR *next, *sp;\n        VICMD cmd, *vp;\n        VI_PRIVATE *vip;\n        int comcount, mapped, rval;\n        int ret;\n\n        /* Get the first screen. */\n        sp = *spp;\n        gp = sp->gp;\n\n        /* Initialize the command structure. */\n        vp = &cmd;\n        memset(vp, 0, sizeof(VICMD));\n\n        /* Reset strange attraction. */\n        F_SET(vp, VM_RCM_SET);\n\n        /* Initialize the vi screen. */\n        if (v_init(sp))\n                return (1);\n\n        /* Set the focus. */\n        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n\n        for (vip = VIP(sp), rval = 0;;) {\n                /* Resolve messages. */\n                if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0))\n                        goto ret;\n\n                /*\n                 * If not skipping a refresh, return to command mode and\n                 * refresh the screen.\n                 */\n                if (F_ISSET(vip, VIP_S_REFRESH))\n                        F_CLR(vip, VIP_S_REFRESH);\n                else {\n                        sp->showmode = SM_COMMAND;\n                        if (vs_refresh(sp, 0))\n                                goto ret;\n                }\n\n                /* Set the new favorite position. */\n                if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) {\n                        F_CLR(vip, VIP_RCM_LAST);\n                        (void)vs_column(sp, &sp->rcm);\n                }\n\n                /*\n                 * If not currently in a map, log the cursor position,\n                 * and set a flag so that this command can become the\n                 * DOT command.\n                 */\n                if (MAPPED_KEYS_WAITING(sp))\n                        mapped = 1;\n                else {\n                        if (log_cursor(sp))\n                                goto err;\n                        mapped = 0;\n                }\n\n                /*\n                 * There may be an ex command waiting, and we returned here\n                 * only because we exited a screen or file.  In this case,\n                 * we simply go back into the ex parser.\n                 */\n                if (EXCMD_RUNNING(gp)) {\n                        vp->kp = &vikeys[':'];\n                        goto ex_continue;\n                }\n\n                /* Refresh the command structure. */\n                memset(vp, 0, sizeof(VICMD));\n\n                /*\n                 * We get a command, which may or may not have an associated\n                 * motion.  If it does, we get it too, calling its underlying\n                 * function to get the resulting mark.  We then call the\n                 * command setting the cursor to the resulting mark.\n                 *\n                 * !!!\n                 * Vi historically flushed mapped characters on error, but\n                 * entering extra <escape> characters at the beginning of\n                 * a map wasn't considered an error -- in fact, users would\n                 * put leading <escape> characters in maps to clean up vi\n                 * state before the map was interpreted.  Beauty!\n                 */\n                switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) {\n                case GC_ERR:\n                        goto err;\n                case GC_ERR_NOFLUSH:\n                        goto gc_err_noflush;\n                case GC_EVENT:\n                        if (v_event_exec(sp, vp))\n                                goto err;\n                        goto gc_event;\n                case GC_FATAL:\n                        goto ret;\n                case GC_INTERRUPT:\n                        goto intr;\n                case GC_OK:\n                        break;\n                }\n\n                /* Check for security setting. */\n                if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) {\n                        ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE);\n                        goto err;\n                }\n\n                /*\n                 * Historical practice: if a dot command gets a new count,\n                 * any motion component goes away, i.e. \"d3w2.\" deletes a\n                 * total of 5 words.\n                 */\n                if (F_ISSET(vp, VC_ISDOT) && comcount)\n                        DOTMOTION->count = 1;\n\n                /* Copy the key flags into the local structure. */\n                F_SET(vp, vp->kp->flags);\n\n                /* Prepare to set the previous context. */\n                if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) {\n                        abs.lno = sp->lno;\n                        abs.cno = sp->cno;\n                }\n\n                /*\n                 * Set the three cursor locations to the current cursor.  The\n                 * underlying routines don't bother if the cursor doesn't move.\n                 * This also handles line commands (e.g. Y) defaulting to the\n                 * current line.\n                 */\n                vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno;\n                vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno;\n\n                /*\n                 * Do any required motion; v_motion sets the from MARK and the\n                 * line mode flag, as well as the VM_RCM flags.\n                 */\n                if (F_ISSET(vp, V_MOTION) &&\n                    v_motion(sp, DOTMOTION, vp, &mapped)) {\n                        if (INTERRUPTED(sp))\n                                goto intr;\n                        goto err;\n                }\n\n                /*\n                 * If a count is set and the command is line oriented, set the\n                 * to MARK here relative to the cursor/from MARK.  This is for\n                 * commands that take both counts and motions, i.e. \"4yy\" and\n                 * \"y%\".  As there's no way the command can know which the user\n                 * did, we have to do it here.  (There are commands that are\n                 * line oriented and that take counts (\"#G\", \"#H\"), for which\n                 * this calculation is either completely meaningless or wrong.\n                 * Each command must validate the value for itself.\n                 */\n                if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE))\n                        vp->m_stop.lno += vp->count - 1;\n\n                /* Increment the command count. */\n                ++sp->ccnt;\n\n#if defined(DEBUG) && defined(COMLOG)\n                v_comlog(sp, vp);\n#endif /* if defined(DEBUG) && defined(COMLOG) */\n                /* Call the function. */\nex_continue:    if (strchr(O_STR(sp, O_IMKEY), vp->key))\n                    sp->gp->scr_imctrl(sp, IMCTRL_ON);\n                ret = vp->kp->func(sp, vp);\n                if (strchr(O_STR(sp, O_IMKEY), vp->key))\n                    sp->gp->scr_imctrl(sp, IMCTRL_OFF);\n                if (ret)\n                    goto err;\ngc_event:\n#ifdef DEBUG\n                /* Make sure no function left the temporary space locked. */\n                if (F_ISSET(gp, G_TMP_INUSE)) {\n                        F_CLR(gp, G_TMP_INUSE);\n                        msgq(sp, M_ERR,\n                            \"vi: temporary buffer not released\");\n                }\n#endif /* ifdef DEBUG */\n                /*\n                 * If we're exiting this screen, move to the next one, or, if\n                 * there aren't any more, return to the main editor loop.  The\n                 * ordering is careful, don't discard the contents of sp until\n                 * the end.\n                 */\n                if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {\n                        if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE)))\n                                goto ret;\n                        if (vs_discard(sp, &next))\n                                goto ret;\n                        if (next == NULL && vs_swap(sp, &next, NULL))\n                                goto ret;\n                        *spp = next;\n                        if (screen_end(sp))\n                                goto ret;\n                        if (next == NULL)\n                                break;\n\n                        /* Switch screens, change focus. */\n                        sp = next;\n                        vip = VIP(sp);\n                        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n\n                        /* Don't trust the cursor. */\n                        F_SET(vip, VIP_CUR_INVALID);\n\n                        continue;\n                }\n\n                /*\n                 * Set the dot command structure.\n                 *\n                 * !!!\n                 * Historically, commands which used mapped keys did not\n                 * set the dot command, with the exception of the text\n                 * input commands.\n                 */\n                if (F_ISSET(vp, V_DOT) && !mapped) {\n                        *DOT = cmd;\n                        F_SET(DOT, VC_ISDOT);\n\n                        /*\n                         * If a count was supplied for both the command and\n                         * its motion, the count was used only for the motion.\n                         * Turn the count back on for the dot structure.\n                         */\n                        if (F_ISSET(vp, VC_C1RESET))\n                                F_SET(DOT, VC_C1SET);\n\n                        /* VM flags aren't retained. */\n                        F_CLR(DOT, VM_COMMASK | VM_RCM_MASK);\n                }\n\n                /*\n                 * Some vi row movements are \"attracted\" to the last position\n                 * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET\n                 * commands' candle.  If the movement is to the EOL the vi\n                 * command handles it.  If it's to the beginning, we handle it\n                 * here.\n                 *\n                 * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB\n                 * flag, but do the work themselves.  The reason is that they\n                 * have to modify the column in case they're being used as a\n                 * motion component.  Other similar commands (e.g. +, -) don't\n                 * have to modify the column because they are always line mode\n                 * operations when used as motions, so the column number isn't\n                 * of any interest.\n                 *\n                 * Does this totally violate the screen and editor layering?\n                 * You betcha.  As they say, if you think you understand it,\n                 * you don't.\n                 */\n                switch (F_ISSET(vp, VM_RCM_MASK)) {\n                case 0:\n                case VM_RCM_SET:\n                        break;\n                case VM_RCM:\n                        vp->m_final.cno = vs_rcm(sp,\n                            vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST));\n                        break;\n                case VM_RCM_SETLAST:\n                        F_SET(vip, VIP_RCM_LAST);\n                        break;\n                case VM_RCM_SETFNB:\n                        vp->m_final.cno = 0;\n                        /* FALLTHROUGH */\n                case VM_RCM_SETNNB:\n                        if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno))\n                                goto err;\n                        break;\n                default:\n                        abort();\n                }\n\n                /* Update the cursor. */\n                sp->lno = vp->m_final.lno;\n                sp->cno = vp->m_final.cno;\n\n                /*\n                 * Set the absolute mark -- set even if a tags or similar\n                 * command, since the tag may be moving to the same file.\n                 */\n                if ((F_ISSET(vp, V_ABS) ||\n                    (F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) ||\n                    (F_ISSET(vp, V_ABS_C) &&\n                    (sp->lno != abs.lno || sp->cno != abs.cno))) &&\n                    mark_set(sp, ABSMARK1, &abs, 1))\n                        goto err;\n\n                if (0) {\nerr:                    if (v_event_flush(sp, CH_MAPPED))\n                                msgq(sp, M_BERR,\n                            \"Vi command failed: mapped keys discarded\");\n                }\n\n                /*\n                 * Check and clear interrupts.  There's an obvious race, but\n                 * it's not worth fixing.\n                 */\ngc_err_noflush: if (INTERRUPTED(sp)) {\nintr:                   CLR_INTERRUPT(sp);\n                        if (v_event_flush(sp, CH_MAPPED))\n                                msgq(sp, M_ERR,\n                                    \"Interrupted: mapped keys discarded\");\n                        else\n                                msgq(sp, M_ERR, \"Interrupted\");\n                }\n\n                /* If the last command switched screens, update. */\n                if (F_ISSET(sp, SC_SSWITCH)) {\n                        F_CLR(sp, SC_SSWITCH);\n\n                        /*\n                         * If the current screen is still displayed, it will\n                         * need a new status line.\n                         */\n                        F_SET(sp, SC_STATUS);\n\n                        /* Switch screens, change focus. */\n                        sp = sp->nextdisp;\n                        vip = VIP(sp);\n                        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n\n                        /* Don't trust the cursor. */\n                        F_SET(vip, VIP_CUR_INVALID);\n\n                        /* Refresh so we can display messages. */\n                        if (vs_refresh(sp, 1))\n                                return (1);\n                }\n\n                /* If the last command switched files, change focus. */\n                if (F_ISSET(sp, SC_FSWITCH)) {\n                        F_CLR(sp, SC_FSWITCH);\n                        F_CLR(sp, SC_SCR_TOP);\n                        F_SET(sp, SC_SCR_CENTER);\n                        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);\n                }\n\n                /* Sync recovery if changes were made. */\n                if (F_ISSET(sp->ep, F_RCV_SYNC))\n                        rcv_sync(sp, 0);\n\n                /* If leaving vi, return to the main editor loop. */\n                if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) {\n                        *spp = sp;\n                        v_dtoh(sp);\n                        break;\n                }\n        }\n        if (0)\nret:            rval = 1;\n        return (rval);\n}\n\n#define KEY(key, ec_flags) {                                            \\\n        if ((gcret = v_key(sp, 0, &ev, (ec_flags))) != GC_OK)           \\\n                return (gcret);                                         \\\n        if (ev.e_value == K_ESCAPE)                                     \\\n                goto esc;                                               \\\n        if (F_ISSET(&ev.e_ch, CH_MAPPED))                               \\\n                *mappedp = 1;                                           \\\n        (key) = ev.e_c;                                                 \\\n}\n\n/*\n * The O_TILDEOP option makes the ~ command take a motion instead\n * of a straight count.  This is the replacement structure we use\n * instead of the one currently in the VIKEYS table.\n *\n * XXX\n * This should probably be deleted -- it's not all that useful, and\n * we get help messages wrong.\n */\nVIKEYS const tmotion = {\n        v_mulcase,      V_CNT|V_DOT|V_MOTION|VM_RCM_SET,\n        \"[count]~[count]motion\",\n        \" ~ change case to motion\"\n};\n\n/*\n * v_cmd --\n *\n * The command structure for vi is less complex than ex (and don't think\n * I'm not grateful!)  The command syntax is:\n *\n *      [count] [buffer] [count] key [[motion] | [buffer] [character]]\n *\n * and there are several special cases.  The motion value is itself a vi\n * command, with the syntax:\n *\n *      [count] key [character]\n */\nstatic gcret_t\nv_cmd(SCR *sp, VICMD *dp, VICMD *vp, VICMD *ismotion, int *comcountp,\n    int *mappedp)\n{\n        enum { COMMANDMODE, ISPARTIAL, NOTPARTIAL } cpart;\n        EVENT ev;\n        VIKEYS const *kp;\n        gcret_t gcret;\n        unsigned int flags;\n        CHAR_T key;\n        char *s;\n\n        /*\n         * Get a key.\n         *\n         * <escape> cancels partial commands, i.e. a command where at least\n         * one non-numeric character has been entered.  Otherwise, it beeps\n         * the terminal.\n         *\n         * !!!\n         * POSIX 1003.2-1992 explicitly disallows cancelling commands where\n         * all that's been entered is a number, requiring that the terminal\n         * be alerted.\n         */\n        cpart = ismotion == NULL ? COMMANDMODE : ISPARTIAL;\n        if ((gcret =\n            v_key(sp, ismotion == NULL, &ev, EC_MAPCOMMAND)) != GC_OK) {\n                if (gcret == GC_EVENT)\n                        vp->ev = ev;\n                return (gcret);\n        }\n        if (ev.e_value == K_ESCAPE)\n                goto esc;\n        if (F_ISSET(&ev.e_ch, CH_MAPPED))\n                *mappedp = 1;\n        key = ev.e_c;\n\n        if (ismotion == NULL)\n                cpart = NOTPARTIAL;\n\n        /* Pick up optional buffer. */\n        if (key == '\"') {\n                cpart = ISPARTIAL;\n                if (ismotion != NULL) {\n                        v_emsg(sp, NULL, VIM_COMBUF);\n                        return (GC_ERR);\n                }\n                KEY(vp->buffer, 0);\n                F_SET(vp, VC_BUFFER);\n\n                KEY(key, EC_MAPCOMMAND);\n        }\n\n        /*\n         * Pick up optional count, where a leading 0 is not a count,\n         * it's a command.\n         */\n        if (isdigit(key) && key != '0') {\n                if (v_count(sp, key, &vp->count))\n                        return (GC_ERR);\n                F_SET(vp, VC_C1SET);\n                *comcountp = 1;\n\n                KEY(key, EC_MAPCOMMAND);\n        } else\n                *comcountp = 0;\n\n        /* Pick up optional buffer. */\n        if (key == '\"') {\n                cpart = ISPARTIAL;\n                if (F_ISSET(vp, VC_BUFFER)) {\n                        msgq(sp, M_ERR, \"Only one buffer may be specified\");\n                        return (GC_ERR);\n                }\n                if (ismotion != NULL) {\n                        v_emsg(sp, NULL, VIM_COMBUF);\n                        return (GC_ERR);\n                }\n                KEY(vp->buffer, 0);\n                F_SET(vp, VC_BUFFER);\n\n                KEY(key, EC_MAPCOMMAND);\n        }\n\n        /* Check for an OOB command key. */\n        cpart = ISPARTIAL;\n        if (key > MAXVIKEY) {\n                v_emsg(sp, KEY_NAME(sp, key), VIM_NOCOM);\n                return (GC_ERR);\n        }\n        kp = &vikeys[vp->key = key];\n\n        /*\n         * !!!\n         * Historically, D accepted and then ignored a count.  Match it.\n         */\n        if (vp->key == 'D' && F_ISSET(vp, VC_C1SET)) {\n                *comcountp = 0;\n                vp->count = 0;\n                F_CLR(vp, VC_C1SET);\n        }\n\n        /* Check for command aliases. */\n        if (kp->func == NULL && (kp = v_alias(sp, vp, kp)) == NULL)\n                return (GC_ERR);\n\n        /* The tildeop option makes the ~ command take a motion. */\n        if (key == '~' && O_ISSET(sp, O_TILDEOP))\n                kp = &tmotion;\n\n        vp->kp = kp;\n\n        /*\n         * Find the command.  The only legal command with no underlying\n         * function is dot.  It's historic practice that <escape> doesn't\n         * just erase the preceding number, it beeps the terminal as well.\n         * It's a common problem, so just beep the terminal unless verbose\n         * was set.\n         */\n        if (kp->func == NULL) {\n                if (key != '.') {\n                        v_emsg(sp, KEY_NAME(sp, key),\n                            ev.e_value == K_ESCAPE ? VIM_NOCOM_B : VIM_NOCOM);\n                        return (GC_ERR);\n                }\n\n                /* If called for a motion command, stop now. */\n                if (dp == NULL)\n                        goto usage;\n\n                /*\n                 * !!!\n                 * If a '.' is immediately entered after an undo command, we\n                 * replay the log instead of redoing the last command.  This\n                 * is necessary because 'u' can't set the dot command -- see\n                 * vi/v_undo.c:v_undo for details.\n                 */\n                if (VIP(sp)->u_ccnt == sp->ccnt) {\n                        vp->kp = &vikeys['u'];\n                        F_SET(vp, VC_ISDOT);\n                        return (GC_OK);\n                }\n\n                /* Otherwise, a repeatable command must have been executed. */\n                if (!F_ISSET(dp, VC_ISDOT)) {\n                        msgq(sp, M_ERR, \"No command to repeat\");\n                        return (GC_ERR);\n                }\n\n                /* Set new count/buffer, if any, and return. */\n                if (F_ISSET(vp, VC_C1SET)) {\n                        F_SET(dp, VC_C1SET);\n                        dp->count = vp->count;\n                }\n                if (F_ISSET(vp, VC_BUFFER))\n                        dp->buffer = vp->buffer;\n\n                *vp = *dp;\n                return (GC_OK);\n        }\n\n        /* Set the flags based on the command flags. */\n        flags = kp->flags;\n\n        /* Check for illegal count. */\n        if (F_ISSET(vp, VC_C1SET) && !LF_ISSET(V_CNT))\n                goto usage;\n\n        /* Illegal motion command. */\n        if (ismotion == NULL) {\n                /* Illegal buffer. */\n                if (!LF_ISSET(V_OBUF) && F_ISSET(vp, VC_BUFFER))\n                        goto usage;\n\n                /* Required buffer. */\n                if (LF_ISSET(V_RBUF)) {\n                        KEY(vp->buffer, 0);\n                        F_SET(vp, VC_BUFFER);\n                }\n        }\n\n        /*\n         * Special case: '[', ']' and 'Z' commands.  Doesn't the fact that\n         * the *single* characters don't mean anything but the *doubled*\n         * characters do, just frost your shorts?\n         */\n        if (vp->key == '[' || vp->key == ']' || vp->key == 'Z') {\n                /*\n                 * Historically, half entered [[, ]] or Z commands weren't\n                 * cancelled by <escape>, the terminal was beeped instead.\n                 * POSIX.2-1992 probably didn't notice, and requires that\n                 * they be cancelled instead of beeping.  Seems fine to me.\n                 *\n                 * Don't set the EC_MAPCOMMAND flag, apparently ] is a popular\n                 * vi meta-character, and we don't want the user to wait while\n                 * we time out a possible mapping.  This *appears* to match\n                 * historic vi practice, but with mapping characters, you Just\n                 * Never Know.\n                 */\n                KEY(key, 0);\n\n                if (vp->key != key) {\nusage:                  if (ismotion == NULL)\n                                s = kp->usage;\n                        else if (ismotion->key == '~' && O_ISSET(sp, O_TILDEOP))\n                                s = tmotion.usage;\n                        else\n                                s = vikeys[ismotion->key].usage;\n                        v_emsg(sp, s, VIM_USAGE);\n                        return (GC_ERR);\n                }\n        }\n        /* Special case: 'z' command. */\n        if (vp->key == 'z') {\n                KEY(vp->character, 0);\n                if (isdigit(vp->character)) {\n                        if (v_count(sp, vp->character, &vp->count2))\n                                return (GC_ERR);\n                        F_SET(vp, VC_C2SET);\n                        KEY(vp->character, 0);\n                }\n        }\n\n        /*\n         * Commands that have motion components can be doubled to\n         * imply the current line.\n         */\n        if (ismotion != NULL && ismotion->key != key && !LF_ISSET(V_MOVE)) {\n                msgq(sp, M_ERR, \"%s may not be used as a motion command\",\n                    KEY_NAME(sp, key));\n                return (GC_ERR);\n        }\n\n        /* Required character. */\n        if (LF_ISSET(V_CHAR))\n        {\n                if (strchr(O_STR(sp, O_IMKEY), vp->key))\n                        sp->gp->scr_imctrl(sp, IMCTRL_ON);\n                KEY(vp->character, 0);\n                if (strchr(O_STR(sp, O_IMKEY), vp->key))\n                        sp->gp->scr_imctrl(sp, IMCTRL_OFF);\n        }\n\n        /* Get any associated cursor word. */\n        if (F_ISSET(kp, V_KEYW) && v_keyword(sp))\n                return (GC_ERR);\n\n        return (GC_OK);\n\nesc:    switch (cpart) {\n        case COMMANDMODE:\n                msgq(sp, M_BERR, \"Already in command mode\");\n                return (GC_ERR_NOFLUSH);\n        case ISPARTIAL:\n                break;\n        case NOTPARTIAL:\n                (void)sp->gp->scr_bell(sp);\n                break;\n        }\n        return (GC_ERR);\n}\n\n/*\n * v_motion --\n *\n * Get resulting motion mark.\n */\nstatic int\nv_motion(SCR *sp, VICMD *dm, VICMD *vp, int *mappedp)\n{\n        VICMD motion;\n        size_t len;\n        unsigned long cnt;\n        unsigned int flags;\n        int tilde_reset, notused;\n#ifdef IMKEY\n        int rval;\n#endif /* ifdef IMKEY */\n\n        /*\n         * If '.' command, use the dot motion, else get the motion command.\n         * Clear any line motion flags, the subsequent motion isn't always\n         * the same, i.e. \"/aaa\" may or may not be a line motion.\n         */\n        if (F_ISSET(vp, VC_ISDOT)) {\n                motion = *dm;\n                F_SET(&motion, VC_ISDOT);\n                F_CLR(&motion, VM_COMMASK);\n        } else {\n                memset(&motion, 0, sizeof(VICMD));\n                if (v_cmd(sp, NULL, &motion, vp, &notused, mappedp) != GC_OK)\n                        return (1);\n        }\n\n        /*\n         * A count may be provided both to the command and to the motion, in\n         * which case the count is multiplicative.  For example, \"3y4y\" is the\n         * same as \"12yy\".  This count is provided to the motion command and\n         * not to the regular function.\n         */\n        cnt = motion.count = F_ISSET(&motion, VC_C1SET) ? motion.count : 1;\n        if (F_ISSET(vp, VC_C1SET)) {\n                motion.count *= vp->count;\n                F_SET(&motion, VC_C1SET);\n\n                /*\n                 * Set flags to restore the original values of the command\n                 * structure so dot commands can change the count values,\n                 * e.g. \"2dw\" \"3.\" deletes a total of five words.\n                 */\n                F_CLR(vp, VC_C1SET);\n                F_SET(vp, VC_C1RESET);\n        }\n\n        /*\n         * Some commands can be repeated to indicate the current line.  In\n         * this case, or if the command is a \"line command\", set the flags\n         * appropriately.  If not a doubled command, run the function to get\n         * the resulting mark.\n         */\n        if (vp->key == motion.key) {\n                F_SET(vp, VM_LDOUBLE | VM_LMODE);\n\n                /* Set the origin of the command. */\n                vp->m_start.lno = sp->lno;\n                vp->m_start.cno = 0;\n\n                /*\n                 * Set the end of the command.\n                 *\n                 * If the current line is missing, i.e. the file is empty,\n                 * historic vi permitted a \"cc\" or \"!!\" command to insert\n                 * text.\n                 */\n                vp->m_stop.lno = sp->lno + motion.count - 1;\n                if (db_get(sp, vp->m_stop.lno, 0, NULL, &len)) {\n                        if (vp->m_stop.lno != 1 ||\n                            (vp->key != 'c' && vp->key != '!')) {\n                                v_emsg(sp, NULL, VIM_EMPTY);\n                                return (1);\n                        }\n                        vp->m_stop.cno = 0;\n                } else\n                        vp->m_stop.cno = len ? len - 1 : 0;\n        } else {\n                /*\n                 * Motion commands change the underlying movement (*snarl*).\n                 * For example, \"l\" is illegal at the end of a line, but \"dl\"\n                 * is not.  Set flags so the function knows the situation.\n                 */\n                motion.rkp = vp->kp;\n\n                /*\n                 * XXX\n                 * Use yank instead of creating a new motion command, it's a\n                 * lot easier for now.\n                 */\n                if (vp->kp == &tmotion) {\n                        tilde_reset = 1;\n                        vp->kp = &vikeys['y'];\n                } else\n                        tilde_reset = 0;\n\n                /*\n                 * Copy the key flags into the local structure, except for the\n                 * RCM flags -- the motion command will set the RCM flags in\n                 * the vp structure if necessary.  This means that the motion\n                 * command is expected to determine where the cursor ends up!\n                 * However, we save off the current RCM mask and restore it if\n                 * it no RCM flags are set by the motion command, with a small\n                 * modification.\n                 *\n                 * We replace the VM_RCM_SET flag with the VM_RCM flag.  This\n                 * is so that cursor movement doesn't set the relative position\n                 * unless the motion command explicitly specified it.  This\n                 * appears to match historic practice, but I've never been able\n                 * to develop a hard-and-fast rule.\n                 */\n                flags = F_ISSET(vp, VM_RCM_MASK);\n                if (LF_ISSET(VM_RCM_SET)) {\n                        LF_SET(VM_RCM);\n                        LF_CLR(VM_RCM_SET);\n                }\n                F_CLR(vp, VM_RCM_MASK);\n                F_SET(&motion, motion.kp->flags & ~VM_RCM_MASK);\n\n                /*\n                 * Set the three cursor locations to the current cursor.  This\n                 * permits commands like 'j' and 'k', that are line oriented\n                 * motions and have special cursor suck semantics when they are\n                 * used as standalone commands, to ignore column positioning.\n                 */\n                motion.m_final.lno =\n                    motion.m_stop.lno = motion.m_start.lno = sp->lno;\n                motion.m_final.cno =\n                    motion.m_stop.cno = motion.m_start.cno = sp->cno;\n\n                /* Run the function. */\n#ifndef IMKEY\n                if ((motion.kp->func)(sp, &motion))\n                        return (1);\n#else\n                if (strchr(O_STR(sp, O_IMKEY), motion.key))\n                        imreset(sp);\n                rval = (motion.kp->func)(sp, &motion);\n                if (strchr(O_STR(sp, O_IMKEY), motion.key))\n                        imoff(sp);\n                if (rval)\n                        return (1);\n#endif /* ifndef IMKEY */\n\n                /*\n                 * If the current line is missing, i.e. the file is empty,\n                 * historic vi allowed \"c<motion>\" or \"!<motion>\" to insert\n                 * text.  Otherwise fail -- most motion commands will have\n                 * already failed, but some, e.g. G, succeed in empty files.\n                 */\n                if (!db_exist(sp, vp->m_stop.lno)) {\n                        if (vp->m_stop.lno != 1 ||\n                            (vp->key != 'c' && vp->key != '!')) {\n                                v_emsg(sp, NULL, VIM_EMPTY);\n                                return (1);\n                        }\n                        vp->m_stop.cno = 0;\n                }\n\n                /*\n                 * XXX\n                 * See above.\n                 */\n                if (tilde_reset)\n                        vp->kp = &tmotion;\n\n                /*\n                 * Copy cut buffer, line mode and cursor position information\n                 * from the motion command structure, i.e. anything that the\n                 * motion command can set for us.  The commands can flag the\n                 * movement as a line motion (see v_sentence) as well as set\n                 * the VM_RCM_* flags explicitly.\n                 */\n                F_SET(vp, F_ISSET(&motion, VM_COMMASK | VM_RCM_MASK));\n\n                /*\n                 * If the motion command set no relative motion flags, use\n                 * the (slightly) modified previous values.\n                 */\n                if (!F_ISSET(vp, VM_RCM_MASK))\n                        F_SET(vp, flags);\n\n                /*\n                 * Commands can change behaviors based on the motion command\n                 * used, for example, the ! command repeated the last bang\n                 * command if N or n was used as the motion.\n                 */\n                vp->rkp = motion.kp;\n\n                /*\n                 * Motion commands can reset all of the cursor information.\n                 * If the motion is in the reverse direction, switch the\n                 * from and to MARK's so that it's in a forward direction.\n                 * Motions are from the from MARK to the to MARK (inclusive).\n                 */\n                if (motion.m_start.lno > motion.m_stop.lno ||\n                    (motion.m_start.lno == motion.m_stop.lno &&\n                    motion.m_start.cno > motion.m_stop.cno)) {\n                        vp->m_start = motion.m_stop;\n                        vp->m_stop = motion.m_start;\n                } else {\n                        vp->m_start = motion.m_start;\n                        vp->m_stop = motion.m_stop;\n                }\n                vp->m_final = motion.m_final;\n        }\n\n        /*\n         * If the command sets dot, save the motion structure.  The motion\n         * count was changed above and needs to be reset, that's why this\n         * is done here, and not in the calling routine.\n         */\n        if (F_ISSET(vp->kp, V_DOT)) {\n                *dm = motion;\n                dm->count = cnt;\n        }\n        return (0);\n}\n\n/*\n * v_init --\n *      Initialize the vi screen.\n */\nstatic int\nv_init(SCR *sp)\n{\n        GS *gp;\n        VI_PRIVATE *vip;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n\n        /* Switch into vi. */\n        if (gp->scr_screen(sp, SC_VI))\n                return (1);\n        (void)gp->scr_attr(sp, SA_ALTERNATE, 1);\n\n        F_CLR(sp, SC_EX | SC_SCR_EX);\n        F_SET(sp, SC_VI);\n\n        /*\n         * Initialize screen values.\n         *\n         * Small windows: see vs_refresh(), section 6a.\n         *\n         * Setup:\n         *      t_minrows is the minimum rows to display\n         *      t_maxrows is the maximum rows to display (rows - 1)\n         *      t_rows is the rows currently being displayed\n         */\n        sp->rows = vip->srows = O_VAL(sp, O_LINES);\n        sp->cols = O_VAL(sp, O_COLUMNS);\n        sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW);\n        /*\n         * To avoid segfaults on terminals with only one line,\n         * catch this corner case now and die explicitly.\n         */\n        if (sp->t_rows == 0) {\n                (void)fprintf(stderr, \"Error: Screen too small for visual mode.\\n\");\n                return 1;\n        }\n        if (sp->rows != 1) {\n                if (sp->t_rows > sp->rows - 1) {\n                        sp->t_minrows = sp->t_rows = sp->rows - 1;\n                        msgq(sp, M_INFO,\n                            \"Windows option value is too large, max is %u\",\n                            sp->t_rows);\n                }\n                sp->t_maxrows = sp->rows - 1;\n        } else\n                sp->t_maxrows = 1;\n        sp->woff = 0;\n\n        /* Create a screen map. */\n        CALLOC_RET(sp, HMAP, SIZE_HMAP(sp), sizeof(SMAP));\n        TMAP = HMAP + (sp->t_rows - 1);\n        HMAP->lno = sp->lno;\n        HMAP->coff = 0;\n        HMAP->soff = 1;\n\n        /*\n         * Fill the screen map from scratch -- try and center the line.  That\n         * way if we're starting with a file we've seen before, we'll put the\n         * line in the middle, otherwise, it won't work and we'll end up with\n         * the line at the top.\n         */\n        F_CLR(sp, SC_SCR_TOP);\n        F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER);\n\n        /* Invalidate the cursor. */\n        F_SET(vip, VIP_CUR_INVALID);\n\n        /* Paint the screen image from scratch. */\n        F_SET(vip, VIP_N_EX_PAINT);\n\n        return (0);\n}\n\n/*\n * v_dtoh --\n *      Move all but the current screen to the hidden queue.\n */\nstatic void\nv_dtoh(SCR *sp)\n{\n        GS *gp;\n        SCR *tsp;\n        int hidden;\n\n        /* Move all screens to the hidden queue, tossing screen maps. */\n        hidden = 0;\n        gp = sp->gp;\n        while ((tsp = TAILQ_FIRST(&gp->dq))) {\n                free(_HMAP(tsp));\n                _HMAP(tsp) = NULL;\n                TAILQ_REMOVE(&gp->dq, tsp, q);\n                TAILQ_INSERT_TAIL(&gp->hq, tsp, q);\n                ++hidden;\n        }\n\n        /* Move current screen back to the display queue. */\n        TAILQ_REMOVE(&gp->hq, sp, q);\n        TAILQ_INSERT_TAIL(&gp->dq, sp, q);\n\n        if (hidden > 2)\n                msgq(sp, M_INFO,\n                    \"%d screens backgrounded; use `:di s' to display details\",\n                    hidden - 1);\n\n        if (hidden == 2)\n                msgq(sp, M_INFO,\n                    \"Screen backgrounded; use `:di s' to display details\");\n}\n\n/*\n * v_keyword --\n *      Get the word (or non-word) the cursor is on.\n */\nstatic int\nv_keyword(SCR *sp)\n{\n        VI_PRIVATE *vip;\n        size_t beg, end, len;\n        int moved, state;\n        char *p;\n\n        if (db_get(sp, sp->lno, DBG_FATAL, &p, &len))\n                return (1);\n\n        /*\n         * !!!\n         * Historically, tag commands skipped over any leading whitespace\n         * characters.  Make this true in general when using cursor words.\n         * If movement, getting a cursor word implies moving the cursor to\n         * its beginning.  Refresh now.\n         *\n         * !!!\n         * Find the beginning/end of the keyword.  Keywords are currently\n         * used for cursor-word searching and for tags.  Historical vi\n         * only used the word in a tag search from the cursor to the end\n         * of the word, i.e. if the cursor was on the 'b' in \" abc \", the\n         * tag was \"bc\".  For consistency, we make cursor word searches\n         * follow the same rule.\n         */\n        for (moved = 0,\n            beg = sp->cno; beg < len && isspace(p[beg]); moved = 1, ++beg);\n        if (beg >= len) {\n                msgq(sp, M_BERR, \"Cursor not in a word\");\n                return (1);\n        }\n        if (moved) {\n                sp->cno = beg;\n                (void)vs_refresh(sp, 0);\n        }\n\n        /* Find the end of the word. */\n        for (state = inword(p[beg]),\n            end = beg; ++end < len && state == inword(p[end]););\n\n        vip = VIP(sp);\n        len = (end - beg);\n        BINC_RET(sp, vip->keyw, vip->klen, len);\n        memmove(vip->keyw, p + beg, len);\n        vip->keyw[len] = '\\0';                          /* XXX */\n        return (0);\n}\n\n/*\n * v_alias --\n *      Check for a command alias.\n */\nstatic VIKEYS const *\nv_alias(SCR *sp, VICMD *vp, VIKEYS const *kp)\n{\n        CHAR_T push;\n\n        switch (vp->key) {\n        case 'C':                       /* C -> c$ */\n                push = '$';\n                vp->key = 'c';\n                break;\n        case 'D':                       /* D -> d$ */\n                push = '$';\n                vp->key = 'd';\n                break;\n        case 'S':                       /* S -> c_ */\n                push = '_';\n                vp->key = 'c';\n                break;\n        case 'Y':                       /* Y -> y_ */\n                push = '_';\n                vp->key = 'y';\n                break;\n        default:\n                return (kp);\n        }\n        return (v_event_push(sp,\n            NULL, &push, 1, CH_NOMAP | CH_QUOTED) ? NULL : &vikeys[vp->key]);\n}\n\n/*\n * v_count --\n *      Return the next count.\n */\nstatic int\nv_count(SCR *sp, CHAR_T fkey, unsigned long *countp)\n{\n        EVENT ev;\n        unsigned long count, tc;\n\n        ev.e_c = fkey;\n        count = tc = 0;\n        (void)tc;\n        do {\n                /*\n                 * XXX\n                 * Assume that overflow results in a smaller number.\n                 */\n                tc = count * 10 + ev.e_c - '0';\n                if (count > tc) {\n                        /* Toss to the next non-digit. */\n                        do {\n                                if (v_key(sp, 0, &ev,\n                                    EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK)\n                                        return (1);\n                        } while (isdigit(ev.e_c));\n                        msgq(sp, M_ERR,\n                            \"Number larger than %lu\", ULONG_MAX);\n                        return (1);\n                }\n                count = tc;\n                if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK)\n                        return (1);\n        } while (isdigit(ev.e_c));\n        *countp = count;\n        return (0);\n}\n\n/*\n * v_key --\n *      Return the next event.\n */\nstatic gcret_t\nv_key(SCR *sp, int command_events, EVENT *evp, u_int32_t ec_flags)\n{\n        u_int32_t quote;\n\n        for (quote = 0;;) {\n                if (v_event_get(sp, evp, 0, ec_flags | quote))\n                        return (GC_FATAL);\n                quote = 0;\n\n                switch (evp->e_event) {\n                case E_CHARACTER:\n                        /*\n                         * !!!\n                         * Historically, ^V was ignored in the command stream,\n                         * although it had a useful side-effect of interrupting\n                         * mappings.  Adding a quoting bit to the call probably\n                         * extends historic practice, but it feels right.\n                         */\n                        if (evp->e_value == K_VLNEXT) {\n                                quote = EC_QUOTED;\n                                break;\n                        }\n                        return (GC_OK);\n                case E_ERR:\n                case E_EOF:\n                        return (GC_FATAL);\n                case E_INTERRUPT:\n                        /*\n                         * !!!\n                         * Historically, vi beeped on command level interrupts.\n                         *\n                         * Historically, vi exited to ex mode if no file was\n                         * named on the command line, and two interrupts were\n                         * generated in a row.  (Just figured you might want\n                         * to know that.)\n                         */\n                        (void)sp->gp->scr_bell(sp);\n                        return (GC_INTERRUPT);\n                case E_REPAINT:\n                        if (vs_repaint(sp, evp))\n                                return (GC_FATAL);\n                        break;\n                case E_WRESIZE:\n                        return (GC_ERR);\n                case E_QUIT:\n                case E_WRITE:\n                        if (command_events)\n                                return (GC_EVENT);\n                        /* FALLTHROUGH */\n                default:\n                        v_event_err(sp, evp);\n                        return (GC_ERR);\n                }\n        }\n        /* NOTREACHED */\n}\n\n#if defined(DEBUG) && defined(COMLOG)\n/*\n * v_comlog --\n *      Log the contents of the command structure.\n */\nstatic void\nv_comlog(SCR *sp, VICMD *vp)\n{\n        TRACE(sp, \"vcmd: %c\", vp->key);\n        if (F_ISSET(vp, VC_BUFFER))\n                TRACE(sp, \" buffer: %c\", vp->buffer);\n        if (F_ISSET(vp, VC_C1SET))\n                TRACE(sp, \" c1: %lu\", vp->count);\n        if (F_ISSET(vp, VC_C2SET))\n                TRACE(sp, \" c2: %lu\", vp->count2);\n        TRACE(sp, \" flags: 0x%x\\n\", vp->flags);\n}\n#endif /* defined(DEBUG) && defined(COMLOG) */\n"
  },
  {
    "path": "vi/vi.h",
    "content": "/*      $OpenBSD: vi.h,v 1.12 2022/12/26 19:16:04 jmc Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n *\n *      @(#)vi.h        10.19 (Berkeley) 6/30/96\n */\n\n#include \"../include/compat.h\"\n\n/* Definition of a vi \"word\". */\n#define inword(ch)      (isalnum(ch) || (ch) == '_')\n\ntypedef struct _vikeys VIKEYS;\n\n/* Structure passed around to functions implementing vi commands. */\ntypedef struct _vicmd {\n        CHAR_T  key;                    /* Command key. */\n        CHAR_T  buffer;                 /* Buffer. */\n        CHAR_T  character;              /* Character. */\n        unsigned long  count;           /* Count. */\n        unsigned long  count2;          /* Second count (only used by z). */\n        EVENT   ev;                     /* Associated event. */\n\n#define ISCMD(p, key)   ((p) == &vikeys[(key)])\n        VIKEYS const *kp;               /* Command/Motion VIKEYS entry. */\n\n#define ISMOTION(vp)    ((vp)->rkp != NULL && F_ISSET((vp)->rkp, V_MOTION))\n        VIKEYS const *rkp;              /* Related C/M VIKEYS entry. */\n\n        /*\n         * Historic vi allowed \"dl\" when the cursor was on the last column,\n         * deleting the last character, and similarly allowed \"dw\" when\n         * the cursor was on the last column of the file.  It didn't allow\n         * \"dh\" when the cursor was on column 1, although these cases are\n         * not strictly analogous.  The point is that some movements would\n         * succeed if they were associated with a motion command, and fail\n         * otherwise.  This is part of the off-by-1 schizophrenia that\n         * plagued vi.  Other examples are that \"dfb\" deleted everything\n         * up to and including the next 'b' character, while \"d/b\" deleted\n         * everything up to the next 'b' character.  While this implementation\n         * regularizes the interface to the extent possible, there are many\n         * special cases that can't be fixed.  The special cases are handled\n         * by setting flags per command so that the underlying command and\n         * motion routines know what's really going on.\n         *\n         * The VM_* flags are set in the vikeys array and by the underlying\n         * functions (motion component or command) as well.  For this reason,\n         * the flags in the VICMD and VIKEYS structures live in the same name\n         * space.\n         */\n\n#define VM_CMDFAILED    0x00000001      /* Command failed. */\n#define VM_CUTREQ       0x00000002      /* Always cut into numeric buffers. */\n#define VM_LDOUBLE      0x00000004      /* Doubled command for line mode. */\n#define VM_LMODE        0x00000008      /* Motion is line oriented. */\n#define VM_COMMASK      0x0000000f      /* Mask for VM flags. */\n\n        /*\n         * The VM_RCM_* flags are single usage, i.e. if you set one, you have\n         * to clear the others.\n         */\n\n#define VM_RCM          0x00000010      /* Use relative cursor movement (RCM). */\n#define VM_RCM_SET      0x00000020      /* RCM: set to current position. */\n#define VM_RCM_SETFNB   0x00000040      /* RCM: set to first non-blank (FNB). */\n#define VM_RCM_SETLAST  0x00000080      /* RCM: set to last character. */\n#define VM_RCM_SETNNB   0x00000100      /* RCM: set to next non-blank. */\n#define VM_RCM_MASK     0x000001f0      /* Mask for RCM flags. */\n\n        /* Flags for the underlying function. */\n#define VC_BUFFER       0x00000200      /* The buffer was set. */\n#define VC_C1RESET      0x00000400      /* Reset C1SET flag for dot commands. */\n#define VC_C1SET        0x00000800      /* Count 1 was set. */\n#define VC_C2SET        0x00001000      /* Count 2 was set. */\n#define VC_ISDOT        0x00002000      /* Command was the dot command. */\n        u_int32_t flags;\n\n        /*\n         * There are four cursor locations that we worry about: the initial\n         * cursor position, the start of the range, the end of the range,\n         * and the final cursor position.  The initial cursor position and\n         * the start of the range are both m_start, and are always the same.\n         * All locations are initialized to the starting cursor position by\n         * the main vi routines, and the underlying functions depend on this.\n         *\n         * Commands that can be motion components set the end of the range\n         * cursor position, m_stop.  All commands must set the ending cursor\n         * position, m_final.  The reason that m_stop isn't the same as m_final\n         * is that there are situations where the final position of the cursor\n         * is outside of the cut/delete range (e.g. 'd[[' from the first column\n         * of a line).  The final cursor position often varies based on the\n         * direction of the movement, as well as the command.  The only special\n         * case that the delete code handles is that it will make adjustments\n         * if the final cursor position is deleted.\n         *\n         * The reason for all of this is that the historic vi semantics were\n         * defined command-by-command.  Every function has to roll its own\n         * starting and stopping positions, and adjust them if it's being used\n         * as a motion component.  The general rules are as follows:\n         *\n         *      1: If not a motion component, the final cursor is at the end\n         *         of the range.\n         *      2: If moving backward in the file, delete and yank move the\n         *         final cursor to the end of the range.\n         *      3: If moving forward in the file, delete and yank leave the\n         *         final cursor at the start of the range.\n         *\n         * Usually, if moving backward in the file and it's a motion component,\n         * the starting cursor is decremented by a single character (or, in a\n         * few cases, to the end of the previous line) so that the starting\n         * cursor character isn't cut or deleted.  No cursor adjustment is\n         * needed for moving forward, because the cut/delete routines handle\n         * m_stop inclusively, i.e. the last character in the range is cut or\n         * deleted.  This makes cutting to the EOF/EOL reasonable.\n         *\n         * The 'c', '<', '>', and '!' commands are special cases.  We ignore\n         * the final cursor position for all of them: for 'c', the text input\n         * routines set the cursor to the last character inserted; for '<',\n         * '>' and '!', the underlying ex commands that do the operation will\n         * set the cursor for us, usually to something related to the first\n         * <nonblank>.\n         */\n\n        MARK     m_start;               /* mark: initial cursor, range start. */\n        MARK     m_stop;                /* mark: range end. */\n        MARK     m_final;               /* mark: final cursor position. */\n} VICMD;\n\n/* Vi command table structure. */\nstruct _vikeys {                        /* Underlying function. */\n        int      (*func)(SCR *, VICMD *);\n#define V_ABS           0x00004000      /* Absolute movement, set '' mark. */\n#define V_ABS_C         0x00008000      /* V_ABS: if the line/column changed. */\n#define V_ABS_L         0x00010000      /* V_ABS: if the line changed. */\n#define V_CHAR          0x00020000      /* Character (required, trailing). */\n#define V_CNT           0x00040000      /* Count (optional, leading). */\n#define V_DOT           0x00080000      /* On success, sets dot command. */\n#define V_KEYW          0x00100000      /* Cursor referenced word. */\n#define V_MOTION        0x00200000      /* Motion (required, trailing). */\n#define V_MOVE          0x00400000      /* Command defines movement. */\n#define V_OBUF          0x00800000      /* Buffer (optional, leading). */\n#define V_RBUF          0x01000000      /* Buffer (required, trailing). */\n#define V_SECURE        0x02000000      /* Permission denied if O_SECURE set. */\n        u_int32_t flags;\n        char    *usage;                 /* Usage line. */\n        char    *help;                  /* Help line. */\n};\n#define MAXVIKEY        126             /* List of vi commands. */\nextern VIKEYS const vikeys[MAXVIKEY + 1];\nextern VIKEYS const tmotion;            /* XXX Hacked ~ command. */\n\n/* Character stream structure, prototypes. */\ntypedef struct _vcs {\n        recno_t  cs_lno;                /* Line. */\n        size_t   cs_cno;                /* Column. */\n        CHAR_T  *cs_bp;                 /* Buffer. */\n        size_t   cs_len;                /* Length. */\n        CHAR_T   cs_ch;                 /* Character. */\n#define CS_EMP  1                       /* Empty line. */\n#define CS_EOF  2                       /* End-of-file. */\n#define CS_EOL  3                       /* End-of-line. */\n#define CS_SOF  4                       /* Start-of-file. */\n        int      cs_flags;              /* Return flags. */\n} VCS;\n\nint     cs_bblank(SCR *, VCS *);\nint     cs_fblank(SCR *, VCS *);\nint     cs_fspace(SCR *, VCS *);\nint     cs_init(SCR *, VCS *);\nint     cs_next(SCR *, VCS *);\nint     cs_prev(SCR *, VCS *);\n\n/*\n * We use a single \"window\" for each set of vi screens.  The model would be\n * simpler with two windows (one for the text, and one for the modeline)\n * because scrolling the text window down would work correctly then, not\n * affecting the mode line.  As it is we have to play games to make it look\n * right.  The reason for this choice is that it would be difficult for\n * curses to optimize the movement, i.e. detect that the downward scroll\n * isn't going to change the modeline, set the scrolling region on the\n * terminal and only scroll the first part of the text window.\n *\n * Structure for mapping lines to the screen.  An SMAP is an array, with one\n * structure element per screen line, which holds information describing the\n * physical line which is displayed in the screen line.  The first two fields\n * (lno and off) are all that are necessary to describe a line.  The rest of\n * the information is useful to keep information from being re-calculated.\n *\n * The SMAP always has an entry for each line of the physical screen, plus a\n * slot for the colon command line, so there is room to add any screen into\n * another one at screen exit.\n *\n * Lno is the line number.  If doing the historic vi long line folding, off\n * is the screen offset into the line.  For example, the pair 2:1 would be\n * the first screen of line 2, and 2:2 would be the second.  In the case of\n * long lines, the screen map will tend to be staggered, e.g., 1:1, 1:2, 1:3,\n * 2:1, 3:1, etc.  If doing left-right scrolling, the off field is the screen\n * column offset into the lines, and can take on any value, as it's adjusted\n * by the user set value O_SIDESCROLL.\n */\n\ntypedef struct _smap {\n        recno_t  lno;           /* 1-N: Physical file line number. */\n        size_t   coff;          /* 0-N: Column offset in the line. */\n        size_t   soff;          /* 1-N: Screen offset in the line. */\n\n                                /* vs_line() cache information. */\n        size_t   c_sboff;       /* 0-N: offset of first character byte. */\n        size_t   c_eboff;       /* 0-N: offset of  last character byte. */\n        u_int8_t c_scoff;       /* 0-N: offset into the first character. */\n        u_int8_t c_eclen;       /* 1-N: columns from the last character. */\n        u_int8_t c_ecsize;      /* 1-N: size of the last character. */\n} SMAP;\n                                /* Macros to flush/test cached information. */\n#define SMAP_CACHE(smp)         ((smp)->c_ecsize != 0)\n#define SMAP_FLUSH(smp)         ((smp)->c_ecsize = 0)\n\n                                /* Character search information. */\ntypedef enum { CNOTSET, FSEARCH, fSEARCH, TSEARCH, tSEARCH } cdir_t;\n\ntypedef enum { AB_NOTSET, AB_NOTWORD, AB_INWORD } abb_t;\ntypedef enum { Q_NOTSET,  Q_VNEXT,    Q_VTHIS   } quote_t;\n\n/* Vi private, per-screen memory. */\ntypedef struct _vi_private {\n        VICMD   cmd;            /* Current command, motion. */\n        VICMD   motion;\n\n        /*\n         * !!!\n         * The saved command structure can be modified by the underlying\n         * vi functions, see v_Put() and v_put().\n         */\n\n        VICMD   sdot;           /* Saved dot, motion command. */\n        VICMD   sdotmotion;\n\n        CHAR_T *keyw;           /* Keyword buffer. */\n        size_t  klen;           /* Keyword length. */\n        size_t  keywlen;        /* Keyword buffer length. */\n\n        CHAR_T  rlast;          /* Last 'r' replacement character. */\n        e_key_t rvalue;         /* Value of last replacement character. */\n\n        EVENT  *rep;            /* Input replay buffer. */\n        size_t  rep_len;        /* Input replay buffer length. */\n        size_t  rep_cnt;        /* Input replay buffer characters. */\n\n        mtype_t mtype;          /* Last displayed message type. */\n        size_t  linecount;      /* 1-N: Output overwrite count. */\n        size_t  lcontinue;      /* 1-N: Output line continue value. */\n        size_t  totalcount;     /* 1-N: Output overwrite count. */\n\n                                /* Busy state. */\n        int     busy_ref;       /* Busy reference count. */\n        int     busy_ch;        /* Busy character. */\n        size_t  busy_fx;        /* Busy character x coordinate. */\n        size_t  busy_oldy;      /* Saved y coordinate. */\n        size_t  busy_oldx;      /* Saved x coordinate. */\n        struct timespec busy_ts;/* Busy timer. */\n\n        char   *ps;             /* Paragraph plus section list. */\n\n        unsigned long  u_ccnt;  /* Undo command count. */\n\n        CHAR_T  lastckey;       /* Last search character. */\n        cdir_t  csearchdir;     /* Character search direction. */\n\n        SMAP   *h_smap;         /* First slot of the line map. */\n        SMAP   *t_smap;         /* Last slot of the line map. */\n\n        /*\n         * One extra slot is always allocated for the map so that we can use\n         * it to do vi :colon command input; see v_tcmd().\n         */\n\n        recno_t sv_tm_lno;      /* tcmd: saved TMAP lno field. */\n        size_t  sv_tm_coff;     /* tcmd: saved TMAP coff field. */\n        size_t  sv_tm_soff;     /* tcmd: saved TMAP soff field. */\n        size_t  sv_t_maxrows;   /* tcmd: saved t_maxrows. */\n        size_t  sv_t_minrows;   /* tcmd: saved t_minrows. */\n        size_t  sv_t_rows;      /* tcmd: saved t_rows. */\n#define SIZE_HMAP(sp)   (VIP(sp)->srows + 1)\n\n        /*\n         * Macros to get to the head/tail of the smap.  If the screen only has\n         * one line, HMAP can be equal to TMAP, so the code has to understand\n         * the off-by-one errors that can result.  If stepping through an SMAP\n         * and operating on each entry, use sp->t_rows as the count of slots,\n         * don't use a loop that compares <= TMAP.\n         */\n\n#define _HMAP(sp)       (VIP(sp)->h_smap)\n#define HMAP            _HMAP(sp)\n#define _TMAP(sp)       (VIP(sp)->t_smap)\n#define TMAP            _TMAP(sp)\n\n        recno_t ss_lno; /* 1-N: vi_opt_screens cached line number. */\n        size_t  ss_screens;     /* vi_opt_screens cached return value. */\n#define VI_SCR_CFLUSH(vip)      ((vip)->ss_lno = OOBLNO)\n\n        size_t  srows;          /* 1-N: rows in the terminal/window. */\n        recno_t olno;           /* 1-N: old cursor file line. */\n        size_t  ocno;           /* 0-N: old file cursor column. */\n        size_t  sc_col;         /* 0-N: LOGICAL screen column. */\n        SMAP   *sc_smap;        /* SMAP entry where sc_col occurs. */\n\n#define VIP_CUR_INVALID 0x0001  /* Cursor position is unknown. */\n#define VIP_DIVIDER     0x0002  /* Divider line was displayed. */\n#define VIP_N_EX_PAINT  0x0004  /* Clear and repaint when ex finishes. */\n#define VIP_N_EX_REDRAW 0x0008  /* Schedule SC_SCR_REDRAW when ex finishes. */\n#define VIP_N_REFRESH   0x0010  /* Repaint (from SMAP) on the next refresh. */\n#define VIP_N_RENUMBER  0x0020  /* Renumber screen on the next refresh. */\n#define VIP_RCM_LAST    0x0040  /* Cursor drawn to the last column. */\n#define VIP_S_MODELINE  0x0080  /* Skip next modeline refresh. */\n#define VIP_S_REFRESH   0x0100  /* Skip next refresh. */\n        u_int16_t flags;\n} VI_PRIVATE;\n\n/* Vi private area. */\n#define VIP(sp) ((VI_PRIVATE *)((sp)->vi_private))\n\n#define O_NUMBER_FMT    \"%6lu \"                 /* O_NUMBER format, length. */\n#define O_NUMBER_LENGTH 7\n\n/* Screen columns. */\n#define SCREEN_COLS(sp) \\\n        ((O_ISSET((sp), O_NUMBER) ? (sp)->cols - O_NUMBER_LENGTH : (sp)->cols))\n\n/*\n * LASTLINE is the zero-based, last line in the screen.  Note that it is correct\n * regardless of the changes in the screen to permit text input on the last line\n * of the screen, or the existence of small screens.\n */\n\n#define LASTLINE(sp) \\\n        ((sp)->t_maxrows < (sp)->rows ? (sp)->t_maxrows : (sp)->rows - 1)\n\n/*\n * Small screen (see vs_refresh.c, section 6a) and one-line screen test.\n * Note, both cannot be true for the same screen.\n */\n\n#define IS_SMALL(sp)    ((sp)->t_minrows != (sp)->t_maxrows)\n#define IS_ONELINE(sp)  ((sp)->rows == 1)\n\n/* Half text. */\n#define HALFTEXT(sp) ((sp)->t_rows == 1 ? 1 : (sp)->t_rows / 2)\n\n/* Half text screen. */\n#define HALFSCREEN(sp) ((sp)->t_maxrows == 1 ? 1 : (sp)->t_maxrows / 2)\n\n/*\n * Next tab offset.\n *\n * !!!\n * There are problems with how the historical vi handled tabs.  For example,\n * by doing \"set ts=3\" and building lines that fold, you can get it to step\n * through tabs as if they were spaces and move inserted characters to new\n * positions when <esc> is entered.  I believe that nvi does tabs correctly,\n * but there are some historical incompatibilities.\n */\n\n#define TAB_OFF(c)      COL_OFF((c), O_VAL(sp, O_TABSTOP))\n\n/* If more than one screen being shown. */\n#define IS_SPLIT(sp)    (TAILQ_NEXT((sp), q) || TAILQ_PREV((sp), _dqh, q))\n\n/* Screen adjustment operations. */\ntypedef enum { A_DECREASE, A_INCREASE, A_SET } adj_t;\n\n/* Screen position operations. */\ntypedef enum { P_BOTTOM, P_FILL, P_MIDDLE, P_TOP } pos_t;\n\n/* Scrolling operations. */\ntypedef enum {\n        CNTRL_B, CNTRL_D, CNTRL_E, CNTRL_F,\n        CNTRL_U, CNTRL_Y, Z_CARAT, Z_PLUS\n} scroll_t;\n\n/* Vi common error messages. */\ntypedef enum {\n        VIM_COMBUF, VIM_EMPTY, VIM_EOF, VIM_EOL,\n        VIM_NOCOM, VIM_NOCOM_B, VIM_USAGE, VIM_WRESIZE\n} vim_t;\n\n#include \"vi_extern.h\"\n"
  },
  {
    "path": "vi/vs_line.c",
    "content": "/*      $OpenBSD: vs_line.c,v 1.17 2022/12/26 19:16:04 jmc Exp $    */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * vs_line --\n *      Update one line on the screen.\n *\n * PUBLIC: int vs_line(SCR *, SMAP *, size_t *, size_t *);\n */\nint\nvs_line(SCR *sp, SMAP *smp, size_t *yp, size_t *xp)\n{\n        CHAR_T *kp;\n        GS *gp;\n        SMAP *tsmp;\n        size_t chlen = 0, cno_cnt, cols_per_screen, len, nlen;\n        size_t offset_in_char, offset_in_line, oldx, oldy;\n        size_t scno, skip_cols, skip_screens;\n        int ch = 0, dne, is_cached, is_partial, is_tab, no_draw;\n        int list_tab, list_dollar;\n        char *p, *cbp, *ecbp, cbuf[128];\n\n#if defined(DEBUG) && 0\n        TRACE(sp, \"vs_line: row %u: line: %u off: %u\\n\",\n            smp - HMAP, smp->lno, smp->off);\n#endif /* if defined(DEBUG) && 0 */\n        /*\n         * If ex modifies the screen after ex output is already on the screen,\n         * don't touch it -- we'll get scrolling wrong, at best.\n         */\n        no_draw = 0;\n        if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1)\n                no_draw = 1;\n        if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp))\n                no_draw = 1;\n\n        /*\n         * Assume that, if the cache entry for the line is filled in, the\n         * line is already on the screen, and all we need to do is return\n         * the cursor position.  If the calling routine doesn't need the\n         * cursor position, we can just return.\n         */\n        is_cached = SMAP_CACHE(smp);\n        if (yp == NULL && (is_cached || no_draw))\n                return (0);\n\n        /*\n         * A nasty side effect of this routine is that it returns the screen\n         * position for the \"current\" character.  Not pretty, but this is the\n         * only routine that really knows what's out there.\n         *\n         * Move to the line.  This routine can be called by vs_sm_position(),\n         * which uses it to fill in the cache entry so it can figure out what\n         * the real contents of the screen are.  Because of this, we have to\n         * return to wherever we started from.\n         */\n        gp = sp->gp;\n        (void)gp->scr_cursor(sp, &oldy, &oldx);\n        (void)gp->scr_move(sp, smp - HMAP, 0);\n\n        /* Get the line. */\n        dne = db_get(sp, smp->lno, 0, &p, &len);\n\n        /*\n         * Special case if we're printing the info/mode line.  Skip printing\n         * the leading number, as well as other minor setup.  The only time\n         * this code paints the mode line is when the user is entering text\n         * for a \":\" command, so we can put the code here instead of dealing\n         * with the empty line logic below.  This is a kludge, but it's pretty\n         * much confined to this module.\n         *\n         * Set the number of columns for this screen.\n         * Set the number of chars or screens to skip until a character is to\n         * be displayed.\n         */\n        cols_per_screen = sp->cols;\n        if (O_ISSET(sp, O_LEFTRIGHT)) {\n                skip_screens = 0;\n                skip_cols = smp->coff;\n        } else {\n                skip_screens = smp->soff - 1;\n                skip_cols = skip_screens * cols_per_screen;\n        }\n\n        list_tab = O_ISSET(sp, O_LIST);\n        if (F_ISSET(sp, SC_TINPUT_INFO))\n                list_dollar = 0;\n        else {\n                list_dollar = list_tab;\n\n                /*\n                 * If O_NUMBER is set, the line doesn't exist and it's line\n                 * number 1, i.e., an empty file, display the line number.\n                 *\n                 * If O_NUMBER is set, the line exists and the first character\n                 * on the screen is the first character in the line, display\n                 * the line number.\n                 *\n                 * !!!\n                 * If O_NUMBER set, decrement the number of columns in the\n                 * first screen.  DO NOT CHANGE THIS -- IT'S RIGHT!  The\n                 * rest of the code expects this to reflect the number of\n                 * columns in the first screen, regardless of the number of\n                 * columns we're going to skip.\n                 */\n                if (O_ISSET(sp, O_NUMBER)) {\n                        cols_per_screen -= O_NUMBER_LENGTH;\n                        if ((!dne || smp->lno == 1) && skip_cols == 0) {\n                                nlen = snprintf(cbuf, sizeof(cbuf),\n                                    O_NUMBER_FMT, (unsigned long)smp->lno);\n                                if (nlen >= sizeof(cbuf))\n                                        nlen = sizeof(cbuf) - 1;\n                                (void)gp->scr_addstr(sp, cbuf, nlen);\n                        }\n                }\n        }\n\n        /*\n         * Special case non-existent lines and the first line of an empty\n         * file.  In both cases, the cursor position is 0, but corrected\n         * as necessary for the O_NUMBER field, if it was displayed.\n         */\n        if (dne || len == 0) {\n                /* Fill in the cursor. */\n                if (yp != NULL && smp->lno == sp->lno) {\n                        *yp = smp - HMAP;\n                        *xp = sp->cols - cols_per_screen;\n                }\n\n                /* If the line is on the screen, quit. */\n                if (is_cached || no_draw)\n                        goto ret1;\n\n                /* Set line cache information. */\n                smp->c_sboff = smp->c_eboff = 0;\n                smp->c_scoff = smp->c_eclen = 0;\n\n                /*\n                 * Lots of special cases for empty lines, but they only apply\n                 * if we're displaying the first screen of the line.\n                 */\n                if (skip_cols == 0) {\n                        if (dne) {\n                                if (smp->lno == 1) {\n                                        if (list_dollar) {\n                                                ch = '$';\n                                                goto empty;\n                                        }\n                                } else {\n                                        ch = '~';\n                                        goto empty;\n                                }\n                        } else\n                                if (list_dollar) {\n                                        ch = '$';\nempty:                                  (void)gp->scr_addstr(sp,\n                                            KEY_NAME(sp, ch), KEY_LEN(sp, ch));\n                                }\n                }\n\n                (void)gp->scr_clrtoeol(sp);\n                (void)gp->scr_move(sp, oldy, oldx);\n                return (0);\n        }\n\n        /*\n         * If we just wrote this or a previous line, we cached the starting\n         * and ending positions of that line.  The way it works is we keep\n         * information about the lines displayed in the SMAP.  If we're\n         * painting the screen in the forward direction, this saves us from\n         * reformatting the physical line for every line on the screen.  This\n         * wins big on binary files with 10K lines.\n         *\n         * Test for the first screen of the line, then the current screen line,\n         * then the line behind us, then do the hard work.  Note, it doesn't\n         * do us any good to have a line in front of us -- it would be really\n         * hard to try and figure out tabs in the reverse direction, i.e. how\n         * many spaces a tab takes up in the reverse direction depends on\n         * what characters preceded it.\n         *\n         * Test for the first screen of the line.\n         */\n        if (skip_cols == 0) {\n                smp->c_sboff = offset_in_line = 0;\n                smp->c_scoff = offset_in_char = 0;\n                p = &p[offset_in_line];\n                goto display;\n        }\n\n        /* Test to see if we've seen this exact line before. */\n        if (is_cached) {\n                offset_in_line = smp->c_sboff;\n                offset_in_char = smp->c_scoff;\n                p = &p[offset_in_line];\n\n                /* Set cols_per_screen to 2nd and later line length. */\n                if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)\n                        cols_per_screen = sp->cols;\n                goto display;\n        }\n\n        /* Test to see if we saw an earlier part of this line before. */\n        if (smp != HMAP &&\n            SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {\n                if (tsmp->c_eclen != tsmp->c_ecsize) {\n                        offset_in_line = tsmp->c_eboff;\n                        offset_in_char = tsmp->c_eclen;\n                } else {\n                        offset_in_line = tsmp->c_eboff + 1;\n                        offset_in_char = 0;\n                }\n\n                /* Put starting info for this line in the cache. */\n                smp->c_sboff = offset_in_line;\n                smp->c_scoff = offset_in_char;\n                p = &p[offset_in_line];\n\n                /* Set cols_per_screen to 2nd and later line length. */\n                if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)\n                        cols_per_screen = sp->cols;\n                goto display;\n        }\n\n        scno = 0;\n        offset_in_line = 0;\n        offset_in_char = 0;\n\n        /* Do it the hard way, for leftright scrolling screens. */\n        if (O_ISSET(sp, O_LEFTRIGHT)) {\n                for (; offset_in_line < len; ++offset_in_line) {\n                        chlen = (ch = *(unsigned char *)p++) == '\\t' && !list_tab ?\n                            TAB_OFF(scno) : KEY_LEN(sp, ch);\n                        if ((scno += chlen) >= skip_cols)\n                                break;\n                }\n\n                /* Set cols_per_screen to 2nd and later line length. */\n                cols_per_screen = sp->cols;\n\n                /* Put starting info for this line in the cache. */\n                if (scno != skip_cols) {\n                        smp->c_sboff = offset_in_line;\n                        smp->c_scoff =\n                            offset_in_char = chlen - (scno - skip_cols);\n                        --p;\n                } else {\n                        smp->c_sboff = ++offset_in_line;\n                        smp->c_scoff = 0;\n                }\n        }\n\n        /* Do it the hard way, for historic line-folding screens. */\n        else {\n                for (; offset_in_line < len; ++offset_in_line) {\n                        chlen = (ch = *(unsigned char *)p++) == '\\t' && !list_tab ?\n                            TAB_OFF(scno) : KEY_LEN(sp, ch);\n                        if ((scno += chlen) < cols_per_screen)\n                                continue;\n                        scno -= cols_per_screen;\n\n                        /* Set cols_per_screen to 2nd and later line length. */\n                        cols_per_screen = sp->cols;\n\n                        /*\n                         * If crossed the last skipped screen boundary, start\n                         * displaying the characters.\n                         */\n                        if (--skip_screens == 0)\n                                break;\n                }\n\n                /* Put starting info for this line in the cache. */\n                if (scno != 0) {\n                        smp->c_sboff = offset_in_line;\n                        smp->c_scoff = offset_in_char = chlen - scno;\n                        --p;\n                } else {\n                        smp->c_sboff = ++offset_in_line;\n                        smp->c_scoff = 0;\n                }\n        }\n\ndisplay:\n        /*\n         * Set the number of characters to skip before reaching the cursor\n         * character.  Offset by 1 and use 0 as a flag value.  Vs_line is\n         * called repeatedly with a valid pointer to a cursor position.\n         * Don't fill anything in unless it's the right line and the right\n         * character, and the right part of the character...\n         */\n        if (yp == NULL ||\n            smp->lno != sp->lno || sp->cno < offset_in_line ||\n            offset_in_line + cols_per_screen < sp->cno) {\n                cno_cnt = 0;\n                /* If the line is on the screen, quit. */\n                if (is_cached || no_draw)\n                        goto ret1;\n        } else\n                cno_cnt = (sp->cno - offset_in_line) + 1;\n\n        ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;\n\n        /* This is the loop that actually displays characters. */\n        for (is_partial = 0, scno = 0;\n            offset_in_line < len; ++offset_in_line, offset_in_char = 0) {\n                if ((ch = *(unsigned char *)p++) == '\\t' && !list_tab) {\n                        scno += chlen = TAB_OFF(scno) - offset_in_char;\n                        is_tab = 1;\n                } else {\n                        scno += chlen = KEY_LEN(sp, ch) - offset_in_char;\n                        is_tab = 0;\n                }\n\n                /*\n                 * Only display up to the right-hand column.  Set a flag if\n                 * the entire character wasn't displayed for use in setting\n                 * the cursor.  If reached the end of the line, set the cache\n                 * info for the screen.  Don't worry about there not being\n                 * characters to display on the next screen, its lno/off won't\n                 * match up in that case.\n                 */\n                if (scno >= cols_per_screen) {\n                        if (is_tab == 1) {\n                                chlen -= scno - cols_per_screen;\n                                smp->c_ecsize = smp->c_eclen = chlen;\n                                scno = cols_per_screen;\n                        } else {\n                                smp->c_ecsize = chlen;\n                                chlen -= scno - cols_per_screen;\n                                smp->c_eclen = chlen;\n\n                                if (scno > cols_per_screen)\n                                        is_partial = 1;\n                        }\n                        smp->c_eboff = offset_in_line;\n\n                        /* Terminate the loop. */\n                        offset_in_line = len;\n                }\n\n                /*\n                 * If the caller wants the cursor value, and this was the\n                 * cursor character, set the value.  There are two ways to\n                 * put the cursor on a character -- if it's normal display\n                 * mode, it goes on the last column of the character.  If\n                 * it's input mode, it goes on the first.  In normal mode,\n                 * set the cursor only if the entire character was displayed.\n                 */\n                if (cno_cnt &&\n                    --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) {\n                        *yp = smp - HMAP;\n                        if (F_ISSET(sp, SC_TINPUT)) {\n                                if (is_partial)\n                                        *xp = scno - smp->c_ecsize;\n                                else\n                                        *xp = scno - chlen;\n                        } else\n                                *xp = scno - 1;\n                        if (O_ISSET(sp, O_NUMBER) &&\n                            !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0)\n                                *xp += O_NUMBER_LENGTH;\n\n                        /* If the line is on the screen, quit. */\n                        if (is_cached || no_draw)\n                                goto ret1;\n                }\n\n                /* If the line is on the screen, don't display anything. */\n                if (is_cached)\n                        continue;\n\n#define FLUSH(gp, sp, cbp, cbuf) do {                                   \\\n        *(cbp) = '\\0';                                                  \\\n        (void)(gp)->scr_addstr((sp), (cbuf), (cbp) - (cbuf));           \\\n        (cbp) = (cbuf);                                                 \\\n} while (0)\n                /*\n                 * Display the character.  We do tab expansion here because\n                 * the screen interface doesn't have any way to set the tab\n                 * length.  Note, it's theoretically possible for chlen to\n                 * be larger than cbuf, if the user set a impossibly large\n                 * tabstop.\n                 */\n                if (is_tab)\n                        while (chlen--) {\n                                if (cbp >= ecbp)\n                                        FLUSH(gp, sp, cbp, cbuf);\n                                if (O_ISSET(sp, O_VISIBLETAB))\n                                    *cbp++ = '~';\n                                else\n                                    *cbp++ = ' ';\n                        }\n                else {\n                        if (cbp + chlen >= ecbp)\n                                FLUSH(gp, sp, cbp, cbuf);\n                        for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;)\n                                *cbp++ = *kp++;\n                }\n        }\n\n        if (scno < cols_per_screen) {\n                /* If we didn't paint the whole line, update the cache. */\n                smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch);\n                smp->c_eboff = len - 1;\n\n                /*\n                 * If not the info/mode line, and O_LIST set, and at the\n                 * end of the line, and the line ended on this screen,\n                 * add a trailing $.\n                 */\n                if (list_dollar) {\n                        ++scno;\n\n                        chlen = KEY_LEN(sp, '$');\n                        if (cbp + chlen >= ecbp)\n                                FLUSH(gp, sp, cbp, cbuf);\n                        for (kp = KEY_NAME(sp, '$'); chlen--;)\n                                *cbp++ = *kp++;\n                }\n\n                /* If still didn't paint the whole line, clear the rest. */\n                if (scno < cols_per_screen)\n                        (void)gp->scr_clrtoeol(sp);\n        }\n\n        /* Flush any buffered characters. */\n        if (cbp > cbuf)\n                FLUSH(gp, sp, cbp, cbuf);\n\nret1:   (void)gp->scr_move(sp, oldy, oldx);\n        return (0);\n}\n\n/*\n * vs_number --\n *      Repaint the numbers on all the lines.\n *\n * PUBLIC: int vs_number(SCR *);\n */\nint\nvs_number(SCR *sp)\n{\n        GS *gp;\n        SMAP *smp;\n        size_t len, oldy, oldx;\n        int exist;\n        char nbuf[10];\n\n        gp = sp->gp;\n\n        /* No reason to do anything if we're in input mode on the info line. */\n        if (F_ISSET(sp, SC_TINPUT_INFO))\n                return (0);\n\n        /*\n         * Try and avoid getting the last line in the file, by getting the\n         * line after the last line in the screen -- if it exists, we know\n         * we have to to number all the lines in the screen.  Get the one\n         * after the last instead of the last, so that the info line doesn't\n         * fool us.  (The problem is that file_lline will lie, and tell us\n         * that the info line is the last line in the file.) If that test\n         * fails, we have to check each line for existence.\n         */\n        exist = db_exist(sp, TMAP->lno + 1);\n\n        (void)gp->scr_cursor(sp, &oldy, &oldx);\n        for (smp = HMAP; smp <= TMAP; ++smp) {\n                /* Numbers are only displayed for the first screen line. */\n                if (O_ISSET(sp, O_LEFTRIGHT)) {\n                        if (smp->coff != 0)\n                                continue;\n                } else\n                        if (smp->soff != 1)\n                                continue;\n\n                /*\n                 * The first line of an empty file gets numbered, otherwise\n                 * number any existing line.\n                 */\n                if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno))\n                        break;\n\n                (void)gp->scr_move(sp, smp - HMAP, 0);\n                len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, (unsigned long)smp->lno);\n                if (len >= sizeof(nbuf))\n                        len = sizeof(nbuf) - 1;\n                (void)gp->scr_addstr(sp, nbuf, len);\n        }\n        (void)gp->scr_move(sp, oldy, oldx);\n        return (0);\n}\n"
  },
  {
    "path": "vi/vs_msg.c",
    "content": "/*      $OpenBSD: vs_msg.c,v 1.20 2017/04/18 01:45:35 deraadt Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <time.h>\n#include <bsd_unistd.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\ntypedef enum {\n        SCROLL_W,                       /* User wait. */\n        SCROLL_W_EX,                    /* User wait, or enter : to continue. */\n        SCROLL_W_QUIT                   /* User wait, or enter q to quit. */\n                                        /*\n                                         * SCROLL_W_QUIT has another semantic\n                                         * -- only wait if the screen is full\n                                         */\n} sw_t;\n\nstatic void     vs_divider(SCR *);\nstatic void     vs_msgsave(SCR *, mtype_t, char *, size_t);\nstatic void     vs_output(SCR *, mtype_t, const char *, int);\nstatic void     vs_scroll(SCR *, int *, sw_t);\nstatic void     vs_wait(SCR *, int *, sw_t);\n\n/*\n * vs_busy --\n *      Display, update or clear a busy message.\n *\n * This routine is the default editor interface for vi busy messages.  It\n * implements a standard strategy of stealing lines from the bottom of the\n * vi text screen.  Screens using an alternate method of displaying busy\n * messages, e.g. X11 clock icons, should set their scr_busy function to the\n * correct function before calling the main editor routine.\n *\n * PUBLIC: void vs_busy(SCR *, const char *, busy_t);\n */\nvoid\nvs_busy(SCR *sp, const char *msg, busy_t btype)\n{\n        GS *gp;\n        VI_PRIVATE *vip;\n        static const char flagc[] = \"|/-\\\\\";\n        struct timespec ts, ts_diff;\n        size_t notused;\n\n        /* Ex doesn't display busy messages. */\n        if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE))\n                return;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n\n        /*\n         * Most of this routine is to deal with the screen sharing real estate\n         * between the normal edit messages and the busy messages.  Logically,\n         * all that's needed is something that puts up a message, periodically\n         * updates it, and then goes away.\n         */\n        switch (btype) {\n        case BUSY_ON:\n                ++vip->busy_ref;\n                if (vip->totalcount != 0 || vip->busy_ref != 1)\n                        break;\n\n                /* Initialize state for updates. */\n                vip->busy_ch = 0;\n                (void)clock_gettime(CLOCK_MONOTONIC, &vip->busy_ts);\n\n                /* Save the current cursor. */\n                (void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx);\n\n                /* Display the busy message. */\n                (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                (void)gp->scr_addstr(sp, msg, strlen(msg));\n                (void)gp->scr_cursor(sp, &notused, &vip->busy_fx);\n                (void)gp->scr_clrtoeol(sp);\n                (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);\n                break;\n        case BUSY_OFF:\n                if (vip->busy_ref == 0)\n                        break;\n                --vip->busy_ref;\n\n                /*\n                 * If the line isn't in use for another purpose, clear it.\n                 * Always return to the original position.\n                 */\n                if (vip->totalcount == 0 && vip->busy_ref == 0) {\n                        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                        (void)gp->scr_clrtoeol(sp);\n                }\n                (void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx);\n                break;\n        case BUSY_UPDATE:\n                if (vip->totalcount != 0 || vip->busy_ref == 0)\n                        break;\n\n                /* Update no more than every 1/8 of a second. */\n                (void)clock_gettime(CLOCK_MONOTONIC, &ts);\n                ts_diff = ts;\n                ts_diff.tv_sec -= vip->busy_ts.tv_sec;\n                ts_diff.tv_nsec -= vip->busy_ts.tv_nsec;\n                if (ts_diff.tv_nsec < 0) {\n                        ts_diff.tv_sec--;\n                        ts_diff.tv_nsec += 1000000000;\n                }\n                if ((ts_diff.tv_sec == 0 && ts_diff.tv_nsec < 125000000) ||\n                    ts_diff.tv_sec < 0)\n                        return;\n                vip->busy_ts = ts;\n\n                /* Display the update. */\n                if (vip->busy_ch == sizeof(flagc) - 1)\n                        vip->busy_ch = 0;\n                (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);\n                (void)gp->scr_addstr(sp, flagc + vip->busy_ch++, 1);\n                (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);\n                break;\n        }\n        (void)gp->scr_refresh(sp, 0);\n}\n\n/*\n * vs_home --\n *      Home the cursor to the bottom row, left-most column.\n *\n * PUBLIC: void vs_home(SCR *);\n */\nvoid\nvs_home(SCR *sp)\n{\n        (void)sp->gp->scr_move(sp, LASTLINE(sp), 0);\n        (void)sp->gp->scr_refresh(sp, 0);\n}\n\n/*\n * vs_update --\n *      Update a command.\n *\n * PUBLIC: void vs_update(SCR *, const char *, const char *);\n */\nvoid\nvs_update(SCR *sp, const char *m1, const char *m2)\n{\n        GS *gp;\n        size_t len, mlen, oldx, oldy;\n\n        gp = sp->gp;\n\n        /*\n         * This routine displays a message on the bottom line of the screen,\n         * without updating any of the command structures that would keep it\n         * there for any period of time, i.e. it is overwritten immediately.\n         *\n         * It's used by the ex read and ! commands when the user's command is\n         * expanded, and by the ex substitution confirmation prompt.\n         */\n        if (F_ISSET(sp, SC_SCR_EXWROTE)) {\n                (void)ex_printf(sp,\n                    \"%s\\n\", m1 == NULL? \"\" : m1, m2 == NULL ? \"\" : m2);\n                (void)ex_fflush(sp);\n        }\n\n        /*\n         * Save the cursor position, the substitute-with-confirmation code\n         * will have already set it correctly.\n         */\n        (void)gp->scr_cursor(sp, &oldy, &oldx);\n\n        /* Clear the bottom line. */\n        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n        (void)gp->scr_clrtoeol(sp);\n\n        /*\n         * XXX\n         * Don't let long file names screw up the screen.\n         */\n        if (m1 != NULL) {\n                mlen = len = strlen(m1);\n                if (len > sp->cols - 2)\n                        mlen = len = sp->cols - 2;\n                (void)gp->scr_addstr(sp, m1, mlen);\n        } else\n                len = 0;\n        if (m2 != NULL) {\n                mlen = strlen(m2);\n                if (len + mlen > sp->cols - 2)\n                        mlen = (sp->cols - 2) - len;\n                (void)gp->scr_addstr(sp, m2, mlen);\n        }\n\n        (void)gp->scr_move(sp, oldy, oldx);\n        (void)gp->scr_refresh(sp, 0);\n}\n\n/*\n * vs_msg --\n *      Display ex output or error messages for the screen.\n *\n * This routine is the default editor interface for all ex output, and all ex\n * and vi error/informational messages.  It implements the standard strategy\n * of stealing lines from the bottom of the vi text screen.  Screens using an\n * alternate method of displaying messages, e.g. dialog boxes, should set their\n * scr_msg function to the correct function before calling the editor.\n *\n * PUBLIC: void vs_msg(SCR *, mtype_t, char *, size_t);\n */\nvoid\nvs_msg(SCR *sp, mtype_t mtype, char *line, size_t len)\n{\n        GS *gp;\n        VI_PRIVATE *vip;\n        size_t maxcols, oldx, oldy, padding;\n        const char *e, *s, *t;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n\n        /*\n         * Ring the bell if it's scheduled.\n         *\n         * XXX\n         * Shouldn't we save this, too?\n         */\n        if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED)) {\n                if (F_ISSET(sp, SC_SCR_VI)) {\n                        F_CLR(gp, G_BELLSCHED);\n                        (void)gp->scr_bell(sp);\n                } else\n                        F_SET(gp, G_BELLSCHED);\n        }\n\n        /*\n         * If vi is using the error line for text input, there's no screen\n         * real-estate for the error message.  Nothing to do without some\n         * information as to how important the error message is.\n         */\n        if (F_ISSET(sp, SC_TINPUT_INFO))\n                return;\n\n        /*\n         * Ex or ex controlled screen output.\n         *\n         * If output happens during startup, e.g., a .exrc file, we may be\n         * in ex mode but haven't initialized the screen.  Initialize here,\n         * and in this case, stay in ex mode.\n         *\n         * If the SC_SCR_EXWROTE bit is set, then we're switching back and\n         * forth between ex and vi, but the screen is trashed and we have\n         * to respect that.  Switch to ex mode long enough to put out the\n         * message.\n         *\n         * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to\n         * the screen, so previous opinions are ignored.\n         */\n        if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {\n                if (!F_ISSET(sp, SC_SCR_EX)) {\n                        if (F_ISSET(sp, SC_SCR_EXWROTE)) {\n                                if (sp->gp->scr_screen(sp, SC_EX))\n                                        return;\n                        } else\n                                if (ex_init(sp))\n                                        return;\n                }\n\n                if (mtype == M_ERR)\n                        (void)gp->scr_attr(sp, SA_INVERSE, 1);\n                (void)printf(\"%.*s\", (int)len, line);\n                if (mtype == M_ERR)\n                        (void)gp->scr_attr(sp, SA_INVERSE, 0);\n                (void)fflush(stdout);\n\n                F_CLR(sp, SC_EX_WAIT_NO);\n\n                if (!F_ISSET(sp, SC_SCR_EX))\n                        (void)sp->gp->scr_screen(sp, SC_VI);\n                return;\n        }\n\n        /* If the vi screen isn't ready, save the message. */\n        if (!F_ISSET(sp, SC_SCR_VI)) {\n                (void)vs_msgsave(sp, mtype, line, len);\n                return;\n        }\n\n        /* Save the cursor position. */\n        (void)gp->scr_cursor(sp, &oldy, &oldx);\n\n        /* If it's an ex output message, just write it out. */\n        if (mtype == M_NONE) {\n                vs_output(sp, mtype, line, len);\n                goto ret;\n        }\n\n        /*\n         * If it's a vi message, strip the trailing <newline> so we can\n         * try and paste messages together.\n         */\n        if (line[len - 1] == '\\n')\n                --len;\n\n        /*\n         * If a message won't fit on a single line, try to split on a <blank>.\n         * If a subsequent message fits on the same line, write a separator\n         * and output it.  Otherwise, put out a newline.\n         *\n         * Need up to two padding characters normally; a semi-colon and a\n         * separating space.  If only a single line on the screen, add some\n         * more for the trailing continuation message.\n         *\n         * XXX\n         * Assume that periods and semi-colons take up a single column on the\n         * screen.\n         *\n         * XXX\n         * There are almost certainly pathological cases that will break this\n         * code.\n         */\n        if (IS_ONELINE(sp))\n                (void)msg_cmsg(sp, CMSG_CONT_S, &padding);\n        else\n                padding = 0;\n        padding += 2;\n\n        maxcols = sp->cols - 1;\n        if (vip->lcontinue != 0) {\n                if (len + vip->lcontinue + padding > maxcols)\n                        vs_output(sp, vip->mtype, \".\\n\", 2);\n                else  {\n                        vs_output(sp, vip->mtype, \";\", 1);\n                        vs_output(sp, M_NONE, \" \", 1);\n                }\n        }\n        vip->mtype = mtype;\n        for (s = line;; s = t) {\n                for (; len > 0 && isblank(*s); --len, ++s);\n                if (len == 0)\n                        break;\n                if (len + vip->lcontinue > maxcols) {\n                        for (e = s + (maxcols - vip->lcontinue);\n                            e > s && !isblank(*e); --e);\n                        if (e == s)\n                                 e = t = s + (maxcols - vip->lcontinue);\n                        else\n                                for (t = e; isblank(e[-1]); --e);\n                } else\n                        e = t = s + len;\n\n                /*\n                 * If the message ends in a period, discard it, we want to\n                 * gang messages where possible.\n                 */\n                len -= t - s;\n                if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.')\n                        --e;\n                vs_output(sp, mtype, s, e - s);\n\n                if (len != 0)\n                        vs_output(sp, M_NONE, \"\\n\", 1);\n\n                if (INTERRUPTED(sp))\n                        break;\n        }\n\nret:    (void)gp->scr_move(sp, oldy, oldx);\n        (void)gp->scr_refresh(sp, 0);\n}\n\n/*\n * vs_output --\n *      Output the text to the screen.\n */\nstatic void\nvs_output(SCR *sp, mtype_t mtype, const char *line, int llen)\n{\n        CHAR_T *kp;\n        GS *gp;\n        VI_PRIVATE *vip;\n        size_t chlen, notused;\n        int ch, len, tlen;\n        const char *p, *t;\n        char *cbp, *ecbp, cbuf[128];\n\n        gp = sp->gp;\n        vip = VIP(sp);\n        for (p = line; llen > 0;) {\n                /* Get the next physical line. */\n                if ((p = memchr(line, '\\n', llen)) == NULL)\n                        len = llen;\n                else\n                        len = p - line;\n\n                /*\n                 * The max is sp->cols characters, and we may have already\n                 * written part of the line.\n                 */\n                if (len + vip->lcontinue > sp->cols)\n                        len = sp->cols - vip->lcontinue;\n\n                /*\n                 * If the first line output, do nothing.  If the second line\n                 * output, draw the divider line.  If drew a full screen, we\n                 * remove the divider line.  If it's a continuation line, move\n                 * to the continuation point, else, move the screen up.\n                 */\n                if (vip->lcontinue == 0) {\n                        if (!IS_ONELINE(sp)) {\n                                if (vip->totalcount == 1) {\n                                        (void)gp->scr_move(sp,\n                                            LASTLINE(sp) - 1, 0);\n                                        (void)gp->scr_clrtoeol(sp);\n                                        (void)vs_divider(sp);\n                                        F_SET(vip, VIP_DIVIDER);\n                                        ++vip->totalcount;\n                                        ++vip->linecount;\n                                }\n                                if (vip->totalcount == sp->t_maxrows &&\n                                    F_ISSET(vip, VIP_DIVIDER)) {\n                                        --vip->totalcount;\n                                        --vip->linecount;\n                                        F_CLR(vip, VIP_DIVIDER);\n                                }\n                        }\n                        if (vip->totalcount != 0)\n                                vs_scroll(sp, NULL, SCROLL_W_QUIT);\n\n                        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                        ++vip->totalcount;\n                        ++vip->linecount;\n\n                        if (INTERRUPTED(sp))\n                                break;\n                } else\n                        (void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue);\n\n                /* Error messages are in inverse video. */\n                if (mtype == M_ERR)\n                        (void)gp->scr_attr(sp, SA_INVERSE, 1);\n\n                /* Display the line, doing character translation. */\n#define FLUSH {                                                         \\\n        *cbp = '\\0';                                                    \\\n        (void)gp->scr_addstr(sp, cbuf, cbp - cbuf);                     \\\n        cbp = cbuf;                                                     \\\n}\n                ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;\n                for (t = line, tlen = len; tlen--; ++t) {\n                        ch = *t;\n                        /*\n                         * Replace tabs with spaces, there are places in\n                         * ex that do column calculations without looking\n                         * at <tabs> -- and all routines that care about\n                         * <tabs> do their own expansions.  This catches\n                         * <tabs> in things like tag search strings.\n                         */\n                        if (ch == '\\t')\n                                ch = ' ';\n                        chlen = KEY_LEN(sp, ch);\n                        if (cbp + chlen >= ecbp)\n                                FLUSH;\n                        for (kp = KEY_NAME(sp, ch); chlen--;)\n                                *cbp++ = *kp++;\n                }\n                if (cbp > cbuf)\n                        FLUSH;\n                if (mtype == M_ERR)\n                        (void)gp->scr_attr(sp, SA_INVERSE, 0);\n\n                /* Clear the rest of the line. */\n                (void)gp->scr_clrtoeol(sp);\n\n                /* If we loop, it's a new line. */\n                vip->lcontinue = 0;\n\n                /* Reset for the next line. */\n                line += len;\n                llen -= len;\n                if (p != NULL) {\n                        ++line;\n                        --llen;\n                }\n        }\n\n        /* Set up next continuation line. */\n        if (p == NULL)\n                gp->scr_cursor(sp, &notused, &vip->lcontinue);\n}\n\n/*\n * vs_ex_resolve --\n *      Deal with ex message output.\n *\n * This routine is called when exiting a colon command to resolve any ex\n * output that may have occurred.\n *\n * PUBLIC: int vs_ex_resolve(SCR *, int *);\n */\nint\nvs_ex_resolve(SCR *sp, int *continuep)\n{\n        EVENT ev;\n        GS *gp;\n        VI_PRIVATE *vip;\n        sw_t wtype;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n        *continuep = 0;\n\n        /* If we ran any ex command, we can't trust the cursor position. */\n        F_SET(vip, VIP_CUR_INVALID);\n\n        /* Terminate any partially written message. */\n        if (vip->lcontinue != 0) {\n                vs_output(sp, vip->mtype, \".\", 1);\n                vip->lcontinue = 0;\n\n                vip->mtype = M_NONE;\n        }\n\n        /*\n         * If we switched out of the vi screen into ex, switch back while we\n         * figure out what to do with the screen and potentially get another\n         * command to execute.\n         *\n         * If we didn't switch into ex, we're not required to wait, and less\n         * than 2 lines of output, we can continue without waiting for the\n         * wait.\n         *\n         * Note, all other code paths require waiting, so we leave the report\n         * of modified lines until later, so that we won't wait for no other\n         * reason than a threshold number of lines were modified.  This means\n         * we display cumulative line modification reports for groups of ex\n         * commands.  That seems right to me (well, at least not wrong).\n         */\n        if (F_ISSET(sp, SC_SCR_EXWROTE)) {\n                if (sp->gp->scr_screen(sp, SC_VI))\n                        return (1);\n        } else\n                if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) {\n                        F_CLR(sp, SC_EX_WAIT_NO);\n                        return (0);\n                }\n\n        /* Clear the required wait flag, it's no longer needed. */\n        F_CLR(sp, SC_EX_WAIT_YES);\n\n        /*\n         * Wait, unless explicitly told not to wait or the user interrupted\n         * the command.  If the user is leaving the screen, for any reason,\n         * they can't continue with further ex commands.\n         */\n        if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) {\n                wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE |\n                    SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX;\n                if (F_ISSET(sp, SC_SCR_EXWROTE))\n                        vs_wait(sp, continuep, wtype);\n                else\n                        vs_scroll(sp, continuep, wtype);\n                if (*continuep)\n                        return (0);\n        }\n\n        /* If ex wrote on the screen, refresh the screen image. */\n        if (F_ISSET(sp, SC_SCR_EXWROTE))\n                F_SET(vip, VIP_N_EX_PAINT);\n\n        /*\n         * If we're not the bottom of the split screen stack, the screen\n         * image itself is wrong, so redraw everything.\n         */\n        if (TAILQ_NEXT(sp, q))\n                F_SET(sp, SC_SCR_REDRAW);\n\n        /* If ex changed the underlying file, the map itself is wrong. */\n        if (F_ISSET(vip, VIP_N_EX_REDRAW))\n                F_SET(sp, SC_SCR_REFORMAT);\n\n        /* Ex may have switched out of the alternate screen, return. */\n        (void)gp->scr_attr(sp, SA_ALTERNATE, 1);\n\n        /*\n         * Whew.  We're finally back home, after what feels like years.\n         * Kiss the ground.\n         */\n        F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO);\n\n        /*\n         * We may need to repaint some of the screen, e.g.:\n         *\n         *      :set\n         *      :!ls\n         *\n         * gives us a combination of some lines that are \"wrong\", and a need\n         * for a full refresh.\n         */\n        if (vip->totalcount > 1) {\n                /* Set up the redraw of the overwritten lines. */\n                ev.e_event = E_REPAINT;\n                ev.e_flno = vip->totalcount >=\n                    sp->rows ? 1 : sp->rows - vip->totalcount;\n                ev.e_tlno = sp->rows;\n\n                /* Reset the count of overwriting lines. */\n                vip->linecount = vip->lcontinue = vip->totalcount = 0;\n\n                /* Redraw. */\n                (void)vs_repaint(sp, &ev);\n        } else\n                /* Reset the count of overwriting lines. */\n                vip->linecount = vip->lcontinue = vip->totalcount = 0;\n\n        return (0);\n}\n\n/*\n * vs_resolve --\n *      Deal with message output.\n *\n * PUBLIC: int vs_resolve(SCR *, SCR *, int);\n */\nint\nvs_resolve(SCR *sp, SCR *csp, int forcewait)\n{\n        EVENT ev;\n        GS *gp;\n        MSGS *mp;\n        VI_PRIVATE *vip;\n        size_t oldy, oldx;\n        int redraw;\n\n        /*\n         * Vs_resolve is called from the main vi loop and the refresh function\n         * to periodically ensure that the user has seen any messages that have\n         * been displayed and that any status lines are correct.  The sp screen\n         * is the screen we're checking, usually the current screen.  When it's\n         * not, csp is the current screen, used for final cursor positioning.\n         */\n        gp = sp->gp;\n        vip = VIP(sp);\n        if (csp == NULL)\n                csp = sp;\n\n        /* Save the cursor position. */\n        (void)gp->scr_cursor(csp, &oldy, &oldx);\n\n        /* Ring the bell if it's scheduled. */\n        if (F_ISSET(gp, G_BELLSCHED)) {\n                F_CLR(gp, G_BELLSCHED);\n                (void)gp->scr_bell(sp);\n        }\n\n        /* Display new file status line. */\n        if (F_ISSET(sp, SC_STATUS)) {\n                F_CLR(sp, SC_STATUS);\n                msgq_status(sp, sp->lno, MSTAT_TRUNCATE);\n        }\n\n        /* Report on line modifications. */\n        mod_rpt(sp);\n\n        /*\n         * Flush any saved messages.  If the screen isn't ready, refresh\n         * it.  (A side-effect of screen refresh is that we can display\n         * messages.)  Once this is done, don't trust the cursor.  That\n         * extra refresh screwed the pooch.\n         */\n        if (LIST_FIRST(&gp->msgq) != NULL) {\n                if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1))\n                        return (1);\n                while ((mp = LIST_FIRST(&gp->msgq)) != NULL) {\n                        gp->scr_msg(sp, mp->mtype, mp->buf, mp->len);\n                        LIST_REMOVE(mp, q);\n                        free(mp->buf);\n                        free(mp);\n                }\n                F_SET(vip, VIP_CUR_INVALID);\n        }\n\n        switch (vip->totalcount) {\n        case 0:\n                redraw = 0;\n                break;\n        case 1:\n                /*\n                 * If we're switching screens, we have to wait for messages,\n                 * regardless.  If we don't wait, skip updating the modeline.\n                 */\n                if (forcewait)\n                        vs_scroll(sp, NULL, SCROLL_W);\n                else\n                        F_SET(vip, VIP_S_MODELINE);\n\n                redraw = 0;\n                break;\n        default:\n                /*\n                 * If >1 message line in use, prompt the user to continue and\n                 * repaint overwritten lines.\n                 */\n                vs_scroll(sp, NULL, SCROLL_W);\n\n                ev.e_event = E_REPAINT;\n                ev.e_flno = vip->totalcount >=\n                    sp->rows ? 1 : sp->rows - vip->totalcount;\n                ev.e_tlno = sp->rows;\n\n                redraw = 1;\n                break;\n        }\n\n        /* Reset the count of overwriting lines. */\n        vip->linecount = vip->lcontinue = vip->totalcount = 0;\n\n        /* Redraw. */\n        if (redraw)\n                (void)vs_repaint(sp, &ev);\n\n        /* Restore the cursor position. */\n        (void)gp->scr_move(csp, oldy, oldx);\n\n        return (0);\n}\n\n/*\n * vs_scroll --\n *      Scroll the screen for output.\n */\nstatic void\nvs_scroll(SCR *sp, int *continuep, sw_t wtype)\n{\n        GS *gp;\n        VI_PRIVATE *vip;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n        if (!IS_ONELINE(sp)) {\n                /*\n                 * Scroll the screen.  Instead of scrolling the entire screen,\n                 * delete the line above the first line output so preserve the\n                 * maximum amount of the screen.\n                 */\n                (void)gp->scr_move(sp, vip->totalcount <\n                    sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0);\n                (void)gp->scr_deleteln(sp);\n\n                /* If there are screens below us, push them back into place. */\n                if (TAILQ_NEXT(sp, q)) {\n                        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                        (void)gp->scr_insertln(sp);\n                }\n        }\n        if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows)\n                return;\n        vs_wait(sp, continuep, wtype);\n}\n\n/*\n * vs_wait --\n *      Prompt the user to continue.\n */\nstatic void\nvs_wait(SCR *sp, int *continuep, sw_t wtype)\n{\n        EVENT ev;\n        VI_PRIVATE *vip;\n        const char *p;\n        GS *gp;\n        size_t len;\n\n        gp = sp->gp;\n        vip = VIP(sp);\n\n        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n        if (IS_ONELINE(sp))\n                p = msg_cmsg(sp, CMSG_CONT_S, &len);\n        else\n                switch (wtype) {\n                case SCROLL_W_QUIT:\n                        p = msg_cmsg(sp, CMSG_CONT_Q, &len);\n                        break;\n                case SCROLL_W_EX:\n                        p = msg_cmsg(sp, CMSG_CONT_EX, &len);\n                        break;\n                case SCROLL_W:\n                        p = msg_cmsg(sp, CMSG_CONT, &len);\n                        break;\n                default:\n                        abort();\n                        /* NOTREACHED */\n                }\n        (void)gp->scr_addstr(sp, p, len);\n\n        ++vip->totalcount;\n        vip->linecount = 0;\n\n        (void)gp->scr_clrtoeol(sp);\n        (void)gp->scr_refresh(sp, 0);\n\n        /* Get a single character from the terminal. */\n        if (continuep != NULL)\n                *continuep = 0;\n        for (;;) {\n                if (v_event_get(sp, &ev, 0, 0))\n                        return;\n                if (ev.e_event == E_CHARACTER)\n                        break;\n                if (ev.e_event == E_INTERRUPT) {\n                        ev.e_c = CH_QUIT;\n                        F_SET(gp, G_INTERRUPTED);\n                        break;\n                }\n                (void)gp->scr_bell(sp);\n        }\n        switch (wtype) {\n        case SCROLL_W_QUIT:\n                if (ev.e_c == CH_QUIT)\n                        F_SET(gp, G_INTERRUPTED);\n                break;\n        case SCROLL_W_EX:\n                if (ev.e_c == ':' && continuep != NULL)\n                        *continuep = 1;\n                break;\n        case SCROLL_W:\n                break;\n        }\n}\n\n/*\n * vs_divider --\n *      Draw a dividing line between the screen and the output.\n */\nstatic void\nvs_divider(SCR *sp)\n{\n        GS *gp;\n        size_t len;\n\n#define DIVIDESTR       \"+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\"\n        len =\n            sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1;\n        gp = sp->gp;\n        (void)gp->scr_attr(sp, SA_INVERSE, 1);\n        (void)gp->scr_addstr(sp, DIVIDESTR, len);\n        (void)gp->scr_attr(sp, SA_INVERSE, 0);\n}\n\n/*\n * vs_msgsave --\n *      Save a message for later display.\n */\nstatic void\nvs_msgsave(SCR *sp, mtype_t mt, char *p, size_t len)\n{\n        GS *gp;\n        MSGS *mp_c, *mp_n;\n\n        /*\n         * We have to handle messages before we have any place to put them.\n         * If there's no screen support yet, allocate a msg structure, copy\n         * in the message, and queue it on the global structure.  If we can't\n         * allocate memory here, we're genuinely screwed, dump the message\n         * to stderr in the (probably) vain hope that someone will see it.\n         */\n        CALLOC_GOTO(sp, mp_n, 1, sizeof(MSGS));\n        MALLOC_GOTO(sp, mp_n->buf, len);\n\n        memmove(mp_n->buf, p, len);\n        mp_n->len = len;\n        mp_n->mtype = mt;\n\n        gp = sp->gp;\n        if ((mp_c = LIST_FIRST(&gp->msgq)) == NULL) {\n                LIST_INSERT_HEAD(&gp->msgq, mp_n, q);\n        } else {\n                for (; LIST_NEXT(mp_c, q) != NULL; mp_c = LIST_NEXT(mp_c, q));\n                LIST_INSERT_AFTER(mp_c, mp_n, q);\n        }\n        return;\n\nalloc_err:\n        free(mp_n);\n        (void)fprintf(stderr, \"%.*s\\n\", (int)len, p);\n}\n"
  },
  {
    "path": "vi/vs_refresh.c",
    "content": "/*      $OpenBSD: vs_refresh.c,v 1.25 2024/04/25 09:58:17 job Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1992, 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1992, 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <ctype.h>\n#include <libgen.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n#define UPDATE_CURSOR   0x01                    /* Update the cursor. */\n#define UPDATE_SCREEN   0x02                    /* Flush to screen. */\n\nstatic void     vs_modeline(SCR *);\nstatic int      vs_paint(SCR *, unsigned int);\n\n/*\n * v_repaint --\n *      Repaint selected lines from the screen.\n *\n * PUBLIC: int vs_repaint(SCR *, EVENT *);\n */\nint\nvs_repaint(SCR *sp, EVENT *evp)\n{\n        SMAP *smp;\n\n        for (; evp->e_flno <= evp->e_tlno; ++evp->e_flno) {\n                smp = HMAP + evp->e_flno - 1;\n                SMAP_FLUSH(smp);\n                if (vs_line(sp, smp, NULL, NULL))\n                        return (1);\n        }\n        return (0);\n}\n\n/*\n * vs_refresh --\n *      Refresh all screens.\n *\n * PUBLIC: int vs_refresh(SCR *, int);\n */\nint\nvs_refresh(SCR *sp, int forcepaint)\n{\n        GS *gp;\n        SCR *tsp;\n        int need_refresh;\n        unsigned int priv_paint, pub_paint;\n\n        gp = sp->gp;\n\n        /*\n         * 1: Refresh the screen.\n         *\n         * If SC_SCR_REDRAW is set in the current screen, repaint everything\n         * that we can find, including status lines.\n         */\n        if (F_ISSET(sp, SC_SCR_REDRAW))\n                TAILQ_FOREACH(tsp, &gp->dq, q)\n                        if (tsp != sp)\n                                F_SET(tsp, SC_SCR_REDRAW | SC_STATUS);\n\n        /*\n         * 2: Related or dirtied screens, or screens with messages.\n         *\n         * If related screens share a view into a file, they may have been\n         * modified as well.  Refresh any screens that aren't exiting that\n         * have paint or dirty bits set.  Always update their screens, we\n         * are not likely to get another chance.  Finally, if we refresh any\n         * screens other than the current one, the cursor will be trashed.\n         */\n        pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW;\n        priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH;\n        if (O_ISSET(sp, O_NUMBER))\n                priv_paint |= VIP_N_RENUMBER;\n        TAILQ_FOREACH(tsp, &gp->dq, q)\n                if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) &&\n                    (F_ISSET(tsp, pub_paint) ||\n                    F_ISSET(VIP(tsp), priv_paint))) {\n                        (void)vs_paint(tsp,\n                            (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ?\n                            UPDATE_CURSOR : 0) | UPDATE_SCREEN);\n                        F_SET(VIP(sp), VIP_CUR_INVALID);\n                }\n\n        /*\n         * 3: Refresh the current screen.\n         *\n         * Always refresh the current screen, it may be a cursor movement.\n         * Also, always do it last -- that way, SC_SCR_REDRAW can be set\n         * in the current screen only, and the screen won't flash.\n         */\n        if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint &&\n            F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN)))\n                return (1);\n\n        /*\n         * 4: Paint any missing status lines.\n         *\n         * XXX\n         * This is fairly evil.  Status lines are written using the vi message\n         * mechanism, since we have no idea how long they are.  Since we may be\n         * painting screens other than the current one, we don't want to make\n         * the user wait.  We depend heavily on there not being any other lines\n         * currently waiting to be displayed and the message truncation code in\n         * the msgq_status routine working.\n         *\n         * And, finally, if we updated any status lines, make sure the cursor\n         * gets back to where it belongs.\n         */\n        need_refresh = 0;\n        TAILQ_FOREACH(tsp, &gp->dq, q)\n                if (F_ISSET(tsp, SC_STATUS)) {\n                        need_refresh = 1;\n                        vs_resolve(tsp, sp, 0);\n                }\n        if (need_refresh)\n                (void)gp->scr_refresh(sp, 0);\n\n        /*\n         * A side-effect of refreshing the screen is that it's now ready\n         * for everything else, i.e. messages.\n         */\n        F_SET(sp, SC_SCR_VI);\n        return (0);\n}\n\n/*\n * vs_paint --\n *      This is the guts of the vi curses screen code.  The idea is that\n *      the SCR structure passed in contains the new coordinates of the\n *      screen.  What makes this hard is that we don't know how big\n *      characters are, doing input can put the cursor in illegal places,\n *      and we're frantically trying to avoid repainting unless it's\n *      absolutely necessary.  If you change this code, you'd better know\n *      what you're doing.  It's subtle and quick to anger.\n */\nstatic int\nvs_paint(SCR *sp, unsigned int flags)\n{\n        GS *gp;\n        SMAP *smp, tmp;\n        VI_PRIVATE *vip;\n        recno_t lastline, lcnt;\n        size_t cwtotal, cnt, len, notused, off, y;\n        int ch = 0, didpaint, isempty, leftright_warp;\n        char *p;\n\n#define LNO     sp->lno                 /* Current file line. */\n#define OLNO    vip->olno               /* Remembered file line. */\n#define CNO     sp->cno                 /* Current file column. */\n#define OCNO    vip->ocno               /* Remembered file column. */\n#define SCNO    vip->sc_col             /* Current screen column. */\n\n        gp = sp->gp;\n        vip = VIP(sp);\n        didpaint = leftright_warp = 0;\n\n        /*\n         * 5: Reformat the lines.\n         *\n         * If the lines themselves have changed (:set list, for example),\n         * fill in the map from scratch.  Adjust the screen that's being\n         * displayed if the leftright flag is set.\n         */\n        if (F_ISSET(sp, SC_SCR_REFORMAT)) {\n                /* Invalidate the line size cache. */\n                VI_SCR_CFLUSH(vip);\n\n                /* Toss vs_line() cached information. */\n                if (F_ISSET(sp, SC_SCR_TOP)) {\n                        if (vs_sm_fill(sp, LNO, P_TOP))\n                                return (1);\n                }\n                else if (F_ISSET(sp, SC_SCR_CENTER)) {\n                        if (vs_sm_fill(sp, LNO, P_MIDDLE))\n                                return (1);\n                } else {\n                        if (LNO == HMAP->lno || LNO == TMAP->lno) {\n                                cnt = vs_screens(sp, LNO, &CNO);\n                                if (LNO == HMAP->lno && cnt < HMAP->soff)\n                                        HMAP->soff = cnt;\n                                if (LNO == TMAP->lno && cnt > TMAP->soff)\n                                        TMAP->soff = cnt;\n                        }\n\n                        if (vs_sm_fill(sp, OOBLNO, P_TOP))\n                                return (1);\n                }\n                F_SET(sp, SC_SCR_REDRAW);\n        }\n\n        /*\n         * 6: Line movement.\n         *\n         * Line changes can cause the top line to change as well.  As\n         * before, if the movement is large, the screen is repainted.\n         *\n         * 6a: Small screens.\n         *\n         * Users can use the window, w300, w1200 and w9600 options to make\n         * the screen artificially small.  The behavior of these options\n         * in the historic vi wasn't all that consistent, and, in fact, it\n         * was never documented how various screen movements affected the\n         * screen size.  Generally, one of three things would happen:\n         *      1: The screen would expand in size, showing the line\n         *      2: The screen would scroll, showing the line\n         *      3: The screen would compress to its smallest size and\n         *              repaint.\n         * In general, scrolling didn't cause compression (200^D was handled\n         * the same as ^D), movement to a specific line would (:N where N\n         * was 1 line below the screen caused a screen compress), and cursor\n         * movement would scroll if it was 11 lines or less, and compress if\n         * it was more than 11 lines.  (And, no, I have no idea where the 11\n         * comes from.)\n         *\n         * What we do is try and figure out if the line is less than half of\n         * a full screen away.  If it is, we expand the screen if there's\n         * room, and then scroll as necessary.  The alternative is to compress\n         * and repaint.\n         *\n         * !!!\n         * This code is a special case from beginning to end.  Unfortunately,\n         * home modems are still slow enough that it's worth having.\n         *\n         * XXX\n         * If the line a really long one, i.e. part of the line is on the\n         * screen but the column offset is not, we'll end up in the adjust\n         * code, when we should probably have compressed the screen.\n         */\n        if (IS_SMALL(sp)) {\n                if (LNO < HMAP->lno) {\n                        lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows);\n                        if (lcnt <= HALFSCREEN(sp))\n                                for (; lcnt && sp->t_rows != sp->t_maxrows;\n                                     --lcnt, ++sp->t_rows) {\n                                        ++TMAP;\n                                        if (vs_sm_1down(sp))\n                                                return (1);\n                                }\n                        else\n                                goto small_fill;\n                } else if (LNO > TMAP->lno) {\n                        lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows);\n                        if (lcnt <= HALFSCREEN(sp))\n                                for (; lcnt && sp->t_rows != sp->t_maxrows;\n                                     --lcnt, ++sp->t_rows) {\n                                        if (vs_sm_next(sp, TMAP, TMAP + 1))\n                                                return (1);\n                                        ++TMAP;\n                                        if (vs_line(sp, TMAP, NULL, NULL))\n                                                return (1);\n                                }\n                        else {\nsmall_fill:                     (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                                (void)gp->scr_clrtoeol(sp);\n                                for (; sp->t_rows > sp->t_minrows;\n                                    --sp->t_rows, --TMAP) {\n                                        (void)gp->scr_move(sp, TMAP - HMAP, 0);\n                                        (void)gp->scr_clrtoeol(sp);\n                                }\n                                if (vs_sm_fill(sp, LNO, P_FILL))\n                                        return (1);\n                                F_SET(sp, SC_SCR_REDRAW);\n                                goto adjust;\n                        }\n                }\n        }\n\n        /*\n         * 6b: Line down, or current screen.\n         */\n        if (LNO >= HMAP->lno) {\n                /* Current screen. */\n                if (LNO <= TMAP->lno)\n                        goto adjust;\n                if (F_ISSET(sp, SC_SCR_TOP))\n                        goto top;\n                if (F_ISSET(sp, SC_SCR_CENTER))\n                        goto middle;\n\n                /*\n                 * If less than half a screen above the line, scroll down\n                 * until the line is on the screen.\n                 */\n                lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp));\n                if (lcnt < HALFTEXT(sp)) {\n                        while (lcnt--)\n                                if (vs_sm_1up(sp))\n                                        return (1);\n                        goto adjust;\n                }\n                goto bottom;\n        }\n\n        /*\n         * 6c: If not on the current screen, may request center or top.\n         */\n        if (F_ISSET(sp, SC_SCR_TOP))\n                goto top;\n        if (F_ISSET(sp, SC_SCR_CENTER))\n                goto middle;\n\n        /*\n         * 6d: Line up.\n         */\n        lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp));\n        if (lcnt < HALFTEXT(sp)) {\n                /*\n                 * If less than half a screen below the line, scroll up until\n                 * the line is the first line on the screen.  Special check so\n                 * that if the screen has been emptied, we refill it.\n                 */\n                if (db_exist(sp, HMAP->lno)) {\n                        while (lcnt--)\n                                if (vs_sm_1down(sp))\n                                        return (1);\n                        goto adjust;\n                }\n\n                /*\n                 * If less than a half screen from the bottom of the file,\n                 * put the last line of the file on the bottom of the screen.\n                 */\nbottom:         if (db_last(sp, &lastline))\n                        return (1);\n                tmp.lno = LNO;\n                tmp.coff = HMAP->coff;\n                tmp.soff = 1;\n                lcnt = vs_sm_nlines(sp, &tmp, lastline, sp->t_rows);\n                if (lcnt < HALFTEXT(sp)) {\n                        if (vs_sm_fill(sp, lastline, P_BOTTOM))\n                                return (1);\n                        F_SET(sp, SC_SCR_REDRAW);\n                        goto adjust;\n                }\n                /* It's not close, just put the line in the middle. */\n                goto middle;\n        }\n\n        /*\n         * If less than half a screen from the top of the file, put the first\n         * line of the file at the top of the screen.  Otherwise, put the line\n         * in the middle of the screen.\n         */\n        tmp.lno = 1;\n        tmp.coff = HMAP->coff;\n        tmp.soff = 1;\n        lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp));\n        if (lcnt < HALFTEXT(sp)) {\n                if (vs_sm_fill(sp, 1, P_TOP))\n                        return (1);\n        } else\nmiddle:         if (vs_sm_fill(sp, LNO, P_MIDDLE))\n                        return (1);\n        if (0) {\ntop:            if (vs_sm_fill(sp, LNO, P_TOP))\n                        return (1);\n        }\n        F_SET(sp, SC_SCR_REDRAW);\n\n        /*\n         * At this point we know part of the line is on the screen.  Since\n         * scrolling is done using logical lines, not physical, all of the\n         * line may not be on the screen.  While that's not necessarily bad,\n         * if the part the cursor is on isn't there, we're going to lose.\n         * This can be tricky; if the line covers the entire screen, lno\n         * may be the same as both ends of the map, that's why we test BOTH\n         * the top and the bottom of the map.  This isn't a problem for\n         * left-right scrolling, the cursor movement code handles the problem.\n         *\n         * There's a performance issue here if editing *really* long lines.\n         * This gets to the right spot by scrolling, and, in a binary, by\n         * scrolling hundreds of lines.  If the adjustment looks like it's\n         * going to be a serious problem, refill the screen and repaint.\n         */\nadjust: if (!O_ISSET(sp, O_LEFTRIGHT) &&\n            (LNO == HMAP->lno || LNO == TMAP->lno)) {\n                cnt = vs_screens(sp, LNO, &CNO);\n                if (LNO == HMAP->lno && cnt < HMAP->soff) {\n                        if ((HMAP->soff - cnt) > HALFTEXT(sp)) {\n                                HMAP->soff = cnt;\n                                vs_sm_fill(sp, OOBLNO, P_TOP);\n                                F_SET(sp, SC_SCR_REDRAW);\n                        } else\n                                while (cnt < HMAP->soff)\n                                        if (vs_sm_1down(sp))\n                                                return (1);\n                }\n                if (LNO == TMAP->lno && cnt > TMAP->soff) {\n                        if ((cnt - TMAP->soff) > HALFTEXT(sp)) {\n                                TMAP->soff = cnt;\n                                vs_sm_fill(sp, OOBLNO, P_BOTTOM);\n                                F_SET(sp, SC_SCR_REDRAW);\n                        } else\n                                while (cnt > TMAP->soff)\n                                        if (vs_sm_1up(sp))\n                                                return (1);\n                }\n        }\n\n        /*\n         * If the screen needs to be repainted, skip cursor optimization.\n         * However, in the code above we skipped leftright scrolling on\n         * the grounds that the cursor code would handle it.  Make sure\n         * the right screen is up.\n         */\n        if (F_ISSET(sp, SC_SCR_REDRAW)) {\n                if (O_ISSET(sp, O_LEFTRIGHT))\n                        goto slow;\n                goto paint;\n        }\n\n        /*\n         * 7: Cursor movements (current screen only).\n         */\n        if (!LF_ISSET(UPDATE_CURSOR))\n                goto number;\n\n        /*\n         * Decide cursor position.  If the line has changed, the cursor has\n         * moved over a tab, or don't know where the cursor was, reparse the\n         * line.  Otherwise, we've just moved over fixed-width characters,\n         * and can calculate the left/right scrolling and cursor movement\n         * without reparsing the line.  Note that we don't know which (if any)\n         * of the characters between the old and new cursor positions changed.\n         *\n         * XXX\n         * With some work, it should be possible to handle tabs quickly, at\n         * least in obvious situations, like moving right and encountering\n         * a tab, without reparsing the whole line.\n         *\n         * If the line we're working with has changed, reread it..\n         */\n        if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO)\n                goto slow;\n\n        /* Otherwise, if nothing's changed, ignore the cursor. */\n        if (CNO == OCNO)\n                goto fast;\n\n        /*\n         * Get the current line.  If this fails, we either have an empty\n         * file and can just repaint, or there's a real problem.  This\n         * isn't a performance issue because there aren't any ways to get\n         * here repeatedly.\n         */\n        if (db_eget(sp, LNO, &p, &len, &isempty)) {\n                if (isempty)\n                        goto slow;\n                return (1);\n        }\n\n#ifdef DEBUG\n        /* Sanity checking. */\n        if (CNO >= len && len != 0) {\n                msgq(sp, M_ERR, \"Error: %s/%d: cno (%u) >= len (%u)\",\n                     openbsd_basename(__FILE__), __LINE__, CNO, len);\n                return (1);\n        }\n#endif /* ifdef DEBUG */\n        /*\n         * The basic scheme here is to look at the characters in between\n         * the old and new positions and decide how big they are on the\n         * screen, and therefore, how many screen positions to move.\n         */\n        if (CNO < OCNO) {\n                /*\n                 * 7a: Cursor moved left.\n                 *\n                 * Point to the old character.  The old cursor position can\n                 * be past EOL if, for example, we just deleted the rest of\n                 * the line.  In this case, since we don't know the width of\n                 * the characters we traversed, we have to do it slowly.\n                 */\n                p += OCNO;\n                cnt = (OCNO - CNO) + 1;\n                if (OCNO >= len)\n                        goto slow;\n\n                /*\n                 * Quick sanity check -- it's hard to figure out exactly when\n                 * we cross a screen boundary as we do in the cursor right\n                 * movement.  If cnt is so large that we're going to cross the\n                 * boundary no matter what, stop now.\n                 */\n                if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)\n                        goto slow;\n\n                /*\n                 * Count up the widths of the characters.  If it's a tab\n                 * character, go do it the slow way.\n                 */\n                for (cwtotal = 0; cnt--; cwtotal += KEY_LEN(sp, ch))\n                        if ((ch = *(unsigned char *)p--) == '\\t')\n                                goto slow;\n\n                /*\n                 * Decrement the screen cursor by the total width of the\n                 * characters minus 1.\n                 */\n                cwtotal -= 1;\n\n                /*\n                 * If we're moving left, and there's a wide character in the\n                 * current position, go to the end of the character.\n                 */\n                if (KEY_LEN(sp, ch) > 1)\n                        cwtotal -= KEY_LEN(sp, ch) - 1;\n\n                /*\n                 * If the new column moved us off of the current logical line,\n                 * calculate a new one.  If doing leftright scrolling, we've\n                 * moved off of the current screen, as well.\n                 */\n                if (SCNO < cwtotal)\n                        goto slow;\n                SCNO -= cwtotal;\n        } else {\n                /*\n                 * 7b: Cursor moved right.\n                 *\n                 * Point to the first character to the right.\n                 */\n                p += OCNO + 1;\n                cnt = CNO - OCNO;\n\n                /*\n                 * Count up the widths of the characters.  If it's a tab\n                 * character, go do it the slow way.  If we cross a\n                 * screen boundary, we can quit.\n                 */\n                for (cwtotal = SCNO; cnt--;) {\n                        if ((ch = *(unsigned char *)p++) == '\\t')\n                                goto slow;\n                        if ((cwtotal += KEY_LEN(sp, ch)) >= SCREEN_COLS(sp))\n                                break;\n                }\n\n                /*\n                 * Increment the screen cursor by the total width of the\n                 * characters.\n                 */\n                SCNO = cwtotal;\n\n                /* See screen change comment in section 6a. */\n                if (SCNO >= SCREEN_COLS(sp))\n                        goto slow;\n        }\n\n        /*\n         * 7c: Fast cursor update.\n         *\n         * We have the current column, retrieve the current row.\n         */\nfast:   (void)gp->scr_cursor(sp, &y, &notused);\n        goto done_cursor;\n\n        /*\n         * 7d: Slow cursor update.\n         *\n         * Walk through the map and find the current line.\n         */\nslow:   for (smp = HMAP; smp->lno != LNO; ++smp);\n(void)(unsigned int)0;\n\n        /*\n         * 7e: Leftright scrolling adjustment.\n         *\n         * If doing left-right scrolling and the cursor movement has changed\n         * the displayed screen, scroll the screen left or right, unless we're\n         * updating the info line in which case we just scroll that one line.\n         * We adjust the offset up or down until we have a window that covers\n         * the current column, making sure that we adjust differently for the\n         * first screen as compared to subsequent ones.\n         */\n        if (O_ISSET(sp, O_LEFTRIGHT)) {\n                /*\n                 * Get the screen column for this character, and correct\n                 * for the number option offset.\n                 */\n                cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);\n                if (O_ISSET(sp, O_NUMBER) && cnt)\n                        cnt -= O_NUMBER_LENGTH;\n\n                /* Adjust the window towards the beginning of the line. */\n                off = smp->coff;\n                if (off >= cnt) {\n                        do {\n                                if (off >= O_VAL(sp, O_SIDESCROLL))\n                                        off -= O_VAL(sp, O_SIDESCROLL);\n                                else {\n                                        off = 0;\n                                        break;\n                                }\n                        } while (off >= cnt);\n                        goto shifted;\n                }\n\n                /* Adjust the window towards the end of the line. */\n                if ((off == 0 && off + SCREEN_COLS(sp) < cnt) ||\n                    (off != 0 && off + sp->cols < cnt)) {\n                        do {\n                                off += O_VAL(sp, O_SIDESCROLL);\n                        } while (off + sp->cols < cnt);\n\nshifted:                /* Fill in screen map with the new offset. */\n                        if (F_ISSET(sp, SC_TINPUT_INFO))\n                                smp->coff = off;\n                        else {\n                                for (smp = HMAP; smp <= TMAP; ++smp)\n                                        smp->coff = off;\n                                leftright_warp = 1;\n                        }\n                        goto paint;\n                }\n\n                /*\n                 * We may have jumped here to adjust a leftright screen because\n                 * redraw was set.  If so, we have to paint the entire screen.\n                 */\n                if (F_ISSET(sp, SC_SCR_REDRAW))\n                        goto paint;\n        }\n\n        /*\n         * Update the screen lines for this particular file line until we\n         * have a new screen cursor position.\n         */\n        for (y = -1,\n            vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {\n                if (vs_line(sp, smp, &y, &SCNO))\n                        return (1);\n                if (y != -1) {\n                        vip->sc_smap = smp;\n                        break;\n                }\n        }\n        goto done_cursor;\n\n        /*\n         * 8: Repaint the entire screen.\n         *\n         * Lost big, do what you have to do.  We flush the cache, since\n         * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and\n         * it's simpler to repaint.  So, don't trust anything that we\n         * think we know about it.\n         */\npaint:  for (smp = HMAP; smp <= TMAP; ++smp)\n                SMAP_FLUSH(smp);\n        for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {\n                if (vs_line(sp, smp, &y, &SCNO))\n                        return (1);\n                if (y != -1 && vip->sc_smap == NULL)\n                        vip->sc_smap = smp;\n        }\n        /*\n         * If it's a small screen and we're redrawing, clear the unused lines,\n         * ex may have overwritten them.\n         */\n        if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))\n                for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {\n                        (void)gp->scr_move(sp, cnt, 0);\n                        (void)gp->scr_clrtoeol(sp);\n                }\n\n        didpaint = 1;\n\ndone_cursor:\n        /*\n         * Sanity checking.  When the repainting code messes up, the usual\n         * result is we don't repaint the cursor and so sc_smap will be\n         * NULL.  If we're debugging, die, otherwise restart from scratch.\n         */\n#ifdef DEBUG\n        if (vip->sc_smap == NULL)\n                abort();\n#else\n        if (vip->sc_smap == NULL) {\n                if (F_ISSET(sp, SC_SCR_REFORMAT))\n                        return (0);\n                F_SET(sp, SC_SCR_REFORMAT);\n                return (vs_paint(sp, flags));\n        }\n#endif /* ifdef DEBUG */\n\n        /*\n         * 9: Set the remembered cursor values.\n         */\n        OCNO = CNO;\n        OLNO = LNO;\n\n        /*\n         * 10: Repaint the line numbers.\n         *\n         * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we\n         * didn't repaint the screen, repaint all of the line numbers,\n         * they've changed.\n         */\nnumber: if (O_ISSET(sp, O_NUMBER) &&\n            F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))\n                return (1);\n\n        /*\n         * 11: Update the mode line, position the cursor, and flush changes.\n         *\n         * If we warped the screen, we have to refresh everything.\n         */\n        if (leftright_warp)\n                LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);\n\n        if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&\n            !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))\n                vs_modeline(sp);\n\n        if (LF_ISSET(UPDATE_CURSOR)) {\n                (void)gp->scr_move(sp, y, SCNO);\n\n                /*\n                 * XXX\n                 * If the screen shifted, we recalculate the \"most favorite\"\n                 * cursor position.  Vi won't know that we've warped the\n                 * screen, so it's going to have a wrong idea about where the\n                 * cursor should be.  This is vi's problem, and fixing it here\n                 * is a gross layering violation.\n                 */\n                if (leftright_warp)\n                        (void)vs_column(sp, &sp->rcm);\n        }\n\n        if (LF_ISSET(UPDATE_SCREEN))\n                (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));\n\n        /* 12: Clear the flags that are handled by this routine. */\n        F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);\n        F_CLR(vip, VIP_CUR_INVALID |\n            VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);\n\n        return (0);\n\n#undef   LNO\n#undef  OLNO\n#undef   CNO\n#undef  OCNO\n#undef  SCNO\n}\n\n/*\n * vs_modeline --\n *      Update the mode line.\n */\nstatic void\nvs_modeline(SCR *sp)\n{\n        static char * const modes[] = {\n                \"Append\",                       /* SM_APPEND */\n                \"Change\",                       /* SM_CHANGE */\n                \"Command\",                      /* SM_COMMAND */\n                \"Insert\",                       /* SM_INSERT */\n                \"Replace\",                      /* SM_REPLACE */\n        };\n        GS *gp;\n        size_t cols, curcol, curlen, endpoint, len, midpoint;\n        const char *t = NULL;\n        int ellipsis;\n        char *p, buf[30];\n        recno_t last = 0;\n\n        /*\n         * It's possible that this routine will be called after sp->frp\n         * has been set to NULL by file_end().  We return immediately\n         * to avoid a SEGV.\n         */\n        if (sp->frp == NULL)\n                return;\n\n        len = 0;\n        (void)len;\n        midpoint = 0;\n        (void)midpoint;\n\n        gp = sp->gp;\n        (void)gp;\n\n        /*\n         * We put down the file name, the ruler, the mode and the dirty flag.\n         * If there's not enough room, there's not enough room, we don't play\n         * any special games.  We try to put the ruler in the middle and the\n         * mode and dirty flag at the end.\n         *\n         * !!!\n         * Leave the last character blank, in case it's a really dumb terminal\n         * with hardware scroll.  Second, don't paint the last character in the\n         * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.\n         *\n         * Move to the last line on the screen.\n         */\n        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n\n        /* If windowname is set, or >1 screen exists, then show the name */\n        curlen = 0;\n        if ((IS_SPLIT(sp)) || O_ISSET(sp, O_WINDOWNAME) ||\n            O_ISSET(sp, O_SHOWFILENAME)) {\n                for (p = sp->frp->name; *p != '\\0'; ++p);\n                for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) {\n                        if (*p == '/') {\n                                ++p;\n                                break;\n                        }\n                        if ((curlen += KEY_LEN(sp, *p)) > cols) {\n                                ellipsis = 3;\n                                curlen +=\n                                    KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');\n                                while (curlen > cols) {\n                                        ++p;\n                                        curlen -= KEY_LEN(sp, *p);\n                                }\n                                break;\n                        }\n                }\n                if (ellipsis) {\n                        while (ellipsis--)\n                                (void)gp->scr_addstr(sp,\n                                    KEY_NAME(sp, '.'), KEY_LEN(sp, '.'));\n                        (void)gp->scr_addstr(sp,\n                            KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));\n                }\n                for (; *p != '\\0'; ++p)\n                        (void)gp->scr_addstr(sp,\n                            KEY_NAME(sp, *p), KEY_LEN(sp, *p));\n        }\n\n        /* Clear the rest of the line. */\n        (void)gp->scr_clrtoeol(sp);\n\n        /*\n         * Display the ruler.  If we're not at the midpoint yet, move there.\n         * Otherwise, add in two extra spaces.\n         *\n         * Adjust the current column for the fact that the editor uses it as\n         * a zero-based number.\n         *\n         * XXX\n         * Assume that numbers, commas, and spaces only take up a single\n         * column on the screen.\n         */\n        cols = sp->cols - 1;\n        if (O_ISSET(sp, O_RULER)) {\n            vs_column(sp, &curcol);\n            if (!(db_last(sp, &last))) {\n                  if (last > 1) {\n                    len = snprintf(buf, sizeof(buf), \"%lu:%lu  %2lu%%\",\n                        (unsigned long)sp->lno, (unsigned long)curcol + 1,\n                        (unsigned long)((((unsigned long)sp->lno) * 100L) / (unsigned long)last));\n                    if (sp->lno >= last)\n                        len = snprintf(buf, sizeof(buf), \"%lu:%lu  Bot\",\n                            (unsigned long)sp->lno, (unsigned long)curcol + 1);\n                    if (sp->lno < 2)\n                        len = snprintf(buf, sizeof(buf), \"%lu:%lu  Top\",\n                            (unsigned long)sp->lno, (unsigned long)curcol + 1);\n                } else {\n                    len = snprintf(buf, sizeof(buf), \"%lu:%lu\",\n                        (unsigned long)sp->lno, (unsigned long)curcol + 1);\n                }\n            } else {\n                len = snprintf(buf, sizeof(buf), \"%lu:%lu\",\n                    (unsigned long)sp->lno, (unsigned long)curcol + 1);\n            }\n                midpoint = (cols - ((len + 1) / 2)) / 2;\n                if (curlen < midpoint) {\n                        (void)gp->scr_move(sp, LASTLINE(sp), midpoint);\n                        curlen += len;\n                } else if (curlen + 2 + len < cols) {\n                        (void)gp->scr_addstr(sp, \"  \", 2);\n                        curlen += 2 + len;\n                }\n                (void)gp->scr_addstr(sp, buf, len);\n        }\n\n        /*\n         * Display the mode and the modified flag, as close to the end of the\n         * line as possible, but guaranteeing at least two spaces between the\n         * ruler and the modified flag.\n         */\n#define MODESIZE        9\n        endpoint = cols;\n        if (O_ISSET(sp, O_SHOWMODE)) {\n                if (F_ISSET(sp->ep, F_MODIFIED))\n                        --endpoint;\n                t = modes[sp->showmode];\n                endpoint -= (len = strlen(t));\n        }\n\n        if (endpoint > curlen + 2) {\n                (void)gp->scr_move(sp, LASTLINE(sp), endpoint);\n                if (O_ISSET(sp, O_SHOWMODE)) {\n                        if (F_ISSET(sp->ep, F_MODIFIED))\n                                (void)gp->scr_addstr(sp,\n                                    KEY_NAME(sp, '*'), KEY_LEN(sp, '*'));\n                        (void)gp->scr_addstr(sp, t, len);\n                }\n        }\n}\n"
  },
  {
    "path": "vi/vs_relative.c",
    "content": "/*      $OpenBSD: vs_relative.c,v 1.9 2014/11/12 04:28:41 bentley Exp $ */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\n/*\n * vs_column --\n *      Return the logical column of the cursor in the line.\n *\n * PUBLIC: int vs_column(SCR *, size_t *);\n */\nint\nvs_column(SCR *sp, size_t *colp)\n{\n        VI_PRIVATE *vip;\n\n        vip = VIP(sp);\n\n        *colp = (O_ISSET(sp, O_LEFTRIGHT) ?\n            vip->sc_smap->coff : (vip->sc_smap->soff - 1) * sp->cols) +\n            vip->sc_col - (O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0);\n        return (0);\n}\n\n/*\n * vs_screens --\n *      Return the screens necessary to display the line, or if specified,\n *      the physical character column within the line, including space\n *      required for the O_NUMBER and O_LIST options.\n *\n * PUBLIC: size_t vs_screens(SCR *, recno_t, size_t *);\n */\nsize_t\nvs_screens(SCR *sp, recno_t lno, size_t *cnop)\n{\n        size_t cols, screens;\n\n        /* Left-right screens are simple, it's always 1. */\n        if (O_ISSET(sp, O_LEFTRIGHT))\n                return (1);\n\n        /*\n         * Check for a cached value.  We maintain a cache because, if the\n         * line is large, this routine gets called repeatedly.  One other\n         * hack, lots of time the cursor is on column one, which is an easy\n         * one.\n         */\n        if (cnop == NULL) {\n                if (VIP(sp)->ss_lno == lno)\n                        return (VIP(sp)->ss_screens);\n        } else if (*cnop == 0)\n                return (1);\n\n        /* Figure out how many columns the line/column needs. */\n        cols = vs_columns(sp, NULL, lno, cnop, NULL);\n\n        screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0));\n        if (screens == 0)\n                screens = 1;\n\n        /* Cache the value. */\n        if (cnop == NULL) {\n                VIP(sp)->ss_lno = lno;\n                VIP(sp)->ss_screens = screens;\n        }\n        return (screens);\n}\n\n/*\n * vs_columns --\n *      Return the screen columns necessary to display the line, or,\n *      if specified, the physical character column within the line.\n *\n * PUBLIC: size_t vs_columns(SCR *, char *, recno_t, size_t *, size_t *);\n */\nsize_t\nvs_columns(SCR *sp, char *lp, recno_t lno, size_t *cnop, size_t *diffp)\n{\n        size_t chlen, cno, curoff, last, len, scno;\n        int ch, leftright, listset;\n        char *p;\n\n        /*\n         * Initialize the screen offset.\n         */\n        scno = 0;\n        curoff = 0;\n\n        /* Leading number if O_NUMBER option set. */\n        if (O_ISSET(sp, O_NUMBER)) {\n                scno += O_NUMBER_LENGTH;\n                curoff += O_NUMBER_LENGTH;\n        }\n\n        /* Need the line to go any further. */\n        if (lp == NULL) {\n                (void)db_get(sp, lno, 0, &lp, &len);\n                if (len == 0)\n                        goto done;\n        }\n\n        /* Missing or empty lines are easy. */\n        if (lp == NULL) {\ndone:           if (diffp != NULL)              /* XXX */\n                        *diffp = 0;\n                return (scno);\n        }\n\n        /* Store away the values of the list and leftright edit options. */\n        listset = O_ISSET(sp, O_LIST);\n        leftright = O_ISSET(sp, O_LEFTRIGHT);\n\n        /*\n         * Initialize the pointer into the buffer.\n         */\n        p = lp;\n        curoff = 0;\n\n        /* Macro to return the display length of any signal character. */\n#define CHLEN(val) (ch = *(unsigned char *)p++) == '\\t' &&                     \\\n            !listset ? TAB_OFF(val) : KEY_LEN(sp, ch);\n\n        /*\n         * If folding screens (the historic vi screen format), past the end\n         * of the current screen, and the character was a tab, reset the\n         * current screen column to 0, and the total screen columns to the\n         * last column of the screen.  Otherwise, display the rest of the\n         * character in the next screen.\n         */\n#define TAB_RESET {                                                     \\\n        curoff += chlen;                                                \\\n        if (!leftright && curoff >= sp->cols) {                         \\\n                if (ch == '\\t') {                                       \\\n                        curoff = 0;                                     \\\n                        scno -= scno % sp->cols;                        \\\n                } else                                                  \\\n                        curoff -= sp->cols;                             \\\n        }                                                               \\\n}\n        if (cnop == NULL)\n                while (len--) {\n                        chlen = CHLEN(curoff);\n                        last = scno;\n                        scno += chlen;\n                        TAB_RESET;\n                }\n        else\n                for (cno = *cnop;; --cno) {\n                        chlen = CHLEN(curoff);\n                        last = scno;\n                        scno += chlen;\n                        TAB_RESET;\n                        if (cno == 0)\n                                break;\n                }\n\n        /* Add the trailing '$' if the O_LIST option set. */\n        if (listset && cnop == NULL)\n                scno += KEY_LEN(sp, '$');\n\n        /*\n         * The text input screen code needs to know how much additional\n         * room the last two characters required, so that it can handle\n         * tab character displays correctly.\n         */\n        if (diffp != NULL)\n                *diffp = scno - last;\n        return (scno);\n}\n\n/*\n * vs_rcm --\n *      Return the physical column from the line that will display a\n *      character closest to the currently most attractive character\n *      position (which is stored as a screen column).\n *\n * PUBLIC: size_t vs_rcm(SCR *, recno_t, int);\n */\nsize_t\nvs_rcm(SCR *sp, recno_t lno, int islast)\n{\n        size_t len;\n\n        /* Last character is easy, and common. */\n        if (islast) {\n                if (db_get(sp, lno, 0, NULL, &len) || len == 0)\n                        return (0);\n                return (len - 1);\n        }\n\n        /* First character is easy, and common. */\n        if (sp->rcm == 0)\n                return (0);\n\n        return (vs_colpos(sp, lno, sp->rcm));\n}\n\n/*\n * vs_colpos --\n *      Return the physical column from the line that will display a\n *      character closest to the specified screen column.\n *\n * PUBLIC: size_t vs_colpos(SCR *, recno_t, size_t);\n */\nsize_t\nvs_colpos(SCR *sp, recno_t lno, size_t cno)\n{\n        size_t chlen, curoff, len, llen, off, scno;\n        int ch, leftright, listset;\n        char *lp, *p;\n\n        /* Need the line to go any further. */\n        (void)db_get(sp, lno, 0, &lp, &llen);\n\n        /* Missing or empty lines are easy. */\n        if (lp == NULL || llen == 0)\n                return (0);\n\n        /* Store away the values of the list and leftright edit options. */\n        listset = O_ISSET(sp, O_LIST);\n        leftright = O_ISSET(sp, O_LEFTRIGHT);\n\n        /* Discard screen (logical) lines. */\n        off = cno / sp->cols;\n        cno %= sp->cols;\n        for (scno = 0, p = lp, len = llen; off--;) {\n                for (; len && scno < sp->cols; --len)\n                        scno += CHLEN(scno);\n\n                /*\n                 * If reached the end of the physical line, return the last\n                 * physical character in the line.\n                 */\n                if (len == 0)\n                        return (llen - 1);\n\n                /*\n                 * If folding screens (the historic vi screen format), past\n                 * the end of the current screen, and the character was a tab,\n                 * reset the current screen column to 0.  Otherwise, the rest\n                 * of the character is displayed in the next screen.\n                 */\n                if (leftright && ch == '\\t')\n                        scno = 0;\n                else\n                        scno -= sp->cols;\n        }\n\n        /* Step through the line until reach the right character or EOL. */\n        for (curoff = scno; len--;) {\n                chlen = CHLEN(curoff);\n\n                /*\n                 * If we've reached the specific character, there are three\n                 * cases.\n                 *\n                 * 1: scno == cno, i.e. the current character ends at the\n                 *    screen character we care about.\n                 *      a: off < llen - 1, i.e. not the last character in\n                 *         the line, return the offset of the next character.\n                 *      b: else return the offset of the last character.\n                 * 2: scno != cno, i.e. this character overruns the character\n                 *    we care about, return the offset of this character.\n                 */\n                if ((scno += chlen) >= cno) {\n                        off = p - lp;\n                        return (scno == cno ?\n                            (off < llen - 1 ? off : llen - 1) : off - 1);\n                }\n\n                TAB_RESET;\n        }\n\n        /* No such character; return the start of the last character. */\n        return (llen - 1);\n}\n"
  },
  {
    "path": "vi/vs_smap.c",
    "content": "/*      $OpenBSD: vs_smap.c,v 1.9 2016/01/06 22:28:52 millert Exp $     */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic int      vs_deleteln(SCR *, int);\nstatic int      vs_insertln(SCR *, int);\nstatic int      vs_sm_delete(SCR *, recno_t);\nstatic int      vs_sm_down(SCR *, MARK *, recno_t, scroll_t, SMAP *);\nstatic int      vs_sm_erase(SCR *);\nstatic int      vs_sm_insert(SCR *, recno_t);\nstatic int      vs_sm_reset(SCR *, recno_t);\nstatic int      vs_sm_up(SCR *, MARK *, recno_t, scroll_t, SMAP *);\n\n/*\n * vs_change --\n *      Make a change to the screen.\n *\n * PUBLIC: int vs_change(SCR *, recno_t, lnop_t);\n */\n\nint\nvs_change(SCR *sp, recno_t lno, lnop_t op)\n{\n        VI_PRIVATE *vip;\n        SMAP *p;\n        size_t cnt, oldy, oldx;\n\n        vip = VIP(sp);\n\n        /*\n         * XXX\n         * Very nasty special case.  The historic vi code displays a single\n         * space (or a '$' if the list option is set) for the first line in\n         * an \"empty\" file.  If we \"insert\" a line, that line gets scrolled\n         * down, not repainted, so it's incorrect when we refresh the screen.\n         * The vi text input functions detect it explicitly and don't insert\n         * a new line.\n         *\n         * Check for line #2 before going to the end of the file.\n         */\n\n        if (((op == LINE_APPEND && lno == 0) || (op == LINE_INSERT && lno == 1)) &&\n            !db_exist(sp, 2)) {\n                lno = 1;\n                op = LINE_RESET;\n        }\n\n        /* Appending is the same as inserting, if the line is incremented. */\n        if (op == LINE_APPEND) {\n                ++lno;\n                op = LINE_INSERT;\n        }\n\n        /* Ignore the change if the line is after the map. */\n        if (lno > TMAP->lno)\n                return (0);\n\n        /*\n         * If the line is before the map, and it's a decrement, decrement\n         * the map.  If it's an increment, increment the map.  Otherwise,\n         * ignore it.\n         */\n        if (lno < HMAP->lno) {\n                switch (op) {\n                case LINE_APPEND:\n                        abort();\n                        /* NOTREACHED */\n                case LINE_DELETE:\n                        for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)\n                                --p->lno;\n                        if (sp->lno >= lno)\n                                --sp->lno;\n                        F_SET(vip, VIP_N_RENUMBER);\n                        break;\n                case LINE_INSERT:\n                        for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)\n                                ++p->lno;\n                        if (sp->lno >= lno)\n                                ++sp->lno;\n                        F_SET(vip, VIP_N_RENUMBER);\n                        break;\n                case LINE_RESET:\n                        break;\n                }\n                return (0);\n        }\n\n        F_SET(vip, VIP_N_REFRESH);\n\n        /*\n         * Invalidate the line size cache, and invalidate the cursor if it's\n         * on this line,\n         */\n        VI_SCR_CFLUSH(vip);\n        if (sp->lno == lno)\n                F_SET(vip, VIP_CUR_INVALID);\n\n        /*\n         * If ex modifies the screen after ex output is already on the screen\n         * or if we've switched into ex canonical mode, don't touch it -- we'll\n         * get scrolling wrong, at best.\n         */\n        if (!F_ISSET(sp, SC_TINPUT_INFO) &&\n            (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {\n                F_SET(vip, VIP_N_EX_REDRAW);\n                return (0);\n        }\n\n        /* Save and restore the cursor for these routines. */\n        (void)sp->gp->scr_cursor(sp, &oldy, &oldx);\n\n        switch (op) {\n        case LINE_DELETE:\n                if (vs_sm_delete(sp, lno))\n                        return (1);\n                F_SET(vip, VIP_N_RENUMBER);\n                break;\n        case LINE_INSERT:\n                if (vs_sm_insert(sp, lno))\n                        return (1);\n                F_SET(vip, VIP_N_RENUMBER);\n                break;\n        case LINE_RESET:\n                if (vs_sm_reset(sp, lno))\n                        return (1);\n                break;\n        default:\n                abort();\n        }\n\n        (void)sp->gp->scr_move(sp, oldy, oldx);\n        return (0);\n}\n\n/*\n * vs_sm_fill --\n *      Fill in the screen map, placing the specified line at the\n *      right position.  There isn't any way to tell if an SMAP\n *      entry has been filled in, so this routine had better be\n *      called with P_FILL set before anything else is done.\n *\n * !!!\n * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP\n * slot is already filled in, P_BOTTOM means that the TMAP slot is\n * already filled in, and we just finish up the job.\n *\n * PUBLIC: int vs_sm_fill(SCR *, recno_t, pos_t);\n */\nint\nvs_sm_fill(SCR *sp, recno_t lno, pos_t pos)\n{\n        SMAP *p, tmp;\n        size_t cnt;\n\n        /* Flush all cached information from the SMAP. */\n        for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)\n                SMAP_FLUSH(p);\n\n        /*\n         * If the map is filled, the screen must be redrawn.\n         *\n         * XXX\n         * This is a bug.  We should try and figure out if the desired line\n         * is already in the map or close by -- scrolling the screen would\n         * be a lot better than redrawing.\n         */\n        F_SET(sp, SC_SCR_REDRAW);\n\n        switch (pos) {\n        case P_FILL:\n                tmp.lno = 1;\n                tmp.coff = 0;\n                tmp.soff = 1;\n\n                /* See if less than half a screen from the top. */\n                if (vs_sm_nlines(sp,\n                    &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {\n                        lno = 1;\n                        goto top;\n                }\n\n                /* See if less than half a screen from the bottom. */\n                if (db_last(sp, &tmp.lno))\n                        return (1);\n                tmp.coff = 0;\n                tmp.soff = vs_screens(sp, tmp.lno, NULL);\n                if (vs_sm_nlines(sp,\n                    &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {\n                        TMAP->lno = tmp.lno;\n                        TMAP->coff = tmp.coff;\n                        TMAP->soff = tmp.soff;\n                        goto bottom;\n                }\n                goto middle;\n        case P_TOP:\n                if (lno != OOBLNO) {\ntop:                    HMAP->lno = lno;\n                        HMAP->coff = 0;\n                        HMAP->soff = 1;\n                } else {\n                        /*\n                         * If number of lines HMAP->lno (top line) spans\n                         * changed due to, say reformatting, and now is\n                         * fewer than HMAP->soff, reset so the line is\n                         * redrawn at the top of the screen.\n                         */\n                        cnt = vs_screens(sp, HMAP->lno, NULL);\n                        if (cnt < HMAP->soff)\n                                HMAP->soff = 1;\n                }\n                /* If we fail, just punt. */\n                for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)\n                        if (vs_sm_next(sp, p, p + 1))\n                                goto err;\n                break;\n        case P_MIDDLE:\n                /* If we fail, guess that the file is too small. */\nmiddle:         p = HMAP + sp->t_rows / 2;\n                p->lno = lno;\n                p->coff = 0;\n                p->soff = 1;\n                for (; p > HMAP; --p)\n                        if (vs_sm_prev(sp, p, p - 1)) {\n                                lno = 1;\n                                goto top;\n                        }\n\n                /* If we fail, just punt. */\n                p = HMAP + sp->t_rows / 2;\n                for (; p < TMAP; ++p)\n                        if (vs_sm_next(sp, p, p + 1))\n                                goto err;\n                break;\n        case P_BOTTOM:\n                if (lno != OOBLNO) {\n                        TMAP->lno = lno;\n                        TMAP->coff = 0;\n                        TMAP->soff = vs_screens(sp, lno, NULL);\n                }\n                /* If we fail, guess that the file is too small. */\nbottom:         for (p = TMAP; p > HMAP; --p)\n                        if (vs_sm_prev(sp, p, p - 1)) {\n                                lno = 1;\n                                goto top;\n                        }\n                break;\n        default:\n                abort();\n        }\n        return (0);\n\n        /*\n         * Try and put *something* on the screen.  If this fails, we have a\n         * serious hard error.\n         */\nerr:    HMAP->lno = 1;\n        HMAP->coff = 0;\n        HMAP->soff = 1;\n        for (p = HMAP; p < TMAP; ++p)\n                if (vs_sm_next(sp, p, p + 1))\n                        return (1);\n        return (0);\n}\n\n/*\n * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the\n * screen contains only a single line (whether because the screen is small\n * or the line large), it gets fairly exciting.  Skip the fun, set a flag\n * so the screen map is refilled and the screen redrawn, and return.  This\n * is amazingly slow, but it's not clear that anyone will care.\n */\n#define HANDLE_WEIRDNESS(cnt) {                                         \\\n        if ((cnt) >= sp->t_rows) {                                      \\\n                F_SET(sp, SC_SCR_REFORMAT);                             \\\n                return (0);                                             \\\n        }                                                               \\\n}\n\n/*\n * vs_sm_delete --\n *      Delete a line out of the SMAP.\n */\nstatic int\nvs_sm_delete(SCR *sp, recno_t lno)\n{\n        SMAP *p, *t;\n        size_t cnt_orig;\n\n        /*\n         * Find the line in the map, and count the number of screen lines\n         * which display any part of the deleted line.\n         */\n        for (p = HMAP; p->lno != lno; ++p);\n        if (O_ISSET(sp, O_LEFTRIGHT))\n                cnt_orig = 1;\n        else\n                for (cnt_orig = 1, t = p + 1;\n                    t <= TMAP && t->lno == lno; ++cnt_orig, ++t);\n\n        HANDLE_WEIRDNESS(cnt_orig);\n\n        /* Delete that many lines from the screen. */\n        (void)sp->gp->scr_move(sp, p - HMAP, 0);\n        if (vs_deleteln(sp, cnt_orig))\n                return (1);\n\n        /* Shift the screen map up. */\n        memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));\n\n        /* Decrement the line numbers for the rest of the map. */\n        for (t = TMAP - cnt_orig; p <= t; ++p)\n                --p->lno;\n\n        /* Display the new lines. */\n        for (p = TMAP - cnt_orig;;) {\n                if (p < TMAP && vs_sm_next(sp, p, p + 1))\n                        return (1);\n                /* vs_sm_next() flushed the cache. */\n                if (vs_line(sp, ++p, NULL, NULL))\n                        return (1);\n                if (p == TMAP)\n                        break;\n        }\n        return (0);\n}\n\n/*\n * vs_sm_insert --\n *      Insert a line into the SMAP.\n */\nstatic int\nvs_sm_insert(SCR *sp, recno_t lno)\n{\n        SMAP *p, *t;\n        size_t cnt_orig, cnt, coff;\n\n        /* Save the offset. */\n        coff = HMAP->coff;\n\n        /*\n         * Find the line in the map, find out how many screen lines\n         * needed to display the line.\n         */\n        for (p = HMAP; p->lno != lno; ++p);\n\n        cnt_orig = vs_screens(sp, lno, NULL);\n        HANDLE_WEIRDNESS(cnt_orig);\n\n        /*\n         * The lines left in the screen override the number of screen\n         * lines in the inserted line.\n         */\n        cnt = (TMAP - p) + 1;\n        if (cnt_orig > cnt)\n                cnt_orig = cnt;\n\n        /* Push down that many lines. */\n        (void)sp->gp->scr_move(sp, p - HMAP, 0);\n        if (vs_insertln(sp, cnt_orig))\n                return (1);\n\n        /* Shift the screen map down. */\n        memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));\n\n        /* Increment the line numbers for the rest of the map. */\n        for (t = p + cnt_orig; t <= TMAP; ++t)\n                ++t->lno;\n\n        /* Fill in the SMAP for the new lines, and display. */\n        for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {\n                t->lno = lno;\n                t->coff = coff;\n                t->soff = cnt;\n                SMAP_FLUSH(t);\n                if (vs_line(sp, t, NULL, NULL))\n                        return (1);\n        }\n        return (0);\n}\n\n/*\n * vs_sm_reset --\n *      Reset a line in the SMAP.\n */\nstatic int\nvs_sm_reset(SCR *sp, recno_t lno)\n{\n        SMAP *p, *t;\n        size_t cnt_orig, cnt_new, cnt, diff;\n\n        /*\n         * See if the number of on-screen rows taken up by the old display\n         * for the line is the same as the number needed for the new one.\n         * If so, repaint, otherwise do it the hard way.\n         */\n        for (p = HMAP; p->lno != lno; ++p);\n        if (O_ISSET(sp, O_LEFTRIGHT)) {\n                t = p;\n                cnt_orig = cnt_new = 1;\n        } else {\n                for (cnt_orig = 0,\n                    t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);\n                cnt_new = vs_screens(sp, lno, NULL);\n        }\n\n        HANDLE_WEIRDNESS(cnt_orig);\n\n        if (cnt_orig == cnt_new) {\n                do {\n                        SMAP_FLUSH(p);\n                        if (vs_line(sp, p, NULL, NULL))\n                                return (1);\n                } while (++p < t);\n                return (0);\n        }\n\n        if (cnt_orig < cnt_new) {\n                /* Get the difference. */\n                diff = cnt_new - cnt_orig;\n\n                /*\n                 * The lines left in the screen override the number of screen\n                 * lines in the inserted line.\n                 */\n                cnt = (TMAP - p) + 1;\n                if (diff > cnt)\n                        diff = cnt;\n\n                /* If there are any following lines, push them down. */\n                if (cnt > 1) {\n                        (void)sp->gp->scr_move(sp, p - HMAP, 0);\n                        if (vs_insertln(sp, diff))\n                                return (1);\n\n                        /* Shift the screen map down. */\n                        memmove(p + diff, p,\n                            (((TMAP - p) - diff) + 1) * sizeof(SMAP));\n                }\n\n                /* Fill in the SMAP for the replaced line, and display. */\n                for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {\n                        t->lno = lno;\n                        t->soff = cnt;\n                        SMAP_FLUSH(t);\n                        if (vs_line(sp, t, NULL, NULL))\n                                return (1);\n                }\n        } else {\n                /* Get the difference. */\n                diff = cnt_orig - cnt_new;\n\n                /* Delete that many lines from the screen. */\n                (void)sp->gp->scr_move(sp, p - HMAP, 0);\n                if (vs_deleteln(sp, diff))\n                        return (1);\n\n                /* Shift the screen map up. */\n                memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));\n\n                /* Fill in the SMAP for the replaced line, and display. */\n                for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {\n                        t->lno = lno;\n                        t->soff = cnt;\n                        SMAP_FLUSH(t);\n                        if (vs_line(sp, t, NULL, NULL))\n                                return (1);\n                }\n\n                /* Display the new lines at the bottom of the screen. */\n                for (t = TMAP - diff;;) {\n                        if (t < TMAP && vs_sm_next(sp, t, t + 1))\n                                return (1);\n                        /* vs_sm_next() flushed the cache. */\n                        if (vs_line(sp, ++t, NULL, NULL))\n                                return (1);\n                        if (t == TMAP)\n                                break;\n                }\n        }\n        return (0);\n}\n\n/*\n * vs_sm_scroll\n *      Scroll the SMAP up/down count logical lines.  Different\n *      semantics based on the vi command, *sigh*.\n *\n * PUBLIC: int vs_sm_scroll(SCR *, MARK *, recno_t, scroll_t);\n */\nint\nvs_sm_scroll(SCR *sp, MARK *rp, recno_t count, scroll_t scmd)\n{\n        SMAP *smp;\n\n        /*\n         * Invalidate the cursor.  The line is probably going to change,\n         * (although for ^E and ^Y it may not).  In any case, the scroll\n         * routines move the cursor to draw things.\n         */\n        F_SET(VIP(sp), VIP_CUR_INVALID);\n\n        /* Find the cursor in the screen. */\n        if (vs_sm_cursor(sp, &smp))\n                return (1);\n\n        switch (scmd) {\n        case CNTRL_B:\n        case CNTRL_U:\n        case CNTRL_Y:\n        case Z_CARAT:\n                if (vs_sm_down(sp, rp, count, scmd, smp))\n                        return (1);\n                break;\n        case CNTRL_D:\n        case CNTRL_E:\n        case CNTRL_F:\n        case Z_PLUS:\n                if (vs_sm_up(sp, rp, count, scmd, smp))\n                        return (1);\n                break;\n        default:\n                abort();\n        }\n\n        /*\n         * !!!\n         * If we're at the start of a line, go for the first non-blank.\n         * This makes it look like the old vi, even though we're moving\n         * around by logical lines, not physical ones.\n         *\n         * XXX\n         * In the presence of a long line, which has more than a screen\n         * width of leading spaces, this code can cause a cursor warp.\n         * Live with it.\n         */\n        if (scmd != CNTRL_E && scmd != CNTRL_Y &&\n            rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))\n                return (1);\n\n        return (0);\n}\n\n/*\n * vs_sm_up --\n *      Scroll the SMAP up count logical lines.\n */\nstatic int\nvs_sm_up(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp)\n{\n        int cursor_set, echanged, zset;\n        SMAP *ssmp, s1, s2;\n\n        /*\n         * Check to see if movement is possible.\n         *\n         * Get the line after the map.  If that line is a new one (and if\n         * O_LEFTRIGHT option is set, this has to be true), and the next\n         * line doesn't exist, and the cursor doesn't move, or the cursor\n         * isn't even on the screen, or the cursor is already at the last\n         * line in the map, it's an error.  If that test succeeded because\n         * the cursor wasn't at the end of the map, test to see if the map\n         * is mostly empty.\n         */\n        if (vs_sm_next(sp, TMAP, &s1))\n                return (1);\n        if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {\n                if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {\n                        v_eof(sp, NULL);\n                        return (1);\n                }\n                if (vs_sm_next(sp, smp, &s1))\n                        return (1);\n                if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {\n                        v_eof(sp, NULL);\n                        return (1);\n                }\n        }\n\n        /*\n         * Small screens: see vs_refresh.c section 6a.\n         *\n         * If it's a small screen, and the movement isn't larger than a\n         * screen, i.e some context will remain, open up the screen and\n         * display by scrolling.  In this case, the cursor moves down one\n         * line for each line displayed.  Otherwise, erase/compress and\n         * repaint, and move the cursor to the first line in the screen.\n         * Note, the ^F command is always in the latter case, for historical\n         * reasons.\n         */\n        cursor_set = 0;\n        if (IS_SMALL(sp)) {\n                if (count >= sp->t_maxrows || scmd == CNTRL_F) {\n                        s1 = TMAP[0];\n                        if (vs_sm_erase(sp))\n                                return (1);\n                        for (; count--; s1 = s2) {\n                                if (vs_sm_next(sp, &s1, &s2))\n                                        return (1);\n                                if (s2.lno != s1.lno && !db_exist(sp, s2.lno))\n                                        break;\n                        }\n                        TMAP[0] = s2;\n                        if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))\n                                return (1);\n                        return (vs_sm_position(sp, rp, 0, P_TOP));\n                }\n                cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);\n                for (; count &&\n                    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {\n                        if (vs_sm_next(sp, TMAP, &s1))\n                                return (1);\n                        if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))\n                                break;\n                        *++TMAP = s1;\n                        /* vs_sm_next() flushed the cache. */\n                        if (vs_line(sp, TMAP, NULL, NULL))\n                                return (1);\n\n                        if (!cursor_set)\n                                ++ssmp;\n                }\n                if (!cursor_set) {\n                        rp->lno = ssmp->lno;\n                        rp->cno = ssmp->c_sboff;\n                }\n                if (count == 0)\n                        return (0);\n        }\n\n        for (echanged = zset = 0; count; --count) {\n                /* Decide what would show up on the screen. */\n                if (vs_sm_next(sp, TMAP, &s1))\n                        return (1);\n\n                /* If the line doesn't exist, we're done. */\n                if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))\n                        break;\n\n                /* Scroll the screen cursor up one logical line. */\n                if (vs_sm_1up(sp))\n                        return (1);\n                switch (scmd) {\n                case CNTRL_E:\n                        if (smp > HMAP)\n                                --smp;\n                        else\n                                echanged = 1;\n                        break;\n                case Z_PLUS:\n                        if (zset) {\n                                if (smp > HMAP)\n                                        --smp;\n                        } else {\n                                smp = TMAP;\n                                zset = 1;\n                        }\n                        /* FALLTHROUGH */\n                default:\n                        break;\n                }\n        }\n\n        if (cursor_set)\n                return(0);\n\n        switch (scmd) {\n        case CNTRL_E:\n                /*\n                 * On a ^E that was forced to change lines, try and keep the\n                 * cursor as close as possible to the last position, but also\n                 * set it up so that the next \"real\" movement will return the\n                 * cursor to the closest position to the last real movement.\n                 */\n                if (echanged) {\n                        rp->lno = smp->lno;\n                        rp->cno = vs_colpos(sp, smp->lno,\n                            (O_ISSET(sp, O_LEFTRIGHT) ?\n                            smp->coff : (smp->soff - 1) * sp->cols) +\n                            sp->rcm % sp->cols);\n                }\n                return (0);\n        case CNTRL_F:\n                /*\n                 * If there are more lines, the ^F command is positioned at\n                 * the first line of the screen.\n                 */\n                if (!count) {\n                        smp = HMAP;\n                        break;\n                }\n                /* FALLTHROUGH */\n        case CNTRL_D:\n                /*\n                 * The ^D and ^F commands move the cursor towards EOF\n                 * if there are more lines to move.  Check to be sure\n                 * the lines actually exist.  (They may not if the\n                 * file is smaller than the screen.)\n                 */\n                for (; count; --count, ++smp)\n                        if (smp == TMAP || !db_exist(sp, smp[1].lno))\n                                break;\n                break;\n        case Z_PLUS:\n                 /* The z+ command moves the cursor to the first new line. */\n                break;\n        default:\n                abort();\n        }\n\n        if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))\n                return (1);\n        rp->lno = smp->lno;\n        rp->cno = smp->c_sboff;\n        return (0);\n}\n\n/*\n * vs_sm_1up --\n *      Scroll the SMAP up one.\n *\n * PUBLIC: int vs_sm_1up(SCR *);\n */\nint\nvs_sm_1up(SCR *sp)\n{\n        /*\n         * Delete the top line of the screen.  Shift the screen map\n         * up and display a new line at the bottom of the screen.\n         */\n        (void)sp->gp->scr_move(sp, 0, 0);\n        if (vs_deleteln(sp, 1))\n                return (1);\n\n        /* One-line screens can fail. */\n        if (IS_ONELINE(sp)) {\n                if (vs_sm_next(sp, TMAP, TMAP))\n                        return (1);\n        } else {\n                memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));\n                if (vs_sm_next(sp, TMAP - 1, TMAP))\n                        return (1);\n        }\n        /* vs_sm_next() flushed the cache. */\n        return (vs_line(sp, TMAP, NULL, NULL));\n}\n\n/*\n * vs_deleteln --\n *      Delete a line a la curses, make sure to put the information\n *      line and other screens back.\n */\nstatic int\nvs_deleteln(SCR *sp, int cnt)\n{\n        GS *gp;\n        size_t oldy, oldx;\n\n        gp = sp->gp;\n        if (IS_ONELINE(sp))\n                (void)gp->scr_clrtoeol(sp);\n        else {\n                (void)gp->scr_cursor(sp, &oldy, &oldx);\n                while (cnt--) {\n                        (void)gp->scr_deleteln(sp);\n                        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                        (void)gp->scr_insertln(sp);\n                        (void)gp->scr_move(sp, oldy, oldx);\n                }\n        }\n        return (0);\n}\n\n/*\n * vs_sm_down --\n *      Scroll the SMAP down count logical lines.\n */\nstatic int\nvs_sm_down(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp)\n{\n        SMAP *ssmp, s1, s2;\n        int cursor_set, ychanged, zset;\n\n        /* Check to see if movement is possible. */\n        if (HMAP->lno == 1 &&\n            (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&\n            (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {\n                v_sof(sp, NULL);\n                return (1);\n        }\n\n        /*\n         * Small screens: see vs_refresh.c section 6a.\n         *\n         * If it's a small screen, and the movement isn't larger than a\n         * screen, i.e some context will remain, open up the screen and\n         * display by scrolling.  In this case, the cursor moves up one\n         * line for each line displayed.  Otherwise, erase/compress and\n         * repaint, and move the cursor to the first line in the screen.\n         * Note, the ^B command is always in the latter case, for historical\n         * reasons.\n         */\n        cursor_set = scmd == CNTRL_Y;\n        if (IS_SMALL(sp)) {\n                if (count >= sp->t_maxrows || scmd == CNTRL_B) {\n                        s1 = HMAP[0];\n                        if (vs_sm_erase(sp))\n                                return (1);\n                        for (; count--; s1 = s2) {\n                                if (vs_sm_prev(sp, &s1, &s2))\n                                        return (1);\n                                if (s2.lno == 1 &&\n                                    (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))\n                                        break;\n                        }\n                        HMAP[0] = s2;\n                        if (vs_sm_fill(sp, OOBLNO, P_TOP))\n                                return (1);\n                        return (vs_sm_position(sp, rp, 0, P_BOTTOM));\n                }\n                cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);\n                for (; count &&\n                    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {\n                        if (HMAP->lno == 1 &&\n                            (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))\n                                break;\n                        ++TMAP;\n                        if (vs_sm_1down(sp))\n                                return (1);\n                }\n                if (!cursor_set) {\n                        rp->lno = ssmp->lno;\n                        rp->cno = ssmp->c_sboff;\n                }\n                if (count == 0)\n                        return (0);\n        }\n\n        for (ychanged = zset = 0; count; --count) {\n                /* If the line doesn't exist, we're done. */\n                if (HMAP->lno == 1 &&\n                    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))\n                        break;\n\n                /* Scroll the screen and cursor down one logical line. */\n                if (vs_sm_1down(sp))\n                        return (1);\n                switch (scmd) {\n                case CNTRL_Y:\n                        if (smp < TMAP)\n                                ++smp;\n                        else\n                                ychanged = 1;\n                        break;\n                case Z_CARAT:\n                        if (zset) {\n                                if (smp < TMAP)\n                                        ++smp;\n                        } else {\n                                smp = HMAP;\n                                zset = 1;\n                        }\n                        /* FALLTHROUGH */\n                default:\n                        break;\n                }\n        }\n\n        if (scmd != CNTRL_Y && cursor_set)\n                return(0);\n\n        switch (scmd) {\n        case CNTRL_B:\n                /*\n                 * If there are more lines, the ^B command is positioned at\n                 * the last line of the screen.  However, the line may not\n                 * exist.\n                 */\n                if (!count) {\n                        for (smp = TMAP; smp > HMAP; --smp)\n                                if (db_exist(sp, smp->lno))\n                                        break;\n                        break;\n                }\n                /* FALLTHROUGH */\n        case CNTRL_U:\n                /*\n                 * The ^B and ^U commands move the cursor towards SOF\n                 * if there are more lines to move.\n                 */\n                if (count < smp - HMAP)\n                        smp -= count;\n                else\n                        smp = HMAP;\n                break;\n        case CNTRL_Y:\n                /*\n                 * On a ^Y that was forced to change lines, try and keep the\n                 * cursor as close as possible to the last position, but also\n                 * set it up so that the next \"real\" movement will return the\n                 * cursor to the closest position to the last real movement.\n                 */\n                if (ychanged) {\n                        rp->lno = smp->lno;\n                        rp->cno = vs_colpos(sp, smp->lno,\n                            (O_ISSET(sp, O_LEFTRIGHT) ?\n                            smp->coff : (smp->soff - 1) * sp->cols) +\n                            sp->rcm % sp->cols);\n                }\n                return (0);\n        case Z_CARAT:\n                 /* The z^ command moves the cursor to the first new line. */\n                break;\n        default:\n                abort();\n        }\n\n        if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))\n                return (1);\n        rp->lno = smp->lno;\n        rp->cno = smp->c_sboff;\n        return (0);\n}\n\n/*\n * vs_sm_erase --\n *      Erase the small screen area for the scrolling functions.\n */\nstatic int\nvs_sm_erase(SCR *sp)\n{\n        GS *gp;\n\n        gp = sp->gp;\n        (void)gp->scr_move(sp, LASTLINE(sp), 0);\n        (void)gp->scr_clrtoeol(sp);\n        for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {\n                (void)gp->scr_move(sp, TMAP - HMAP, 0);\n                (void)gp->scr_clrtoeol(sp);\n        }\n        return (0);\n}\n\n/*\n * vs_sm_1down --\n *      Scroll the SMAP down one.\n *\n * PUBLIC: int vs_sm_1down(SCR *);\n */\nint\nvs_sm_1down(SCR *sp)\n{\n        /*\n         * Insert a line at the top of the screen.  Shift the screen map\n         * down and display a new line at the top of the screen.\n         */\n        (void)sp->gp->scr_move(sp, 0, 0);\n        if (vs_insertln(sp, 1))\n                return (1);\n\n        /* One-line screens can fail. */\n        if (IS_ONELINE(sp)) {\n                if (vs_sm_prev(sp, HMAP, HMAP))\n                        return (1);\n        } else {\n                memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));\n                if (vs_sm_prev(sp, HMAP + 1, HMAP))\n                        return (1);\n        }\n        /* vs_sm_prev() flushed the cache. */\n        return (vs_line(sp, HMAP, NULL, NULL));\n}\n\n/*\n * vs_insertln --\n *      Insert a line a la curses, make sure to put the information\n *      line and other screens back.\n */\nstatic int\nvs_insertln(SCR *sp, int cnt)\n{\n        GS *gp;\n        size_t oldy, oldx;\n\n        gp = sp->gp;\n        if (IS_ONELINE(sp)) {\n                (void)gp->scr_move(sp, LASTLINE(sp), 0);\n                (void)gp->scr_clrtoeol(sp);\n        } else {\n                (void)gp->scr_cursor(sp, &oldy, &oldx);\n                while (cnt--) {\n                        (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);\n                        (void)gp->scr_deleteln(sp);\n                        (void)gp->scr_move(sp, oldy, oldx);\n                        (void)gp->scr_insertln(sp);\n                }\n        }\n        return (0);\n}\n\n/*\n * vs_sm_next --\n *      Fill in the next entry in the SMAP.\n *\n * PUBLIC: int vs_sm_next(SCR *, SMAP *, SMAP *);\n */\nint\nvs_sm_next(SCR *sp, SMAP *p, SMAP *t)\n{\n        size_t lcnt;\n\n        SMAP_FLUSH(t);\n        if (O_ISSET(sp, O_LEFTRIGHT)) {\n                t->lno = p->lno + 1;\n                t->coff = p->coff;\n        } else {\n                lcnt = vs_screens(sp, p->lno, NULL);\n                if (lcnt == p->soff) {\n                        t->lno = p->lno + 1;\n                        t->soff = 1;\n                } else {\n                        t->lno = p->lno;\n                        t->soff = p->soff + 1;\n                }\n        }\n        return (0);\n}\n\n/*\n * vs_sm_prev --\n *      Fill in the previous entry in the SMAP.\n *\n * PUBLIC: int vs_sm_prev(SCR *, SMAP *, SMAP *);\n */\nint\nvs_sm_prev(SCR *sp, SMAP *p, SMAP *t)\n{\n        SMAP_FLUSH(t);\n        if (O_ISSET(sp, O_LEFTRIGHT)) {\n                t->lno = p->lno - 1;\n                t->coff = p->coff;\n        } else {\n                if (p->soff != 1) {\n                        t->lno = p->lno;\n                        t->soff = p->soff - 1;\n                } else {\n                        t->lno = p->lno - 1;\n                        t->soff = vs_screens(sp, t->lno, NULL);\n                }\n        }\n        return (t->lno == 0);\n}\n\n/*\n * vs_sm_cursor --\n *      Return the SMAP entry referenced by the cursor.\n *\n * PUBLIC: int vs_sm_cursor(SCR *, SMAP **);\n */\nint\nvs_sm_cursor(SCR *sp, SMAP **smpp)\n{\n        SMAP *p;\n\n        /* See if the cursor is not in the map. */\n        if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)\n                return (1);\n\n        /* Find the first occurrence of the line. */\n        for (p = HMAP; p->lno != sp->lno; ++p);\n\n        /* Fill in the map information until we find the right line. */\n        for (; p <= TMAP; ++p) {\n                /* Short lines are common and easy to detect. */\n                if (p != TMAP && (p + 1)->lno != p->lno) {\n                        *smpp = p;\n                        return (0);\n                }\n                if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))\n                        return (1);\n                if (p->c_eboff >= sp->cno) {\n                        *smpp = p;\n                        return (0);\n                }\n        }\n\n        /* It was past the end of the map after all. */\n        return (1);\n}\n\n/*\n * vs_sm_position --\n *      Return the line/column of the top, middle or last line on the screen.\n *      (The vi H, M and L commands.)  Here because only the screen routines\n *      know what's really out there.\n *\n * PUBLIC: int vs_sm_position(SCR *, MARK *, unsigned long, pos_t);\n */\nint\nvs_sm_position(SCR *sp, MARK *rp, unsigned long cnt, pos_t pos)\n{\n        SMAP *smp;\n        recno_t last;\n\n        switch (pos) {\n        case P_TOP:\n                /*\n                 * !!!\n                 * Historically, an invalid count to the H command failed.\n                 * We do nothing special here, just making sure that H in\n                 * an empty screen works.\n                 */\n                if (cnt > TMAP - HMAP)\n                        goto sof;\n                smp = HMAP + cnt;\n                if (cnt && !db_exist(sp, smp->lno)) {\nsof:                    msgq(sp, M_BERR, \"Movement past the end-of-screen\");\n                        return (1);\n                }\n                break;\n        case P_MIDDLE:\n                /*\n                 * !!!\n                 * Historically, a count to the M command was ignored.\n                 * If the screen isn't filled, find the middle of what's\n                 * real and move there.\n                 */\n                if (!db_exist(sp, TMAP->lno)) {\n                        if (db_last(sp, &last))\n                                return (1);\n                        for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);\n                        if (smp > HMAP)\n                                smp -= (smp - HMAP) / 2;\n                } else\n                        smp = (HMAP + (TMAP - HMAP) / 2) + cnt;\n                break;\n        case P_BOTTOM:\n                /*\n                 * !!!\n                 * Historically, an invalid count to the L command failed.\n                 * If the screen isn't filled, find the bottom of what's\n                 * real and try to offset from there.\n                 */\n                if (cnt > TMAP - HMAP)\n                        goto eof;\n                smp = TMAP - cnt;\n                if (!db_exist(sp, smp->lno)) {\n                        if (db_last(sp, &last))\n                                return (1);\n                        for (; smp->lno > last && smp > HMAP; --smp);\n                        if (cnt > smp - HMAP) {\neof:                            msgq(sp, M_BERR,\n                            \"Movement past the beginning-of-screen\");\n                                return (1);\n                        }\n                        smp -= cnt;\n                }\n                break;\n        default:\n                abort();\n        }\n\n        /* Make sure that the cached information is valid. */\n        if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))\n                return (1);\n        rp->lno = smp->lno;\n        rp->cno = smp->c_sboff;\n\n        return (0);\n}\n\n/*\n * vs_sm_nlines --\n *      Return the number of screen lines from an SMAP entry to the\n *      start of some file line, less than a maximum value.\n *\n * PUBLIC: recno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t);\n */\nrecno_t\nvs_sm_nlines(SCR *sp, SMAP *from_sp, recno_t to_lno, size_t max)\n{\n        recno_t lno, lcnt;\n\n        if (O_ISSET(sp, O_LEFTRIGHT))\n                return (from_sp->lno > to_lno ?\n                    from_sp->lno - to_lno : to_lno - from_sp->lno);\n\n        if (from_sp->lno == to_lno)\n                return (from_sp->soff - 1);\n\n        if (from_sp->lno > to_lno) {\n                lcnt = from_sp->soff - 1;       /* Correct for off-by-one. */\n                for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)\n                        lcnt += vs_screens(sp, lno, NULL);\n        } else {\n                lno = from_sp->lno;\n                lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;\n                for (; ++lno < to_lno && lcnt <= max;)\n                        lcnt += vs_screens(sp, lno, NULL);\n        }\n        return (lcnt);\n}\n"
  },
  {
    "path": "vi/vs_split.c",
    "content": "/*      $OpenBSD: vs_split.c,v 1.16 2016/05/27 09:18:12 martijn Exp $   */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1993, 1994\n *      The Regents of the University of California.  All rights reserved.\n * Copyright (c) 1993, 1994, 1995, 1996\n *      Keith Bostic.  All rights reserved.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * See the LICENSE.md file for redistribution information.\n */\n\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <sys/time.h>\n\n#include <bitstring.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n\n#include \"../common/common.h\"\n#include \"vi.h\"\n\nstatic SCR *vs_getbg(SCR *, char *);\n\n/*\n * vs_split --\n *      Create a new screen.\n *\n * PUBLIC: int vs_split(SCR *, SCR *, int);\n */\nint\nvs_split(SCR *sp, SCR *new, int ccl)\n{\n        GS *gp;\n        SMAP *smp;\n        size_t half;\n        int issmallscreen, splitup;\n\n        gp = sp->gp;\n\n        /* Check to see if it's possible. */\n        /* XXX: The IS_ONELINE fix will change this, too. */\n        if (sp->rows < 4) {\n                msgq(sp, M_ERR,\n                    \"Screen must be larger than %d lines to split\", 4 - 1);\n                return (1);\n        }\n\n        /* Wait for any messages in the screen. */\n        vs_resolve(sp, NULL, 1);\n\n        half = sp->rows / 2;\n        if (ccl && half > 6)\n                half = 6;\n\n        /* Get a new screen map. */\n        CALLOC(sp, _HMAP(new), SIZE_HMAP(sp), sizeof(SMAP));\n        if (_HMAP(new) == NULL)\n                return (1);\n        _HMAP(new)->lno = sp->lno;\n        _HMAP(new)->coff = 0;\n        _HMAP(new)->soff = 1;\n\n        /*\n         * Small screens: see vs_refresh.c section 6a.  Set a flag so\n         * we know to fix the screen up later.\n         */\n        issmallscreen = IS_SMALL(sp);\n\n        /* The columns in the screen don't change. */\n        new->cols = sp->cols;\n\n        /*\n         * Split the screen, and link the screens together.  If creating a\n         * screen to edit the colon command line or the cursor is in the top\n         * half of the current screen, the new screen goes under the current\n         * screen.  Else, it goes above the current screen.\n         *\n         * Recalculate current cursor position based on sp->lno, we're called\n         * with the cursor on the colon command line.  Then split the screen\n         * in half and update the shared information.\n         */\n        splitup =\n            !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half;\n        if (splitup) {                          /* Old is bottom half. */\n                new->rows = sp->rows - half;    /* New. */\n                new->woff = sp->woff;\n                sp->rows = half;                /* Old. */\n                sp->woff += new->rows;\n                                                /* Link in before old. */\n                TAILQ_INSERT_BEFORE(sp, new, q);\n\n                /*\n                 * If the parent is the bottom half of the screen, shift\n                 * the map down to match on-screen text.\n                 */\n                memmove(_HMAP(sp), _HMAP(sp) + new->rows,\n                    (sp->t_maxrows - new->rows) * sizeof(SMAP));\n        } else {                                /* Old is top half. */\n                new->rows = half;               /* New. */\n                sp->rows -= half;               /* Old. */\n                new->woff = sp->woff + sp->rows;\n                                                /* Link in after old. */\n                TAILQ_INSERT_AFTER(&gp->dq, sp, new, q);\n        }\n\n        /* Adjust maximum text count. */\n        sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1;\n        new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1;\n\n        /*\n         * Small screens: see vs_refresh.c, section 6a.\n         *\n         * The child may have different screen options sizes than the parent,\n         * so use them.  Guarantee that text counts aren't larger than the\n         * new screen sizes.\n         */\n        if (issmallscreen) {\n                /* Fix the text line count for the parent. */\n                if (splitup)\n                        sp->t_rows -= new->rows;\n\n                /* Fix the parent screen. */\n                if (sp->t_rows > sp->t_maxrows)\n                        sp->t_rows = sp->t_maxrows;\n                if (sp->t_minrows > sp->t_maxrows)\n                        sp->t_minrows = sp->t_maxrows;\n\n                /* Fix the child screen. */\n                new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);\n                if (new->t_rows > new->t_maxrows)\n                        new->t_rows = new->t_maxrows;\n                if (new->t_minrows > new->t_maxrows)\n                        new->t_minrows = new->t_maxrows;\n        } else {\n                sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1;\n\n                /*\n                 * The new screen may be a small screen, even if the parent\n                 * was not.  Don't complain if O_WINDOW is too large, we're\n                 * splitting the screen so the screen is much smaller than\n                 * normal.\n                 */\n                new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);\n                if (new->t_rows > new->rows - 1)\n                        new->t_minrows = new->t_rows =\n                            IS_ONELINE(new) ? 1 : new->rows - 1;\n        }\n\n        /* Adjust the ends of the new and old maps. */\n        _TMAP(sp) = IS_ONELINE(sp) ?\n            _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1);\n        _TMAP(new) = IS_ONELINE(new) ?\n            _HMAP(new) : _HMAP(new) + (new->t_rows - 1);\n\n        /* Reset the length of the default scroll. */\n        if ((sp->defscroll = sp->t_maxrows / 2) == 0)\n                sp->defscroll = 1;\n        if ((new->defscroll = new->t_maxrows / 2) == 0)\n                new->defscroll = 1;\n\n        /*\n         * Initialize the screen flags:\n         *\n         * If we're in vi mode in one screen, we don't have to reinitialize.\n         * This isn't just a cosmetic fix.  The path goes like this:\n         *\n         *      return into vi(), SC_SSWITCH set\n         *      call vs_refresh() with SC_STATUS set\n         *      call vs_resolve to display the status message\n         *      call vs_refresh() because the SC_SCR_VI bit isn't set\n         *\n         * Things go downhill at this point.\n         *\n         * Draw the new screen from scratch, and add a status line.\n         */\n        F_SET(new,\n            SC_SCR_REFORMAT | SC_STATUS |\n            F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX));\n        return (0);\n}\n\n/*\n * vs_discard --\n *      Discard the screen, folding the real-estate into a related screen,\n *      if one exists, and return that screen.\n *\n * PUBLIC: int vs_discard(SCR *, SCR **);\n */\nint\nvs_discard(SCR *sp, SCR **spp)\n{\n        SCR *nsp;\n        dir_t dir;\n\n        /*\n         * Save the old screen's cursor information.\n         *\n         * XXX\n         * If called after file_end(), and the underlying file was a tmp\n         * file, it may have gone away.\n         */\n        if (sp->frp != NULL) {\n                sp->frp->lno = sp->lno;\n                sp->frp->cno = sp->cno;\n                F_SET(sp->frp, FR_CURSORSET);\n        }\n\n        /*\n         * Add into a previous screen and then into a subsequent screen, as\n         * they're the closest to the current screen.  If that doesn't work,\n         * there was no screen to join.\n         */\n        if ((nsp = TAILQ_PREV(sp, _dqh, q))) {\n                nsp->rows += sp->rows;\n                sp = nsp;\n                dir = FORWARD;\n        } else if ((nsp = TAILQ_NEXT(sp, q))) {\n                nsp->woff = sp->woff;\n                nsp->rows += sp->rows;\n                sp = nsp;\n                dir = BACKWARD;\n        } else {\n                sp = NULL;\n                dir = 0;        /* unused */\n        }\n\n        if (spp != NULL)\n                *spp = sp;\n        if (sp == NULL)\n                return (0);\n\n        /*\n         * Make no effort to clean up the discarded screen's information.  If\n         * it's not exiting, we'll do the work when the user redisplays it.\n         *\n         * Small screens: see vs_refresh.c section 6a.  Adjust text line info,\n         * unless it's a small screen.\n         *\n         * Reset the length of the default scroll.\n         */\n        if (!IS_SMALL(sp))\n                sp->t_rows = sp->t_minrows = sp->rows - 1;\n        sp->t_maxrows = sp->rows - 1;\n        sp->defscroll = sp->t_maxrows / 2;\n        *(HMAP + (sp->t_rows - 1)) = *TMAP;\n        TMAP = HMAP + (sp->t_rows - 1);\n\n        /*\n         * Draw the new screen from scratch, and add a status line.\n         *\n         * XXX\n         * We could play games with the map, if this were ever to be a\n         * performance problem, but I wrote the code a few times and it\n         * was never clean or easy.\n         */\n        switch (dir) {\n        case FORWARD:\n                vs_sm_fill(sp, OOBLNO, P_TOP);\n                break;\n        case BACKWARD:\n                vs_sm_fill(sp, OOBLNO, P_BOTTOM);\n                break;\n        default:\n                abort();\n        }\n\n        F_SET(sp, SC_STATUS);\n        return (0);\n}\n\n/*\n * vs_fg --\n *      Background the current screen, and foreground a new one.\n *\n * PUBLIC: int vs_fg(SCR *, SCR **, CHAR_T *, int);\n */\nint\nvs_fg(SCR *sp, SCR **nspp, CHAR_T *name, int newscreen)\n{\n        GS *gp;\n        SCR *nsp;\n\n        gp = sp->gp;\n\n        if (newscreen)\n                /* Get the specified background screen. */\n                nsp = vs_getbg(sp, name);\n        else\n                /* Swap screens. */\n                if (vs_swap(sp, &nsp, name))\n                        return (1);\n\n        if ((*nspp = nsp) == NULL) {\n                msgq_str(sp, M_ERR, name,\n                    name == NULL ?\n                    \"There are no background screens\" :\n                    \"There's no background screen editing a file named %s\");\n                return (1);\n        }\n\n        if (newscreen) {\n                /* Remove the new screen from the background queue. */\n                TAILQ_REMOVE(&gp->hq, nsp, q);\n\n                /* Split the screen; if we fail, hook the screen back in. */\n                if (vs_split(sp, nsp, 0)) {\n                        TAILQ_INSERT_TAIL(&gp->hq, nsp, q);\n                        return (1);\n                }\n        } else {\n                /* Move the old screen to the background queue. */\n                TAILQ_REMOVE(&gp->dq, sp, q);\n                TAILQ_INSERT_TAIL(&gp->hq, sp, q);\n        }\n        return (0);\n}\n\n/*\n * vs_bg --\n *      Background the screen, and switch to the next one.\n *\n * PUBLIC: int vs_bg(SCR *);\n */\nint\nvs_bg(SCR *sp)\n{\n        GS *gp;\n        SCR *nsp;\n\n        gp = sp->gp;\n\n        /* Try and join with another screen. */\n        if (vs_discard(sp, &nsp))\n                return (1);\n        if (nsp == NULL) {\n                msgq(sp, M_ERR,\n                    \"You may not background your only displayed screen\");\n                return (1);\n        }\n\n        /* Move the old screen to the background queue. */\n        TAILQ_REMOVE(&gp->dq, sp, q);\n        TAILQ_INSERT_TAIL(&gp->hq, sp, q);\n\n        /* Toss the screen map. */\n        free(_HMAP(sp));\n        _HMAP(sp) = NULL;\n\n        /* Switch screens. */\n        sp->nextdisp = nsp;\n        F_SET(sp, SC_SSWITCH);\n\n        return (0);\n}\n\n/*\n * vs_swap --\n *      Swap the current screen with a backgrounded one.\n *\n * PUBLIC: int vs_swap(SCR *, SCR **, char *);\n */\nint\nvs_swap(SCR *sp, SCR **nspp, char *name)\n{\n        GS *gp;\n        SCR *nsp;\n\n        gp = sp->gp;\n\n        /* Get the specified background screen. */\n        if ((*nspp = nsp = vs_getbg(sp, name)) == NULL)\n                return (0);\n\n        /*\n         * Save the old screen's cursor information.\n         *\n         * XXX\n         * If called after file_end(), and the underlying file was a tmp\n         * file, it may have gone away.\n         */\n        if (sp->frp != NULL) {\n                sp->frp->lno = sp->lno;\n                sp->frp->cno = sp->cno;\n                F_SET(sp->frp, FR_CURSORSET);\n        }\n\n        /* Switch screens. */\n        sp->nextdisp = nsp;\n        F_SET(sp, SC_SSWITCH);\n\n        /* Initialize terminal information. */\n        VIP(nsp)->srows = VIP(sp)->srows;\n\n        /* Initialize screen information. */\n        nsp->cols = sp->cols;\n        nsp->rows = sp->rows;   /* XXX: Only place in vi that sets rows. */\n        nsp->woff = sp->woff;\n\n        /*\n         * Small screens: see vs_refresh.c, section 6a.\n         *\n         * The new screens may have different screen options sizes than the\n         * old one, so use them.  Make sure that text counts aren't larger\n         * than the new screen sizes.\n         */\n        if (IS_SMALL(nsp)) {\n                nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW);\n                if (nsp->t_rows > sp->t_maxrows)\n                        nsp->t_rows = nsp->t_maxrows;\n                if (nsp->t_minrows > sp->t_maxrows)\n                        nsp->t_minrows = nsp->t_maxrows;\n        } else\n                nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1;\n\n        /* Reset the length of the default scroll. */\n        nsp->defscroll = nsp->t_maxrows / 2;\n\n        /* Allocate a new screen map. */\n        CALLOC_RET(nsp, _HMAP(nsp), SIZE_HMAP(nsp), sizeof(SMAP));\n        _TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1);\n\n        /* Fill the map. */\n        if (vs_sm_fill(nsp, nsp->lno, P_FILL))\n                return (1);\n\n        /*\n         * The new screen replaces the old screen in the parent/child list.\n         * We insert the new screen after the old one.  If we're exiting,\n         * the exit will delete the old one, if we're foregrounding, the fg\n         * code will move the old one to the background queue.\n         */\n        TAILQ_REMOVE(&gp->hq, nsp, q);\n        TAILQ_INSERT_AFTER(&gp->dq, sp, nsp, q);\n\n        /*\n         * Don't change the screen's cursor information other than to\n         * note that the cursor is wrong.\n         */\n        F_SET(VIP(nsp), VIP_CUR_INVALID);\n\n        /* Draw the new screen from scratch, and add a status line. */\n        F_SET(nsp, SC_SCR_REDRAW | SC_STATUS);\n        return (0);\n}\n\n/*\n * vs_resize --\n *      Change the absolute size of the current screen.\n *\n * PUBLIC: int vs_resize(SCR *, long, adj_t);\n */\nint\nvs_resize(SCR *sp, long count, adj_t adj)\n{\n        GS *gp;\n        SCR *g, *s;\n        size_t g_off, s_off;\n\n        gp = sp->gp;\n        (void)gp;\n\n        /*\n         * Figure out which screens will grow, which will shrink, and\n         * make sure it's possible.\n         */\n        if (count == 0)\n                return (0);\n        if (adj == A_SET) {\n                if (sp->t_maxrows == count)\n                        return (0);\n                if (sp->t_maxrows > count) {\n                        adj = A_DECREASE;\n                        count = sp->t_maxrows - count;\n                } else {\n                        adj = A_INCREASE;\n                        count = count - sp->t_maxrows;\n                }\n        }\n\n        g_off = s_off = 0;\n        if (adj == A_DECREASE) {\n                if (count < 0)\n                        count = -count;\n                s = sp;\n                if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)\n                        goto toosmall;\n                if ((g = TAILQ_PREV(sp, _dqh, q)) == NULL) {\n                        if ((g = TAILQ_NEXT(sp, q)) == NULL)\n                                goto toobig;\n                        g_off = -count;\n                } else\n                        s_off = count;\n        } else {\n                g = sp;\n                if ((s = TAILQ_NEXT(sp, q)))\n                        if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)\n                                s = NULL;\n                        else\n                                s_off = count;\n                else\n                        s = NULL;\n                if (s == NULL) {\n                        if ((s = TAILQ_PREV(sp, _dqh, q)) == NULL) {\ntoobig:                         msgq(sp, M_BERR, adj == A_DECREASE ?\n                                    \"The screen cannot shrink\" :\n                                    \"The screen cannot grow\");\n                                return (1);\n                        }\n                        if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) {\ntoosmall:                       msgq(sp, M_BERR,\n                                    \"The screen can only shrink to %d rows\",\n                                    MINIMUM_SCREEN_ROWS);\n                                return (1);\n                        }\n                        g_off = -count;\n                }\n        }\n\n        /*\n         * Fix up the screens; we could optimize the reformatting of the\n         * screen, but this isn't likely to be a common enough operation\n         * to make it worthwhile.\n         */\n        s->rows += -count;\n        s->woff += s_off;\n        g->rows += count;\n        g->woff += g_off;\n\n        g->t_rows += count;\n        if (g->t_minrows == g->t_maxrows)\n                g->t_minrows += count;\n        g->t_maxrows += count;\n        _TMAP(g) += count;\n        F_SET(g, SC_SCR_REFORMAT | SC_STATUS);\n\n        s->t_rows -= count;\n        s->t_maxrows -= count;\n        if (s->t_minrows > s->t_maxrows)\n                s->t_minrows = s->t_maxrows;\n        _TMAP(s) -= count;\n        F_SET(s, SC_SCR_REFORMAT | SC_STATUS);\n\n        return (0);\n}\n\n/*\n * vs_getbg --\n *      Get the specified background screen, or, if name is NULL, the first\n *      background screen.\n */\nstatic SCR *\nvs_getbg(SCR *sp, char *name)\n{\n        GS *gp;\n        SCR *nsp;\n        char *p;\n\n        gp = sp->gp;\n\n        /* If name is NULL, return the first background screen on the list. */\n        if (name == NULL)\n                return (TAILQ_FIRST(&gp->hq));\n\n        /* Search for a full match. */\n        TAILQ_FOREACH(nsp, &gp->hq, q) {\n                if (!strcmp(nsp->frp->name, name))\n                        return(nsp);\n        }\n\n        /* Search for a last-component match. */\n        TAILQ_FOREACH(nsp, &gp->hq, q) {\n                if ((p = strrchr(nsp->frp->name, '/')) == NULL)\n                        p = nsp->frp->name;\n                else\n                        ++p;\n                if (!strcmp(p, name))\n                        return(nsp);\n        }\n\n        return (NULL);\n}\n"
  },
  {
    "path": "xinstall/xinstall.1",
    "content": ".\\\"        $OpenBSD: install.1,v 1.31 2019/02/08 12:53:44 schwarze Exp $\n.\\\"        $NetBSD: install.1,v 1.4 1994/11/14 04:57:17 jtc Exp $\n.\\\"\n.\\\" SPDX-License-Identifier: BSD-3-Clause\n.\\\"\n.\\\" Copyright (c) 1987, 1990, 1993\n.\\\"        The Regents of the University of California.\n.\\\" Copyright (c) 2022-2024 Jeffrey H. Johnson\n.\\\"\n.\\\" All rights reserved.\n.\\\"\n.\\\" Redistribution and use in source and binary forms, with or without\n.\\\" modification, are permitted provided that the following conditions\n.\\\" are met:\n.\\\"\n.\\\" 1. Redistributions of source code must retain the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer.\n.\\\"\n.\\\" 2. Redistributions in binary form must reproduce the above copyright\n.\\\"    notice, this list of conditions and the following disclaimer in the\n.\\\"    documentation and/or other materials provided with the distribution.\n.\\\"\n.\\\" 3. Neither the name of the University nor the names of its contributors\n.\\\"    may be used to endorse or promote products derived from this software\n.\\\"    without specific prior written permission.\n.\\\"\n.\\\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n.\\\" SUCH DAMAGE.\n.\\\"\n.\\\"     @(#)install.1        8.1 (Berkeley) 6/6/93\n.\\\"\n.Dd $Mdocdate: March 2 2022 $\n.Dt XINSTALL 1\n.Os\n.Sh NAME\n.Nm xinstall\n.Nd install binaries\n.Sh SYNOPSIS\n.Nm xinstall\n.Op Fl bCcDdFpSsUv\n.Op Fl B Ar suffix\n.Op Fl g Ar group\n.Op Fl m Ar mode\n.Op Fl o Ar owner\n.Ar source ... target ...\n.Sh DESCRIPTION\nThe\n.Ar source\nfile(s) are copied to the\n.Ar target\nfile or directory.\nIf the\n.Ar target\nfile already exists, it is either renamed to\n.Ar file.bak\nif the\n.Fl b\noption is given\nor overwritten\nif permissions allow.\nAn alternate backup suffix may be specified via the\n.Fl B\noption's argument.\nIf the\n.Fl d\noption is given,\n.Ar target\ndirectories are created, and no files are copied.\n.Pp\nThe options are as follows:\n.Bl -tag -width \"-B suffix\"\n.It Fl B Ar suffix\nUse\n.Ar suffix\nas the backup suffix if\n.Fl b\nis given.\n.It Fl b\nBackup any existing files before overwriting them by renaming\nthem to\n.Ar file.bak .\nSee\n.Fl B\nfor specifying a different backup suffix.\n.It Fl C\nCompare and copy the file.\nIf the target file already exists and the files are the same,\nthen installation does not change the modification time of the\ntarget.\n.It Fl c\nCopy the file.\nThis is the default.\nThe\n.Fl c\noption is only included for backwards compatibility.\n.It Fl D\nCreate all leading components of the target before installing into it.\nWhen the\n.Fl D\noption is specified, any new or existing directory components will have\nthe default (0755) permissions applied.  If the\n.Fl D\noption is specified in conjunction with\n.Fl m ,\nthe requested mode will be set for the target file.\nIf more restrictive directory permissions are required,\n.Nm\n.Fl d\n.Fl m\nshould be used first to create the directories, followed by\n.Nm\n.Fl c\nto install the files.\n.It Fl d\nCreate directories, without installing files.\nMissing parent directories are created as required. If a directory\nalready exists, the owner, group, and mode is set according to the\nvalues specified on the command line. This option cannot be used with\nthe\n.Fl B , b , C , c ,\n.Fl p ,\nor\n.Fl s\noptions.\n.It Fl F\nFlush the file's contents to disk.\nWhen copying a file, use the\n.Xr fsync 2\nfunction to synchronize the installed file's contents with the\non-disk version.\n.It Fl g Ar group\nSpecify a\n.Ar group .\nA numeric GID is allowed.\n.It Fl m Ar mode\nSpecify an alternate\n.Ar mode .\nThe default mode is set to rwxr-xr-x (0755).\nThe specified mode may be either an octal or symbolic value; see\n.Xr chmod 1\nfor a description of possible mode values.\n.It Fl M\nDisable use of\n.Xr mmap 2\nwhen installing the target.\n.It Fl o Ar owner\nSpecify an\n.Ar owner .\nA numeric UID is allowed.\n.It Fl p\nPreserve the modification time.\nCopy the file, as if the\n.Fl C\n(compare and copy) option is specified,\nexcept if the target file does not exist or is different, then\npreserve the modification time of the source file.\n.It Fl S\nSafe copy.\nThis is the default.  Using this option has no effect, and is\nsupported only for compatibility. When installing a file, a\ntemporary file is safely created and written first in the\ndestination directory, then atomically renamed. This avoids\nboth race conditions and the destruction of existing\nfiles in case of disk or system failures.\n.It Fl s\nStrip the file. The external command\n.Pa /usr/bin/strip\nis called to actually strip the file, so\n.Nm\ncan be portable to a large number of systems and binary types.\nSee below for how\n.Nm\ncan be instructed to use a different program for stripping binaries.\n.It Fl U\nIndicate that\n.Nm\nis running unprivileged. Any errors while setting the owner, group,\nor mode of the target will be non-fatal to the installation\nprocess.\n.It Fl v\nCause\n.Nm\nto be verbose. Progress information will be printed to standard output\nas directories are created and files are installed or backed up.\n.El\n.Pp\nThe\n.Nm\nutility attempts to prevent moving a file onto itself.\n.Pp\nInstalling\n.Pa /dev/null\ncreates an empty file.\n.Sh ENVIRONMENT\nThe\n.Nm\nutility checks for the presence of the\nSTRIPBIN\nand\nSTRIP\nenvironment variables. If defined, the assigned value will be\nused as the\n.Xr strip 1\nprogram to run. If both variables are set,\nSTRIP\nis overriding.\nThe default strip(1) program is\n.Pa /usr/bin/strip .\n.Pp\nIf the\nDONTSTRIP\nenvironment variable is present,\n.Nm\nwill not strip files, ignoring any specification of the\n.Fl s\noption.\n.Sh FILES\n.Bl -tag -width INS@XXXXXX -compact\n.It Pa INS@XXXXXX\nTemporary files created in the target directory by\n.Xr mkstemp 3 .\n.El\n.Sh EXIT STATUS\n.Ex -std xinstall\n.Sh SEE ALSO\n.Xr chgrp 1 ,\n.Xr chmod 1 ,\n.Xr cmp 1 ,\n.Xr cp 1 ,\n.Xr ln 1 ,\n.Xr mv 1 ,\n.Xr strip 1 ,\n.Xr mmap 2 ,\n.Xr chown 8\n.Sh HISTORY\nThe\n.Nm\nutility first appeared in\n.Bx 4.2 .\n.Sh CAVEATS\n.Nm\nis not standardized by POSIX.  Furthermore, there there is no fully\ncompatible subset of options available across all systems which\nimplement the\n.Nm\nutility. Even amongst the BSD-derived systems, the\n.Fl C ,\n.Fl D ,\n.Fl F ,\n.Fl p ,\n.Fl S ,\n.Fl U ,\nand\n.Fl v\nflags are non-standard, and cannot be relied upon for portability.\n.Pp\nErrors when stripping files are not fatal to the\n.Nm\nprocess.\n.Pp\nTemporary files may be left in the target directory if\n.Nm\nexits abnormally.\n.Pp\n.Nm\noptions should be specified before any sources or the target, to\navoid ambiguities between filenames, directories, and options\nwhen\n.Nm\nparses the command line.\n.Pp\nThe exact behavior of\n.Nm\nvaries depending on the operating system and filesystem.\n.Pp\nError messages, warnings, and verbose feedback could be improved.\n"
  },
  {
    "path": "xinstall/xinstall.c",
    "content": "/*      $OpenBSD: xinstall.c,v 1.78 2024/10/17 15:38:38 millert Exp $        */\n/*      $NetBSD:  xinstall.c,v 1.9  1995/12/20 10:25:17 jonathan Exp $       */\n\n/* SPDX-License-Identifier: BSD-3-Clause */\n\n/*\n * Copyright (c) 1987, 1990, 1993\n *        The Regents of the University of California.\n * Copyright (c) 2022-2024 Jeffrey H. Johnson\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"../include/compat.h\"\n\n#ifdef __solaris__\n# define _XPG7\n#endif /* ifdef __solaris__ */\n\n#include <sys/mman.h>\n\n#ifdef __illumos__\n# undef madvise\n# define madvise posix_madvise\n#endif /* ifdef __illumos__ */\n\n#ifdef _AIX\n# define _POSIX_SOURCE\n# define _XOPEN_SOURCE 700\n# undef _ALL_SOURCE\n#endif /* ifdef _AIX */\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n\n#include <ctype.h>\n#include <bsd_err.h>\n#include <errno.h>\n#include <bsd_fcntl.h>\n#include <grp.h>\n#include <libgen.h>\n#include <limits.h>\n#include <paths.h>\n#include <pwd.h>\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <bsd_stdlib.h>\n#include <bsd_string.h>\n#include <bsd_unistd.h>\n\n#include \"errc.h\"\n#include \"setmode.h\"\n#include \"minpwcache.h\"\n#include \"pathnames.h\"\n\n#ifndef EFTYPE\n# define EFTYPE EINVAL\n#endif /* ifndef EFTYPE */\n\n#undef open\n\n#define _MAXBSIZE ( 64 * 1024 )\n\n#define MINIMUM(a, b) ((( a ) < ( b )) ? ( a ) : ( b ))\n\n#define DIRECTORY      0x01   /* Tell install it's a directory. */\n#define USEFSYNC       0x04   /* Tell install to use fsync(2).  */\n#define BACKUP_SUFFIX \".bak\"\n\nint dobackup, docompare, dodest, dodir, nommap;\nint dopreserve, dostrip, dounpriv, doverbose;\nint mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;\n\nchar pathbuf[PATH_MAX], tempfile[PATH_MAX];\nchar *suffix = BACKUP_SUFFIX;\n\nuid_t uid = (uid_t)-1;\ngid_t gid = (gid_t)-1;\n\nstatic void copy(int, char *, int, char *, off_t, int);\nstatic int  compare(int, const char *, off_t, int, const char *, off_t);\nstatic void install(char *, char *, unsigned int);\nstatic void install_dir(char *, int);\nstatic void strip(char *);\nstatic void usage(void);\nstatic int  create_tempfile(char *, char *, size_t);\nstatic int  file_write(int, char *, size_t, int *, int *, int);\nstatic void file_flush(int, int);\n\nint\nmain(int argc, char *argv[])\n{\n  struct stat from_sb, to_sb;\n  void *set;\n  unsigned int iflags;\n  int ch, no_target;\n  char *to_name, *group = NULL, *owner = NULL;\n  const char *errstr;\n\n  iflags = 0;\n  while (( ch = openbsd_getopt(argc, argv, \"B:bCcDdFg:Mm:o:pSsUv\")) != -1)\n    {\n      switch (ch)\n        {\n        case 'C':\n          docompare = 1;\n          break;\n\n        case 'B':\n          suffix = openbsd_optarg;\n        /*FALLTHROUGH*/ /* fall through */\n\n        /* fall through; -B implies -b */\n        case 'b':\n          dobackup = 1;\n          break;\n\n        case 'c':\n          /* For backwards compatibility. */\n          break;\n\n        case 'F':\n          iflags |= USEFSYNC;\n          break;\n\n        case 'g':\n          group = openbsd_optarg;\n          break;\n\n        case 'm':\n          if (!( set = openbsd_setmode(openbsd_optarg)))\n            {\n              openbsd_errx(1, \"%s: invalid file mode\", openbsd_optarg);\n            }\n\n          mode = openbsd_getmode(set, 0);\n          free(set);\n          break;\n\n        case 'M':\n          nommap = 1;\n          break;\n\n        case 'o':\n          owner = openbsd_optarg;\n          break;\n\n        case 'p':\n          docompare = dopreserve = 1;\n          break;\n\n        case 'S':\n          /* For backwards compatibility. */\n          break;\n\n        case 's':\n          if (getenv(\"DONTSTRIP\") == NULL)\n            dostrip = 1;\n          break;\n\n        case 'U':\n          dounpriv = 1;\n          break;\n\n        case 'v':\n          doverbose = 1;\n          break;\n\n        case 'D':\n          dodest = 1;\n          break;\n\n        case 'd':\n          dodir = 1;\n          break;\n\n        default:\n          usage();\n        }\n    }\n  argc -= openbsd_optind;\n  argv += openbsd_optind;\n\n  /* some options make no sense when creating directories */\n  if (( docompare || dostrip ) && dodir)\n    {\n      usage();\n    }\n\n  /* must have at least two arguments, except when creating directories */\n  if (argc == 0 || ( argc == 1 && !dodir ))\n    {\n      usage();\n    }\n\n  /* get group and owner id's */\n  if (group != NULL && openbsd_gid_from_group(group, &gid) == -1)\n    {\n      gid = strtonum(group, 0, GID_MAX, &errstr);\n      if (errstr != NULL)\n        {\n          openbsd_errx(1, \"unknown group '%s'\", group);\n        }\n    }\n\n  if (owner != NULL && openbsd_uid_from_user(owner, &uid) == -1)\n    {\n      uid = strtonum(owner, 0, UID_MAX, &errstr);\n      if (errstr != NULL)\n        {\n          openbsd_errx(1, \"unknown user '%s'\", owner);\n        }\n    }\n\n  if (dodir)\n    {\n      for (; *argv != NULL; ++argv)\n        {\n          install_dir(*argv, mode);\n        }\n\n      exit(0);\n      /*NOTREACHED*/ /* unreachable */\n    }\n\n  if (dodest)\n    {\n      char *odst = strdup(argv[argc - 1]);\n      const int wlen = strlen(odst);\n      if ( wlen > 0 && odst[wlen-1] == '/' )\n        {\n          openbsd_errc(1, EFTYPE, \"%s\", odst);\n        }\n      char *dest = openbsd_dirname(odst);\n      if (dest == NULL)\n        {\n          openbsd_errx(1, \"cannot determine dirname\");\n        }\n\n      /*\n       * When -D is passed, do not chmod the directory with the mode set for\n       * the target file. If more restrictive permissions are required then\n       * '-d -m' ought to be used instead.\n       */\n\n      if (strcmp(dest, \".\"))\n        install_dir(dest, 0755);\n    }\n\n  no_target = stat(to_name = argv[argc - 1], &to_sb);\n  if (!no_target && S_ISDIR(to_sb.st_mode))\n    {\n      for (; *argv != to_name; ++argv)\n        {\n          install(*argv, to_name, iflags | DIRECTORY);\n        }\n\n      exit(0);\n      /*NOTREACHED*/ /* unreachable */\n    }\n\n  /* can't do file1 file2 directory/file */\n  if (argc != 2)\n    {\n      openbsd_errx(\n        1,\n        \"Invalid arguments for target '%s'; verify specified options.\",\n        argv[argc - 1]);\n    }\n\n  if (!no_target)\n    {\n      if (stat(*argv, &from_sb))\n        {\n          openbsd_err(1, \"%s\", *argv);\n        }\n\n      if (!S_ISREG(to_sb.st_mode))\n        {\n          openbsd_errc(1, EFTYPE, \"%s\", to_name);\n        }\n\n      if (to_sb.st_dev == from_sb.st_dev && to_sb.st_ino == from_sb.st_ino)\n        {\n          openbsd_errx(1, \"'%s' and '%s' are the same file\", *argv, to_name);\n        }\n    }\n\n  install(*argv, to_name, iflags);\n  exit(0);\n  /*NOTREACHED*/ /* unreachable */\n}\n\n/*\n * install --\n *      build a path name and install the file\n */\n\nstatic void\ninstall(char *from_name, char *to_name, unsigned int flags)\n{\n  struct stat from_sb, to_sb;\n  struct timespec ts[2];\n  int devnull, from_fd, to_fd, serrno, files_match = 0;\n  char *p = NULL;\n  char *target_name = tempfile;\n\n  (void)memset((void *)&from_sb, 0, sizeof ( from_sb ));\n  (void)memset((void *)&to_sb, 0, sizeof ( to_sb ));\n\n  /* If try to install NULL file to a directory, fails. */\n  if (flags & DIRECTORY || strcmp(from_name, \"/dev/null\"))\n    {\n      if (stat(from_name, &from_sb))\n        {\n          openbsd_err(1, \"%s\", from_name);\n        }\n\n      if (!S_ISREG(from_sb.st_mode))\n        {\n          openbsd_errc(1, EFTYPE, \"%s\", from_name);\n        }\n\n      /* Build the target path. */\n      if (flags & DIRECTORY)\n        {\n          (void)snprintf(pathbuf, sizeof ( pathbuf ), \"%s/%s\", to_name,\n            ( p = strrchr(from_name, '/')) ? ++p : from_name);\n          to_name = pathbuf;\n        }\n\n      devnull = 0;\n    }\n  else\n    {\n      devnull = 1;\n    }\n\n  if (stat(to_name, &to_sb) == 0)\n    {\n      /* Only compare against regular files. */\n      if (docompare && !S_ISREG(to_sb.st_mode))\n        {\n          docompare = 0;\n          openbsd_warnc(EFTYPE, \"%s\", to_name);\n        }\n    }\n  else if (docompare)\n    {\n      /* File does not exist so silently ignore compare flag. */\n      docompare = 0;\n    }\n\n  if (!devnull)\n    {\n      if (( from_fd = open(from_name, O_RDONLY)) == -1)\n        {\n          openbsd_err(1, \"%s\", from_name);\n        }\n    }\n\n  to_fd = create_tempfile(to_name, tempfile, sizeof ( tempfile ));\n  if (to_fd < 0)\n    {\n      openbsd_err(1, \"%s\", tempfile);\n    }\n\n  if (!devnull)\n    {\n      copy(from_fd, from_name, to_fd, tempfile, from_sb.st_size,\n        ((off_t)from_sb.st_blocks * S_BLKSIZE < from_sb.st_size ));\n    }\n\n  if (dostrip)\n    {\n      strip(tempfile);\n\n      /*\n       * Re-open our fd on the target, in case we used a strip\n       *  that does not work in-place -- like gnu binutils strip.\n       */\n\n      (void)close(to_fd);\n      if (( to_fd = open(tempfile, O_RDONLY)) == -1)\n        {\n          openbsd_err(1, \"stripping %s\", to_name);\n        }\n    }\n\n  /*\n   * Compare the (possibly stripped) temp file to the target.\n   */\n\n  if (docompare)\n    {\n      int temp_fd = to_fd;\n      struct stat temp_sb;\n\n      /* Re-open to_fd using the real target name. */\n      if (( to_fd = open(to_name, O_RDONLY)) == -1)\n        {\n          openbsd_err(1, \"%s\", to_name);\n        }\n\n      if (fstat(temp_fd, &temp_sb))\n        {\n          serrno = errno;\n          (void)unlink(tempfile);\n          openbsd_errc(1, serrno, \"%s\", tempfile);\n        }\n\n      if (compare(temp_fd, tempfile, temp_sb.st_size, to_fd, to_name,\n            to_sb.st_size) == 0)\n        {\n          /*\n           * If target has more than one link we need to\n           * replace it in order to snap the extra links.\n           * Need to preserve target file times, though.\n           */\n          if (to_sb.st_nlink != 1)\n            {\n              ts[0] = to_sb.st_atim;\n              ts[1] = to_sb.st_mtim;\n              (void)futimens(temp_fd, ts);\n            }\n          else\n            {\n              files_match = 1;\n              (void)unlink(tempfile);\n              target_name = to_name;\n              (void)close(temp_fd);\n            }\n        }\n\n      if (!files_match)\n        {\n          (void)close(to_fd);\n          to_fd = temp_fd;\n        }\n    }\n\n  /*\n   * Preserve the timestamp of the source file if necessary.\n   */\n\n  if (dopreserve && !files_match)\n    {\n      ts[0] = from_sb.st_atim;\n      ts[1] = from_sb.st_mtim;\n      (void)futimens(to_fd, ts);\n    }\n\n  /*\n   * Set owner, group, mode for target; do the chown first,\n   * chown may lose the setuid bits.\n   */\n\n  if (!dounpriv && ( gid != (gid_t)-1 || uid != (uid_t)-1 ) && fchown(to_fd, uid, gid))\n    {\n      serrno = errno;\n      if (target_name == tempfile)\n        {\n          (void)unlink(target_name);\n        }\n\n      openbsd_errx(1, \"%s: chown/chgrp: %s\", target_name, strerror(serrno));\n    }\n\n  if (!dounpriv && fchmod(to_fd, mode))\n    {\n      serrno = errno;\n      if (target_name == tempfile)\n        {\n          (void)unlink(target_name);\n        }\n\n      openbsd_errx(1, \"%s: chmod: %s\", target_name, strerror(serrno));\n    }\n\n  if (flags & USEFSYNC)\n    {\n      (void)fsync(to_fd);\n    }\n\n  (void)close(to_fd);\n  if (!devnull)\n    {\n      (void)close(from_fd);\n    }\n\n  /*\n   * Move the new file into place if the files are different\n   * or were not compared.\n   */\n\n  if (!files_match)\n    {\n      if (dobackup)\n        {\n          char backup[PATH_MAX];\n          (void)snprintf(backup, PATH_MAX, \"%s%s\", to_name, suffix);\n          struct stat bdst;\n          int bdir = stat(to_name, &bdst);\n          /* It is ok for the target file not to exist. */\n          int bres = 0;\n          if (!bdir && !(S_ISDIR(bdst.st_mode)))\n            bres = rename(to_name, backup);\n          int berr = errno;\n          if (bres == -1 && berr != ENOENT)\n            {\n              serrno = errno;\n              (void)unlink(tempfile);\n              openbsd_errx(1, \"rename: '%s' to '%s': %s\",\n                           to_name, backup, strerror(serrno));\n            }\n          if (berr != ENOENT && doverbose && (!bdir && !S_ISDIR(bdst.st_mode)))\n            (void)fprintf(stdout, \"%s: created backup '%s' -> '%s'\\n\",\n                    bsd_getprogname(), to_name, backup);\n        }\n\n      if (rename(tempfile, to_name) == -1)\n        {\n          serrno = errno;\n          (void)unlink(tempfile);\n          openbsd_errx(1, \"rename: '%s' to '%s': %s\",\n                       tempfile, to_name, strerror(serrno));\n        }\n    }\n  if (doverbose)\n    (void)fprintf(stdout, \"%s: installed '%s' -> '%s' (mode %o)\\n\",\n            bsd_getprogname(), from_name, to_name, mode);\n}\n\n/*\n * copy --\n *      copy from one file to another\n */\n\nstatic void\ncopy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size,\n     int sparse)\n{\n  ssize_t nr, nw;\n  int serrno;\n  char *p = NULL;\n  char buf[_MAXBSIZE];\n\n  if (size == 0)\n    {\n      return;\n    }\n\n  /* Rewind file descriptors. */\n  if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)\n    {\n      openbsd_err(1, \"lseek: %s\", from_name);\n    }\n\n  if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)\n    {\n      openbsd_err(1, \"lseek: %s\", to_name);\n    }\n\n  /*\n   * Mmap and write if less than 2 MiB (the limit is so we don't totally\n   * trash memory on big files.  This is really a minor hack, but it wins\n   * some CPU back.  Sparse files need special treatment.\n   */\n\n  if (!nommap && !sparse && size <= 2 * 1048576)\n    {\n      size_t siz;\n\n      if (( p = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE, from_fd,\n              (off_t)0)) == MAP_FAILED)\n        {\n          serrno = errno;\n          (void)unlink(to_name);\n          openbsd_errc(1, serrno, \"%s\", from_name);\n        }\n\n#ifdef MADV_SEQUENTIAL\n      (void)madvise(p, size, MADV_SEQUENTIAL);\n#endif /* ifdef MADV_SEQUENTIAL */\n      siz = (size_t)size;\n      if (( nw = write(to_fd, p, siz)) != siz)\n        {\n          serrno = errno;\n          (void)unlink(to_name);\n          openbsd_errx(1, \"%s: %s\", to_name, strerror(nw > 0 ? EIO : serrno));\n        }\n\n      (void)munmap(p, (size_t)size);\n    }\n  else\n    {\n      int sz, rem, isem = 1;\n      struct stat sb;\n\n      /*\n       * Pass the blocksize of the file being written to the write\n       * routine.  If the size is zero, use the default S_BLKSIZE.\n       */\n\n      if (fstat(to_fd, &sb) != 0 || sb.st_blksize == 0)\n        {\n          sz = S_BLKSIZE;\n        }\n      else\n        {\n          sz = sb.st_blksize;\n        }\n\n      rem = sz;\n\n      while (( nr = read(from_fd, buf, sizeof ( buf ))) > 0)\n        {\n          if (sparse)\n            {\n              nw = file_write(to_fd, buf, nr, &rem, &isem, sz);\n            }\n          else\n            {\n              nw = write(to_fd, buf, nr);\n            }\n\n          if (nw != nr)\n            {\n              serrno = errno;\n              (void)unlink(to_name);\n              openbsd_errx(1, \"%s: %s\", to_name, strerror(nw > 0 ? EIO : serrno));\n            }\n        }\n      if (sparse)\n        {\n          file_flush(to_fd, isem);\n        }\n\n      if (nr != 0)\n        {\n          serrno = errno;\n          (void)unlink(to_name);\n          openbsd_errc(1, serrno, \"%s\", from_name);\n        }\n    }\n}\n\n/*\n * compare --\n *      compare two files; non-zero means files differ\n */\n\nstatic int\ncompare(int from_fd, const char *from_name, off_t from_len, int to_fd,\n        const char *to_name, off_t to_len)\n{\n  caddr_t p1, p2;\n  size_t length;\n  off_t from_off, to_off, remainder;\n  int dfound;\n\n  if (from_len == 0 && from_len == to_len)\n    {\n      return 0;\n    }\n\n  if (from_len != to_len)\n    {\n      return 1;\n    }\n\n  /*\n   * Compare the two files being careful not to mmap\n   * more than 2 MiB at a time.\n   */\n\n  from_off = to_off = (off_t)0;\n  remainder = from_len;\n  do\n    {\n      length = MINIMUM(remainder, 2 * 1048576);\n      remainder -= length;\n\n      if (( p1 = mmap(NULL, length, PROT_READ, MAP_PRIVATE, from_fd, from_off))\n          == MAP_FAILED)\n        {\n          openbsd_err(1, \"%s\", from_name);\n        }\n\n      if (( p2 = mmap(NULL, length, PROT_READ, MAP_PRIVATE, to_fd, to_off))\n          == MAP_FAILED)\n        {\n          openbsd_err(1, \"%s\", to_name);\n        }\n\n#ifdef MADV_SEQUENTIAL\n      if (length)\n        {\n          (void)madvise(p1, length, MADV_SEQUENTIAL);\n          (void)madvise(p2, length, MADV_SEQUENTIAL);\n        }\n#endif /* ifdef MADV_SEQUENTIAL */\n\n      dfound = memcmp(p1, p2, length);\n\n      (void)munmap(p1, length);\n      (void)munmap(p2, length);\n\n      from_off += length;\n      to_off += length;\n    }\n  while ( !dfound && remainder > 0 );\n\n  return dfound;\n}\n\n/*\n * strip --\n *      use strip(1) to strip the target file\n */\n\nstatic void\nstrip(char *to_name)\n{\n  int serrno, status;\n  char *volatile path_strip;\n  pid_t pid;\n\n  if ( issetugid() || ( path_strip = getenv(\"STRIP\")) == NULL )\n    if ( issetugid() || ( path_strip = getenv(\"STRIPBIN\")) == NULL )\n      {\n        path_strip = _PATH_STRIP;\n      }\n\n  switch (( pid = fork()))\n    {\n    case -1:\n      serrno = errno;\n      (void)unlink(to_name);\n      openbsd_errc(1, serrno, \"forks\");\n      /* FALLTHROUGH */\n\n    case 0:\n      (void)execl(path_strip, \"strip\", to_name, (char *)NULL);\n      openbsd_warn(\"%s\", path_strip);\n      _exit(1);\n\n    default:\n      while (waitpid(pid, &status, 0) == -1)\n        {\n          if (errno != EINTR)\n            {\n              break;\n            }\n        }\n      if (!WIFEXITED(status))\n        {\n          (void)unlink(to_name);\n        }\n    }\n}\n\n/*\n * install_dir --\n *      build directory hierarchy\n */\n\nstatic void\ninstall_dir(char *path, int mode)\n{\n  char *p = NULL;\n  struct stat sb;\n  int ch;\n\n  for (p = path;; ++p)\n    {\n      if (!*p || ( p != path && *p == '/' ))\n        {\n          ch = *p;\n          *p = '\\0';\n          if (mkdir(path, 0777))\n            {\n              int mkdir_errno = errno;\n              if (stat(path, &sb))\n                {\n                  /* Not there; use mkdir()s errno */\n                  openbsd_errc(1, mkdir_errno, \"%s\", path);\n                  /*NOTREACHED*/ /* unreachable */\n                }\n\n              if (!S_ISDIR(sb.st_mode))\n                {\n                  /* Is there, but isn't a directory */\n                  openbsd_errc(1, ENOTDIR, \"%s\", path);\n                  /*NOTREACHED*/ /* unreachable */\n                }\n            }\n\n          if (!( *p = ch ))\n            {\n              break;\n            }\n        }\n    }\n\n  if ((( gid != (gid_t)-1 || uid != (uid_t)-1 ) && chown(path, uid, gid))\n      || chmod(path, mode))\n    {\n      openbsd_warn(\"%s\", path);\n    }\n  else\n    {\n      if (doverbose)\n        (void)fprintf(stdout, \"%s: created directory '%s' (mode %o)\\n\",\n                bsd_getprogname(), path, mode);\n    }\n}\n\n/*\n * usage --\n *      print a usage message and die\n */\n\nstatic void\nusage(void)\n{\n  (void)fprintf(stderr,\n    \"Usage: %s [-bCcDdFMpSsUv] [-B suffix] [-g group] [-m mode] [-o owner] source ... target ...\\n\",\n    bsd_getprogname());\n  exit(1);\n  /*NOTREACHED*/ /* unreachable */\n}\n\n/*\n * create_tempfile --\n *      create a temporary file based on path and open it\n */\n\nstatic int\ncreate_tempfile(char *path, char *temp, size_t tsize)\n{\n  char *p = NULL;\n\n  if (openbsd_strlcpy(temp, path, tsize) >= tsize)\n    {\n#if defined(ENAMETOOLONG)\n      errno = ENAMETOOLONG;\n#endif\n      return(-1);\n    }\n  if (( p = strrchr(temp, '/')) != NULL)\n    {\n      p++;\n    }\n  else\n    {\n      p = temp;\n    }\n\n  *p = '\\0';\n  if (openbsd_strlcat(temp, \"INS@XXXXXXXXXX\", tsize) >= tsize)\n    {\n#if defined(ENAMETOOLONG)\n      errno = ENAMETOOLONG;\n#endif\n      return(-1);\n    }\n\n  return mkstemp(temp);\n}\n\n/*\n * file_write()\n *\n *      Write/copy a file (during copy or archive extract). This routine knows\n *      how to copy files with lseek holes in it. (Which are read as file\n *      blocks containing all 0's but do not have any file blocks associated\n *      with the data). Typical examples of these are files created by dbm\n *      variants (.pag files). While the file size of these files are huge, the\n *      actual storage is quite small (the files are sparse). The problem is\n *      the holes read as all zeros so are probably stored on the archive that\n *      way (there is no way to determine if the file block is really a hole,\n *      we only know that a file block of all zero's can be a hole).\n *\n *      At this writing, no major archive format knows how to archive files\n *      with holes. However, on extraction (or during copy, -rw) we have to\n *      deal with these files. Without detecting the holes, the files can\n *      consume a lot of file space if just written to disk. This replacement\n *      for write when passed the basic allocation size of a file system block,\n *      uses lseek whenever it detects the input data is all 0 within that\n *      file block. In more detail, the strategy is as follows:\n *\n *      While the input is all zero keep doing an lseek. Keep track of when we\n *      pass over file block boundaries. Only write when we hit a non zero\n *      input. once we have written a file block, we continue to write it to\n *      the end (we stop looking at the input). When we reach the start of the\n *      next file block, start checking for zero blocks again. Working on file\n *      block boundaries significantly reduces the overhead when copying files\n *      that are NOT very sparse. This overhead (when compared to a write) is\n *      almost below the measurement resolution on many systems. Without it,\n *      files with holes cannot be safely copied. It does has a side effect as\n *      it can put holes into files that did not have them before, but that is\n *      not a problem since the file contents are unchanged (in fact it saves\n *      file space). (Except on paging files for diskless clients. But since we\n *      cannot determine one of those file from here, we ignore them). If this\n *      ever ends up on a system where CTG files are supported and the holes\n *      are not desired, just do a conditional test in those routines that\n *      call file_write() and have it call write() instead. BEFORE CLOSING THE\n *      FILE, make sure to call file_flush() when the last write finishes with\n *      an empty block. A lot of file systems will not create an lseek hole at\n *      the end. In this case we drop a single 0 at the end to force the\n *      trailing 0's in the file.\n *\n *      ---Parameters---\n *\n *      rem:    how many bytes left in this file system block\n *      isempt: have we written to the file block yet (is it empty)\n *      sz:     basic file block allocation size\n *      cnt:    number of bytes on this write\n *      str:    buffer to write\n *\n * Return:\n *      number of bytes written, -1 on write (or lseek) error.\n */\n\nstatic int\nfile_write(int fd, char *str, size_t cnt, int *rem, int *isempt, int sz)\n{\n  char *pt;\n  char *end;\n  char *st = str;\n\n  /*\n   * while we have data to process\n   */\n\n  while (cnt)\n    {\n      if (!*rem)\n        {\n\n          /*\n           * We are now at the start of file system block again\n           * (or what we think one is...). start looking for\n           * empty blocks again\n           */\n\n          *isempt = 1;\n          *rem = sz;\n        }\n\n      /*\n       * only examine up to the end of the current file block or\n       * remaining characters to write, whatever is smaller\n       */\n\n      size_t wcnt = MINIMUM(cnt, *rem);\n      cnt -= wcnt;\n      *rem -= wcnt;\n      if (*isempt)\n        {\n\n          /*\n           * have not written to this block yet, so we keep\n           * looking for zero's\n           */\n\n          pt = st;\n          end = st + wcnt;\n\n          /*\n           * look for a zero filled buffer\n           */\n\n          while (( pt < end ) && ( *pt == '\\0' ))\n            {\n              ++pt;\n            }\n\n          if (pt == end)\n            {\n\n              /*\n               * skip, buf is empty so far\n               */\n\n              if (lseek(fd, (off_t)wcnt, SEEK_CUR) == -1)\n                {\n                  openbsd_warn(\"lseek\");\n                  return -1;\n                }\n\n              st = pt;\n              continue;\n            }\n\n          /*\n           * drat, the buf is not zero filled\n           */\n\n          *isempt = 0;\n        }\n\n      /*\n       * have non-zero data in this file system block, have to write\n       */\n\n      if (write(fd, st, wcnt) != wcnt)\n        {\n          openbsd_warn(\"write\");\n          return -1;\n        }\n\n      st += wcnt;\n    }\n  return st - str;\n}\n\n/*\n * file_flush()\n *      when the last file block in a file is zero, many file systems will not\n *      let us create a hole at the end. To get the last block with zeros, we\n *      write the last BYTE with a zero (back up one byte and write a zero).\n */\n\nstatic void\nfile_flush(int fd, int isempt)\n{\n  static char blnk[] = \"\\0\";\n\n  /*\n   * silly test, but make sure we are only called when the last block is\n   * filled with all zeros.\n   */\n\n  if (!isempt)\n    {\n      return;\n    }\n\n  /*\n   * move back one byte and write a zero\n   */\n\n  if (lseek(fd, (off_t)-1, SEEK_CUR) == -1)\n    {\n      openbsd_warn(\"Failed seek on file\");\n      return;\n    }\n\n  if (write(fd, blnk, 1) == -1)\n    {\n      openbsd_warn(\"Failed write to file\");\n    }\n\n  return;\n}\n"
  }
]