/* 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 exception];

	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
