8000 ds4/linenoise.c at main · kiminh/ds4 · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
2801 lines (2536 loc) · 97.2 KB

linenoise.c

File metadata and controls

2801 lines (2536 loc) · 97.2 KB
/* linenoise.c -- 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-2023, 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.
*
* 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 n chars
*
* CUB (CUrsor Backward)
* Sequence: ESC [ n D
* Effect: moves cursor backward n chars
*
* The following is used to get the terminal width if getting
* the width with the TIOCGWINSZ ioctl fails
*
* DSR (Device Status Report)
* Sequence: ESC [ 6 n
* Effect: reports the current cursor position as ESC [ n ; m R
* where n is the row and m is the column
*
* 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.
*
* When linenoiseClearScreen() is called, two additional escape sequences
* are used in order to clear the screen and position the cursor at home
* position.
*
* CUP (Cursor position)
* Sequence: ESC [ H
* Effect: moves the cursor to upper left corner
*
* ED (Erase display)
* Sequence: ESC [ 2 J
* Effect: clear the whole screen
*
*/
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdint.h>
#include "linenoise.h"
static struct {
char *text;
size_t len, cap;
int fd, depth;
} terminal_update;
static int terminalWrite(int fd, const char *text, size_t len) {
while (len) {
ssize_t n = write(fd, text, len);
if (n < 0 && errno == EINTR) continue;
if (n <= 0) { if (!n) errno = EIO; return -1; }
text += n;
len -= (size_t)n;
}
return 0;
}
int linenoiseWrite(int fd, const char *text, size_t len) {
if (!terminal_update.depth || terminal_update.fd != fd)
return terminalWrite(fd, text, len);
if (len > SIZE_MAX - terminal_update.len) { errno = ENOMEM; return -1; }
size_t need = terminal_update.len + len;
if (need > terminal_update.cap) {
size_t cap = need <= SIZE_MAX / 2 ? need * 2 : need;
char *p = realloc(terminal_update.text, cap);
if (!p) return -1;
terminal_update.text = p;
terminal_update.cap = cap;
}
if (len) memcpy(terminal_update.text + terminal_update.len, text, len);
terminal_update.len = need;
return 0;
}
void linenoiseBeginUpdate(int fd) {
if (terminal_update.depth++) return;
terminal_update.fd = fd;
terminal_update.len = 0;
linenoiseWrite(fd, "\x1b[?2026h", 8);
}
void linenoiseEndUpdate(void) {
if (!terminal_update.depth || terminal_update.depth > 1) {
if (terminal_update.depth) terminal_update.depth--;
return;
}
linenoiseWrite(terminal_update.fd, "\x1b[?2026l", 8);
terminal_update.depth = 0;
terminalWrite(terminal_update.fd, terminal_update.text, terminal_update.len);
free(terminal_update.text);
terminal_update.text = NULL;
terminal_update.len = terminal_update.cap = 0;
}
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE (1024*1024) // That will get dynamically allocated
#define LINENOISE_INITIAL_BUFLEN 4096
#define PASTE_FOLD_THRESHOLD 200 // Min bytes to fold a single-line paste.
#define PASTE_FOLD_CONTEXT 8 // Context chars kept around generic folds.
#define HISTORY_FOLD_THRESHOLD 4096 // Min bytes to fold single-line history.
#define HISTORY_FOLD_MULTILINE_LINES 16 // Min lines to fold shorter history.
#define HISTORY_FOLD_CONTEXT 96 // Context chars kept around history folds.
#define PASTE_MAX_BYTES LINENOISE_MAX_LINE
static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
static linenoiseCompletionCallback *completionCallback = NULL;
static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
static char *linenoiseReadLine(FILE *fp, int *err);
static char *linenoiseNoTTY(void);
static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags);
static void refreshLineWithFlags(struct linenoiseState *l, int flags);
static void linenoiseFoldClear(struct linenoiseState *l);
static struct termios orig_termios; /* In order to restore at exit.*/
static int maskmode = 0; /* Show "***" instead of input. For passwords. */
static int rawmode = 0; /* For atexit() function to check if restore is needed*/
static int rawmode_output = STDOUT_FILENO; /* fd used for terminal escapes. */
static int mlmode = 0; /* Multi line mode. Default is single line. */
static int atexit_registered = 0; /* Register atexit just 1 time. */
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0;
static char **history = NULL;
/* =========================== UTF-8 support ================================ */
/* Return the number of bytes that compose the UTF-8 character starting at
* 'c'. This function assumes a valid UTF-8 encoding and handles the four
* standard byte patterns:
* 0xxxxxxx -> 1 byte (ASCII)
* 110xxxxx -> 2 bytes
* 1110xxxx -> 3 bytes
* 11110xxx -> 4 bytes */
static int utf8ByteLen(char c) {
unsigned char uc = (unsigned char)c;
if ((uc & 0x80) == 0) return 1; /* 0xxxxxxx: ASCII */
if ((uc & 0xE0) == 0xC0) return 2; /* 110xxxxx: 2-byte seq */
if ((uc & 0xF0) == 0xE0) return 3; /* 1110xxxx: 3-byte seq */
if ((uc & 0xF8) == 0xF0) return 4; /* 11110xxx: 4-byte seq */
return 1; /* Fallback for invalid encoding, treat as single byte. */
}
/* Decode a UTF-8 sequence starting at 's' into a Unicode codepoint.
* Returns the codepoint value. Assumes valid UTF-8 encoding. */
static uint32_t utf8DecodeChar(const char *s, size_t *len) {
unsigned char *p = (unsigned char *)s;
uint32_t cp;
if ((*p & 0x80) == 0) {
*len = 1;
return *p;
} else if ((*p & 0xE0) == 0xC0) {
*len = 2;
cp = (*p & 0x1F) << 6;
cp |= (p[1] & 0x3F);
return cp;
} else if ((*p & 0xF0) == 0xE0) {
*len = 3;
cp = (*p & 0x0F) << 12;
cp |= (p[1] & 0x3F) << 6;
cp |= (p[2] & 0x3F);
return cp;
} else if ((*p & 0xF8) == 0xF0) {
*len = 4;
cp = (*p & 0x07) << 18;
cp |= (p[1] & 0x3F) << 12;
cp |= (p[2] & 0x3F) << 6;
cp |= (p[3] & 0x3F);
return cp;
}
*len = 1;
return *p; /* Fallback for invalid sequences. */
}
/* Zero means more bytes are needed. Invalid sequences consume one byte. */
size_t linenoiseUtf8Decode(const char *s, size_t len, uint32_t *cp) {
if (!len) return 0;
unsigned char lead = (unsigned char)s[0];
size_t n = (size_t)utf8ByteLen(s[0]);
*cp = 0xfffd;
if (lead < 0x80) { *cp = lead; return 1; }
if (lead < 0xc2 || lead > 0xf4) return 1;
for (size_t i = 1; i < n && i < len; i++)
if (((unsigned char)s[i] & 0xc0) != 0x80) return 1;
if (len < n) return 0;
size_t decoded;
uint32_t value = utf8DecodeChar(s, &decoded);
if (value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff) ||
(n == 3 && value < 0x800) || (n == 4 && value < 0x10000)) return 1;
*cp = value;
return n;
}
/* Check if codepoint is a variation selector (emoji style modifiers). */
static int isVariationSelector(uint32_t cp) {
return cp == 0xFE0E || cp == 0xFE0F; /* Text/emoji style */
}
/* Check if codepoint is a skin tone modifier. */
static int isSkinToneModifier(uint32_t cp) {
return cp >= 0x1F3FB && cp <= 0x1F3FF;
}
/* Check if codepoint is Zero Width Joiner. */
static int isZWJ(uint32_t cp) {
return cp == 0x200D;
}
/* Check if codepoint is a Regional Indicator (for flag emoji). */
static int isRegionalIndicator(uint32_t cp) {
return cp >= 0x1F1E6 && cp <= 0x1F1FF;
}
/* Check if codepoint is a combining mark or other zero-width character. */
static int isCombiningMark(uint32_t cp) {
return (cp >= 0x0300 && cp <= 0x036F) || /* Combining Diacriticals */
(cp >= 0x1AB0 && cp <= 0x1AFF) || /* Combining Diacriticals Extended */
(cp >= 0x1DC0 && cp <= 0x1DFF) || /* Combining Diacriticals Supplement */
(cp >= 0x20D0 && cp <= 0x20FF) || /* Combining Diacriticals for Symbols */
(cp >= 0xFE20 && cp <= 0xFE2F); /* Combining Half Marks */
}
/* Check if codepoint extends the previous character (doesn't start a new grapheme). */
static int isGraphemeExtend(uint32_t cp) {
return isVariationSelector(cp) || isSkinToneModifier(cp) ||
isZWJ(cp) || isCombiningMark(cp);
}
/* Decode the UTF-8 codepoint ending at position 'pos' (exclusive) and
* return its value. Also sets *cplen to the byte length of the codepoint. */
static uint32_t utf8DecodePrev(const char *buf, size_t pos, size_t *cplen) {
if (pos == 0) {
*cplen = 0;
return 0;
}
/* Scan backwards to find the start byte. */
size_t i = pos;
do {
i--;
} while (i > 0 && (pos - i) < 4 && ((unsigned char)buf[i] & 0xC0) == 0x80);
*cplen = pos - i;
size_t dummy;
return utf8DecodeChar(buf + i, &dummy);
}
/* Given a buffer and a position, return the byte length of the grapheme
* cluster before that position. A grapheme cluster includes:
* - The base character
* - Any following variation selectors, skin tone modifiers
* - ZWJ sequences (emoji joined by Zero Width Joiner)
* - Regional indicator pairs (flag emoji) */
static size_t utf8PrevCharLen(const char *buf, size_t pos) {
if (pos == 0) return 0;
size_t total = 0;
size_t curpos = pos;
/* First, get the last codepoint. */
size_t cplen;
uint32_t cp = utf8DecodePrev(buf, curpos, &cplen);
if (cplen == 0) return 0;
total += cplen;
curpos -= cplen;
/* If we're at an extending character, we need to find what it extends.
* Keep going back through the grapheme cluster. */
while (curpos > 0) {
size_t prevlen;
uint32_t prevcp = utf8DecodePrev(buf, curpos, &prevlen);
if (prevlen == 0) break;
if (isZWJ(prevcp)) {
/* ZWJ joins two emoji. Include the ZWJ and continue to get
* the preceding character. */
total += prevlen;
curpos -= prevlen;
/* Now get the character before ZWJ. */
prevcp = utf8DecodePrev(buf, curpos, &prevlen);
if (prevlen == 0) break;
total += prevlen;
curpos -= prevlen;
cp = prevcp;
continue; /* Check if there's more extending before this. */
} else if (isGraphemeExtend(cp)) {
/* Current cp is an extending character; include previous. */
total += prevlen;
curpos -= prevlen;
cp = prevcp;
continue;
} else if (isRegionalIndicator(cp) && isRegionalIndicator(prevcp)) {
/* Two regional indicators form a flag. But we need to be careful:
* flags are always pairs, so only join if we're at an even boundary.
* For simplicity, just join one pair. */
total += prevlen;
curpos -= prevlen;
break;
} else {
/* No more extending; we've found the start of the cluster. */
break;
}
}
return total;
}
/* Given a buffer, position and total length, return the byte length of the
* grapheme cluster at the current position. */
static size_t utf8NextCharLen(const char *buf, size_t pos, size_t len) {
if (pos >= len) return 0;
size_t n = linenoiseNextGrapheme(buf + pos, len - pos, NULL);
return n ? n : 1;
}
/* Return the display width of a Unicode codepoint. This is a heuristic
* that works for most common cases:
* - Control chars and zero-width: 0 columns
* - Grapheme-extending chars (VS, skin tone, ZWJ): 0 columns
* - ASCII printable: 1 column
* - Wide chars (CJK, emoji, fullwidth): 2 columns
* - Everything else: 1 column
*
* This is not a full wcwidth() implementation, but a minimal heuristic
* that handles emoji and CJK characters reasonably well. */
static int utf8CharWidth(uint32_t cp) {
/* Control characters and combining marks: zero width. */
if (cp < 32 || (cp >= 0x7F && cp < 0xA0)) return 0;
if (isCombiningMark(cp)) return 0;
/* Grapheme-extending characters: zero width.
* These modify the preceding character rather than taking space. */
if (isVariationSelector(cp)) return 0;
if (isSkinToneModifier(cp)) return 0;
if (isZWJ(cp)) return 0;
/* Wide character ranges - these display as 2 columns:
* - CJK Unified Ideographs and Extensions
* - Fullwidth forms
* - Various emoji ranges */
if (cp >= 0x1100 &&
(cp <= 0x115F || /* Hangul Jamo */
cp == 0x2329 || cp == 0x232A || /* Angle brackets */
(cp >= 0x231A && cp <= 0x231B) || /* Watch, Hourglass */
(cp >= 0x23E9 && cp <= 0x23F3) || /* Various symbols */
(cp >= 0x23F8 && cp <= 0x23FA) || /* Various symbols */
(cp >= 0x25AA && cp <= 0x25AB) || /* Small squares */
(cp >= 0x25FB && cp <= 0x25FE) || /* Squares */
(cp >= 0x2600 && cp <= 0x26FF) || /* Misc Symbols (sun, cloud, etc) */
(cp >= 0x2700 && cp <= 0x27BF) || /* Dingbats (❤, ✂, etc) */
(cp >= 0x2934 && cp <= 0x2935) || /* Arrows */
(cp >= 0x2B05 && cp <= 0x2B07) || /* Arrows */
(cp >= 0x2B1B && cp <= 0x2B1C) || /* Squares */
cp == 0x2B50 || cp == 0x2B55 || /* Star, circle */
(cp >= 0x2E80 && cp <= 0xA4CF &&
cp != 0x303F) || /* CJK ... Yi */
(cp >= 0xAC00 && cp <= 0xD7A3) || /* Hangul Syllables */
(cp >= 0xF900 && cp <= 0xFAFF) || /* CJK Compatibility Ideographs */
(cp >= 0xFE10 && cp <= 0xFE1F) || /* Vertical forms */
(cp >= 0xFE30 && cp <= 0xFE6F) || /* CJK Compatibility Forms */
(cp >= 0xFF00 && cp <= 0xFF60) || /* Fullwidth Forms */
(cp >= 0xFFE0 && cp <= 0xFFE6) || /* Fullwidth Signs */
(cp >= 0x1F1E6 && cp <= 0x1F1FF) || /* Regional Indicators (flags) */
(cp >= 0x1F300 && cp <= 0x1F64F) || /* Misc Symbols and Emoticons */
(cp >= 0x1F680 && cp <= 0x1F6FF) || /* Transport and Map Symbols */
(cp >= 0x1F900 && cp <= 0x1F9FF) || /* Supplemental Symbols */
(cp >= 0x1FA00 && cp <= 0x1FAFF) || /* Chess, Extended-A */
(cp >= 0x20000 && cp <= 0x2FFFF))) /* CJK Extension B and beyond */
return 2;
return 1; /* Default: single width */
}
int linenoiseCharacterWidth(uint32_t cp) {
return utf8CharWidth(cp);
}
size_t linenoiseNextGrapheme(const char *s, size_t len, int *width) {
uint32_t cp;
size_t used = linenoiseUtf8Decode(s, len, &cp);
if (!used) { if (width) *width = 0; return 0; }
int w = utf8CharWidth(cp), regional = isRegionalIndicator(cp), joined = 0;
while (used < len) {
size_t n = linenoiseUtf8Decode(s + used, len - used, &cp);
if (!n) break;
if (joined) {
if (utf8CharWidth(cp) > w) w = utf8CharWidth(cp);
joined = 0;
} else if (isZWJ(cp)) {
joined = 1;
} else if (isGraphemeExtend(cp)) {
if (cp == 0xfe0f && w) w = 2;
} else if (regional && isRegionalIndicator(cp)) {
regional = 0;
} else break;
used += n;
}
if (width) *width = w;
return used;
}
/* If s[] points at an ANSI CSI escape sequence (e.g. a color change like
* ESC [ 1 ; 32 m), return its length in bytes. Otherwise return 0.
*
* The caller must have already verified that s[0] == ESC (0x1b). The
* sequence layout follows ECMA-48: ESC '[' , parameter bytes (0x30-0x3f),
* intermediate bytes (0x20-0x2f), and a final byte (0x40-0x7e). */
static size_t ansiEscapeLen(const char *s, size_t len) {
size_t i;
if (len < 2 || s[1] != '[') return 0;
i = 2;
while (i < len && (unsigned char)s[i] >= 0x30 && (unsigned char)s[i] <= 0x3f) i++;
while (i < len && (unsigned char)s[i] >= 0x20 && (unsigned char)s[i] <= 0x2f) i++;
if (i >= len || (unsigned char)s[i] < 0x40 || (unsigned char)s[i] > 0x7e) return 0;
return i + 1;
}
/* Calculate the display width of a UTF-8 string of 'len' bytes.
* This is used for cursor positioning in the terminal.
* Handles grapheme clusters: characters joined by ZWJ contribute 0 width
* after the first character in the sequence.
* ANSI CSI escape sequences (e.g. color codes in the prompt) are treated
* as zero-width. */
static size_t utf8StrWidth(const char *s, size_t len) {
size_t width = 0;
for (size_t i = 0; i < len;) {
if (s[i] == 0x1b) {
size_t skip = ansiEscapeLen(s + i, len - i);
if (skip) { i += skip; continue; }
}
int w = 0;
size_t n = linenoiseNextGrapheme(s + i, len - i, &w);
if (!n) break;
width += (size_t)w;
i += n;
}
return width;
}
/* Return the display width of a single UTF-8 character at position 's'. */
static int utf8SingleCharWidth(const char *s, size_t len) {
int width = 0;
linenoiseNextGrapheme(s, len, &width);
return width;
}
enum KEY_ACTION{
KEY_NULL = 0, /* NULL */
CTRL_A = 1, /* Ctrl+a */
CTRL_B = 2, /* Ctrl-b */
CTRL_C = 3, /* Ctrl-c */
CTRL_D = 4, /* Ctrl-d */
CTRL_E = 5, /* Ctrl-e */
CTRL_F = 6, /* Ctrl-f */
CTRL_H = 8, /* Ctrl-h */
TAB = 9, /* Tab */
CTRL_K = 11, /* Ctrl+k */
CTRL_L = 12, /* Ctrl+l */
ENTER = 13, /* Enter */
CTRL_N = 14, /* Ctrl-n */
CTRL_P = 16, /* Ctrl-p */
CTRL_T = 20, /* Ctrl-t */
CTRL_U = 21, /* Ctrl+u */
CTRL_W = 23, /* Ctrl+w */
ESC = 27, /* Escape */
BACKSPACE = 127 /* Backspace */
};
static void linenoiseAtExit(void);
int linenoiseHistoryAdd(const char *line);
#define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen
#define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen.
#define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both.
static void refreshLine(struct linenoiseState *l);
/* Debugging macro. */
#if 0
FILE *lndebug_fp = NULL;
#define lndebug(...) \
do { \
if (lndebug_fp == NULL) { \
lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
fprintf(lndebug_fp, \
"[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
(int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
(int)l->oldrows,old_rows); \
} \
fprintf(lndebug_fp, ", " __VA_ARGS__); \
fflush(lndebug_fp); \
} while (0)
#else
#define lndebug(fmt, ...)
#endif
/* ======================= Low level terminal handling ====================== */
/* Enable "mask mode". When it is enabled, instead of the input that
* the user is typing, the terminal will just display a corresponding
* number of asterisks, like "****". This is useful for passwords and other
* secrets that should not be displayed. */
void linenoiseMaskModeEnable(void) {
maskmode = 1;
}
/* Disable mask mode. */
void linenoiseMaskModeDisable(void) {
maskmode = 0;
}
/* Set if to use or not the multi line mode. */
void linenoiseSetMultiLine(int ml) {
mlmode = ml;
}
/* Return true if the terminal name is in the list of terminals we know are
* not able to understand basic escape sequences. */
static int isUnsupportedTerm(void) {
char *term = getenv("TERM");
int j;
if (term == NULL) return 0;
for (j = 0; unsupported_term[j]; j++)
if (!strcasecmp(term,unsupported_term[j])) return 1;
return 0;
}
static void linenoiseMakeRawMode(struct termios *raw) {
raw->c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw->c_oflag &= ~(OPOST);
raw->c_cflag |= (CS8);
raw->c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw->c_cc[VMIN] = 1;
raw->c_cc[VTIME] = 0;
}
static int linenoiseTermiosRawFieldsEqual(const struct termios *a,
const struct termios *b) {
return a->c_iflag == b->c_iflag &&
a->c_oflag == b->c_oflag &&
a->c_cflag == b->c_cflag &&
a->c_lflag == b->c_lflag &&
a->c_cc[VMIN] == b->c_cc[VMIN] &&
a->c_cc[VTIME] == b->c_cc[VTIME];
}
/* Raw mode: 1960 magic shit. */
static int enableRawMode(int fd) {
struct termios raw;
/* Test mode: when LINENOISE_ASSUME_TTY is set, skip terminal setup.
* This allows testing via pipes without a real terminal. */
if (getenv("LINENOISE_ASSUME_TTY")) {
rawmode = 1;
return 0;
}
if (!isatty(STDIN_FILENO)) goto fatal;
if (!atexit_registered) {
atexit(linenoiseAtExit);
atexit_registered = 1;
}
if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
raw = orig_termios; /* modify the original mode */
linenoiseMakeRawMode(&raw);
/* put terminal in raw mode after flushing */
if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
rawmode = 1;
/* Ask the terminal to wrap paste input between ESC[200~ and ESC[201~. */
if (write(rawmode_output, "\x1b[?2004h", 8) == -1) {}
return 0;
fatal:
errno = ENOTTY;
return -1;
}
static void disableRawMode(int fd) {
/* Test mode: nothing to restore. */
if (getenv("LINENOISE_ASSUME_TTY")) {
rawmode = 0;
return;
}
/* Don't even check the return value as it's too late. */
if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) {
/* Leave bracketed paste mode when leaving raw mode. */
if (write(rawmode_output, "\x1b[?2004l", 8) == -1) {}
rawmode = 0;
}
}
/* Re-enable raw mode if a child process changed the terminal state. This is
* useful when the application spawns external processes that may reset the
* terminal to cooked mode. This is deliberately conservative: while an editor
* is active, orig_termios must remain the cooked-mode state used by
* disableRawMode(), so never replace it with the current terminal attributes.
* TCSANOW avoids discarding bytes the user typed while the child was running. */
void linenoiseRestoreRawMode(void) {
if (getenv("LINENOISE_ASSUME_TTY")) return;
if (!rawmode) return;
if (!isatty(STDIN_FILENO)) return;
struct termios current, raw = orig_termios;
linenoiseMakeRawMode(&raw);
if (tcgetattr(STDIN_FILENO, &current) == -1) return;
if (!linenoiseTermiosRawFieldsEqual(&current, &raw))
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
/* Re-enable bracketed paste mode in case the child disabled it. */
if (write(rawmode_output, "\x1b[?2004h", 8) == -1) {}
}
/* Use the ESC [6n escape sequence to query the horizontal cursor position
* and return it. On error -1 is returned, on success the position of the
* cursor. */
static int getCursorPosition(int ifd, int ofd) {
char buf[32];
int cols, rows;
unsigned int i = 0;
/* Report cursor location */
if (write(ofd, "\x1b[6n", 4) != 4) return -1;
/* Read the response: ESC [ rows ; cols R */
while (i < sizeof(buf)-1) {
if (read(ifd,buf+i,1) != 1) break;
if (buf[i] == 'R') break;
i++;
}
buf[i] = '\0';
/* Parse it. */
if (buf[0] != ESC || buf[1] != '[') return -1;
if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
return cols;
}
/* Try to get the number of columns in the current terminal, or assume 80
* if it fails. */
static int getColumns(int ifd, int ofd) {
struct winsize ws;
/* Test mode: use LINENOISE_COLS env var for fixed width. */
char *cols_env = getenv("LINENOISE_COLS");
if (cols_env) return atoi(cols_env);
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
/* ioctl() failed. Try to query the terminal itself. */
int start, cols;
/* Get the initial position so we can restore it later. */
start = getCursorPosition(ifd,ofd);
if (start == -1) goto failed;
/* Go to right margin and get position. */
if (write(ofd,"\x1b[999C",6) != 6) goto failed;
cols = getCursorPosition(ifd,ofd);
if (cols == -1) goto failed;
/* Restore position. */
if (cols > start) {
char seq[32];
snprintf(seq,32,"\x1b[%dD",cols-start);
if (write(ofd,seq,strlen(seq)) == -1) {
/* Can't recover... */
}
}
return cols;
} else {
return ws.ws_col;
}
failed:
return 80;
}
/* Clear the screen. Used to handle ctrl+l */
void linenoiseClearScreen(void) {
if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
/* nothing to do, just to avoid warning. */
}
}
/* Beep, used for completion when there is nothing to complete or when all
* the choices were already shown. */
static void linenoiseBeep(void) {
fprintf(stderr, "\x7");
fflush(stderr);
}
/* ============================== Completion ================================ */
/* Free a list of completion option populated by linenoiseAddCompletion(). */
static void freeCompletions(linenoiseCompletions *lc) {
size_t i;
for (i = 0; i < lc->len; i++)
free(lc->cvec[i]);
if (lc->cvec != NULL)
free(lc->cvec);
}
/* Called by completeLine() and linenoiseShow() to render the current
* edited line with the proposed completion. If the current completion table
* is already available, it is passed as second argument, otherwise the
* function will use the callback to obtain it.
*
* Flags are the same as refreshLine*(), that is REFRESH_* macros. */
static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) {
/* Obtain the table of completions if the caller didn't provide one. */
linenoiseCompletions ctable = { 0, NULL };
if (lc == NULL) {
completionCallback(ls->buf,&ctable);
lc = &ctable;
}
/* Show the edited line with completion if possible, or just refresh. */
if (ls->completion_idx < lc->len) {
struct linenoiseState saved = *ls;
ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]);
ls->buf = lc->cvec[ls->completion_idx];
ls->fold_count = 0;
refreshLineWithFlags(ls,flags);
ls->len = saved.len;
ls->pos = saved.pos;
ls->buf = saved.buf;
ls->fold_count = saved.fold_count;
} else {
refreshLineWithFlags(ls,flags);
}
/* Free the completions table if needed. */
if (lc != &ctable) freeCompletions(&ctable);
}
/* 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.
*
* If the function returns non-zero, the caller should handle the
* returned value as a byte read from the standard input, and process
* it as usually: this basically means that the function may return a byte
* read from the terminal but not processed. Otherwise, if zero is returned,
* the input was consumed by the completeLine() function to navigate the
* possible completions, and the caller should read for the next characters
* from stdin. */
static int linenoiseEditGrow(struct linenoiseState *l, size_t needed);
static int completeLine(struct linenoiseState *ls, int keypressed) {
linenoiseCompletions lc = { 0, NULL };
char c = keypressed;
completionCallback(ls->buf,&lc);
if (lc.len == 0) {
linenoiseBeep();
ls->in_completion = 0;
c = 0;
} else {
switch(c) {
case 9: /* tab */
if (ls->in_completion == 0) {
ls->in_completion = 1;
ls->completion_idx = 0;
} else {
ls->completion_idx = (ls->completion_idx+1) % (lc.len+1);
if (ls->completion_idx == lc.len) linenoiseBeep();
}
c = 0;
break;
case 27: /* escape */
/* Re-show original buffer */
if (ls->completion_idx < lc.len) refreshLine(ls);
ls->in_completion = 0;
c = 0;
break;
default:
/* Update buffer and return */
if (ls->completion_idx < lc.len) {
size_t len = strlen(lc.cvec[ls->completion_idx]);
if (linenoiseEditGrow(ls, len) == -1) {
linenoiseBeep();
ls->in_completion = 0;
c = 0;
break;
}
memcpy(ls->buf, lc.cvec[ls->completion_idx], len + 1);
ls->len = ls->pos = len;
linenoiseFoldClear(ls);
}
ls->in_completion = 0;
break;
}
/* Show completion or original buffer */
if (ls->in_completion && ls->completion_idx < lc.len) {
refreshLineWithCompletion(ls,&lc,REFRESH_ALL);
} else {
refreshLine(ls);
}
}
freeCompletions(&lc);
return c; /* Return last read character */
}
/* Register a callback function to be called for tab-completion. */
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
completionCallback = fn;
}
/* Register a hits function to be called to show hits to the user at the
* right of the prompt. */
void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
hintsCallback = fn;
}
/* Register a function to free the hints returned by the hints callback
* registered with linenoiseSetHintsCallback(). */
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
freeHintsCallback = fn;
}
/* This function is used by the callback function registered by the user
* in order to add completion options given the input string when the
* user typed <tab>. See the example.c source code for a very easy to
* understand example. */
void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
size_t len = strlen(str);
char *copy, **cvec;
copy = malloc(len+1);
if (copy == NULL) return;
memcpy(copy,str,len+1);
cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
if (cvec == NULL) {
free(copy);
return;
}
lc->cvec = cvec;
lc->cvec[lc->len++] = copy;
}
/* =========================== Line editing ================================= */
/* We define a very simple "append buffer" structure, that is an heap
* allocated string where we can append to. This is useful in order to
* write all the escape sequences in a buffer and flush them to the standard
* output in a single call, to avoid flickering effects. */
struct abuf {
char *b;
int len;
};
static void abInit(struct abuf *ab) {
ab->b = NULL;
ab->len = 0;
}
static void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b,ab->len+len);
if (new == NULL) return;
memcpy(new+ab->len,s,len);
ab->b = new;
ab->len += len;
}
static void abFree(struct abuf *ab) {
free(ab->b);
}
static int linenoiseStatusActive(struct linenoiseState *l) {
return l->status && l->status[0];
}
static int linenoiseStatusRows(struct linenoiseState *l) {
if (!linenoiseStatusActive(l)) return 0;
int rows = 1;
for (char *p = l->status; *p; p++) {
if (*p == '\n') rows++;
}
return rows;
}
/* Append at most maxcols terminal cells of UTF-8 text. Status text is
* expected to be plain text, with styling supplied separately by the caller,
* but we still treat ANSI CSI escapes as zero-width so a caller can pass a
* pre-colored status without corrupting cursor accounting. */
static size_t abAppendUtf8Clipped(struct abuf *ab, const char *s, size_t len,
size_t maxcols) {
size_t i = 0, width = 0;
while (i < len) {
if (s[i] == 0x1b) {
size_t skip = ansiEscapeLen(s + i, len - i);
if (skip > 0) {
abAppend(ab, s + i, (int)skip);
i += skip;
continue;
}
}
int cwidth;
size_t clen = linenoiseNextGrapheme(s + i, len - i, &cwidth);
if (!clen) break;
if (width + (size_t)cwidth > maxcols) break;
abAppend(ab, s + i, (int)clen);
width += cwidth;
i += clen;
}
return width;
}
static void refreshStatusLinePart(struct abuf *ab, struct linenoiseState *l,
const char *s, size_t len, int row) {
size_t width = 0;
size_t cols = l->cols ? l->cols : 80;
if (row > 0) {
char seq[64];
int n = snprintf(seq, sizeof(seq), "\x1b[%d;1H", row);
abAppend(ab, seq, n);
} else abAppend(ab, "\r\n", 2);
/* Fill the complete status row explicitly. Some terminals do not paint
0