--- /dev/null
+/* linenoise.m -- guerrilla line editing library against the idea that a
+ * line editing lib needs to be 20,000 lines of C code.
+ *
+ * You can find the latest source code at:
+ *
+ * http://github.com/antirez/linenoise
+ *
+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
+ * the 2010 UNIX computers around.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2013, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * References:
+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
+ *
+ * Todo list:
+ * - Filter bogus Ctrl+<char> combinations.
+ * - Win32 support
+ *
+ * Bloat:
+ * - History search like Ctrl+r in readline?
+ *
+ * List of escape sequences used by this program, we do everything just
+ * with three sequences. In order to be so cheap we may have some
+ * flickering effect with some slow terminal, but the lesser sequences
+ * the more compatible.
+ *
+ * CHA (Cursor Horizontal Absolute)
+ * Sequence: ESC [ n G
+ * Effect: moves cursor to column n
+ *
+ * EL (Erase Line)
+ * Sequence: ESC [ n K
+ * Effect: if n is 0 or missing, clear from cursor to end of line
+ * Effect: if n is 1, clear from beginning of line to cursor
+ * Effect: if n is 2, clear entire line
+ *
+ * CUF (CUrsor Forward)
+ * Sequence: ESC [ n C
+ * Effect: moves cursor forward of n chars
+ *
+ * When multi line mode is enabled, we also use an additional escape
+ * sequence. However multi line editing is disabled by default.
+ *
+ * CUU (Cursor Up)
+ * Sequence: ESC [ n A
+ * Effect: moves cursor up of n chars.
+ *
+ * CUD (Cursor Down)
+ * Sequence: ESC [ n B
+ * Effect: moves cursor down of n chars.
+ *
+ * The following are used to clear the screen: ESC [ H ESC [ 2 J
+ * This is actually composed of two sequences:
+ *
+ * cursorhome
+ * Sequence: ESC [ H
+ * Effect: moves the cursor to upper left corner
+ *
+ * ED2 (Clear entire screen)
+ * Sequence: ESC [ 2 J
+ * Effect: clear the whole screen
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#import "linenoise.h"
+
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
+#define LINENOISE_MAX_LINE 4096
+
+static Linenoise *instance = nil;
+
+// At exit we'll try to fix the terminal to the initial conditions.
+static void linenoiseAtExit(void)
+{
+ [instance LN_disableRawModeForFD: STDIN_FILENO];
+}
+
+
+@implementation Linenoise
+@synthesize multiline = _multiline;
+@synthesize completionCallback = _completionCallback;
+
++ (Linenoise*)sharedLinenoise
+{
+ return instance;
+}
+
++ (void)initialize
+{
+ static bool initialized = false;
+ if (!initialized) {
+ initialized = true;
+ instance = [[self alloc] init];
+ atexit(linenoiseAtExit);
+ }
+}
+
+- init
+{
+ self = [super init];
+
+ _maximalHistoryLength = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
+ _history =
+ [[OFMutableArray alloc] initWithCapacity: _maximalHistoryLength];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_history release];
+
+ [super dealloc];
+}
+
+/* =========================== Line editing ================================= */
+
+
+/* Insert the character 'c' at cursor current position.
+ *
+ * On error writing to the terminal -1 is returned, otherwise 0. */
+- (int)LN_editInsertCharacter: (int)c
+{
+ static char tmp[7];
+ static int fill = 0;
+ tmp[fill++] = c;
+ tmp[fill] = '\0';
+ OFString *ins;
+
+ @try {
+ ins = @(tmp);
+ }
+ @catch (id e) {
+ return 0;
+ }
+
+ fill = 0;
+ [_buf insertString: ins
+ atIndex: _pos++];
+ [self refreshLine];
+
+ return 0;
+}
+
+/* Move cursor on the left. */
+- (void)LN_editMoveLeft
+{
+ if (_pos > 0) {
+ _pos--;
+ [self refreshLine];
+ }
+}
+
+/* Move cursor on the right. */
+- (void)LN_editMoveRight
+{
+ if (_pos != [_buf length]) {
+ _pos++;
+ [self refreshLine];
+ }
+}
+
+/* Substitute the currently edited line with the next or previous history
+ * entry as specified by 'dir'. */
+- (void)LN_editHistoryNextInDirection: (enum linenoiseDirection)dir
+{
+ size_t count = [_history count];
+ if (count > 1) {
+ /* Update the current history entry before to
+ * overwrite it with the next one. */
+ _history[count - 1 - _historyIndex] = _buf;
+ // Show the new entry
+ _historyIndex += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
+ if (_historyIndex < 0) {
+ _historyIndex = 0;
+ return;
+ } else if (_historyIndex >= count) {
+ _historyIndex = count - 1;
+ return;
+ }
+ [_buf release];
+ _buf = [_history[count - 1 - _historyIndex] mutableCopy];
+ _pos = [_buf length];
+ [self refreshLine];
+ }
+}
+
+/* Delete the character at the right of the cursor without altering the cursor
+ * position. Basically this is what happens with the "Delete" keyboard key. */
+- (void)LN_editDelete
+{
+ size_t len = [_buf length];
+ if (len > 0 && _pos < len) {
+ [_buf deleteCharactersInRange: of_range(_pos, 1)];
+ [self refreshLine];
+ }
+}
+
+/* Backspace implementation. */
+- (void)LN_editBackspace
+{
+ size_t len = [_buf length];
+ if (_pos > 0 && len > 0) {
+ _pos--;
+ [_buf deleteCharactersInRange: of_range(_pos, 1)];
+ [self refreshLine];
+ }
+}
+
+/* Delete the previosu word, maintaining the cursor at the start of the
+ * current word. */
+- (void)LN_editDeletePreviousWord
+{
+ size_t old_pos = _pos;
+
+ while (_pos > 0 && [_buf characterAtIndex: _pos - 1] == ' ')
+ _pos--;
+ while (_pos > 0 && [_buf characterAtIndex: _pos - 1] != ' ')
+ _pos--;
+
+ [_buf deleteCharactersInRange: of_range(_pos, old_pos - _pos)];
+ [self refreshLine];
+}
+
+/* This function is the core of the line editing capability of linenoise.
+ * It expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned ASAP to read().
+ *
+ * The resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * The function returns the length of the current buffer. */
+- (OFString*)LN_editWithFD: (int)fd
+ prompt: (OFString*)prompt
+{
+ /* Populate the linenoise state that we pass to functions implementing
+ * specific editing functionalities. */
+ _term = [OFFile fileWithFileDescriptor: fd];
+ _buf = [@"" mutableCopy];
+ _prompt = prompt;
+ _oldpos = _pos = 0;
+ _cols = [self LN_getColumns];
+ _maxrows = 0;
+ _historyIndex = 0;
+
+ /* The latest history entry is always our current buffer, that
+ * initially is just an empty string. */
+ [self addHistoryItem: @""];
+
+ [_term writeString: prompt];
+
+ while (1) {
+ char c;
+ size_t nread;
+ char seq[2], seq2[2];
+
+ nread = [_term readIntoBuffer: &c
+ length: 1];
+ if (nread == 0) {
+ return [_buf autorelease];
+ }
+
+ /* Only autocomplete when the callback is set.
+ * It returns < 0 when there was an error reading from fd.
+ * Otherwise it will return the character that should be
+ * handled next. */
+ if (c == 9 && _completionCallback != NULL) {
+ c = [self LN_completeLine];
+ // Return on errors
+ if (c < 0) {
+ return [_buf autorelease];
+ }
+ // Read next character when 0
+ if (c == 0)
+ continue;
+ }
+
+ switch (c) {
+ case 13: // enter
+ [_history removeLastObject];
+ return [_buf autorelease];
+ case 3: // ctrl-c
+ errno = EAGAIN;
+ return nil;
+ case 8: // ctrl-h
+ case 127: // backspace
+ [self LN_editBackspace];
+ break;
+ case 4: // ctrl-d
+ /* remove char at right of cursor, or if the
+ * line is empty, act as end-of-file. */
+ if ([_buf length] > 0) {
+ [self LN_editDelete];
+ } else {
+ [_history removeLastObject];
+ return nil;
+ }
+ break;
+ case 20: { // ctrl-t, swaps current character with previous.
+ size_t pos = _pos;
+ if (pos <= 0 || pos >= [_buf length])
+ break;
+
+ OFMutableString *reverse =
+ [[_buf substringWithRange: of_range(pos - 1, 2)]
+ mutableCopy];
+ [reverse reverse];
+
+ [_buf replaceCharactersInRange: of_range(pos - 1, 2)
+ withString: reverse];
+
+ if (pos != [_buf length])
+ _pos++;
+ [self refreshLine];
+ break;
+ }
+ case 2: // ctrl-b
+ [self LN_editMoveLeft];
+ break;
+ case 6: // ctrl-f
+ [self LN_editMoveRight];
+ break;
+ case 16: // ctrl-p
+ [self LN_editHistoryNextInDirection:
+ LINENOISE_HISTORY_PREV];
+ break;
+ case 14: // ctrl-n
+ [self LN_editHistoryNextInDirection:
+ LINENOISE_HISTORY_NEXT];
+ break;
+ case 27: // escape sequence
+ /* Read the next two bytes representing the
+ * escape sequence. */
+ if ([_term readIntoBuffer: seq
+ length: 2] != 2)
+ break;
+
+ if (seq[0] == 91 && seq[1] == 68) {
+ /* Left arrow */
+ [self LN_editMoveLeft];
+ } else if (seq[0] == 91 && seq[1] == 67) {
+ /* Right arrow */
+ [self LN_editMoveRight];
+ } else if (seq[0] == 91 &&
+ (seq[1] == 65 || seq[1] == 66)) {
+ /* Up and Down arrows */
+ [self LN_editHistoryNextInDirection:
+ (seq[1] == 65) ? LINENOISE_HISTORY_PREV
+ : LINENOISE_HISTORY_NEXT];
+ } else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
+ // extended escape, read additional two bytes.
+ if ([_term readIntoBuffer: seq2
+ length: 2] < 1)
+ break;
+ if (seq[1] == 51 && seq2[0] == 126) {
+ /* Delete key. */
+ [self LN_editDelete];
+ }
+ }
+ break;
+ default:
+ if ([self LN_editInsertCharacter: c])
+ return nil;
+ break;
+ case 21: // Ctrl+u, delete the whole line.
+ _buf = [@"" mutableCopy];
+ _pos = 0;
+ [self refreshLine];
+ break;
+ case 11: { // Ctrl+k, delete from current to end of line.
+ size_t pos = _pos;
+ size_t diff = [_buf length] - pos;
+ [_buf deleteCharactersInRange:
+ of_range(pos, diff)];
+ [self refreshLine];
+ break;
+ }
+ case 1: // Ctrl+a, go to the start of the line
+ _pos = 0;
+ [self refreshLine];
+ break;
+ case 5: // ctrl+e, go to the end of the line
+ _pos = [_buf length];
+ [self refreshLine];
+ break;
+ case 12: // ctrl+l, clear screen
+ [self clearScreen];
+ [self refreshLine];
+ break;
+ case 23: // ctrl+w, delete previous word
+ [self LN_editDeletePreviousWord];
+ break;
+ }
+ }
+ return [_buf autorelease];
+}
+
+/* This function calls the line editing function linenoiseEdit() using
+ * the STDIN file descriptor set in raw mode. */
+- (OFString*)LN_editRawWithPrompt: (OFString*)prompt
+{
+ OFString *ret;
+ int fd = [of_stdin fileDescriptorForReading];
+
+ if (!isatty(fd))
+ return [of_stdin readLine];
+
+ if ([self LN_enableRawModeForFD: fd] == -1)
+ return nil;
+ ret = [self LN_editWithFD: fd
+ prompt: prompt];
+ [self LN_disableRawModeForFD: fd];
+ [of_stdout writeString: @"\n"];
+
+ return ret;
+}
+
+/* The high level function that is the main API of the linenoise library.
+ * This function checks if the terminal has basic capabilities, just checking
+ * for a blacklist of stupid terminals, and later either calls the line
+ * editing function or uses dummy fgets() so that you will be able to type
+ * something even in the most desperate of the conditions. */
+- (OFString*)readInputWithPrompt: (OFString*)prompt
+{
+ if ([self LN_isUnsupportedTerm]) {
+ OFString *ret;
+
+ _prompt = [prompt retain];
+
+ [of_stdout writeString: prompt];
+ [of_stdout flushWriteBuffer];
+
+ ret = [of_stdin readLine];
+
+ [_prompt release];
+ _prompt = nil;
+
+ return ret;
+ } else
+ return [self LN_editRawWithPrompt: prompt];
+}
+
+
+/* ============================== Completion ================================ */
+
+/* This is an helper function for linenoiseEdit() and is called when the
+ * user types the <tab> key in order to complete the string currently in the
+ * input.
+ *
+ * The state of the editing is encapsulated into the pointed linenoiseState
+ * structure as described in the structure definition. */
+- (int)LN_completeLine
+{
+ OFList *lc = [OFList new];
+ int nread;
+ char c = 0;
+
+ _completionCallback(_buf, lc);
+ if ([lc count] == 0) {
+ [self LN_beep];
+ } else {
+ bool stop = false;
+ size_t i = 0;
+ of_list_object_t *completion = [lc firstListObject];
+ size_t count = [lc count];
+
+ while (!stop) {
+ // Show completion or original buffer
+ if (i < count) {
+ size_t saved_pos = _pos;
+ OFMutableString *saved_buf = _buf;
+
+ _buf = [completion->object mutableCopy];
+ _pos = [_buf length];
+ [self refreshLine];
+ _pos = saved_pos;
+ _buf = saved_buf;
+ } else {
+ [self refreshLine];
+ }
+
+ nread = [_term readIntoBuffer: &c
+ length: 1];
+ if (nread == 0) {
+ [lc release];
+ return -1;
+ }
+
+ switch (c) {
+ case 9: // tab
+ i = (i + 1) % (count + 1);
+ if (i == count) {
+ [self LN_beep];
+ break;
+ }
+
+ completion = completion->next;
+ if (completion == NULL)
+ completion = [lc firstListObject];
+ break;
+ case 27: // escape
+ // Re-show original buffer
+ if (i < count)
+ [self refreshLine];
+ stop = true;
+ break;
+ default:
+ // Update buffer and return
+ if (i < count) {
+ [_buf release];
+ _buf =
+ [completion->object mutableCopy];
+ _pos =
+ [_buf length];
+ }
+ stop = true;
+ break;
+ }
+ }
+ }
+
+ [lc release];
+ return c; // Return last read character
+}
+
+/* ================================ History ================================= */
+
+// Using a circular buffer is smarter, but a bit more complex to handle.
+- (int)addHistoryItem: (OFString*)line
+{
+ if (_maximalHistoryLength == 0)
+ return 0;
+
+ size_t len = [_history count];
+
+ if (len == _maximalHistoryLength) {
+ for (size_t i = 0; i < len - 1; i++)
+ _history[i] = _history[i+1];
+ [_history removeLastObject];
+ }
+
+ [_history addObject: line];
+ return 1;
+}
+
+/* Set the maximum length for the history. This function can be called even
+ * if there is already some history, the function will make sure to retain
+ * just the latest 'len' elements if the new history length value is smaller
+ * than the amount of items already inside the history. */
+- (void)setMaximalHistoryLength: (size_t)len
+{
+ if (len < 1)
+ @throw [OFInvalidArgumentException
+ exceptionWithClass: [self class]
+ selector: _cmd];
+
+ OFMutableArray *old = _history, *new;
+ int tocopy = len < [old count] ? len : [old count];
+
+ new = [[OFMutableArray alloc] initWithCapacity: len];
+
+ for (int i = 0; i < tocopy; i++)
+ [new addObject: old[i]];
+
+ _history = new;
+ [old release];
+
+ _maximalHistoryLength = len;
+}
+
+- (size_t)maximalHistoryLength
+{
+ return _maximalHistoryLength;
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+- (void)saveHistoryToFile: (OFString*)filename
+{
+ OFFile *file = [OFFile fileWithPath: filename
+ mode: @"w"];
+ for (int j = 0; j < [_history count]; j++)
+ [file writeLine: _history[j]];
+ [file close];
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+- (void)loadHistoryFromFile: (OFString*)filename
+{
+ OFFile *file = [OFFile fileWithPath: filename
+ mode: @"r"];
+ OFString *line;
+ while ((line = [file readLine]) != nil)
+ [self addHistoryItem: line];
+ [file close];
+}
+
+
+/* =========================== Line editing ================================= */
+
+// Clear the screen. Used to handle ctrl+l
+- (void)clearScreen
+{
+ [of_stdout writeString: @"\x1b[H\x1b[2J"];
+}
+
+/* Calls the two low level functions LN_refreshSingleLine or
+ * LN_refreshMultiLine according to the selected mode. */
+- (void)refreshLine
+{
+ if (_buf == nil)
+ return;
+
+ if (_multiline)
+ [self LN_refreshMultiLine];
+ else
+ [self LN_refreshSingleLine];
+}
+
+/* Single line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+- (void)LN_refreshSingleLine;
+{
+ size_t plen = [_prompt length];
+ size_t pos = _pos;
+ OFString *buf = _buf;
+
+ if ((plen + pos) >= _cols) {
+ size_t offset = (plen + pos) - _cols;
+ size_t buflen = [buf length] - offset;
+ buf = [buf substringWithRange: of_range(offset, buflen)];
+ pos -= offset;
+ }
+
+ // Cursor to left edge
+ [_term writeString: @"\x1b[0G"];
+ // Write the prompt and the current buffer content
+ [_term writeString: _prompt];
+ [_term writeString: buf];
+ // Erase to right
+ [_term writeString: @"\x1b[0K"];
+ // Move cursor to original position
+ [_term writeFormat: @"\x1b[0G\x1b[%zdC", pos + plen];
+}
+
+/* Multi line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+- (void)LN_refreshMultiLine
+{
+ size_t plen = [_prompt length];
+
+ // rows used by current buf.
+ size_t rows = (plen + [_buf length] + _cols - 1) / _cols;
+ // cursor relative row.
+ size_t rpos = (plen + _oldpos + _cols) / _cols;
+ // rpos after refresh.
+ size_t rpos2;
+ ssize_t old_rows = _maxrows, j;
+
+ // Update maxrows if needed.
+ if (rows > _maxrows)
+ _maxrows = rows;
+
+#ifdef LN_DEBUG
+ OFFile *file = [OFFile fileWithPath: @"/tmp/debug.txt"
+ mode: @"a"];
+ [file writeFormat: @"[%zd %zd %zd] p: %zd, rows: %zd, rpos: %zd, "
+ @"max: %zd, oldmax: %zd", [_buf length], _pos, _oldpos, plen, rows,
+ rpos, _maxrows, old_rows];
+#endif
+
+ /* First step: clear all the lines used before. To do so start by
+ * going to the last row. */
+ if (old_rows > rpos) {
+#ifdef LN_DEBUG
+ [file writeFormat: @", go down %zd", old_rows - rpos];
+#endif
+ [_term writeFormat: @"\x1b[%zdB", old_rows - rpos];
+ }
+
+ // Now for every row clear it, go up.
+ for (j = 0; j < old_rows - 1; j++) {
+#ifdef LN_DEBUG
+ [file writeString: @", clear+up"];
+#endif
+ [_term writeString: @"\x1b[0G\x1b[0K\x1b[1A"];
+ }
+
+ // Clean the top line.
+#ifdef LN_DEBUG
+ [file writeString: @", clear"];
+#endif
+ [_term writeString: @"\x1b[0G\x1b[0K"];
+
+ // Write the prompt and the current buffer content
+ [_term writeString: _prompt];
+ [_term writeString: _buf];
+
+ /* If we are at the very end of the screen with our prompt, we need to
+ * emit a newline and move the prompt to the first column. */
+ if (_pos && _pos == [_buf length] && (_pos + plen) % _cols == 0) {
+#ifdef LN_DEBUG
+ [file writeString: @", <newline>"];
+#endif
+ [_term writeString: @"\n\x1b[0G"];
+ rows++;
+ if (rows > _maxrows)
+ _maxrows = rows;
+ }
+
+ // Move cursor to right position.
+ rpos2 = (plen + _pos + _cols) / _cols;
+ // current cursor relative row.
+#ifdef LN_DEBUG
+ [file writeFormat: @", rpos2 %zd", rpos2];
+#endif
+ // Go up till we reach the expected positon.
+ if (rows - rpos2 > 0) {
+#ifdef LN_DEBUG
+ [file writeFormat: @", go-up %zd", rows - rpos2];
+#endif
+ [_term writeFormat: @"\x1b[%zdA", rows - rpos2];
+ }
+ /* Set column. */
+#ifdef LN_DEBUG
+ [file writeFormat: @", set col %zd", 1 + ((plen + _pos) % _cols)];
+#endif
+ [_term writeFormat: @"\x1b[%zdG", 1 + ((plen + _pos) % _cols)];
+
+ _oldpos = _pos;
+
+#ifdef LN_DEBUG
+ [file writeString: @"\n"];
+ [file close];
+#endif
+}
+
+/* ======================= Low level terminal handling ====================== */
+
+/* Return true if the terminal name is in the list of terminals we know are
+ * not able to understand basic escape sequences. */
+
+- (bool)LN_isUnsupportedTerm
+{
+ static char *unsupported_term[] = { "dumb", "cons25", NULL };
+ char *term = getenv("TERM");
+ int j;
+
+ if (term == NULL)
+ return false;
+ for (j = 0; unsupported_term[j]; j++)
+ if (!strcasecmp(term, unsupported_term[j]))
+ return true;
+ return false;
+}
+
+/* Raw mode: 1960 magic shit. */
+- (int)LN_enableRawModeForFD: (int)fd
+{
+ struct termios raw;
+
+ if (!isatty(STDIN_FILENO))
+ goto fatal;
+ if (tcgetattr(fd, &_orig_termios) == -1)
+ goto fatal;
+
+ // modify the original mode
+ raw = _orig_termios;
+
+ /* input modes: no break, no CR to NL, no parity check, no strip char,
+ * no start/stop output control. */
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ // output modes - disable post processing
+ raw.c_oflag &= ~(OPOST);
+ // control modes - set 8 bit chars
+ raw.c_cflag |= (CS8);
+ /* local modes - choing off, canonical off, no extended functions,
+ * no signal chars (^Z, ^C) */
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ /* control chars - set return condition: min number of bytes and timer.
+ * We want read to return every single byte, without timeout. */
+ raw.c_cc[VMIN] = 1;
+ // 1 byte, no timer
+ raw.c_cc[VTIME] = 0;
+
+ // put terminal in raw mode after flushing
+ if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
+ goto fatal;
+ _rawmode = true;
+ return 0;
+
+fatal:
+ errno = ENOTTY;
+ return -1;
+}
+
+- (void)LN_disableRawModeForFD: (int)fd
+{
+ if (_rawmode && tcsetattr(fd, TCSAFLUSH, &_orig_termios) != -1)
+ _rawmode = false;
+}
+
+/* Try to get the number of columns in the current terminal, or assume 80
+ * if it fails. */
+- (int)LN_getColumns
+{
+ struct winsize ws;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0)
+ return 80;
+ return ws.ws_col;
+}
+
+/* Beep, used for completion when there is nothing to complete or when all
+ * the choices were already shown. */
+- (void)LN_beep
+{
+ fprintf(stderr, "\x7");
+ fflush(stderr);
+}
+@end