From 9a97e08178208cd89283192ee11760193e5d10b1 Mon Sep 17 00:00:00 2001 From: Mid Favila Date: Thu, 25 Aug 2022 21:24:09 -0400 Subject: [PATCH] Initial commit. --- ChangeLog.md | 276 +++++++ LICENSE | 18 + Makefile | 29 + README.md | 272 +++++++ complete.c | 442 +++++++++++ config.h | 169 ++++ editline.3 | 286 +++++++ editline.c | 2064 ++++++++++++++++++++++++++++++++++++++++++++++++ editline.h | 232 ++++++ libeditline.pc | 12 + sysunix.c | 251 ++++++ unix.h | 36 + 12 files changed, 4087 insertions(+) create mode 100644 ChangeLog.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 complete.c create mode 100644 config.h create mode 100644 editline.3 create mode 100644 editline.c create mode 100644 editline.h create mode 100644 libeditline.pc create mode 100644 sysunix.c create mode 100644 unix.h diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..231c8ad --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,276 @@ +Change Log +========== + +All notable changes to the project are documented in this file. + +[1.17.1][] - 2022-08-25 +----------------------- +### Changes +- Revert to Make. + + +[1.17.1][] - 2020-02-23 +----------------------- + +### Fixes +- Fix #38: Fix for multiline representing as one line +- Fix packaging, missing files in libeditline1, regression from 1.16.0 +- Fix packaging, update to latest std version +- Fix formatting of function names in man page +- Restore tar.gz distribution, for usability on systems that do not + have xz in their default install + + +[1.17.0][] - 2020-01-05 +----------------------- + +### Changes +- Simple multi-line support by Dima Volynets, @dvolynets + +### Fixes +- Fix return value from `read_history()` and `write_history()`, could + return `errno` instead of `EOF` to indicate error. Now both functions + have uniform return values on error +- Handle internal `realloc()` errors better. Now memory is not leaked + if `realloc()` fails +- Fix possible NULL pointer dereference in key binding lookup function + + +[1.16.1][] - 2019-06-07 +----------------------- + +### Changes +- Major updates to the `editline.3` man page +- Cleanup of examples `cli.c` and `fileman.c` +- Add example of hidden input prompt to `cli.c` + +### Fixes +- Fix #20: `configure --disable-eof` does not bite +- Fix #23: Make Ctrl-L clear the screan instead of starting a new line + Like Ctrl-D, which exits, Ctrl-L only clears the screen when the line + is empty and the cursor is at the start of the line, otherwise Ctrl-L + will redraw/refresh the current line. +- Fix #24: Fix behavior when TTY is narrower than column width, by Will Dietz +- Fix #25: Avoid continuously duplicate commands in history +- Fix #31: Aborting i-search with Ctrl-C should not generate signal + + +[1.16.0][] - 2018-09-16 +----------------------- + +Event loop callback support. + +### Changes +- `rl_unintialize()`, new function to free all memory, by Claus Fischer +- `rl_insert_text()`, new GNU Readline compat function +- `rl_refresh_line()`, new GNU Readline compat function +- `rl_callback_*()`, alternate interface to plain `readline()` for event + loops. Modeled after the GNU Readline API +- `rl_completion_entry_function`, and `rl_attempted_completion_function` + are two new GNU Readline compat user hooks for the completion framework +- `rl_completion_matches()` and `rl_filename_completion_function()` + are two new GNU Readline compat functions +- Add new example: `fileman.c` from GNU Readline to demonstrate the + level of compatibility of the revamped completion framework +- Add support for Ctrl-Right and Ctrl-Left, forward/backward word +- Add .deb package to official release target + +### Fixes +- Fixed header guards, avoid using leading `__` +- Spell check fixes +- Remove duplicate code in history check +- Use `NULL` instead of `0`, and `-1` instead of `NULL`, where applicable +- Misc. minor Coverity Scan fixes +- Misc. minor fixes to `testit.c` example code +- Add `-Wextra` to std `CFLAGS` +- Check `fclose()` return value in in `write_history()` and `read_history()` +- Initialize global variables and reset to `NULL` on `free()` +- Fix off-by-one in forward kill word, avoid deleting too much +- Skip (or kill) leading whitespace when skipping (or killing) forwards + + +[1.15.3][] - 2017-09-07 +----------------------- + +Bug fix release. + +### Changes +- Refactor all enable/disable configure options, same problem as in #7 + +### Fixes +- Fix #7: `--enable-termcap` configure option does not work. The script + enabled termcap by default rather than the other way around. + + Also, check for terminfo as well, when `--enable-termcap` is selected. + + +[1.15.2][] - 2016-06-06 +----------------------- + +Bug fixes and minor feature creep in `pkg-config` support. + +### Changes +- Prevent mangling of symbols when linking with C++. Patch courtesy of + Jakub Pawlowski +- Add `libeditline.pc` for `pkg-config` + +### Fixes +- Do not assume a termcap library exists, check for `tgetent()` in + curses, ncurses, tinfo and termcap libraries +- Call `tty_flush()` when user calls `rl_forced_update_display()` + to prevent screen becoming garbled. Patch by Jakub Pawlowski + + +[1.15.1][] - 2015-11-16 +----------------------- + +Bug fixes only. + +### Changes +- Update README with origin of this version of editline + +### Fixes +- Fix build system, don't force automake v1.11, require at least v1.11 +- Fix build problem with examples using `--enable-termcap` + + +[1.15.0][] - 2015-09-10 +----------------------- + +### Changes +- Add support for `--disable-eof` and `--disable-sigint` to disable + default Ctrl-D and Ctrl-C behavior +- Add support for `el_no_hist` to disable access to and auto-save of history +- GNU readline compat functions for prompt handling and redisplay +- Refactor: replace variables named 'new' with non-reserved word +- Add support for [Travis-CI][], continuous integration with GitHub +- Add support for [Coverity Scan][], the best static code analyzer, + integrated with [Travis-CI][] -- scan runs for each push to master +- Rename NEWS.md --> ChangeLog.md, with symlinks for make install +- Attempt to align with http://keepachangelog.com/ for this file +- Cleanup and improve Markdown syntax in [README.md][] +- Add API and example to [README.md][], inspired by [libuEv][] +- Removed generated files from version control. Use `./autogen.sh` + to generate the `configure` script when working from GIT. This + does not affect distributed tarballs + +### Fixes +- Fix issue #2, regression in Ctrl-D (EOF) behavior. Regression + introduced in [1.14.1][]. Fixed by @TobyGoodwin +- Fix memory leak in completion handler. Found by [Coverity Scan][]. +- Fix suspicious use of `sizeof(char **)`, same as `sizeof(char *)` but + non-portable. Found by [Coverity Scan][] +- Fix out-of-bounds access in user key binding routines + Found by [Coverity Scan][]. +- Fix invisible example code in man page + + +[1.14.2][] - 2014-09-14 +----------------------- + +Bug fixes only. + +### Fixes + - Fix `el_no_echo` bug causing secrets to leak when disabling no-echo + - Handle `EINTR` in syscalls better + + +[1.14.1][] - 2014-09-14 +----------------------- + +Minor fixes and additions. + +### Changes +- Don't print status message on `stderr` in key binding funcions +- Export `el_del_char()` +- Check for and return pending signals when detected +- Allow custom key bindings ... + +### Fixes +- Bug fixes ... + + +[1.14.0][] - 2010-08-10 +----------------------- + +Major cleanups and further merges with Debian editline package. + +### Changes +- Merge in changes to `debian/` from `editline_1.12-6.debian.tar.gz` +- Migrate to use libtool +- Make `UNIQUE_HISTORY` configurable +- Make scrollback history (`HIST_SIZE`) configurable +- Configure options for toggling terminal bell and `SIGSTOP` (Ctrl-Z) +- Configure option for using termcap to read/control terminal size +- Rename Signal to `el_intr_pending`, from Festival speech-tools +- Merge support for capitalizing words (`M-c`) from Festival + speech-tools by Alan W Black +- Fallback backspace handling, in case `tgetstr("le")` fails + +### Fixes +- Cleanups and fixes thanks to the Sparse static code analysis tool +- Merge `el_no_echo` patch from Festival speech-tools +- Merge fixes from Heimdal project +- Completely refactor `rl_complete()` and `rl_list_possib()` with + fixes from the Heimdal project. Use `rl_set_complete_func()` and + `rl_set_list_possib_func()`. Default completion callbacks are now + available as a configure option `--enable-default-complete` +- Memory leak fixes +- Actually fix 8-bit handling by reverting old Debian patch +- Merge patch to improve compatibility with GNU readline, thanks to + Steve Tell from way back in 1997 and 1998 + + +[1.13.0][] - 2010-03-09 +----------------------- + +Adaptations to Debian editline package. + +### Changes +- Major version number bump, adapt to Jim Studt's v1.12 +- Import `debian/` directory and adapt it to configure et al. +- Change library name to libeditline to distinguish it from BSD libedit + + +[0.3.0][] - 2009-02-08 +---------------------- + +### Changes +- Support for ANSI arrow keys using configure --enable-arrow-keys + + +[0.2.3][] - 2008-12-02 +---------------------- + +### Changes +- Patches from Debian package merged +- Support for custom command completion + + +[0.1.0][] - 2008-06-07 +---------------------- + +### Changes +- First version, forked from Minix current 2008-06-06 + + +[UNRELEASED]: https://github.com/troglobit/finit/compare/1.17.1...HEAD +[1.17.1]: https://github.com/troglobit/finit/compare/1.17.0...1.17.1 +[1.17.0]: https://github.com/troglobit/finit/compare/1.16.1...1.17.0 +[1.16.1]: https://github.com/troglobit/finit/compare/1.16.0...1.16.1 +[1.16.0]: https://github.com/troglobit/finit/compare/1.15.3...1.16.0 +[1.15.3]: https://github.com/troglobit/finit/compare/1.15.2...1.15.3 +[1.15.2]: https://github.com/troglobit/finit/compare/1.15.1...1.15.2 +[1.15.1]: https://github.com/troglobit/finit/compare/1.15.0...1.15.1 +[1.15.0]: https://github.com/troglobit/finit/compare/1.14.2...1.15.0 +[1.14.2]: https://github.com/troglobit/finit/compare/1.14.1...1.14.2 +[1.14.1]: https://github.com/troglobit/finit/compare/1.14.0...1.14.1 +[1.14.0]: https://github.com/troglobit/finit/compare/1.13.0...1.14.0 +[1.13.0]: https://github.com/troglobit/finit/compare/0.3.0...1.13.0 +[0.3.0]: https://github.com/troglobit/finit/compare/0.2.3...0.3.0 +[0.2.3]: https://github.com/troglobit/finit/compare/0.1.0...0.2.3 +[0.1.0]: https://github.com/troglobit/finit/compare/0.0.0...0.1.0 +[libuEv]: http://github.com/troglobit/libuev +[Travis-CI]: https://travis-ci.org/troglobit/uftpd +[Coverity Scan]: https://scan.coverity.com/projects/2947 +[README.md]: https://github.com/troglobit/editline/blob/master/README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1c0d909 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ + Copyright 1992,1993 Simmule Turner and Rich Salz + All rights reserved. + + This software is not subject to any license of the American Telephone + and Telegraph Company or of the Regents of the University of California. + + Permission is granted to anyone to use this software for any purpose on + any computer system, and to alter it and redistribute it freely, subject + to the following restrictions: + 1. The authors are not responsible for the consequences of use of this + software, no matter how awful, even if they arise from flaws in it. + 2. The origin of this software must not be misrepresented, either by + explicit claim or by omission. Since few users ever read sources, + credits must appear in the documentation. + 3. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original software. Since few users + ever read sources, credits must appear in the documentation. + 4. This notice may not be removed or altered. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c3b90ba --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.POSIX: + +TARGETS = libeditline.so libeditline.so.1 libeditline.so.1.0.2 libeditline.a + +all: dynamic static + +install: all + install -Dm644 ${TARGETS} ${DESTDIR}/${PREFIX}/lib/ + install -Dm644 editline.3 ${DESTDIR}/${PREFIX}/share/man/man3/ + install -Dm644 libeditline.pc ${DESTDIR}/${PREFIX}/lib/pkgconfig/ + +dynamic: object + cc -o libeditline.so -shared *.o + ln -s libeditline.so libeditline.so.1 + ln -s libeditline.so libeditline.so.1.0.2 + +static: object + ar -r *.o + mv a.out libeditline.a + +object: complete.o editline.o sysunix.o + +clean: + rm *.so* *.a *.o + +.SUFFIX: +.c.o: + cc -c $< + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b73a047 --- /dev/null +++ b/README.md @@ -0,0 +1,272 @@ +Editline +======== +[![License Badge][]][License] [![Travis Status]][Travis] [![Coverity Status]][Coverity Scan] + + +Table of Contents +----------------- + +* [Introduction](#introduction) +* [API](#api) +* [Example](#example) +* [Build & Install](#build--install) +* [Origin & References](#origin--references) + + +Introduction +------------ + +This is a small [line editing][] library. It can be linked into almost +any program to provide command line editing and history functions. It +is call compatible with the [FSF readline][] library, but at a fraction +of the size, and as a result fewer features. It is also distributed +under a much more liberal [License][]. + +The small size (<30k), lack of dependencies (ncurses not needed!), and +the free license should make this library interesting to many embedded +developers. + +Editline has several optional build-time features that can be enabled by +supplying different options to the GNU configure script. See the output +from configure --help for details. Some useful hints on how +to use the library is available in the `examples/` directory. + +Editline is maintained collaboratively at [GitHub][]. + + +Example +------- + +Below is a very brief example to illustrate how one can use Editline to +create a simple CLI, Ctrl-D exits the program. A slightly more advanced +example is Jush, , a small and very +simplistic UNIX shell. The Editline sources also include an `examples/` +sub-directory. + +1. Build and install the library, preferably using a [release tarball][] + The configure script defaults to a `/usr/local` prefix. + + tar xf editline-1.15.3.tar.xz + cd editline-1.15.3/ + ./configure --prefix=/usr + make all + sudo make install + +2. Place the below source code in a separate project directory, + e.g. `~/src/example.c` + +```C + #include + #include + + int main(void) + { + char *p; + + while ((p = readline("CLI> ")) != NULL) { + puts(p); + free(p); + } + + return 0; + } +``` + +3. Compile the example: + + cd ~/src/ + make LDLIBS=-leditline example + +Here I use `make` and rely on its implicit (built-in) rules to handle +all the compiler magic, but you may want to create your own Makefile for +the project. In particular if you don't change the default prefix +(above), because then you need to specify the search path for the +include file(s) and the library manually. + +A simple `~/src/Makefile` could look like this: + + CFLAGS = -I/usr/local/include + LDFLAGS = -L/usr/local/lib + LDLIBS = -leditline + EXEC = example + OBJS = example.o + + all: $(EXEC) + + $(EXEC): $(OBJS) + + clean: + $(RM) $(OBJS) $(EXEC) + + distclean: clean + $(RM) *.o *~ *.bak + +Then simply type `make` from your `~/src/` directory. You can also use +`pkg-config` for your `~/src/Makefile`, replace the following lines: + + CFLAGS = $(shell pkg-config --cflags libeditline) + LDFLAGS = $(shell pkg-config --libs-only-L libeditline) + LDLIBS = $(shell pkg-config --libs-only-l libeditline) + +Then simply type make, like above. + +However, most `.rpm` based distributions `pkg-config` doesn't search in +`/usr/local` anymore, so you need to call make like this: + + PKG_CONFIG_LIBDIR=/usr/local/lib/pkgconfig make + +Debian/Ubuntu based systems do not have this problem. + + +API +--- + +Here is the libeditline interfaces. It has a small compatibility layer +to [FSF readline][], which may not be entirely up-to-date. + +```C + /* Editline specific global variables. */ + int el_no_echo; /* Do not echo input characters */ + int el_no_hist; /* Disable auto-save of and access to history, + * e.g. for password prompts or wizards */ + int el_hist_size; /* Size of history scrollback buffer, default: 15 */ + + /* Editline specific functions. */ + char * el_find_word (void); + void el_print_columns (int ac, char **av); + el_status_t el_ring_bell (void); + el_status_t el_del_char (void); + + /* Callback function for key binding */ + typedef el_status_t el_keymap_func_t(void); + + /* Bind key to a callback, use CTL('f') to change Ctrl-F, for example */ + el_status_t el_bind_key (int key, el_keymap_func_t function); + el_status_t el_bind_key_in_metamap (int key, el_keymap_func_t function); + + /* For compatibility with FSF readline. */ + int rl_point; + int rl_mark; + int rl_end; + int rl_inhibit_complete; + char *rl_line_buffer; + const char *rl_readline_name; + + void (*rl_deprep_term_function)(void); + void rl_deprep_terminal (void); + void rl_reset_terminal (const char *terminal_name); + + void rl_initialize (void); + void rl_uninitialize (void); /* Free all internal memory */ + + void rl_save_prompt (void); + void rl_restore_prompt (void); + void rl_set_prompt (const char *prompt); + + void rl_clear_message (void); + void rl_forced_update_display (void); + + /* Main function to use, saves history by default */ + char *readline (const char *prompt); + + /* Use to save a read line to history, when el_no_hist is set */ + void add_history (const char *line); + + /* Load and save editline history from/to a file. */ + int read_history (const char *filename); + int write_history (const char *filename); + + /* Magic completion API, see examples/cli.c for more info */ + rl_complete_func_t *rl_set_complete_func (rl_complete_func_t *func); + rl_list_possib_func_t *rl_set_list_possib_func (rl_list_possib_func_t *func); + + /* Alternate interface to plain readline(), for event loops */ + void rl_callback_handler_install (const char *prompt, rl_vcpfunc_t *lhandler); + void rl_callback_read_char (void); + void rl_callback_handler_remove (void); +``` + + +Build & Install +--------------- + +Editline was originally designed for older UNIX systems and Plan 9. The +current maintainer works exclusively on GNU/Linux systems, so it may use +GCC and GNU Make specific extensions here and there. This is not on +purpose and patches or pull requests to correct this are most welcome! + +1. Configure editline with default features: ./configure +2. Build the library and examples: make all +3. Install using make install + +The `$DESTDIR` environment variable is honored at install. For more +options, see ./configure --help + +Remember to run `ldconfig` after install to update the linker cache. If +you've installed to a non-standard location (`--prefix`) you may also +have to update your `/etc/ld.so.conf`, or use `pkg-confg` to build your +application (above). + +**NOTE:** RedHat/Fedora/CentOS and other `.rpm`-based distributions do + not consider `/usr/local` as standard path anymore. So make sure to + `./configure --prefix=/usr`, otherwise the build system use the GNU + default, which is `/usr/local`. The Debian based distributions, like + Ubuntu, do not have this problem. + + +Origin & References +-------------------- + +This [line editing][] library was created by [Rich Salz][] and Simmule +Turner and in 1992. It is distributed with a “[C News][]-like” license, +similar to the [BSD license][]. Rich's current version is however under +the Apache license. For details on the licensing terms of this version +of the software, see [License][]. + +This version of the editline library was forked from the [Minix 2][] +source tree and is *not* related to the similarily named NetBSD version +that [Jess Thrysøe][jess] disitributes to the world outside *BSD. The +libraries have much in common, but the latter is heavily refactored and +also relies on libtermcap (usually supplied by ncurses), whereas this +library only uses termios from the standard C library. + +Patches and bug fixes from the following forks, based on the original +[comp.sources.unix][] posting, have been merged: + +* Debian [libeditline][] +* [Heimdal][] +* [Festival][] speech-tools +* [Steve Tell][]'s editline patches + +The version numbering scheme today follows that of the Debian version, +details available in the [ChangeLog.md][]. The current [maintainer][] +was unaware of the Debian version for quite some time, so a different +name and versioning scheme was used. In June 2009 this was changed to +line up alongside Debian, with the intent is to eventually merge the +efforts. + +Outstanding issues are listed in the [TODO.md][] file. + +[GitHub]: https://github.com/troglobit/editline +[line editing]: https://github.com/troglobit/editline/blob/master/docs/README +[release tarball]: https://github.com/troglobit/editline/releases +[maintainer]: http://troglobit.com +[C News]: https://en.wikipedia.org/wiki/C_News +[TODO.md]: https://github.com/troglobit/editline/blob/master/docs/TODO.md +[ChangeLog.md]: https://github.com/troglobit/editline/blob/master/ChangeLog.md +[FSF readline]: http://www.gnu.org/software/readline/ +[Rich Salz]: https://github.com/richsalz/editline/ +[comp.sources.unix]: http://ftp.cs.toronto.edu/pub/white/pub/rc/editline.shar +[Minix 2]: http://www.cise.ufl.edu/~cop4600/cgi-bin/lxr/http/source.cgi/lib/editline/ +[jess]: http://thrysoee.dk/editline/ +[BSD license]: http://en.wikipedia.org/wiki/BSD_licenses +[libeditline]: http://packages.qa.debian.org/e/editline.html +[Heimdal]: http://www.h5l.org +[Festival]: http://festvox.org/festival/ +[Steve Tell]: http://www.cs.unc.edu/~tell/dist.html +[License]: https://github.com/troglobit/editline/blob/master/LICENSE +[License Badge]: https://img.shields.io/badge/License-C%20News-orange.svg +[Travis]: https://travis-ci.org/troglobit/editline +[Travis Status]: https://travis-ci.org/troglobit/editline.png?branch=master +[Coverity Scan]: https://scan.coverity.com/projects/2982 +[Coverity Status]: https://scan.coverity.com/projects/2982/badge.svg diff --git a/complete.c b/complete.c new file mode 100644 index 0000000..d6b8a4b --- /dev/null +++ b/complete.c @@ -0,0 +1,442 @@ +/* History and file completion functions for editline library. + * + * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz + * All rights reserved. + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * 1. The authors are not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits must appear in the documentation. + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits must appear in the documentation. + * 4. This notice may not be removed or altered. + */ + +#include +#include "editline.h" + +#define MAX_TOTAL_MATCHES (256 << sizeof(char *)) + +int rl_attempted_completion_over = 0; +rl_completion_func_t *rl_attempted_completion_function = NULL; +rl_compentry_func_t *rl_completion_entry_function = NULL; + +/* Wrap strcmp() for qsort() -- weird construct to pass -Wcast-qual */ +static int compare(const void *p1, const void *p2) +{ + char *const *v1 = (char *const *)p1; + char *const *v2 = (char *const *)p2; + + return strcmp(*v1, *v2); +} + +/* Fill in *avp with an array of names that match file, up to its length. + * Ignore . and .. . */ +static int FindMatches(char *dir, char *file, char ***avp) +{ + char **av; + char **word; + char *p; + DIR *dp; + DIRENTRY *ep; + size_t ac; + size_t len; + size_t choices; + size_t total; + + if ((dp = opendir(dir)) == NULL) + return 0; + + av = NULL; + ac = 0; + len = strlen(file); + choices = 0; + total = 0; + while ((ep = readdir(dp)) != NULL) { + p = ep->d_name; + if (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0'))) + continue; + if (len && strncmp(p, file, len) != 0) + continue; + + choices++; + if ((total += strlen(p)) > MAX_TOTAL_MATCHES) { + /* This is a bit too much. */ + while (ac > 0) free(av[--ac]); + continue; + } + + if ((ac % MEM_INC) == 0) { + word = malloc(sizeof(char *) * (ac + MEM_INC)); + if (!word) { + total = 0; + break; + } + + if (ac) { + memcpy(word, av, ac * sizeof(char *)); + free(av); + } + *avp = av = word; + } + + if ((av[ac] = strdup(p)) == NULL) { + if (ac == 0) + free(av); + total = 0; + break; + } + ac++; + } + + /* Clean up and return. */ + closedir(dp); + if (total > MAX_TOTAL_MATCHES) { + char many[sizeof(total) * 3]; + + p = many + sizeof(many); + *--p = '\0'; + while (choices > 0) { + *--p = '0' + choices % 10; + choices /= 10; + } + + while (p > many + sizeof(many) - 8) + *--p = ' '; + + if ((p = strdup(p)) != NULL) + av[ac++] = p; + + if ((p = strdup("choices")) != NULL) + av[ac++] = p; + } else { + if (ac) + qsort(av, ac, sizeof(char *), compare); + } + + return ac; +} + +/* Split a pathname into allocated directory and trailing filename parts. */ +static int SplitPath(const char *path, char **dirpart, char **filepart) +{ + static char DOT[] = "."; + char *dpart; + char *fpart; + + if ((fpart = strrchr(path, '/')) == NULL) { + if ((dpart = strdup(DOT)) == NULL) + return -1; + + if ((fpart = strdup(path)) == NULL) { + free(dpart); + return -1; + } + } else { + if ((dpart = strdup(path)) == NULL) + return -1; + + dpart[fpart - path + 1] = '\0'; + if ((fpart = strdup(fpart + 1)) == NULL) { + free(dpart); + return -1; + } + } + *dirpart = dpart; + *filepart = fpart; + + return 0; +} + +static rl_complete_func_t *el_complete_func = NULL; + +/* For compatibility with the Heimdal project. */ +rl_complete_func_t *rl_set_complete_func(rl_complete_func_t *func) +{ + rl_complete_func_t *old = el_complete_func; + el_complete_func = func; + + return old; +} + +/* Attempt to complete the pathname, returning an allocated copy. + * Fill in *match if we completed it, or set it to 0 if ambiguous. */ +char *el_filename_complete(char *pathname, int *match) +{ + char **av; + char *dir; + char *file; + char *path; + char *p; + size_t ac; + size_t end; + size_t i; + size_t j; + size_t len; + + if (SplitPath((const char *)pathname, &dir, &file) < 0) + return NULL; + + if ((ac = FindMatches(dir, file, &av)) == 0) { + free(dir); + free(file); + + return NULL; + } + + p = NULL; + len = strlen(file); + if (ac == 1) { + /* Exactly one match -- finish it off. */ + *match = 1; + j = strlen(av[0]) - len + 2; + p = malloc(sizeof(char) * (j + 1)); + if (p) { + memcpy(p, av[0] + len, j); + len = strlen(dir) + strlen(av[0]) + 2; + path = malloc(sizeof(char) * len); + if (path) { + snprintf(path, len, "%s/%s", dir, av[0]); + rl_add_slash(path, p); + free(path); + } + } + } else { + *match = 0; + if (len) { + /* Find largest matching substring. */ + for (i = len, end = strlen(av[0]); i < end; i++) { + for (j = 1; j < ac; j++) { + if (av[0][i] != av[j][i]) + goto breakout; + } + } + breakout: + if (i > len) { + j = i - len + 1; + p = malloc(sizeof(char) * j); + if (p) { + memcpy(p, av[0] + len, j); + p[j - 1] = '\0'; + } + } + } + } + + /* Clean up and return. */ + free(dir); + free(file); + for (i = 0; i < ac; i++) + free(av[i]); + free(av); + + return p; +} + +char *rl_filename_completion_function(const char *text, int state) +{ + char *dir; + char *file; + static char **av; + static size_t i, ac; + + if (!state) { + if (SplitPath(text, &dir, &file) < 0) + return NULL; + + ac = FindMatches(dir, file, &av); + free(dir); + free(file); + if (!ac) + return NULL; + + i = 0; + } + + if (i < ac) + return av[i++]; + + do { + free(av[--i]); + } while (i > 0); + + return NULL; +} + +/* Similar to el_find_word(), but used by GNU Readline API */ +static char *rl_find_token(size_t *len) +{ + char *ptr; + int pos; + + for (pos = rl_point; pos < rl_end; pos++) { + if (isspace(rl_line_buffer[pos])) { + if (pos > 0) + pos--; + break; + } + } + + ptr = &rl_line_buffer[pos]; + while (pos >= 0 && !isspace(rl_line_buffer[pos])) { + if (pos == 0) + break; + + pos--; + } + + if (ptr != &rl_line_buffer[pos]) { + *len = (size_t)(ptr - &rl_line_buffer[pos]); + return &rl_line_buffer[pos]; + } + + return NULL; +} + +/* + * "uses an application-supplied generator function to generate the list + * of possible matches, and then returns the array of these matches. The + * caller should place the address of its generator function in + * rl_completion_entry_function" + */ +char **rl_completion_matches(const char *token, rl_compentry_func_t *generator) +{ + int state = 0, num = 0; + char **array, *entry; + + if (!generator) { + generator = rl_completion_entry_function; + if (!generator) + generator = rl_filename_completion_function; + } + + if (!generator) + return NULL; + + array = malloc(512 * sizeof(char *)); + if (!array) + return NULL; + + while (num < 511 && (entry = generator(token, state))) { + state = 1; + array[num++] = entry; + } + array[num] = NULL; + + if (!num) { + free(array); + return NULL; + } + + return array; +} + +static char *complete(char *token, int *match) +{ + size_t len = 0; + char *word, **words = NULL; + int start, end; + + word = rl_find_token(&len); + if (!word) + goto fallback; + + start = word - rl_line_buffer; + end = start + len; + + word = strndup(word, len); + if (!word) + goto fallback; + + rl_attempted_completion_over = 0; + words = rl_attempted_completion_function(word, start, end); + + if (!rl_attempted_completion_over && !words) + words = rl_completion_matches(word, NULL); + + if (words) { + int i = 0; + + free(word); + word = NULL; + if (words[0]) + word = strdup(words[0] + len); + + while (words[i]) + free(words[i++]); + free(words); + + if (word) + return word; + } + +fallback: + return el_filename_complete(token, match); +} + +/* + * First check for editline specific custom completion function, then + * for any GNU Readline compat, then fallback to filename completion. + */ +char *rl_complete(char *token, int *match) +{ + if (el_complete_func) + return el_complete_func(token, match); + + if (rl_attempted_completion_function) + return complete(token, match); + + return el_filename_complete(token, match); +} + +static rl_list_possib_func_t *el_list_possib_func = NULL; + +/* For compatibility with the Heimdal project. */ +rl_list_possib_func_t *rl_set_list_possib_func(rl_list_possib_func_t *func) +{ + rl_list_possib_func_t *old = el_list_possib_func; + el_list_possib_func = func; + return old; +} + +/* Default possible completions. */ +int el_filename_list_possib(char *pathname, char ***av) +{ + char *dir; + char *file; + int ac; + + if (SplitPath(pathname, &dir, &file) < 0) + return 0; + + ac = FindMatches(dir, file, av); + free(dir); + free(file); + + return ac; +} + +/* Return all possible completions. */ +int rl_list_possib(char *token, char ***av) +{ + if (el_list_possib_func) + return el_list_possib_func(token, av); + + return el_filename_list_possib(token, av); +} + + +/** + * Local Variables: + * c-file-style: "k&r" + * c-basic-offset: 4 + * End: + */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..e16fb02 --- /dev/null +++ b/config.h @@ -0,0 +1,169 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to include ANSI arrow keys support. */ +#define CONFIG_ANSI_ARROWS 1 + +/* Define to enable EOF (Ctrl-D) key. */ +#define CONFIG_EOF 1 + +/* Define to enable SIGINT (Ctrl-C) key. */ +#define CONFIG_SIGINT 1 + +/* Define to enable SIGSTOP (Ctrl-Z) key. */ +/* #undef CONFIG_SIGSTOP */ + +/* Define to enable terminal bell on completion. */ +/* #undef CONFIG_TERMINAL_BELL */ + +/* Define to skip duplicate lines in the scrollback history. */ +#define CONFIG_UNIQUE_HISTORY 1 + +/* Define to use the termcap library for terminal size. */ +/* #undef CONFIG_USE_TERMCAP */ + +/* Define to 1 if `TIOCGWINSZ' requires . */ +#define GWINSZ_IN_SYS_IOCTL 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `curses' library (-lcurses). */ +/* #undef HAVE_LIBCURSES */ + +/* Define to 1 if you have the `ncurses' library (-lncurses). */ +/* #undef HAVE_LIBNCURSES */ + +/* Define to 1 if you have the `termcap' library (-ltermcap). */ +/* #undef HAVE_LIBTERMCAP */ + +/* Define to 1 if you have the `terminfo' library (-lterminfo). */ +/* #undef HAVE_LIBTERMINFO */ + +/* Define to 1 if you have the `tinfo' library (-ltinfo). */ +/* #undef HAVE_LIBTINFO */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MALLOC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the `perror' function. */ +#define HAVE_PERROR 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SGTTY_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +/* #undef HAVE_STAT_EMPTY_STRING_BUG */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strchr' function. */ +#define HAVE_STRCHR 1 + +/* Define to 1 if you have the `strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strrchr' function. */ +#define HAVE_STRRCHR 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the `tcgetattr' function. */ +#define HAVE_TCGETATTR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMCAP_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TERMIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +#define LSTAT_FOLLOWS_SLASHED_SYMLINK 1 + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "editline" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "https://github.com/troglobit/editline/issues" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "editline" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "editline 1.17.1" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "editline" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.17.1" + +/* Define to 1 if the `S_IS*' macros in do not work properly. */ +/* #undef STAT_MACROS_BROKEN */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Default to UNIX backend, should be detected. */ +#define SYS_UNIX 1 + +/* Version number of package */ +#define VERSION "1.17.1" + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ diff --git a/editline.3 b/editline.3 new file mode 100644 index 0000000..5ef16c7 --- /dev/null +++ b/editline.3 @@ -0,0 +1,286 @@ +.Dd February 23, 2020 +.Dt EDITLINE 3 +.Os +.Sh NAME +.Nm editline +.Nd command-line editing library with history +.Sh LIBRARY +.Lb libeditline +.Sh SYNOPSIS +.In editline.h +.Ft char * +.Fo readline +.Fa const char *prompt +.Fc +.Ft void +.Fo add_history +.Fa const char *line +.Fc +.Ft int +.Fo read_history +.Fa const char *filename +.Fc +.Ft int +.Fo write_history +.Fa const char *filename +.Fc +.Sh DESCRIPTION +.Nm +is a library that provides n line-editing interface with history. It +is intended to be functionally equivalent with the +.Nm readline +library provided by the Free Software Foundation, but much smaller. The +bulk of this manual page describes the basic user interface. More APIs, +both native and for +.Nm readline +compatibility , +are also available. See the +.Cm editline.h +header file for details. +.Pp +The +.Fn readline +function displays the given +.Fa prompt +on stdout, waits for user input on stdin and then returns a line of text +with the trailing newline removed. The data is returned in a buffer +allocated with +.Xr malloc 3 , +so the space should be released with +.Xr free 3 +when the calling program is done with it. +.Pp +Each line returned is automatically saved in the internal history list, +unless it happens to be equal to the previous line. This is +configurable if you are building editline from source, i.e. if you would +rather like to call +.Fn add_history +manually. +.Pp +The +.Fn read_history +and +.Fn write_history +functions can be used to load and store the history of your application. +.Em Note: +these APIs do not do any tilde or environment variable expansion of the +given filename. +.Ss User Interface +A program that uses this library provides a simple emacs-like editing +interface to its users. A line may be edited before it is sent to the +calling program by typing either control characters or escape sequences. +A control character, shown as a caret followed by a letter, is typed by +holding down the control key while the letter is typed. For example, +.Cm ^A +is a control-A. An escape sequence is entered by typing the escape key +followed by one or more characters. The escape key is abbreviated as +.Cm ESC . +Note that unlike control keys, case matters in escape sequences; +.Cm ESC F +is not the same as +.Cm ESC f . +.Pp +An editing command may be typed anywhere on the line, not just at the +beginning. In addition, a return may also be typed anywhere on the +line, not just at the end. +.Pp +Most editing commands may be given a repeat count, +.Ar n , +where +.Ar n +is a number. To enter a repeat count, type the escape key, the number, +and then the command to execute. For example, +.Cm ESC 4 ^f +moves forward four characters. If a command may be given a repeat count +then the text +.Cm [n] +is given at the end of its description. +.Pp +The following control characters are accepted: +.Pp +.Bl -tag -width "ESC DEL " -compact +.It ^A +Move to the beginning of the line +.It ^B +Move left (backwards) [n] +.It ^D +Delete character [n] +.It ^E +Move to end of line +.It ^F +Move right (forwards) [n] +.It ^G +Ring the bell +.It ^H +Delete character before cursor (backspace key) [n] +.It ^I +Complete filename (tab key); see below +.It ^J +Done with line (return key) +.It ^K +Kill to end of line (or column [n]) +.It ^L +Redisplay line +.It ^M +Done with line (alternate return key) +.It ^N +Get next line from history [n] +.It ^P +Get previous line from history [n] +.It ^R +Search backward (forward if [n]) through history for text; prefixing the +string with a caret (^) forces it to match only at the beginning of a +history line +.It ^T +Transpose characters +.It ^V +Insert next character, even if it is an edit command +.It ^W +Wipe to the mark +.It ^X^X +Exchange current location and mark +.It ^Y +Yank back last killed text +.It ^[ +Start an escape sequence (escape key) +.It ^]c +Move forward to next character +.Cm c +.It ^? +Delete character before cursor (delete key) [n] +.El +.Pp +The following escape sequences are provided: +.Pp +.Bl -tag -width "ESC DEL " -compact +.It ESC ^H +Delete previous word (backspace key) [n] +.It ESC DEL +Delete previous word (delete key) [n] +.It ESC SP +Set the mark (space key); see ^X^X and ^Y above +.It ESC\ . +Get the last (or [n]'th) word from previous line +.It ESC\ ? +Show possible completions; see below +.It ESC < +Move to start of history +.It ESC > +Move to end of history +.It ESC b +Move backward a word [n] +.It ESC d +Delete word under cursor [n] +.It ESC f +Move forward a word [n] +.It ESC l +Make word lowercase [n] +.It ESC m +Toggle if 8bit chars display normally or with an +.Ar M- +prefix +.It ESC u +Make word uppercase [n] +.It ESC y +Yank back last killed text +.It ESC v +Show library version +.It ESC w +Make area up to mark yankable +.It ESC nn +Set repeat count to the number nn +.It ESC C +Read from environment variable +.Ar $C , +where +.Ar C +is an uppercase letter +.El +.Pp +The +.Nm +library has a small macro facility. If you type the escape key followed +by an uppercase letter, +.Ar C , +then the contents of the environment variable +.Ar $C +are read in as if you had typed them at the keyboard. For example, if +the variable +.Ar $L +contains the following: +.Pp +.Dl ^A^Kecho '^V^[[H^V^[[2J'^M +.Pp +Then typing +.Cm ESC L +will move to the beginning of the line, kill the entire line, enter the +echo command needed to clear the terminal (if your terminal is like a +VT-100), and send the line back to the shell. +.Pp +The +.Nm +library also does filename completion. Suppose the root directory has +the following files in it: +.Pp +.Dl bin vmunix +.Dl core vmunix.old +.Pp +If you type +.Cm rm /v +and then the tab key, +.Nm +will then finish off as much of the name as possible by adding +.Ar munix . +Because the name is not unique, it will then beep. If you type the +escape key and a question mark, it will display the two choices. If you +then type a period and a tab, the library will finish off the filename +for you: +.Pp +.Bd -ragged -offset indent +rm /v[TAB] +.Em munix +\&.[TAB] +.Em old +.Ed +.Pp +The tab key is shown by [TAB] and the automatically-entered text +is shown in italics, or underline. +.Sh USAGE +To include +.Nm +in your program, call it as you do any other function and link your +program with +.Ar -leditline . +.Ss Example +The following brief example lets you enter a line and edit it, then displays it. +.Pp +.Bd -literal -offset indent +#include +#include +#include + +int main(void) +{ + char *p; + + while ((p = readline("CLI> "))) { + puts(p); + free(p); + } + + return 0; +} +.El +.Sh AUTHORS +The original editline library was posted to comp.sources.unix newsgroup +by created by Simmule R. Turner and Rich Salz in 1992. It now exists in +several forks: Debian, Minix, Heimdal, Festival speech tools, Mozilla, +Google Gadgets for Linux, and many other places. The original manual +page was made by David W. Sanderson. +.Pp +This version was originally based on the Minix 2 sources, but has since +evolved to include patches from all relevant forks. It is currently +maintained by Joachim Nilsson at GitHub, +.Aq http://github.com/troglobit/editline +.Sh BUGS +Does not handle multiple lines or unicode characters well. diff --git a/editline.c b/editline.c new file mode 100644 index 0000000..69cc9d1 --- /dev/null +++ b/editline.c @@ -0,0 +1,2064 @@ +/* + * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz + * All rights reserved. + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * 1. The authors are not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits must appear in the documentation. + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits must appear in the documentation. + * 4. This notice may not be removed or altered. + */ + +#include +#include +#include +#include + +#include "editline.h" + +/* +** Manifest constants. +*/ +#define SCREEN_COLS 80 +#define SCREEN_ROWS 24 +#define EL_STDIN 0 +#define EL_STDOUT 1 +#define NO_ARG (-1) +#define DEL 127 +#define SEPS "\"#$&'()*:;<=>?[\\]^`{|}~\n\t " + +/* +** The type of case-changing to perform. +*/ +typedef enum { + TOupper, TOlower, TOcapitalize +} el_case_t; + +/* +** Key to command mapping. +*/ +typedef struct { + int Key; + el_status_t (*Function)(void); +} el_keymap_t; + +/* +** Command history structure. +*/ +typedef struct { + int Size; + int Pos; + char **Lines; +} el_hist_t; + +/* User definable callbacks. */ +rl_getc_func_t *rl_getc_function = rl_getc; +rl_hook_func_t *rl_event_hook; +rl_vintfunc_t *rl_prep_term_function = rl_prep_terminal; +rl_voidfunc_t *rl_deprep_term_function = rl_deprep_terminal; + +/* +** Globals. +*/ +int rl_eof; +int rl_erase; +int rl_intr; +int rl_kill; +int rl_quit; +#ifdef CONFIG_SIGSTOP +int rl_susp; +#endif + +int el_hist_size = 15; +static el_hist_t H = { + .Size = 0, + .Pos = 0, + .Lines = NULL, +}; + +static char NILSTR[] = ""; +static const char *el_input = NILSTR; +static char *Yanked; +static char *Screen; +static char NEWLINE[]= CRLF; +static char CLEAR[]= "\ec"; +static const char *el_term = "dumb"; +static int Repeat; +static int old_point; +static int el_push_back; +static int el_pushed; +static int el_intr_pending; +static int el_infd = EL_STDIN; +static int el_outfd = EL_STDOUT; +static el_keymap_t Map[]; +static el_keymap_t MetaMap[]; +static size_t Length = 0; +static size_t ScreenCount; +static size_t ScreenSize; +static char *backspace = "\b"; +static char *old_search = NULL; +static int tty_cols = SCREEN_COLS; +static int tty_rows = SCREEN_ROWS; +static int Searching = 0; +static const char *(*search_move)(void); +static const char *old_prompt = NULL; +static rl_vcpfunc_t *line_handler = NULL; +static char *line_up = "\x1b[A"; +static char *line_down = "\x1b[B"; +int prompt_len = 0; + +int el_no_echo = 0; /* e.g., under Emacs */ +int el_no_hist = 0; +int rl_point; +int rl_mark; +int rl_end; +int rl_meta_chars = 0; /* Display 8-bit chars as the actual char(0) or as `M-x'(1)? */ +int rl_inhibit_complete = 0; +char *rl_line_buffer = NULL; +const char *rl_prompt = NULL; +const char *rl_readline_name = NULL; /* Set by calling program, for conditional parsing of ~/.inputrc - Not supported yet! */ +FILE *rl_instream = NULL; /* The stdio stream from which input is read. Defaults to stdin if NULL */ +FILE *rl_outstream = NULL; /* The stdio stream to which output is flushed. Defaults to stdout if NULL */ + +/* Declarations. */ +static char *editinput(int complete); +#ifdef CONFIG_USE_TERMCAP +extern char *tgetstr(const char *, char **); +extern int tgetent(char *, const char *); +extern int tgetnum(const char *); +#endif + +/* +** Misc. local helper functions. +*/ +static int is_alpha_num(unsigned char c) +{ + if (isalnum(c)) + return 1; + if (ISMETA(c)) + return 1; + if (ISCTL(c)) + return 1; + + return 0; +} + +/* +** TTY input/output functions. +*/ + +static void tty_flush(void) +{ + ssize_t res; + + if (!ScreenCount) + return; + + if (!el_no_echo) { + res = write(el_outfd, Screen, ScreenCount); + if (res > 0) + ScreenCount = 0; + } +} + +static void tty_put(const char c) +{ + if (el_no_echo) + return; + + Screen[ScreenCount] = c; + if (++ScreenCount >= ScreenSize) { + char *ptr; + + ScreenSize += SCREEN_INC; + ptr = realloc(Screen, sizeof(char) * ScreenSize); + if (ptr) + Screen = ptr; + } +} + +static void tty_puts(const char *p) +{ + while (*p) + tty_put(*p++); +} + +static void tty_show(unsigned char c) +{ + if (c == DEL) { + tty_put('^'); + tty_put('?'); + } else if (ISCTL(c)) { + tty_put('^'); + tty_put(UNCTL(c)); + } else if (rl_meta_chars && ISMETA(c)) { + tty_put('M'); + tty_put('-'); + tty_put(UNMETA(c)); + } else { + tty_put(c); + } +} + +static void tty_string(char *p) +{ + int i = rl_point + prompt_len + 1; + + while (*p) { + tty_show(*p++); + if ((i++) % tty_cols == 0) { + tty_put(' '); + tty_put('\b'); + } + } +} + +static void tty_push(int c) +{ + el_pushed = 1; + el_push_back = c; +} + +int rl_getc(void) +{ + int r; + char c; + + do { + r = read(el_infd, &c, 1); + } while (r == -1 && errno == EINTR); + + return r == 1 ? c : EOF; +} + +static int tty_get(void) +{ + tty_flush(); + + if (el_pushed) { + el_pushed = 0; + return el_push_back; + } + + if (*el_input) + return *el_input++; + + return rl_getc_function(); +} + +#define tty_back() tty_puts(backspace) + +static void tty_backn(int n) +{ + while (--n >= 0) + tty_back(); +} + +static void tty_forwardn(int n) +{ + char buf[12]; + + snprintf(buf, sizeof(buf), "\x1b[%dC", n); + tty_puts(buf); +} + +static void tty_info(void) +{ + rl_reset_terminal(NULL); +} + +/* +** Glue routines to rl_ttyset() +*/ +void rl_prep_terminal(int meta_flag) +{ + rl_meta_chars = !meta_flag; + rl_ttyset(0); +} + +void rl_deprep_terminal(void) +{ + rl_ttyset(1); +} + +/* +** Print an array of words in columns. +*/ +void el_print_columns(int ac, char **av) +{ + char *p; + int i; + int j; + int k; + int len; + int skip; + int longest; + int cols; + int colwidth; + + /* Find longest name, determine column count from that. */ + for (longest = 0, i = 0; i < ac; i++) { + if ((j = strlen((char *)av[i])) > longest) + longest = j; + } + + colwidth = longest + 3; + if (colwidth > tty_cols) + colwidth = tty_cols; + cols = tty_cols / colwidth; + + tty_puts(NEWLINE); + for (skip = ac / cols + 1, i = 0; i < skip; i++) { + for (j = i; j < ac; j += skip) { + for (p = av[j], len = strlen((char *)p), k = len; --k >= 0; p++) + tty_put(*p); + + if (j + skip < ac) { + while (++len < colwidth) + tty_put(' '); + } + } + + tty_puts(NEWLINE); + } +} + +static void reposition(int key) +{ + int len_with_prompt = prompt_len + rl_end; + int n = len_with_prompt / tty_cols; /* determine the number of lines */ + int i = 0; + + tty_put('\r'); + + if (n > 0) { + int line; + + /* determine num of current line */ + if (key == CTL('A') || key == CTL('E') || key == rl_kill) + line = (prompt_len + old_point) / tty_cols; + else + line = len_with_prompt / tty_cols; + + /* move to end of line(s) */ + if (key == CTL('E')) { + int k; + + for (k = line; k < n; k++) + tty_puts(line_down); + + /* determine reminder of last line and redraw only it */ + i = rl_point - (len_with_prompt % tty_cols); + } else { + int k; + + /* CTRL-A, CTRL-U, insert (end, middle), remove (end, middle) */ + for (k = line; k > 0; k--) + tty_puts(line_up); /* redraw characters until changed data */ + + tty_puts(rl_prompt); + } + } else if (n == 0) { + tty_puts(rl_prompt); + } + + for (; i < rl_point; i++) { + tty_show(rl_line_buffer[i]); + + /* move to the next line */ + if ((i + prompt_len + 1) % tty_cols == 0) + tty_put('\n'); + } +} + +static void left(el_status_t Change) +{ + if (rl_point) { + if ((rl_point + prompt_len) % tty_cols == 0) { + tty_puts(line_up); + tty_forwardn(tty_cols); + } else { + tty_back(); + } + + if (ISMETA(rl_line_buffer[rl_point - 1])) { + if (rl_meta_chars) { + tty_back(); + tty_back(); + } + } else if (ISCTL(rl_line_buffer[rl_point - 1])) { + tty_back(); + } + } + + if (Change == CSmove) + rl_point--; +} + +static void right(el_status_t Change) +{ + if ((rl_point + prompt_len + 1) % tty_cols == 0) + tty_put('\n'); + else + tty_show(rl_line_buffer[rl_point]); + + if (Change == CSmove) + rl_point++; +} + +el_status_t el_ring_bell(void) +{ + tty_put('\07'); + tty_flush(); + + return CSstay; +} + +static el_status_t do_macro(int c) +{ + char name[4]; + + name[0] = '_'; + name[1] = c; + name[2] = '_'; + name[3] = '\0'; + + if ((el_input = (char *)getenv((char *)name)) == NULL) { + el_input = NILSTR; + return el_ring_bell(); + } + + return CSstay; +} + +/* Skip forward to start of next word. If @move is set we also move the cursor. */ +static el_status_t do_forward(el_status_t move) +{ + int i; + char *p; + + i = 0; + do { + p = &rl_line_buffer[rl_point]; + + /* Skip leading whitespace, like FSF Readline */ + for ( ; rl_point < rl_end && (p[0] == ' ' || !is_alpha_num(p[0])); rl_point++, p++) { + if (move == CSmove) + right(CSstay); + } + + /* Skip to end of word, if inside a word. */ + for (; rl_point < rl_end && is_alpha_num(p[0]); rl_point++, p++) { + if (move == CSmove) + right(CSstay); + } + + /* Skip to next word, or skip leading white space if outside a word. */ + for ( ; rl_point < rl_end && (p[0] == ' ' || !is_alpha_num(p[0])); rl_point++, p++) { + if (move == CSmove) + right(CSstay); + } + + if (rl_point == rl_end) + break; + } while (++i < Repeat); + + return CSstay; +} + +static el_status_t do_case(el_case_t type) +{ + int i; + int end; + int count; + char *p; + + do_forward(CSstay); + if (old_point != rl_point) { + if ((count = rl_point - old_point) < 0) + count = -count; + + rl_point = old_point; + if ((end = rl_point + count) > rl_end) + end = rl_end; + + for (i = rl_point, p = &rl_line_buffer[i]; rl_point < end; p++) { + if ((type == TOupper) || (type == TOcapitalize && rl_point == i)) { + if (islower(*p)) + *p = toupper(*p); + } else if (isupper(*p)) { + *p = tolower(*p); + } + right(CSmove); + } + } + + return CSstay; +} + +static el_status_t case_down_word(void) +{ + return do_case(TOlower); +} + +static el_status_t case_up_word(void) +{ + return do_case(TOupper); +} + +static el_status_t case_cap_word(void) +{ + return do_case(TOcapitalize); +} + +static void ceol(void) +{ + int extras = 0; + int i; + char *p; + + while (rl_point < 0) { + tty_put(' '); + rl_point++; + } + + for (i = rl_point, p = &rl_line_buffer[i]; i <= rl_end; i++, p++) { + if ((i + prompt_len + 1) % tty_cols == 0){ + tty_put(' '); + tty_put('\n'); + } + else + tty_put(' '); + if (ISMETA(*p)) { + if (rl_meta_chars) { + tty_put(' '); + tty_put(' '); + extras += 2; + } + } else if (ISCTL(*p)) { + tty_put(' '); + extras++; + } + } + + for (i += extras; i > rl_point; i--) { + if ((i + prompt_len) % tty_cols == 0) { + tty_puts(line_up); + tty_forwardn(tty_cols); + } else { + tty_back(); + } + } +} + +static void clear_line(void) +{ + int n = (rl_point + prompt_len) / tty_cols; + rl_point = -(int)strlen(rl_prompt); + + if (n > 0) { + for(int k = 0; k < n; k++) + tty_puts(line_up); + tty_put('\r'); + } + else { + tty_put('\r'); + } + + ceol(); + + rl_point = 0; + rl_end = 0; + rl_line_buffer[0] = '\0'; +} + +static el_status_t insert_string(const char *p) +{ + size_t len; + int i; + char *line; + char *q; + + len = strlen(p); + if (rl_end + len >= Length) { + line = malloc(sizeof(char) * (Length + len + MEM_INC)); + if (!line) + return CSstay; + + if (Length) { + memcpy(line, rl_line_buffer, Length); + free(rl_line_buffer); + } + + rl_line_buffer = line; + Length += len + MEM_INC; + } + + for (q = &rl_line_buffer[rl_point], i = rl_end - rl_point; --i >= 0; ) + q[len + i] = q[i]; + + memcpy(&rl_line_buffer[rl_point], p, len); + rl_end += len; + rl_line_buffer[rl_end] = '\0'; + tty_string(&rl_line_buffer[rl_point]); + rl_point += len; + + return rl_point == rl_end ? CSstay : CSmove; +} + +int rl_insert_text(const char *text) +{ + int mark = rl_point; + + insert_string(text); + ceol(); + + return rl_point - mark; +} + +static el_status_t redisplay(int cls) +{ + if (cls) + tty_puts(CLEAR); + else + tty_puts("\r\e[K"); + + tty_puts(rl_prompt); + rl_point = 0; + tty_string(rl_line_buffer); + rl_point = rl_end; + return CSmove; +} + +static el_status_t refresh(void) +{ + return redisplay(1); +} + +int rl_refresh_line(int ignore1 __attribute__((unused)), int ignore2 __attribute__((unused))) +{ + redisplay(0); + return 0; +} + +static el_status_t toggle_meta_mode(void) +{ + rl_meta_chars = ! rl_meta_chars; + return redisplay(0); +} + +const char *el_next_hist(void) +{ + return H.Pos >= H.Size - 1 ? NULL : H.Lines[++H.Pos]; +} + +const char *el_prev_hist(void) +{ + return H.Pos == 0 ? NULL : H.Lines[--H.Pos]; +} + +static el_status_t do_insert_hist(const char *p) +{ + if (p == NULL) + return el_ring_bell(); + + clear_line(); + + rl_point = 0; + reposition(EOF); + rl_end = 0; + + return insert_string(p); +} + +static el_status_t do_hist(const char *(*move)(void)) +{ + const char *p; + int i = 0; + + do { + if ((p = move()) == NULL) + return el_ring_bell(); + } while (++i < Repeat); + + return do_insert_hist(p); +} + +static el_status_t h_next(void) +{ + if (el_no_hist) + return CSstay; + + return do_hist(el_next_hist); +} + +static el_status_t h_prev(void) +{ + if (el_no_hist) + return CSstay; + + return do_hist(el_prev_hist); +} + +static el_status_t h_first(void) +{ + return do_insert_hist(H.Lines[H.Pos = 0]); +} + +static el_status_t h_last(void) +{ + return do_insert_hist(H.Lines[H.Pos = H.Size - 1]); +} + +/* +** Return zero if pat appears as a substring in text. +*/ +static int substrcmp(const char *text, const char *pat, size_t len) +{ + char c; + + if ((c = *pat) == '\0') + return *text == '\0'; + + for ( ; *text; text++) { + if (*text == c && strncmp(text, pat, len) == 0) + return 0; + } + + return 1; +} + +static const char *search_hist(const char *search, const char *(*move)(void)) +{ + int len; + int pos; + int (*match)(const char *s1, const char *s2, size_t n); + const char *pat; + + /* Save or get remembered search pattern. */ + if (search && *search) { + if (old_search) + free(old_search); + old_search = strdup(search); + } else { + if (old_search == NULL || *old_search == '\0') + return NULL; + search = old_search; + } + + /* Set up pattern-finder. */ + if (*search == '^') { + match = strncmp; + pat = search + 1; + } else { + match = substrcmp; + pat = search; + } + len = strlen(pat); + + pos = H.Pos; /* Save H.Pos */ + while (move()) { + if (match(H.Lines[H.Pos], pat, len) == 0) + return H.Lines[H.Pos]; + } + H.Pos = pos; /* Restore H.Pos */ + + return NULL; +} + +static el_status_t h_search_end(const char *p) +{ + rl_prompt = old_prompt; + Searching = 0; + + if (el_intr_pending > 0) { + el_intr_pending = 0; + clear_line(); + return redisplay(0); + } + + p = search_hist(p, search_move); + if (p == NULL) { + el_ring_bell(); + clear_line(); + return redisplay(0); + } + + return do_insert_hist(p); +} + +static el_status_t h_search(void) +{ + if (Searching) + return el_ring_bell(); + Searching = 1; + + clear_line(); + old_prompt = rl_prompt; + rl_prompt = "Search: "; + tty_puts(rl_prompt); + + search_move = Repeat == NO_ARG ? el_prev_hist : el_next_hist; + if (line_handler) { + editinput(0); + return CSstay; + } + + return h_search_end(editinput(1)); +} + +static el_status_t fd_char(void) +{ + int i = 0; + + do { + if (rl_point >= rl_end) + break; + right(CSmove); + } while (++i < Repeat); + return CSstay; +} + +static void save_yank(int begin, int i) +{ + if (Yanked) { + free(Yanked); + Yanked = NULL; + } + + if (i < 1) + return; + + Yanked = malloc(sizeof(char) * (i + 1)); + if (Yanked) { + memcpy(Yanked, &rl_line_buffer[begin], i); + Yanked[i] = '\0'; + } +} + +static el_status_t delete_string(int count) +{ + int i; + char *p; + + if (count <= 0 || rl_end == rl_point) + return el_ring_bell(); + + if (count == 1 && rl_point == rl_end - 1) { + /* Optimize common case of delete at end of line. */ + rl_end--; + p = &rl_line_buffer[rl_point]; + i = 1; + tty_put(' '); + if (ISCTL(*p)) { + i = 2; + tty_put(' '); + } else if (rl_meta_chars && ISMETA(*p)) { + i = 3; + tty_put(' '); + tty_put(' '); + } + tty_backn(i); + *p = '\0'; + return CSmove; + } + + if (rl_point + count > rl_end && (count = rl_end - rl_point) <= 0) + return CSstay; + + if (count > 1) + save_yank(rl_point, count); + + for (p = &rl_line_buffer[rl_point], i = rl_end - (rl_point + count) + 1; --i >= 0; p++) + p[0] = p[count]; + ceol(); + + rl_end -= count; + tty_string(&rl_line_buffer[rl_point]); + + return CSmove; +} + +static el_status_t bk_char(void) +{ + int i = 0; + + do { + if (rl_point == 0) + break; + left(CSmove); + } while (++i < Repeat); + + return CSstay; +} + +static el_status_t bk_del_char(void) +{ + int i = 0; + + do { + if (rl_point == 0) + break; + left(CSmove); + } while (++i < Repeat); + + return delete_string(i); +} + +static el_status_t kill_line(void) +{ + int i; + + if (Repeat != NO_ARG) { + if (Repeat < rl_point) { + i = rl_point; + rl_point = Repeat; + reposition(EOF); + delete_string(i - rl_point); + } else if (Repeat > rl_point) { + right(CSmove); + delete_string(Repeat - rl_point - 1); + } + + return CSmove; + } + + save_yank(rl_point, rl_end - rl_point); + rl_line_buffer[rl_point] = '\0'; + ceol(); + rl_end = rl_point; + + return CSstay; +} + +static el_status_t insert_char(int c) +{ + el_status_t s; + char buff[2]; + char *p; + char *q; + int i; + + if (Repeat == NO_ARG || Repeat < 2) { + buff[0] = c; + buff[1] = '\0'; + + return insert_string(buff); + } + + p = malloc(sizeof(char) * (Repeat + 1)); + if (!p) + return CSstay; + + for (i = Repeat, q = p; --i >= 0; ) + *q++ = c; + *q = '\0'; + Repeat = 0; + s = insert_string(p); + free(p); + + return s; +} + +static el_status_t beg_line(void) +{ + if (rl_point) { + rl_point = 0; + return CSmove; + } + + return CSstay; +} + +static el_status_t end_line(void) +{ + if (rl_point != rl_end) { + rl_point = rl_end; + return CSmove; + } + + return CSstay; +} + +static el_status_t del_char(void) +{ + return delete_string(Repeat == NO_ARG ? CSeof : Repeat); +} + +el_status_t el_del_char(void) +{ + return del_char(); +} + +static el_status_t fd_word(void) +{ + return do_forward(CSmove); +} + +static el_status_t bk_word(void) +{ + int i; + char *p; + + i = 0; + do { + for (p = &rl_line_buffer[rl_point]; p > rl_line_buffer && !is_alpha_num(p[-1]); p--) + left(CSmove); + + for (; p > rl_line_buffer && !isblank(p[-1]) && is_alpha_num(p[-1]); p--) + left(CSmove); + + if (rl_point == 0) + break; + } while (++i < Repeat); + + return CSstay; +} + +static el_status_t meta(void) +{ + int c; + el_keymap_t *kp; + + if ((c = tty_get()) == EOF) + return CSeof; + +#ifdef CONFIG_ANSI_ARROWS + /* Also include VT-100 arrows. */ + if (c == '[' || c == 'O') { + switch (tty_get()) { + case EOF: return CSeof; + case '1': + { + char seq[4] = { 0 }; + + for (c = 0; c < 3; c++) + seq[c] = tty_get(); + + if (!strncmp(seq, ";5C", 3)) + return fd_word(); /* Ctrl+Right */ + if (!strncmp(seq, ";5D", 3)) + return bk_word(); /* Ctrl+Left */ + + break; + } + case '2': tty_get(); return CSstay; /* Insert */ + case '3': tty_get(); return del_char(); /* Delete */ + case '5': tty_get(); return CSstay; /* PgUp */ + case '6': tty_get(); return CSstay; /* PgDn */ + case 'A': return h_prev(); /* Up */ + case 'B': return h_next(); /* Down */ + case 'C': return fd_char(); /* Left */ + case 'D': return bk_char(); /* Right */ + case 'F': return end_line(); /* End */ + case 'H': return beg_line(); /* Home */ + default: /* Fall through */ + break; + } + + return el_ring_bell(); + } +#endif /* CONFIG_ANSI_ARROWS */ + + if (isdigit(c)) { + for (Repeat = c - '0'; (c = tty_get()) != EOF && isdigit(c); ) + Repeat = Repeat * 10 + c - '0'; + tty_push(c); + + return CSstay; + } + + if (isupper(c)) + return do_macro(c); + + for (kp = MetaMap; kp->Function; kp++) { + if (kp->Key == c) + return kp->Function(); + } + + return el_ring_bell(); +} + +static el_status_t emacs(int c) +{ + el_status_t s; + el_keymap_t *kp; + + /* Save point before interpreting input character 'c'. */ + old_point = rl_point; + + if (rl_meta_chars && ISMETA(c)) { + tty_push(UNMETA(c)); + return meta(); + } + + for (kp = Map; kp->Function; kp++) { + if (kp->Key == c) + break; + } + + if (kp->Function) { + s = kp->Function(); + if (s == CSdispatch) /* If Function is inhibited. */ + s = insert_char(c); + } else { + s = insert_char(c); + } + + if (!el_pushed) { + /* No pushback means no repeat count; hacky, but true. */ + Repeat = NO_ARG; + } + + return s; +} + +static el_status_t tty_special(int c) +{ +#ifdef CONFIG_SIGINT + if (c == rl_intr) { + el_intr_pending = SIGINT; + return CSsignal; + } +#endif + if (c == rl_quit) { + el_intr_pending = SIGQUIT; + return CSeof; + } +#ifdef CONFIG_SIGSTOP + if (c == rl_susp) { + el_intr_pending = SIGTSTP; + return CSsignal; + } +#endif + + if (rl_meta_chars && ISMETA(c)) + return CSdispatch; + + if (c == rl_erase || c == DEL) + return bk_del_char(); + + if (c == rl_kill) { + if (rl_point != 0) { + old_point = rl_point; + rl_point = 0; + reposition(c); + } + Repeat = NO_ARG; + + return kill_line(); + } + +#ifdef CONFIG_EOF + if (c == rl_eof && rl_point == 0 && rl_end == 0) + return CSeof; +#endif + + return CSdispatch; +} + +static char *editinput(int complete) +{ + int c; + + do { + c = tty_get(); + if (c == EOF) + break; + + switch (tty_special(c)) { + case CSdone: + return rl_line_buffer; + + case CSeof: + return NULL; + + case CSsignal: + return (char *)""; + + case CSmove: + reposition(c); + break; + + case CSdispatch: + switch (emacs(c)) { + case CSdone: + return rl_line_buffer; + + case CSeof: + return NULL; + + case CSsignal: + return (char *)""; + + case CSmove: + reposition(c); + break; + + case CSdispatch: + case CSstay: + break; + } + break; + + case CSstay: + break; + } + } while (complete); + + return NULL; +} + +static void hist_alloc(void) +{ + if (!H.Lines) + H.Lines = calloc(el_hist_size, sizeof(char *)); +} + +static void hist_add(const char *p) +{ + int i; + char *s; + +#ifdef CONFIG_UNIQUE_HISTORY + if (H.Size && strcmp(p, H.Lines[H.Size - 1]) == 0) + return; +#endif + + s = strdup(p); + if (s == NULL) + return; + + if (H.Size < el_hist_size) { + H.Lines[H.Size++] = s; + } else { + free(H.Lines[0]); + for (i = 0; i < el_hist_size - 1; i++) + H.Lines[i] = H.Lines[i + 1]; + H.Lines[i] = s; + } + H.Pos = H.Size - 1; +} + +static char *read_redirected(void) +{ + int size = MEM_INC; + char *p; + char *line; + char *end; + + p = line = malloc(sizeof(char) * size); + if (!p) + return NULL; + + end = p + size; + while (1) { + if (p == end) { + int oldpos = end - line; + + size += MEM_INC; + p = realloc(line, sizeof(char) * size); + if (!p) { + free(line); + return NULL; + } + line = p; + end = p + size; + + p += oldpos; /* Continue where we left off... */ + } + + if (read(el_infd, p, 1) <= 0) { + /* Ignore "incomplete" lines at EOF, just like we do for a tty. */ + free(line); + return NULL; + } + + if (*p == '\n') + break; + p++; + } + *p = '\0'; + + return line; +} + +/* For compatibility with FSF readline. */ +void rl_reset_terminal(const char *terminal_name) +{ +#ifdef CONFIG_USE_TERMCAP + char buf[1024]; + char *bp; +#endif +#ifdef TIOCGWINSZ + struct winsize W; +#endif + + if (terminal_name) { + el_term = terminal_name; + } else if ((el_term = getenv("TERM")) == NULL) { + el_term = "dumb"; + } + + /* Initialize to faulty values to trigger fallback if nothing else works. */ + tty_cols = tty_rows = -1; + +#ifdef CONFIG_USE_TERMCAP + bp = buf; + if (-1 != tgetent(buf, el_term)) { + if ((backspace = tgetstr("le", &bp)) != NULL) + backspace = strdup(backspace); + tty_cols = tgetnum("co"); + tty_rows = tgetnum("li"); + } + /* Make sure to check width & rows and fallback to TIOCGWINSZ if available. */ +#endif + + if (tty_cols <= 0 || tty_rows <= 0) { +#ifdef TIOCGWINSZ + if (ioctl(el_outfd, TIOCGWINSZ, &W) >= 0 && W.ws_col > 0 && W.ws_row > 0) { + tty_cols = (int)W.ws_col; + tty_rows = (int)W.ws_row; + return; + } +#endif + tty_cols = SCREEN_COLS; + tty_rows = SCREEN_ROWS; + } +} + +void rl_initialize(void) +{ + if (!rl_prompt) + rl_prompt = "? "; + + hist_alloc(); + + /* Setup I/O descriptors */ + if (!rl_instream) el_infd = EL_STDIN; + else el_infd = fileno(rl_instream); + if (el_infd < 0) el_infd = EL_STDIN; + if (!rl_outstream) el_outfd = EL_STDOUT; + else el_outfd = fileno(rl_outstream); + if (el_outfd < 0) el_outfd = EL_STDOUT; +} + +void rl_uninitialize(void) +{ + int i; + + /* Uninitialize the history */ + if (H.Lines) { + for (i = 0; i < el_hist_size; i++) { + if (H.Lines[i]) + free(H.Lines[i]); + H.Lines[i] = NULL; + } + free(H.Lines); + H.Lines = NULL; + } + H.Size = 0; + H.Pos = 0; + + if (old_search) + free(old_search); + old_search = NULL; + + /* Uninitialize the line buffer */ + if (rl_line_buffer) + free(rl_line_buffer); + rl_line_buffer = NULL; + Length = 0; +} + +static const char *rl_saved_prompt = NULL; +void rl_save_prompt(void) +{ + rl_saved_prompt = rl_prompt; +} + +void rl_restore_prompt(void) +{ + if (rl_saved_prompt) + rl_prompt = rl_saved_prompt; +} + +void rl_set_prompt(const char *prompt) +{ + rl_prompt = prompt; +} + +void rl_clear_message(void) +{ + /* Nothing to do atm. */ +} + +void rl_forced_update_display() +{ + redisplay(0); + tty_flush(); +} + +static int el_prep(const char *prompt) +{ + rl_initialize(); + + if (!rl_line_buffer) { + Length = MEM_INC; + rl_line_buffer = malloc(sizeof(char) * Length); + if (!rl_line_buffer) + return -1; + } + + tty_info(); + rl_prep_term_function(!rl_meta_chars); + hist_add(NILSTR); + ScreenSize = SCREEN_INC; + Screen = malloc(sizeof(char) * ScreenSize); + if (!Screen) + return -1; + + rl_prompt = prompt ? prompt : NILSTR; + prompt_len = strlen(rl_prompt); + + if (el_no_echo) { + int old = el_no_echo; + + el_no_echo = 0; + tty_puts(rl_prompt); + tty_flush(); + el_no_echo = old; + } else { + tty_puts(rl_prompt); + } + + Repeat = NO_ARG; + old_point = rl_point = rl_mark = rl_end = 0; + rl_line_buffer[0] = '\0'; + el_intr_pending = -1; + + return 0; +} + +static char *el_deprep(char *line) +{ + if (line) { + line = strdup(line); + tty_puts(NEWLINE); + tty_flush(); + } + + rl_deprep_term_function(); + if (Screen) { + free(Screen); + Screen = NULL; + } + + free(H.Lines[--H.Size]); + H.Lines[H.Size] = NULL; + + /* Add to history, unless no-echo or no-history mode ... */ + if (!el_no_echo && !el_no_hist) { + if (line != NULL && *line != '\0') + hist_add(line); + } + + if (el_intr_pending > 0) { + int signo = el_intr_pending; + + el_intr_pending = 0; + kill(getpid(), signo); + } + + return line; +} + +void rl_callback_handler_install(const char *prompt, rl_vcpfunc_t *lhandler) +{ + if (!lhandler) + return; + line_handler = lhandler; + + /* + * Any error from el_prep() is handled by the lhandler callbck as + * soon as the user calls rl_callback_read_char(). + */ + el_prep(prompt); + tty_flush(); +} + +/* + * Reads one character at a time, when a complete line has been received + * the lhandler from rl_callback_handler_install() is called with the + * line as argument. + * + * If the callback returns the terminal is prepped for reading a new + * line. + * + * If any error occurs, either in the _install() phase, or while reading + * one character, this function restores the terminal and calls lhandler + * with a NULL argument. + */ +void rl_callback_read_char(void) +{ + char *line; + + if (!line_handler) { + errno = EINVAL; + return; + } + + /* + * Check if rl_callback_handler_install() failed + * This is the only point where we can tell user + */ + if (!Screen || !rl_line_buffer) { + errno = ENOMEM; + line_handler(el_deprep(NULL)); + return; + } + + line = editinput(0); + if (line) { + char *l; + + if (Searching) { + h_search_end(line); + tty_flush(); + return; + } + + l = el_deprep(line); + line_handler(l); + + if (el_prep(rl_prompt)) + line_handler(NULL); + } + tty_flush(); +} + +void rl_callback_handler_remove(void) +{ + if (!line_handler) + return; + + el_deprep(NULL); + line_handler = NULL; +} + +char *readline(const char *prompt) +{ + /* Unless called by the user already. */ + rl_initialize(); + + if (!isatty(el_infd)) { + tty_flush(); + + return read_redirected(); + } + + if (el_prep(prompt)) + return NULL; + + return el_deprep(editinput(1)); +} + +/* + * Even though readline() itself adds history automatically, the user + * can also add lines. This is for compatibility with GNU Readline. + */ +void add_history(const char *p) +{ + if (p == NULL || *p == '\0') + return; + + hist_add(p); +} + + +int read_history(const char *filename) +{ + FILE *fp; + char buf[SCREEN_INC]; + + hist_alloc(); + + fp = fopen(filename, "r"); + if (!fp) + return EOF; + + H.Size = 0; + while (H.Size < el_hist_size) { + if (!fgets(buf, SCREEN_INC, fp)) + break; + + buf[strlen(buf) - 1] = 0; /* Remove '\n' */ + add_history(buf); + } + + return fclose(fp); +} + +int write_history(const char *filename) +{ + FILE *fp; + int i = 0; + + hist_alloc(); + + fp = fopen(filename, "w"); + if (!fp) + return EOF; + + while (i < H.Size) + fprintf(fp, "%s\n", H.Lines[i++]); + + return fclose(fp); +} + +/* +** Move back to the beginning of the current word and return an +** allocated copy of it. +*/ +char *el_find_word(void) +{ + char *p, *q; + char *word; + size_t len; + + p = &rl_line_buffer[rl_point]; + while (p > rl_line_buffer) { + p--; + if (p > rl_line_buffer && p[-1] == '\\') { + p--; + } else { + if (strchr(SEPS, (char) *p) != NULL) { + p++; + break; + } + } + } + + len = rl_point - (p - rl_line_buffer) + 1; + word = malloc(sizeof(char) * len); + if (!word) + return NULL; + + q = word; + while (p < &rl_line_buffer[rl_point]) { + if (*p == '\\') { + if (++p == &rl_line_buffer[rl_point]) + break; + } + *q++ = *p++; + } + *q = '\0'; + + return word; +} + +static el_status_t c_possible(void) +{ + char **av; + char *word; + int ac; + + word = el_find_word(); + ac = rl_list_possib(word, &av); + if (word) + free(word); + if (ac) { + el_print_columns(ac, av); + while (--ac >= 0) + free(av[ac]); + free(av); + + return CSmove; + } + + return el_ring_bell(); +} + +static el_status_t c_complete(void) +{ + char *p, *q; + char *word, *string; + size_t len; + int unique; + el_status_t s = CSdone; + + if (rl_inhibit_complete) + return CSdispatch; + + word = el_find_word(); + p = rl_complete(word, &unique); + if (word) + free(word); + if (p) { + len = strlen(p); + word = p; + + string = q = malloc(sizeof(char) * (2 * len + 1)); + if (!string) { + free(word); + return CSstay; + } + + while (*p) { + if ((*p < ' ' || strchr(SEPS, *p) != NULL) + && (!unique || p[1] != 0)) { + *q++ = '\\'; + } + *q++ = *p++; + } + *q = '\0'; + free(word); + + if (len > 0) { + s = insert_string(string); +#ifdef CONFIG_TERMINAL_BELL + if (!unique) + el_ring_bell(); +#endif + } + free(string); + + if (len > 0) + return s; + } + + return c_possible(); +} + +static el_status_t accept_line(void) +{ + rl_line_buffer[rl_end] = '\0'; + return CSdone; +} + +#ifdef SYSTEM_IS_WIN32 +static el_status_t end_of_input(void) +{ + rl_line_buffer[rl_end] = '\0'; + return CSeof; +} +#endif + +static el_status_t transpose(void) +{ + char c; + + if (rl_point) { + if (rl_point == rl_end) + left(CSmove); + c = rl_line_buffer[rl_point - 1]; + left(CSstay); + rl_line_buffer[rl_point - 1] = rl_line_buffer[rl_point]; + tty_show(rl_line_buffer[rl_point - 1]); + rl_line_buffer[rl_point++] = c; + tty_show(c); + } + + return CSstay; +} + +static el_status_t quote(void) +{ + int c; + + return (c = tty_get()) == EOF ? CSeof : insert_char((int)c); +} + +static el_status_t mk_set(void) +{ + rl_mark = rl_point; + return CSstay; +} + +static el_status_t exchange(void) +{ + int c; + + if ((c = tty_get()) != CTL('X')) + return c == EOF ? CSeof : el_ring_bell(); + + if ((c = rl_mark) <= rl_end) { + rl_mark = rl_point; + rl_point = c; + return CSmove; + } + + return CSstay; +} + +static el_status_t yank(void) +{ + if (Yanked && *Yanked) + return insert_string(Yanked); + + return CSstay; +} + +static el_status_t copy_region(void) +{ + if (rl_mark > rl_end) + return el_ring_bell(); + + if (rl_point > rl_mark) + save_yank(rl_mark, rl_point - rl_mark); + else + save_yank(rl_point, rl_mark - rl_point); + + return CSstay; +} + +static el_status_t move_to_char(void) +{ + int i, c; + char *p; + + if ((c = tty_get()) == EOF) + return CSeof; + + for (i = rl_point + 1, p = &rl_line_buffer[i]; i < rl_end; i++, p++) { + if (*p == c) { + rl_point = i; + return CSmove; + } + } + + return CSstay; +} + +static el_status_t fd_kill_word(void) +{ + int i; + + do_forward(CSstay); + if (old_point != rl_point) { + i = rl_point - old_point - 1; + rl_point = old_point; + return delete_string(i); + } + + return CSstay; +} + +static el_status_t bk_kill_word(void) +{ + bk_word(); + if (old_point != rl_point) + return delete_string(old_point - rl_point); + + return CSstay; +} + +static int argify(char *line, char ***avp) +{ + char *c; + char **p; + char **arg; + int ac; + int i; + + i = MEM_INC; + *avp = p = malloc(sizeof(char *) * i); + if (!p) + return 0; + + for (c = line; isspace(*c); c++) + continue; + + if (*c == '\n' || *c == '\0') + return 0; + + for (ac = 0, p[ac++] = c; *c && *c != '\n'; ) { + if (!isspace(*c)) { + c++; + continue; + } + + *c++ = '\0'; + if (*c && *c != '\n') { + if (ac + 1 == i) { + arg = malloc(sizeof(char *) * (i + MEM_INC)); + if (!arg) { + p[ac] = NULL; + return ac; + } + + memcpy(arg, p, i * sizeof(char *)); + i += MEM_INC; + free(p); + *avp = p = arg; + } + p[ac++] = c; + } + } + + *c = '\0'; + p[ac] = NULL; + + return ac; +} + +static el_status_t last_argument(void) +{ + char **av = NULL; + char *p; + el_status_t s; + int ac; + + if (H.Size == 1 || (p = (char *)H.Lines[H.Size - 2]) == NULL) + return el_ring_bell(); + + p = strdup(p); + if (!p) + return CSstay; + + ac = argify(p, &av); + if (Repeat != NO_ARG) + s = Repeat < ac ? insert_string(av[Repeat]) : el_ring_bell(); + else + s = ac ? insert_string(av[ac - 1]) : CSstay; + + if (av) + free(av); + free(p); + + return s; +} + +static el_keymap_t Map[64] = { + { CTL('@'), mk_set }, + { CTL('A'), beg_line }, + { CTL('B'), bk_char }, + { CTL('D'), del_char }, + { CTL('E'), end_line }, + { CTL('F'), fd_char }, + { CTL('G'), el_ring_bell }, + { CTL('H'), bk_del_char }, + { CTL('I'), c_complete }, + { CTL('J'), accept_line }, + { CTL('K'), kill_line }, + { CTL('L'), refresh }, + { CTL('M'), accept_line }, + { CTL('N'), h_next }, + { CTL('O'), el_ring_bell }, + { CTL('P'), h_prev }, + { CTL('Q'), el_ring_bell }, + { CTL('R'), h_search }, + { CTL('S'), el_ring_bell }, + { CTL('T'), transpose }, + { CTL('U'), el_ring_bell }, + { CTL('V'), quote }, + { CTL('W'), bk_kill_word }, + { CTL('X'), exchange }, + { CTL('Y'), yank }, +#ifdef SYSTEM_IS_WIN32 + { CTL('Z'), end_of_input }, +#else + { CTL('Z'), el_ring_bell }, +#endif + { CTL('['), meta }, + { CTL(']'), move_to_char }, + { CTL('^'), el_ring_bell }, + { CTL('_'), el_ring_bell }, + { 0, NULL } +}; + +static el_keymap_t MetaMap[64]= { + { CTL('H'), bk_kill_word }, + { DEL, bk_kill_word }, + { ' ', mk_set }, + { '.', last_argument }, + { '<', h_first }, + { '>', h_last }, + { '?', c_possible }, + { 'b', bk_word }, + { 'c', case_cap_word }, + { 'd', fd_kill_word }, + { 'f', fd_word }, + { 'l', case_down_word }, + { 'm', toggle_meta_mode }, + { 'u', case_up_word }, + { 'y', yank }, + { 'w', copy_region }, + { 0, NULL } +}; + +static size_t find_key_in_map(int key, el_keymap_t map[], size_t mapsz) +{ + size_t i; + + for (i = 0; i < mapsz && map[i].Function; i++) { + if (map[i].Key == key) + return i; + } + + if (i < mapsz) + return i; + + return mapsz; +} + +static el_status_t el_bind_key_in_map(int key, el_keymap_func_t function, el_keymap_t map[], size_t mapsz) +{ + size_t creat, pos = find_key_in_map(key, map, mapsz); + + /* Must check that pos is not the next to last array position, + * otherwise we will write out-of-bounds to terminate the list. */ + if (pos + 1 >= mapsz) { + errno = ENOMEM; + return CSeof; + } + + /* Add at end, create new? */ + creat = map[pos].Function == NULL; + + /* A new key so have to add it to end */ + map[pos].Key = key; + map[pos].Function = function; + + /* Terminate list */ + if (creat) { + map[pos + 1].Key = 0; + map[pos + 1].Function = NULL; + } + + return CSdone; +} + +el_status_t el_bind_key(int key, el_keymap_func_t function) +{ + return el_bind_key_in_map(key, function, Map, NELEMS(Map)); +} + +el_status_t el_bind_key_in_metamap(int key, el_keymap_func_t function) +{ + return el_bind_key_in_map(key, function, MetaMap, NELEMS(MetaMap)); +} + +rl_getc_func_t *rl_set_getc_func(rl_getc_func_t *func) +{ + rl_getc_func_t *old = rl_getc_function; + rl_getc_function = func; + return old; +} + +/** + * Local Variables: + * c-file-style: "k&r" + * c-basic-offset: 4 + * End: + */ diff --git a/editline.h b/editline.h new file mode 100644 index 0000000..bbf7708 --- /dev/null +++ b/editline.h @@ -0,0 +1,232 @@ +/* Internal header file for editline library. + * + * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz + * All rights reserved. + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * 1. The authors are not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits must appear in the documentation. + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits must appear in the documentation. + * 4. This notice may not be removed or altered. + */ + +#ifndef EDITLINE_PRIVATE_H_ +#define EDITLINE_PRIVATE_H_ + +#include "config.h" +#include +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_DIRENT_H +#include +#endif +#ifdef HAVE_SIGNAL_H +#include +#endif +#ifdef SYS_UNIX +#include "unix.h" +#endif +#ifdef SYS_OS9 +#include "os9.h" +#endif +/* The following two are for TIOCGWINSZ */ +#ifdef HAVE_TERMIOS_H +# include +#endif +#ifdef GWINSZ_IN_SYS_IOCTL +# include +#endif + +#define MEM_INC 64 +#define SCREEN_INC 256 + +/* From The Practice of Programming, by Kernighan and Pike */ +#ifndef NELEMS +#define NELEMS(array) (sizeof(array) / sizeof(array[0])) +#endif + +/* +** Variables and routines internal to this package. +*/ +extern int rl_eof; +extern int rl_erase; +extern int rl_intr; +extern int rl_kill; +extern int rl_quit; +#ifdef CONFIG_SIGSTOP +extern int rl_susp; +#endif +void rl_ttyset(int Reset); +void rl_add_slash(char *path, char *p); +char *rl_complete(char *token, int *match); +int rl_list_possib(char *token, char ***av); + +#ifndef HAVE_STDLIB_H +extern char *getenv(const char *name); +extern char *malloc(size_t size); +extern char *realloc(void *ptr, size_t size); +extern char *memcpy(void *dest, const void *src, size_t n); +extern char *strcat(char *dest, const char *src); +extern char *strchr(const char *s, int c); +extern char *strrchr(const char *s, int c); +extern char *strcpy(char *dest, const char *src); +extern char *strdup(const char *s); +extern int strcmp(const char *s1, const char *s2); +extern int strlen(const char *s); +extern int strncmp(const char *s1, const char *s2, size_t n); +#endif/* !HAVE_STDLIB_H */ + +#ifndef HAVE_STRDUP +extern char *strdup(const char *s); +#endif + +/* + * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz + * All rights reserved. + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * 1. The authors are not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits must appear in the documentation. + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits must appear in the documentation. + * 4. This notice may not be removed or altered. + */ +#ifndef EDITLINE_H_ +#define EDITLINE_H_ + +/* Handy macros when binding keys. */ +#define CTL(x) ((x) & 0x1F) +#define ISCTL(x) ((x) && (x) < ' ') +#define UNCTL(x) ((x) + 64) +#define META(x) ((x) | 0x80) +#define ISMETA(x) ((x) & 0x80) +#define UNMETA(x) ((x) & 0x7F) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Command status codes. */ +typedef enum { + CSdone = 0, /* OK */ + CSeof, /* Error, or EOF */ + CSmove, + CSdispatch, + CSstay, + CSsignal +} el_status_t; + +/* Editline specific types, despite rl_ prefix. From Heimdal project. */ +typedef int rl_list_possib_func_t(char*, char***); +typedef el_status_t el_keymap_func_t(void); +typedef int rl_hook_func_t(void); +typedef int rl_getc_func_t(void); +typedef void rl_voidfunc_t(void); +typedef void rl_vintfunc_t(int); +typedef void rl_vcpfunc_t(char *); + +/* FSF Readline compat tupes */ +typedef char *rl_complete_func_t (char *, int*); +typedef char *rl_compentry_func_t (const char *, int); +typedef char **rl_completion_func_t (const char *, int, int); + +/* Display 8-bit chars "as-is" or as `M-x'? Toggle with M-m. (Default:0 - "as-is") */ +extern int rl_meta_chars; + +/* Editline specific functions. */ +extern char * el_find_word(void); +extern void el_print_columns(int ac, char **av); +extern el_status_t el_ring_bell(void); +extern el_status_t el_del_char(void); + +extern el_status_t el_bind_key(int key, el_keymap_func_t function); +extern el_status_t el_bind_key_in_metamap(int key, el_keymap_func_t function); + +extern const char *el_next_hist(void); +extern const char *el_prev_hist(void); + +extern char *rl_complete(char *token, int *match); +extern int rl_list_possib(char *token, char ***av); +extern char **rl_completion_matches(const char *token, rl_compentry_func_t *generator); +extern char *rl_filename_completion_function(const char *text, int state); + +/* For compatibility with FSF readline. */ +extern int rl_point; +extern int rl_mark; +extern int rl_end; +extern int rl_inhibit_complete; +extern char *rl_line_buffer; +extern const char *rl_readline_name; +extern FILE *rl_instream; /* The stdio stream from which input is read. Defaults to stdin if NULL - Not supported yet! */ +extern FILE *rl_outstream; /* The stdio stream to which output is flushed. Defaults to stdout if NULL - Not supported yet! */ +extern int el_no_echo; /* E.g under emacs, don't echo except prompt */ +extern int el_no_hist; /* Disable auto-save of and access to history -- e.g. for password prompts or wizards */ +extern int el_hist_size; /* size of history scrollback buffer, default: 15 */ + +extern void rl_initialize (void); +extern void rl_reset_terminal (const char *terminal_name); +extern void rl_uninitialize (void); + +extern void rl_save_prompt (void); +extern void rl_restore_prompt (void); +extern void rl_set_prompt (const char *prompt); + +extern void rl_clear_message (void); +extern void rl_forced_update_display(void); + +extern void rl_prep_terminal (int meta_flag); +extern void rl_deprep_terminal (void); + +extern int rl_getc(void); +extern int rl_insert_text (const char *text); +extern int rl_refresh_line (int ignore1, int ignore2); + +extern char *readline (const char *prompt); + +extern void add_history (const char *line); +extern int read_history (const char *filename); +extern int write_history (const char *filename); + +extern rl_getc_func_t *rl_set_getc_func(rl_getc_func_t *func); + +extern rl_completion_func_t *rl_attempted_completion_function; +extern rl_complete_func_t *rl_set_complete_func (rl_complete_func_t *func); +extern rl_list_possib_func_t *rl_set_list_possib_func (rl_list_possib_func_t *func); + +/* Alternate interface to plain readline(), for event loops */ +extern void rl_callback_handler_install (const char *prompt, rl_vcpfunc_t *lhandler); +extern void rl_callback_read_char (void); +extern void rl_callback_handler_remove (void); + +#ifdef __cplusplus +} +#endif + +#endif /* EDITLINE_H_ */ +#endif /* EDITLINE_PRIVATE_H_ */ diff --git a/libeditline.pc b/libeditline.pc new file mode 100644 index 0000000..1d8d970 --- /dev/null +++ b/libeditline.pc @@ -0,0 +1,12 @@ +prefix=/usr/local +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: editline +Description: A small line editing library without termcap/curses +Version: 1.17.1 +Requires: +Libs: -L${libdir} -leditline +Cflags: -I${includedir} + diff --git a/sysunix.c b/sysunix.c new file mode 100644 index 0000000..4699496 --- /dev/null +++ b/sysunix.c @@ -0,0 +1,251 @@ +/* Unix system-dependant routines for editline library. + * + * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz + * All rights reserved. + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * 1. The authors are not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits must appear in the documentation. + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits must appear in the documentation. + * 4. This notice may not be removed or altered. + */ + +#include +#include "editline.h" + +#ifndef HAVE_TCGETATTR +/* Wrapper for ioctl syscalls to restart on signal */ +static int ioctl_wrap(int fd, int req, void *arg) +{ + int result, retries = 3; + + while (-1 == (result = ioctl(fd, req, arg)) && retries > 0) { + retries--; + + if (EINTR == errno) + continue; + + break; + } + + return result; +} +#endif + +/* Prefer termios over the others since it is likely the most portable. */ +#if defined(HAVE_TCGETATTR) +#include + +/* Wrapper for tcgetattr */ +static int getattr(int fd, struct termios *arg) +{ + int result, retries = 3; + + while (-1 == (result = tcgetattr(fd, arg)) && retries > 0) { + retries--; + + if (EINTR == errno) + continue; + + break; + } + + return result; +} + +/* Wrapper for tcgetattr */ +static int setattr(int fd, int opt, const struct termios *arg) +{ + int result, retries = 3; + + while (-1 == (result = tcsetattr(fd, opt, arg)) && retries > 0) { + retries--; + + if (EINTR == errno) + continue; + + break; + } + + return result; +} + +void rl_ttyset(int Reset) +{ + static struct termios old; + struct termios new; + + if (!Reset) { + if (-1 == getattr(0, &old)) + perror("Failed tcgetattr()"); + + rl_erase = old.c_cc[VERASE]; + rl_kill = old.c_cc[VKILL]; + rl_eof = old.c_cc[VEOF]; + rl_intr = old.c_cc[VINTR]; + rl_quit = old.c_cc[VQUIT]; +#ifdef CONFIG_SIGSTOP + rl_susp = old.c_cc[VSUSP]; +#endif + + new = old; + new.c_lflag &= ~(ECHO | ICANON | ISIG); + new.c_iflag &= ~INPCK; + if (rl_meta_chars) + new.c_iflag |= ISTRIP; + else + new.c_iflag &= ~ISTRIP; + new.c_cc[VMIN] = 1; + new.c_cc[VTIME] = 0; + if (-1 == setattr(0, TCSADRAIN, &new)) + perror("Failed tcsetattr(TCSADRAIN)"); + } else { + if (-1 == setattr(0, TCSADRAIN, &old)) + perror("Failed tcsetattr(TCSADRAIN)"); + } +} + +#elif defined(HAVE_TERMIO_H) +#include + +void rl_ttyset(int Reset) +{ + static struct termio old; + struct termio new; + + if (!Reset) { + if (-1 == ioctl_wrap(0, TCGETA, &old)) + perror("Failed ioctl(TCGETA)"); + + rl_erase = old.c_cc[VERASE]; + rl_kill = old.c_cc[VKILL]; + rl_eof = old.c_cc[VEOF]; + rl_intr = old.c_cc[VINTR]; + rl_quit = old.c_cc[VQUIT]; +#ifdef CONFIG_SIGSTOP + rl_susp = old.c_cc[VSUSP]; +#endif + + new = old; + new.c_lflag &= ~(ECHO | ICANON | ISIG); + new.c_iflag &= ~INPCK; + if (rl_meta_chars) + new.c_iflag |= ISTRIP; + else + new.c_iflag &= ~ISTRIP; + + new.c_cc[VMIN] = 1; + new.c_cc[VTIME] = 0; + if (-1 == ioctl_wrap(0, TCSETAW, &new)) + perror("Failed ioctl(TCSETAW)"); + } else { + if (-1 == ioctl_wrap(0, TCSETAW, &old)) + perror("Failed ioctl(TCSETAW)"); + } +} + +#elif defined(HAVE_SGTTY_H) +#include + +void rl_ttyset(int Reset) +{ + static struct sgttyb old_sgttyb; + static struct tchars old_tchars; + struct sgttyb new_sgttyb; + struct tchars new_tchars; +#ifdef CONFIG_SIGSTOP + struct ltchars old_ltchars; +#endif + + if (!Reset) { + if (-1 == ioctl_wrap(0, TIOCGETP, &old_sgttyb)) + perror("Failed TIOCGETP"); + + rl_erase = old_sgttyb.sg_erase; + rl_kill = old_sgttyb.sg_kill; + + if (-1 == ioctl_wrap(0, TIOCGETC, &old_tchars)) + perror("Failed TIOCGETC"); + + rl_eof = old_tchars.t_eofc; + rl_intr = old_tchars.t_intrc; + rl_quit = old_tchars.t_quitc; + +#ifdef CONFIG_SIGSTOP + if (-1 == ioctl_wrap(0, TIOCGLTC, &old_ltchars)) + perror("Failed TIOCGLTC"); + + rl_susp = old_ltchars.t_suspc; +#endif + + new_sgttyb = old_sgttyb; + new_sgttyb.sg_flags &= ~ECHO; + new_sgttyb.sg_flags |= RAW; + if (rl_meta_chars) + new_sgttyb.sg_flags &= ~PASS8; + else + new_sgttyb.sg_flags |= PASS8; + + if (-1 == ioctl_wrap(0, TIOCSETP, &new_sgttyb)) + perror("Failed TIOCSETP"); + + new_tchars = old_tchars; + new_tchars.t_intrc = -1; + new_tchars.t_quitc = -1; + if (-1 == ioctl_wrap(0, TIOCSETC, &new_tchars)) + perror("Failed TIOCSETC"); + } else { + if (-1 == ioctl_wrap(0, TIOCSETP, &old_sgttyb)) + perror("Failed TIOCSETP"); + + if (-1 == ioctl_wrap(0, TIOCSETC, &old_tchars)) + perror("Failed TIOCSETC"); + } +} +#else /* Neither HAVE_SGTTY_H, HAVE_TERMIO_H or HAVE_TCGETATTR */ +#error Unsupported platform, missing tcgetattr(), termio.h and sgtty.h +#endif /* Neither HAVE_SGTTY_H, HAVE_TERMIO_H or HAVE_TCGETATTR */ + +#ifndef HAVE_STRDUP +/* Return an allocated copy of a string. */ +char *strdup(const char *s) +{ + size_t len; + char *ptr; + + if (!s) + return NULL; + + len = strlen(s) + 1; + ptr = malloc(len); + if (ptr) + return memcpy(ptr, s, len); + + return NULL; +} +#endif + +void rl_add_slash(char *path, char *p) +{ + struct stat Sb; + + if (stat(path, &Sb) >= 0) + strcat(p, S_ISDIR(Sb.st_mode) ? "/" : " "); +} + +/** + * Local Variables: + * c-file-style: "k&r" + * c-basic-offset: 4 + * End: + */ diff --git a/unix.h b/unix.h new file mode 100644 index 0000000..8ab8f10 --- /dev/null +++ b/unix.h @@ -0,0 +1,36 @@ +/* Editline system header file for Unix. + * + * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz + * All rights reserved. + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * 1. The authors are not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits must appear in the documentation. + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits must appear in the documentation. + * 4. This notice may not be removed or altered. + */ + +#ifndef EDITLINE_UNIX_H_ +#define EDITLINE_UNIX_H_ + +#define CRLF "\r\n" +#define FORWARD STATIC + +#include +#include +#include + +#include +typedef struct dirent DIRENTRY; + +#endif /* EDITLINE_UNIX_H_ */