From 9d5489819727a96113b6cd5757f30d0e1263881c Mon Sep 17 00:00:00 2001 From: Ivan Baravy Date: Sun, 5 Feb 2023 07:32:43 +0000 Subject: [PATCH] Use isocline instead of bestline for portability Something more lightweight is definitely required. --- README | 4 +- deps/bestline/LICENSE | 30 - deps/bestline/bestline.c | 3583 -------------------- deps/bestline/bestline.h | 42 - deps/isocline/LICENSE | 21 + deps/isocline/include/isocline.h | 627 ++++ deps/isocline/src/attr.c | 294 ++ deps/isocline/src/attr.h | 70 + deps/isocline/src/bbcode.c | 842 +++++ deps/isocline/src/bbcode.h | 37 + deps/isocline/src/bbcode_colors.c | 194 ++ deps/isocline/src/common.c | 347 ++ deps/isocline/src/common.h | 187 + deps/isocline/src/completers.c | 675 ++++ deps/isocline/src/completions.c | 326 ++ deps/isocline/src/completions.h | 52 + deps/isocline/src/editline.c | 1142 +++++++ deps/isocline/src/editline_completion.c | 277 ++ deps/isocline/src/editline_help.c | 140 + deps/isocline/src/editline_history.c | 260 ++ deps/isocline/src/env.h | 60 + deps/isocline/src/highlight.c | 259 ++ deps/isocline/src/highlight.h | 24 + deps/isocline/src/history.c | 269 ++ deps/isocline/src/history.h | 38 + deps/isocline/src/isocline.c | 594 ++++ deps/isocline/src/stringbuf.c | 1038 ++++++ deps/isocline/src/stringbuf.h | 121 + deps/isocline/src/term.c | 1124 ++++++ deps/isocline/src/term.h | 85 + deps/isocline/src/term_color.c | 371 ++ deps/isocline/src/tty.c | 889 +++++ deps/isocline/src/tty.h | 160 + deps/isocline/src/tty_esc.c | 401 +++ deps/isocline/src/undo.c | 67 + deps/isocline/src/undo.h | 24 + deps/isocline/src/wcwidth.c | 292 ++ makefile | 26 +- shell.c | 67 +- shell.h | 2 +- test/016_#f01_#draw_all.ref.log | 2 +- test/066_#f01_#draw_#draw16bit_all.ref.log | 2 +- test/067_#f01_#draw_#draw24bit_all.ref.log | 2 +- umka_os.c | 35 +- umka_shell.c | 8 +- windows/vnet/tap.c | 2 +- 46 files changed, 11386 insertions(+), 3726 deletions(-) delete mode 100644 deps/bestline/LICENSE delete mode 100644 deps/bestline/bestline.c delete mode 100644 deps/bestline/bestline.h create mode 100644 deps/isocline/LICENSE create mode 100644 deps/isocline/include/isocline.h create mode 100644 deps/isocline/src/attr.c create mode 100644 deps/isocline/src/attr.h create mode 100644 deps/isocline/src/bbcode.c create mode 100644 deps/isocline/src/bbcode.h create mode 100644 deps/isocline/src/bbcode_colors.c create mode 100644 deps/isocline/src/common.c create mode 100644 deps/isocline/src/common.h create mode 100644 deps/isocline/src/completers.c create mode 100644 deps/isocline/src/completions.c create mode 100644 deps/isocline/src/completions.h create mode 100644 deps/isocline/src/editline.c create mode 100644 deps/isocline/src/editline_completion.c create mode 100644 deps/isocline/src/editline_help.c create mode 100644 deps/isocline/src/editline_history.c create mode 100644 deps/isocline/src/env.h create mode 100644 deps/isocline/src/highlight.c create mode 100644 deps/isocline/src/highlight.h create mode 100644 deps/isocline/src/history.c create mode 100644 deps/isocline/src/history.h create mode 100644 deps/isocline/src/isocline.c create mode 100644 deps/isocline/src/stringbuf.c create mode 100644 deps/isocline/src/stringbuf.h create mode 100644 deps/isocline/src/term.c create mode 100644 deps/isocline/src/term.h create mode 100644 deps/isocline/src/term_color.c create mode 100644 deps/isocline/src/tty.c create mode 100644 deps/isocline/src/tty.h create mode 100644 deps/isocline/src/tty_esc.c create mode 100644 deps/isocline/src/undo.c create mode 100644 deps/isocline/src/undo.h create mode 100644 deps/isocline/src/wcwidth.c diff --git a/README b/README index da7b56e..09342d0 100644 --- a/README +++ b/README @@ -136,8 +136,8 @@ Links & Acknowledgements [ 5] Universal TUN/TAP device driver by Maxim Krasnyansky and others https://www.kernel.org/doc/html/v5.12/networking/tuntap.html -[ 6] Bestline by Justine Tunney - https://github.com/jart/bestline +[ 6] Isocline by Daan Leijen + https://github.com/daanx/isocline [ 7] em_inflate by Emmanuel Marty https://github.com/emmanuel-marty/em_inflate diff --git a/deps/bestline/LICENSE b/deps/bestline/LICENSE deleted file mode 100644 index 3a4d06d..0000000 --- a/deps/bestline/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -Bestline is released under the 2-clause BSD license. - -Copyright (c) 2018-2021 Justine Tunney -Copyright (c) 2010-2016 Salvatore Sanfilippo -Copyright (c) 2010-2013 Pieter Noordhuis - -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. diff --git a/deps/bestline/bestline.c b/deps/bestline/bestline.c deleted file mode 100644 index 53a3438..0000000 --- a/deps/bestline/bestline.c +++ /dev/null @@ -1,3583 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=4 sts=4 sw=4 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ │ -│ Bestline ── Library for interactive pseudoteletypewriter command │ -│ sessions using ANSI Standard X3.64 control sequences │ -│ │ -│ OVERVIEW │ -│ │ -│ Bestline is a fork of linenoise (a popular readline alternative) │ -│ that fixes its bugs and adds the missing features while reducing │ -│ binary footprint (surprisingly) by removing bloated dependencies │ -│ which means you can finally have a permissively-licensed command │ -│ prompt w/ a 30kb footprint that's nearly as good as gnu readline │ -│ │ -│ EXAMPLE │ -│ │ -│ main() { │ -│ char *line; │ -│ while ((line = bestlineWithHistory("IN> ", "foo"))) { │ -│ fputs("OUT> ", stdout); │ -│ fputs(line, stdout); │ -│ fputs("\n", stdout); │ -│ free(line); │ -│ } │ -│ } │ -│ │ -│ CHANGES │ -│ │ -│ - Remove bell │ -│ - Add kill ring │ -│ - Fix flickering │ -│ - Add UTF-8 editing │ -│ - Add CTRL-R search │ -│ - Support unlimited lines │ -│ - Add parentheses awareness │ -│ - React to terminal resizing │ -│ - Don't generate .data section │ -│ - Support terminal flow control │ -│ - Make history loading 10x faster │ -│ - Make multiline mode the only mode │ -│ - Accommodate O_NONBLOCK file descriptors │ -│ - Restore raw mode on process foregrounding │ -│ - Make source code compatible with C++ compilers │ -│ - Fix corruption issues by using generalized parsing │ -│ - Implement nearly all GNU readline editing shortcuts │ -│ - Remove heavyweight dependencies like printf/sprintf │ -│ - Remove ISIG→^C→EAGAIN hack and use ephemeral handlers │ -│ - Support running on Windows in MinTTY or CMD.EXE on Win10+ │ -│ - Support diacratics, русский, Ελληνικά, 中国人, 日本語, 한국인 │ -│ │ -│ SHORTCUTS │ -│ │ -│ CTRL-E END │ -│ CTRL-A START │ -│ CTRL-B BACK │ -│ CTRL-F FORWARD │ -│ CTRL-L CLEAR │ -│ CTRL-H BACKSPACE │ -│ CTRL-D DELETE │ -│ CTRL-Y YANK │ -│ CTRL-D EOF (IF EMPTY) │ -│ CTRL-N NEXT HISTORY │ -│ CTRL-P PREVIOUS HISTORY │ -│ CTRL-R SEARCH HISTORY │ -│ CTRL-G CANCEL SEARCH │ -│ ALT-< BEGINNING OF HISTORY │ -│ ALT-> END OF HISTORY │ -│ ALT-F FORWARD WORD │ -│ ALT-B BACKWARD WORD │ -│ CTRL-ALT-F FORWARD EXPR │ -│ CTRL-ALT-B BACKWARD EXPR │ -│ ALT-RIGHT FORWARD EXPR │ -│ ALT-LEFT BACKWARD EXPR │ -│ ALT-SHIFT-B BARF EXPR │ -│ ALT-SHIFT-S SLURP EXPR │ -│ ALT-SHIFT-R RAISE EXPR │ -│ CTRL-K KILL LINE FORWARDS │ -│ CTRL-U KILL LINE BACKWARDS │ -│ ALT-H KILL WORD BACKWARDS │ -│ CTRL-W KILL WORD BACKWARDS │ -│ CTRL-ALT-H KILL WORD BACKWARDS │ -│ ALT-D KILL WORD FORWARDS │ -│ ALT-Y ROTATE KILL RING AND YANK AGAIN │ -│ ALT-\ SQUEEZE ADJACENT WHITESPACE │ -│ CTRL-T TRANSPOSE │ -│ ALT-T TRANSPOSE WORD │ -│ ALT-U UPPERCASE WORD │ -│ ALT-L LOWERCASE WORD │ -│ ALT-C CAPITALIZE WORD │ -│ CTRL-Z SUSPEND PROCESS │ -│ CTRL-\ QUIT PROCESS │ -│ CTRL-S PAUSE OUTPUT │ -│ CTRL-Q UNPAUSE OUTPUT (IF PAUSED) │ -│ CTRL-Q ESCAPED INSERT │ -│ CTRL-SPACE SET MARK │ -│ CTRL-X CTRL-X GOTO MARK │ -│ PROTIP REMAP CAPS LOCK TO CTRL │ -│ │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ │ -│ Copyright 2018-2021 Justine Tunney │ -│ Copyright 2010-2016 Salvatore Sanfilippo │ -│ Copyright 2010-2013 Pieter Noordhuis │ -│ │ -│ 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. │ -│ │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "bestline.h" - -#ifndef __COSMOPOLITAN__ -#ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 1 /* so GCC builds in ANSI mode */ -#endif -#define _XOPEN_SOURCE 700 /* so GCC builds in ANSI mode */ -#define _DARWIN_C_SOURCE 1 /* so SIGWINCH / IUTF8 on XNU */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef SIGWINCH -#define SIGWINCH 28 /* GNU/Systemd + XNU + FreeBSD + NetBSD + OpenBSD */ -#endif -#ifndef IUTF8 -#define IUTF8 0 -#endif -#endif - -__asm__(".ident\t\"\\n\\n\ -Bestline (BSD-2)\\n\ -Copyright 2018-2020 Justine Tunney \\n\ -Copyright 2010-2016 Salvatore Sanfilippo \\n\ -Copyright 2010-2013 Pieter Noordhuis \""); - -#ifndef BESTLINE_MAX_RING -#define BESTLINE_MAX_RING 8 -#endif - -#ifndef BESTLINE_MAX_HISTORY -#define BESTLINE_MAX_HISTORY 1024 -#endif - -#define BESTLINE_HISTORY_FIRST +BESTLINE_MAX_HISTORY -#define BESTLINE_HISTORY_PREV +1 -#define BESTLINE_HISTORY_NEXT -1 -#define BESTLINE_HISTORY_LAST -BESTLINE_MAX_HISTORY - -#define Ctrl(C) ((C) ^ 0100) -#define Min(X, Y) ((Y) > (X) ? (X) : (Y)) -#define Max(X, Y) ((Y) < (X) ? (X) : (Y)) -#define Case(X, Y) case X: Y; break -#define Read16le(X) \ - ((255 & (X)[0]) << 000 | \ - (255 & (X)[1]) << 010) -#define Read32le(X) \ - ((unsigned)(255 & (X)[0]) << 000 | \ - (unsigned)(255 & (X)[1]) << 010 | \ - (unsigned)(255 & (X)[2]) << 020 | \ - (unsigned)(255 & (X)[3]) << 030) - -struct abuf { - char *b; - unsigned len; - unsigned cap; -}; - -struct rune { - unsigned c; - unsigned n; -}; - -struct bestlineRing { - unsigned i; - char *p[BESTLINE_MAX_RING]; -}; - -/* The bestlineState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct bestlineState { - int ifd; /* terminal stdin file descriptor */ - int ofd; /* terminal stdout file descriptor */ - struct winsize ws; /* rows and columns in terminal */ - char *buf; /* edited line buffer */ - const char *prompt; /* prompt to display */ - int hindex; /* history index */ - int rows; /* rows being used */ - int oldpos; /* previous refresh cursor position */ - unsigned buflen; /* edited line buffer size */ - unsigned pos; /* current buffer index */ - unsigned len; /* current edited line length */ - unsigned mark; /* saved cursor position */ - unsigned yi, yj; /* boundaries of last yank */ - char seq[2][16]; /* keystroke history for yanking code */ - char final; /* set to true on last update */ - char dirty; /* if an update was squashed */ -}; - -static const char *const kUnsupported[] = {"dumb","cons25","emacs"}; - -static int gotint; -static int gotcont; -static int gotwinch; -static signed char rawmode; -static char maskmode; -static char ispaused; -static char iscapital; -static unsigned historylen; -static struct bestlineRing ring; -static struct sigaction orig_cont; -static struct sigaction orig_winch; -static struct termios orig_termios; -static char *history[BESTLINE_MAX_HISTORY]; -static bestlineXlatCallback *xlatCallback; -static bestlineHintsCallback *hintsCallback; -static bestlineFreeHintsCallback *freeHintsCallback; -static bestlineCompletionCallback *completionCallback; - -static void bestlineAtExit(void); -static void bestlineRefreshLine(struct bestlineState *); - -static void bestlineOnInt(int sig) { - gotint = sig; -} - -static void bestlineOnCont(int sig) { - gotcont = sig; -} - -static void bestlineOnWinch(int sig) { - gotwinch = sig; -} - -static char IsControl(unsigned c) { - return c <= 0x1F || (0x7F <= c && c <= 0x9F); -} - -static int GetMonospaceCharacterWidth(unsigned c) { - return !IsControl(c) - + (c >= 0x1100 && - (c <= 0x115f || c == 0x2329 || c == 0x232a || - (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || - (c >= 0xac00 && c <= 0xd7a3) || - (c >= 0xf900 && c <= 0xfaff) || - (c >= 0xfe10 && c <= 0xfe19) || - (c >= 0xfe30 && c <= 0xfe6f) || - (c >= 0xff00 && c <= 0xff60) || - (c >= 0xffe0 && c <= 0xffe6) || - (c >= 0x20000 && c <= 0x2fffd) || - (c >= 0x30000 && c <= 0x3fffd))); -} - -/** - * Returns nonzero if 𝑐 isn't alphanumeric. - * - * Line reading interfaces generally define this operation as UNICODE - * characters that aren't in the letter category (Lu, Ll, Lt, Lm, Lo) - * and aren't in the number categorie (Nd, Nl, No). We also add a few - * other things like blocks and emoji (So). - */ -char bestlineIsSeparator(unsigned c) { - int m, l, r, n; - if (c < 0200) { - return !(('0' <= c && c <= '9') || - ('A' <= c && c <= 'Z') || - ('a' <= c && c <= 'z')); - } - if (c <= 0xffff) { - static const unsigned short kGlyphs[][2] = { - {0x00aa, 0x00aa}, /* 1x English */ - {0x00b2, 0x00b3}, /* 2x English Arabic */ - {0x00b5, 0x00b5}, /* 1x Greek */ - {0x00b9, 0x00ba}, /* 2x English Arabic */ - {0x00bc, 0x00be}, /* 3x Vulgar English Arabic */ - {0x00c0, 0x00d6}, /* 23x Watin */ - {0x00d8, 0x00f6}, /* 31x Watin */ - {0x0100, 0x02c1}, /* 450x Watin-AB,IPA,Spacemod */ - {0x02c6, 0x02d1}, /* 12x Spacemod */ - {0x02e0, 0x02e4}, /* 5x Spacemod */ - {0x02ec, 0x02ec}, /* 1x Spacemod */ - {0x02ee, 0x02ee}, /* 1x Spacemod */ - {0x0370, 0x0374}, /* 5x Greek */ - {0x0376, 0x0377}, /* 2x Greek */ - {0x037a, 0x037d}, /* 4x Greek */ - {0x037f, 0x037f}, /* 1x Greek */ - {0x0386, 0x0386}, /* 1x Greek */ - {0x0388, 0x038a}, /* 3x Greek */ - {0x038c, 0x038c}, /* 1x Greek */ - {0x038e, 0x03a1}, /* 20x Greek */ - {0x03a3, 0x03f5}, /* 83x Greek */ - {0x03f7, 0x0481}, /* 139x Greek */ - {0x048a, 0x052f}, /* 166x Cyrillic */ - {0x0531, 0x0556}, /* 38x Armenian */ - {0x0560, 0x0588}, /* 41x Armenian */ - {0x05d0, 0x05ea}, /* 27x Hebrew */ - {0x0620, 0x064a}, /* 43x Arabic */ - {0x0660, 0x0669}, /* 10x Arabic */ - {0x0671, 0x06d3}, /* 99x Arabic */ - {0x06ee, 0x06fc}, /* 15x Arabic */ - {0x0712, 0x072f}, /* 30x Syriac */ - {0x074d, 0x07a5}, /* 89x Syriac,Arabic2,Thaana */ - {0x07c0, 0x07ea}, /* 43x NKo */ - {0x0800, 0x0815}, /* 22x Samaritan */ - {0x0840, 0x0858}, /* 25x Mandaic */ - {0x0904, 0x0939}, /* 54x Devanagari */ - {0x0993, 0x09a8}, /* 22x Bengali */ - {0x09e6, 0x09f1}, /* 12x Bengali */ - {0x0a13, 0x0a28}, /* 22x Gurmukhi */ - {0x0a66, 0x0a6f}, /* 10x Gurmukhi */ - {0x0a93, 0x0aa8}, /* 22x Gujarati */ - {0x0b13, 0x0b28}, /* 22x Oriya */ - {0x0c92, 0x0ca8}, /* 23x Kannada */ - {0x0caa, 0x0cb3}, /* 10x Kannada */ - {0x0ce6, 0x0cef}, /* 10x Kannada */ - {0x0d12, 0x0d3a}, /* 41x Malayalam */ - {0x0d85, 0x0d96}, /* 18x Sinhala */ - {0x0d9a, 0x0db1}, /* 24x Sinhala */ - {0x0de6, 0x0def}, /* 10x Sinhala */ - {0x0e01, 0x0e30}, /* 48x Thai */ - {0x0e8c, 0x0ea3}, /* 24x Lao */ - {0x0f20, 0x0f33}, /* 20x Tibetan */ - {0x0f49, 0x0f6c}, /* 36x Tibetan */ - {0x109e, 0x10c5}, /* 40x Myanmar,Georgian */ - {0x10d0, 0x10fa}, /* 43x Georgian */ - {0x10fc, 0x1248}, /* 333x Georgian,Hangul,Ethiopic */ - {0x13a0, 0x13f5}, /* 86x Cherokee */ - {0x1401, 0x166d}, /* 621x Aboriginal */ - {0x16a0, 0x16ea}, /* 75x Runic */ - {0x1700, 0x170c}, /* 13x Tagalog */ - {0x1780, 0x17b3}, /* 52x Khmer */ - {0x1820, 0x1878}, /* 89x Mongolian */ - {0x1a00, 0x1a16}, /* 23x Buginese */ - {0x1a20, 0x1a54}, /* 53x Tai Tham */ - {0x1a80, 0x1a89}, /* 10x Tai Tham */ - {0x1a90, 0x1a99}, /* 10x Tai Tham */ - {0x1b05, 0x1b33}, /* 47x Balinese */ - {0x1b50, 0x1b59}, /* 10x Balinese */ - {0x1b83, 0x1ba0}, /* 30x Sundanese */ - {0x1bae, 0x1be5}, /* 56x Sundanese */ - {0x1c90, 0x1cba}, /* 43x Georgian2 */ - {0x1cbd, 0x1cbf}, /* 3x Georgian2 */ - {0x1e00, 0x1f15}, /* 278x Watin-C,Greek2 */ - {0x2070, 0x2071}, /* 2x Supersub */ - {0x2074, 0x2079}, /* 6x Supersub */ - {0x207f, 0x2089}, /* 11x Supersub */ - {0x2090, 0x209c}, /* 13x Supersub */ - {0x2100, 0x2117}, /* 24x Letterlike */ - {0x2119, 0x213f}, /* 39x Letterlike */ - {0x2145, 0x214a}, /* 6x Letterlike */ - {0x214c, 0x218b}, /* 64x Letterlike,Numbery */ - {0x21af, 0x21cd}, /* 31x Arrows */ - {0x21d5, 0x21f3}, /* 31x Arrows */ - {0x230c, 0x231f}, /* 20x Technical */ - {0x232b, 0x237b}, /* 81x Technical */ - {0x237d, 0x239a}, /* 30x Technical */ - {0x23b4, 0x23db}, /* 40x Technical */ - {0x23e2, 0x2426}, /* 69x Technical,ControlPictures */ - {0x2460, 0x25b6}, /* 343x Enclosed,Boxes,Blocks,Shapes */ - {0x25c2, 0x25f7}, /* 54x Shapes */ - {0x2600, 0x266e}, /* 111x Symbols */ - {0x2670, 0x2767}, /* 248x Symbols,Dingbats */ - {0x2776, 0x27bf}, /* 74x Dingbats */ - {0x2800, 0x28ff}, /* 256x Braille */ - {0x2c00, 0x2c2e}, /* 47x Glagolitic */ - {0x2c30, 0x2c5e}, /* 47x Glagolitic */ - {0x2c60, 0x2ce4}, /* 133x Watin-D */ - {0x2d00, 0x2d25}, /* 38x Georgian2 */ - {0x2d30, 0x2d67}, /* 56x Tifinagh */ - {0x2d80, 0x2d96}, /* 23x Ethiopic2 */ - {0x2e2f, 0x2e2f}, /* 1x Punctuation2 */ - {0x3005, 0x3007}, /* 3x CJK Symbols & Punctuation */ - {0x3021, 0x3029}, /* 9x CJK Symbols & Punctuation */ - {0x3031, 0x3035}, /* 5x CJK Symbols & Punctuation */ - {0x3038, 0x303c}, /* 5x CJK Symbols & Punctuation */ - {0x3041, 0x3096}, /* 86x Hiragana */ - {0x30a1, 0x30fa}, /* 90x Katakana */ - {0x3105, 0x312f}, /* 43x Bopomofo */ - {0x3131, 0x318e}, /* 94x Hangul Compatibility Jamo */ - {0x31a0, 0x31ba}, /* 27x Bopomofo Extended */ - {0x31f0, 0x31ff}, /* 16x Katakana Phonetic Extensions */ - {0x3220, 0x3229}, /* 10x Enclosed CJK Letters & Months */ - {0x3248, 0x324f}, /* 8x Enclosed CJK Letters & Months */ - {0x3251, 0x325f}, /* 15x Enclosed CJK Letters & Months */ - {0x3280, 0x3289}, /* 10x Enclosed CJK Letters & Months */ - {0x32b1, 0x32bf}, /* 15x Enclosed CJK Letters & Months */ - {0x3400, 0x4db5}, /* 6582x CJK Unified Ideographs Extension A */ - {0x4dc0, 0x9fef}, /* 21040x Yijing Hexagram, CJK Unified Ideographs */ - {0xa000, 0xa48c}, /* 1165x Yi Syllables */ - {0xa4d0, 0xa4fd}, /* 46x Lisu */ - {0xa500, 0xa60c}, /* 269x Vai */ - {0xa610, 0xa62b}, /* 28x Vai */ - {0xa6a0, 0xa6ef}, /* 80x Bamum */ - {0xa80c, 0xa822}, /* 23x Syloti Nagri */ - {0xa840, 0xa873}, /* 52x Phags-pa */ - {0xa882, 0xa8b3}, /* 50x Saurashtra */ - {0xa8d0, 0xa8d9}, /* 10x Saurashtra */ - {0xa900, 0xa925}, /* 38x Kayah Li */ - {0xa930, 0xa946}, /* 23x Rejang */ - {0xa960, 0xa97c}, /* 29x Hangul Jamo Extended-A */ - {0xa984, 0xa9b2}, /* 47x Javanese */ - {0xa9cf, 0xa9d9}, /* 11x Javanese */ - {0xaa00, 0xaa28}, /* 41x Cham */ - {0xaa50, 0xaa59}, /* 10x Cham */ - {0xabf0, 0xabf9}, /* 10x Meetei Mayek */ - {0xac00, 0xd7a3}, /* 11172x Hangul Syllables */ - {0xf900, 0xfa6d}, /* 366x CJK Compatibility Ideographs */ - {0xfa70, 0xfad9}, /* 106x CJK Compatibility Ideographs */ - {0xfb1f, 0xfb28}, /* 10x Alphabetic Presentation Forms */ - {0xfb2a, 0xfb36}, /* 13x Alphabetic Presentation Forms */ - {0xfb46, 0xfbb1}, /* 108x Alphabetic Presentation Forms */ - {0xfbd3, 0xfd3d}, /* 363x Arabic Presentation Forms-A */ - {0xfe76, 0xfefc}, /* 135x Arabic Presentation Forms-B */ - {0xff10, 0xff19}, /* 10x Dubs */ - {0xff21, 0xff3a}, /* 26x Dubs */ - {0xff41, 0xff5a}, /* 26x Dubs */ - {0xff66, 0xffbe}, /* 89x Dubs */ - {0xffc2, 0xffc7}, /* 6x Dubs */ - {0xffca, 0xffcf}, /* 6x Dubs */ - {0xffd2, 0xffd7}, /* 6x Dubs */ - {0xffda, 0xffdc}, /* 3x Dubs */ - }; - l = 0; - r = n = sizeof(kGlyphs) / sizeof(kGlyphs[0]); - while (l < r) { - m = (l + r) >> 1; - if (kGlyphs[m][1] < c) { - l = m + 1; - } else { - r = m; - } - } - return !(l < n && kGlyphs[l][0] <= c && c <= kGlyphs[l][1]); - } else { - static const unsigned kAstralGlyphs[][2] = { - {0x10107, 0x10133}, /* 45x Aegean */ - {0x10140, 0x10178}, /* 57x Ancient Greek Numbers */ - {0x1018a, 0x1018b}, /* 2x Ancient Greek Numbers */ - {0x10280, 0x1029c}, /* 29x Lycian */ - {0x102a0, 0x102d0}, /* 49x Carian */ - {0x102e1, 0x102fb}, /* 27x Coptic Epact Numbers */ - {0x10300, 0x10323}, /* 36x Old Italic */ - {0x1032d, 0x1034a}, /* 30x Old Italic, Gothic */ - {0x10350, 0x10375}, /* 38x Old Permic */ - {0x10380, 0x1039d}, /* 30x Ugaritic */ - {0x103a0, 0x103c3}, /* 36x Old Persian */ - {0x103c8, 0x103cf}, /* 8x Old Persian */ - {0x103d1, 0x103d5}, /* 5x Old Persian */ - {0x10400, 0x1049d}, /* 158x Deseret, Shavian, Osmanya */ - {0x104b0, 0x104d3}, /* 36x Osage */ - {0x104d8, 0x104fb}, /* 36x Osage */ - {0x10500, 0x10527}, /* 40x Elbasan */ - {0x10530, 0x10563}, /* 52x Caucasian Albanian */ - {0x10600, 0x10736}, /* 311x Linear A */ - {0x10800, 0x10805}, /* 6x Cypriot Syllabary */ - {0x1080a, 0x10835}, /* 44x Cypriot Syllabary */ - {0x10837, 0x10838}, /* 2x Cypriot Syllabary */ - {0x1083f, 0x1089e}, /* 86x Cypriot,ImperialAramaic,Palmyrene,Nabataean */ - {0x108e0, 0x108f2}, /* 19x Hatran */ - {0x108f4, 0x108f5}, /* 2x Hatran */ - {0x108fb, 0x1091b}, /* 33x Hatran */ - {0x10920, 0x10939}, /* 26x Lydian */ - {0x10980, 0x109b7}, /* 56x Meroitic Hieroglyphs */ - {0x109bc, 0x109cf}, /* 20x Meroitic Cursive */ - {0x109d2, 0x10a00}, /* 47x Meroitic Cursive */ - {0x10a10, 0x10a13}, /* 4x Kharoshthi */ - {0x10a15, 0x10a17}, /* 3x Kharoshthi */ - {0x10a19, 0x10a35}, /* 29x Kharoshthi */ - {0x10a40, 0x10a48}, /* 9x Kharoshthi */ - {0x10a60, 0x10a7e}, /* 31x Old South Arabian */ - {0x10a80, 0x10a9f}, /* 32x Old North Arabian */ - {0x10ac0, 0x10ac7}, /* 8x Manichaean */ - {0x10ac9, 0x10ae4}, /* 28x Manichaean */ - {0x10aeb, 0x10aef}, /* 5x Manichaean */ - {0x10b00, 0x10b35}, /* 54x Avestan */ - {0x10b40, 0x10b55}, /* 22x Inscriptional Parthian */ - {0x10b58, 0x10b72}, /* 27x Inscriptional Parthian and Pahlavi */ - {0x10b78, 0x10b91}, /* 26x Inscriptional Pahlavi, Psalter Pahlavi */ - {0x10c00, 0x10c48}, /* 73x Old Turkic */ - {0x10c80, 0x10cb2}, /* 51x Old Hungarian */ - {0x10cc0, 0x10cf2}, /* 51x Old Hungarian */ - {0x10cfa, 0x10d23}, /* 42x Old Hungarian, Hanifi Rohingya */ - {0x10d30, 0x10d39}, /* 10x Hanifi Rohingya */ - {0x10e60, 0x10e7e}, /* 31x Rumi Numeral Symbols */ - {0x10f00, 0x10f27}, /* 40x Old Sogdian */ - {0x10f30, 0x10f45}, /* 22x Sogdian */ - {0x10f51, 0x10f54}, /* 4x Sogdian */ - {0x10fe0, 0x10ff6}, /* 23x Elymaic */ - {0x11003, 0x11037}, /* 53x Brahmi */ - {0x11052, 0x1106f}, /* 30x Brahmi */ - {0x11083, 0x110af}, /* 45x Kaithi */ - {0x110d0, 0x110e8}, /* 25x Sora Sompeng */ - {0x110f0, 0x110f9}, /* 10x Sora Sompeng */ - {0x11103, 0x11126}, /* 36x Chakma */ - {0x11136, 0x1113f}, /* 10x Chakma */ - {0x11144, 0x11144}, /* 1x Chakma */ - {0x11150, 0x11172}, /* 35x Mahajani */ - {0x11176, 0x11176}, /* 1x Mahajani */ - {0x11183, 0x111b2}, /* 48x Sharada */ - {0x111c1, 0x111c4}, /* 4x Sharada */ - {0x111d0, 0x111da}, /* 11x Sharada */ - {0x111dc, 0x111dc}, /* 1x Sharada */ - {0x111e1, 0x111f4}, /* 20x Sinhala Archaic Numbers */ - {0x11200, 0x11211}, /* 18x Khojki */ - {0x11213, 0x1122b}, /* 25x Khojki */ - {0x11280, 0x11286}, /* 7x Multani */ - {0x11288, 0x11288}, /* 1x Multani */ - {0x1128a, 0x1128d}, /* 4x Multani */ - {0x1128f, 0x1129d}, /* 15x Multani */ - {0x1129f, 0x112a8}, /* 10x Multani */ - {0x112b0, 0x112de}, /* 47x Khudawadi */ - {0x112f0, 0x112f9}, /* 10x Khudawadi */ - {0x11305, 0x1130c}, /* 8x Grantha */ - {0x1130f, 0x11310}, /* 2x Grantha */ - {0x11313, 0x11328}, /* 22x Grantha */ - {0x1132a, 0x11330}, /* 7x Grantha */ - {0x11332, 0x11333}, /* 2x Grantha */ - {0x11335, 0x11339}, /* 5x Grantha */ - {0x1133d, 0x1133d}, /* 1x Grantha */ - {0x11350, 0x11350}, /* 1x Grantha */ - {0x1135d, 0x11361}, /* 5x Grantha */ - {0x11400, 0x11434}, /* 53x Newa */ - {0x11447, 0x1144a}, /* 4x Newa */ - {0x11450, 0x11459}, /* 10x Newa */ - {0x1145f, 0x1145f}, /* 1x Newa */ - {0x11480, 0x114af}, /* 48x Tirhuta */ - {0x114c4, 0x114c5}, /* 2x Tirhuta */ - {0x114c7, 0x114c7}, /* 1x Tirhuta */ - {0x114d0, 0x114d9}, /* 10x Tirhuta */ - {0x11580, 0x115ae}, /* 47x Siddham */ - {0x115d8, 0x115db}, /* 4x Siddham */ - {0x11600, 0x1162f}, /* 48x Modi */ - {0x11644, 0x11644}, /* 1x Modi */ - {0x11650, 0x11659}, /* 10x Modi */ - {0x11680, 0x116aa}, /* 43x Takri */ - {0x116b8, 0x116b8}, /* 1x Takri */ - {0x116c0, 0x116c9}, /* 10x Takri */ - {0x11700, 0x1171a}, /* 27x Ahom */ - {0x11730, 0x1173b}, /* 12x Ahom */ - {0x11800, 0x1182b}, /* 44x Dogra */ - {0x118a0, 0x118f2}, /* 83x Warang Citi */ - {0x118ff, 0x118ff}, /* 1x Warang Citi */ - {0x119a0, 0x119a7}, /* 8x Nandinagari */ - {0x119aa, 0x119d0}, /* 39x Nandinagari */ - {0x119e1, 0x119e1}, /* 1x Nandinagari */ - {0x119e3, 0x119e3}, /* 1x Nandinagari */ - {0x11a00, 0x11a00}, /* 1x Zanabazar Square */ - {0x11a0b, 0x11a32}, /* 40x Zanabazar Square */ - {0x11a3a, 0x11a3a}, /* 1x Zanabazar Square */ - {0x11a50, 0x11a50}, /* 1x Soyombo */ - {0x11a5c, 0x11a89}, /* 46x Soyombo */ - {0x11a9d, 0x11a9d}, /* 1x Soyombo */ - {0x11ac0, 0x11af8}, /* 57x Pau Cin Hau */ - {0x11c00, 0x11c08}, /* 9x Bhaiksuki */ - {0x11c0a, 0x11c2e}, /* 37x Bhaiksuki */ - {0x11c40, 0x11c40}, /* 1x Bhaiksuki */ - {0x11c50, 0x11c6c}, /* 29x Bhaiksuki */ - {0x11c72, 0x11c8f}, /* 30x Marchen */ - {0x11d00, 0x11d06}, /* 7x Masaram Gondi */ - {0x11d08, 0x11d09}, /* 2x Masaram Gondi */ - {0x11d0b, 0x11d30}, /* 38x Masaram Gondi */ - {0x11d46, 0x11d46}, /* 1x Masaram Gondi */ - {0x11d50, 0x11d59}, /* 10x Masaram Gondi */ - {0x11d60, 0x11d65}, /* 6x Gunjala Gondi */ - {0x11d67, 0x11d68}, /* 2x Gunjala Gondi */ - {0x11d6a, 0x11d89}, /* 32x Gunjala Gondi */ - {0x11d98, 0x11d98}, /* 1x Gunjala Gondi */ - {0x11da0, 0x11da9}, /* 10x Gunjala Gondi */ - {0x11ee0, 0x11ef2}, /* 19x Makasar */ - {0x11fc0, 0x11fd4}, /* 21x Tamil Supplement */ - {0x12000, 0x12399}, /* 922x Cuneiform */ - {0x12400, 0x1246e}, /* 111x Cuneiform Numbers & Punctuation */ - {0x12480, 0x12543}, /* 196x Early Dynastic Cuneiform */ - {0x13000, 0x1342e}, /* 1071x Egyptian Hieroglyphs */ - {0x14400, 0x14646}, /* 583x Anatolian Hieroglyphs */ - {0x16800, 0x16a38}, /* 569x Bamum Supplement */ - {0x16a40, 0x16a5e}, /* 31x Mro */ - {0x16a60, 0x16a69}, /* 10x Mro */ - {0x16ad0, 0x16aed}, /* 30x Bassa Vah */ - {0x16b00, 0x16b2f}, /* 48x Pahawh Hmong */ - {0x16b40, 0x16b43}, /* 4x Pahawh Hmong */ - {0x16b50, 0x16b59}, /* 10x Pahawh Hmong */ - {0x16b5b, 0x16b61}, /* 7x Pahawh Hmong */ - {0x16b63, 0x16b77}, /* 21x Pahawh Hmong */ - {0x16b7d, 0x16b8f}, /* 19x Pahawh Hmong */ - {0x16e40, 0x16e96}, /* 87x Medefaidrin */ - {0x16f00, 0x16f4a}, /* 75x Miao */ - {0x16f50, 0x16f50}, /* 1x Miao */ - {0x16f93, 0x16f9f}, /* 13x Miao */ - {0x16fe0, 0x16fe1}, /* 2x Ideographic Symbols & Punctuation */ - {0x16fe3, 0x16fe3}, /* 1x Ideographic Symbols & Punctuation */ - {0x17000, 0x187f7}, /* 6136x Tangut */ - {0x18800, 0x18af2}, /* 755x Tangut Components */ - {0x1b000, 0x1b11e}, /* 287x Kana Supplement */ - {0x1b150, 0x1b152}, /* 3x Small Kana Extension */ - {0x1b164, 0x1b167}, /* 4x Small Kana Extension */ - {0x1b170, 0x1b2fb}, /* 396x Nushu */ - {0x1bc00, 0x1bc6a}, /* 107x Duployan */ - {0x1bc70, 0x1bc7c}, /* 13x Duployan */ - {0x1bc80, 0x1bc88}, /* 9x Duployan */ - {0x1bc90, 0x1bc99}, /* 10x Duployan */ - {0x1d2e0, 0x1d2f3}, /* 20x Mayan Numerals */ - {0x1d360, 0x1d378}, /* 25x Counting Rod Numerals */ - {0x1d400, 0x1d454}, /* 85x 𝐀..𝑔 Math */ - {0x1d456, 0x1d49c}, /* 71x 𝑖..𝒜 Math */ - {0x1d49e, 0x1d49f}, /* 2x 𝒞..𝒟 Math */ - {0x1d4a2, 0x1d4a2}, /* 1x 𝒢..𝒢 Math */ - {0x1d4a5, 0x1d4a6}, /* 2x 𝒥..𝒦 Math */ - {0x1d4a9, 0x1d4ac}, /* 4x 𝒩..𝒬 Math */ - {0x1d4ae, 0x1d4b9}, /* 12x 𝒮..𝒹 Math */ - {0x1d4bb, 0x1d4bb}, /* 1x 𝒻..𝒻 Math */ - {0x1d4bd, 0x1d4c3}, /* 7x 𝒽..𝓃 Math */ - {0x1d4c5, 0x1d505}, /* 65x 𝓅..𝔅 Math */ - {0x1d507, 0x1d50a}, /* 4x 𝔇..𝔊 Math */ - {0x1d50d, 0x1d514}, /* 8x 𝔍..𝔔 Math */ - {0x1d516, 0x1d51c}, /* 7x 𝔖..𝔜 Math */ - {0x1d51e, 0x1d539}, /* 28x 𝔞..𝔹 Math */ - {0x1d53b, 0x1d53e}, /* 4x 𝔻..𝔾 Math */ - {0x1d540, 0x1d544}, /* 5x 𝕀..𝕄 Math */ - {0x1d546, 0x1d546}, /* 1x 𝕆..𝕆 Math */ - {0x1d54a, 0x1d550}, /* 7x 𝕊..𝕐 Math */ - {0x1d552, 0x1d6a5}, /* 340x 𝕒..𝚥 Math */ - {0x1d6a8, 0x1d6c0}, /* 25x 𝚨..𝛀 Math */ - {0x1d6c2, 0x1d6da}, /* 25x 𝛂..𝛚 Math */ - {0x1d6dc, 0x1d6fa}, /* 31x 𝛜..𝛺 Math */ - {0x1d6fc, 0x1d714}, /* 25x 𝛼..𝜔 Math */ - {0x1d716, 0x1d734}, /* 31x 𝜖..𝜴 Math */ - {0x1d736, 0x1d74e}, /* 25x 𝜶..𝝎 Math */ - {0x1d750, 0x1d76e}, /* 31x 𝝐..𝝮 Math */ - {0x1d770, 0x1d788}, /* 25x 𝝰..𝞈 Math */ - {0x1d78a, 0x1d7a8}, /* 31x 𝞊..𝞨 Math */ - {0x1d7aa, 0x1d7c2}, /* 25x 𝞪..𝟂 Math */ - {0x1d7c4, 0x1d7cb}, /* 8x 𝟄..𝟋 Math */ - {0x1d7ce, 0x1d9ff}, /* 562x Math, Sutton SignWriting */ - {0x1f100, 0x1f10c}, /* 13x Enclosed Alphanumeric Supplement */ - {0x20000, 0x2a6d6}, /* 42711x CJK Unified Ideographs Extension B */ - {0x2a700, 0x2b734}, /* 4149x CJK Unified Ideographs Extension C */ - {0x2b740, 0x2b81d}, /* 222x CJK Unified Ideographs Extension D */ - {0x2b820, 0x2cea1}, /* 5762x CJK Unified Ideographs Extension E */ - {0x2ceb0, 0x2ebe0}, /* 7473x CJK Unified Ideographs Extension F */ - {0x2f800, 0x2fa1d}, /* 542x CJK Compatibility Ideographs Supplement */ - }; - l = 0; - r = n = sizeof(kAstralGlyphs) / sizeof(kAstralGlyphs[0]); - while (l < r) { - m = (l + r) >> 1; - if (kAstralGlyphs[m][1] < c) { - l = m + 1; - } else { - r = m; - } - } - return !(l < n && kAstralGlyphs[l][0] <= c && c <= kAstralGlyphs[l][1]); - } -} - -unsigned bestlineLowercase(unsigned c) { - int m, l, r, n; - if (c < 0200) { - if ('A' <= c && c <= 'Z') { - return c + 32; - } else { - return c; - } - } else if (c <= 0xffff) { - if ((0x0100 <= c && c <= 0x0176) || /* 60x Ā..ā → ā..ŵ Watin-A */ - (0x01de <= c && c <= 0x01ee) || /* 9x Ǟ..Ǯ → ǟ..ǯ Watin-B */ - (0x01f8 <= c && c <= 0x021e) || /* 20x Ǹ..Ȟ → ǹ..ȟ Watin-B */ - (0x0222 <= c && c <= 0x0232) || /* 9x Ȣ..Ȳ → ȣ..ȳ Watin-B */ - (0x1e00 <= c && c <= 0x1eff)) { /*256x Ḁ..Ỿ → ḁ..ỿ Watin-C */ - if (c == 0x0130) return c - 199; - if (c == 0x1e9e) return c; - return c + (~c & 1); - } else if (0x01cf <= c && c <= 0x01db) { - return c + (c & 1); /* 7x Ǐ..Ǜ → ǐ..ǜ Watin-B */ - } else if (0x13a0 <= c && c <= 0x13ef) { - return c + 38864; /* 80x Ꭰ ..Ꮿ → ꭰ ..ꮿ Cherokee */ - } else { - static const struct { - unsigned short a; - unsigned short b; - short d; - } kLower[] = { - {0x00c0, 0x00d6, +32}, /* 23x À ..Ö → à ..ö Watin */ - {0x00d8, 0x00de, +32}, /* 7x Ø ..Þ → ø ..þ Watin */ - {0x0178, 0x0178, -121}, /* 1x Ÿ ..Ÿ → ÿ ..ÿ Watin-A */ - {0x0179, 0x0179, +1}, /* 1x Ź ..Ź → ź ..ź Watin-A */ - {0x017b, 0x017b, +1}, /* 1x Ż ..Ż → ż ..ż Watin-A */ - {0x017d, 0x017d, +1}, /* 1x Ž ..Ž → ž ..ž Watin-A */ - {0x0181, 0x0181, +210}, /* 1x Ɓ ..Ɓ → ɓ ..ɓ Watin-B */ - {0x0182, 0x0182, +1}, /* 1x Ƃ ..Ƃ → ƃ ..ƃ Watin-B */ - {0x0184, 0x0184, +1}, /* 1x Ƅ ..Ƅ → ƅ ..ƅ Watin-B */ - {0x0186, 0x0186, +206}, /* 1x Ɔ ..Ɔ → ɔ ..ɔ Watin-B */ - {0x0187, 0x0187, +1}, /* 1x Ƈ ..Ƈ → ƈ ..ƈ Watin-B */ - {0x0189, 0x018a, +205}, /* 2x Ɖ ..Ɗ → ɖ ..ɗ Watin-B */ - {0x018b, 0x018b, +1}, /* 1x Ƌ ..Ƌ → ƌ ..ƌ Watin-B */ - {0x018e, 0x018e, +79}, /* 1x Ǝ ..Ǝ → ǝ ..ǝ Watin-B */ - {0x018f, 0x018f, +202}, /* 1x Ə ..Ə → ə ..ə Watin-B */ - {0x0190, 0x0190, +203}, /* 1x Ɛ ..Ɛ → ɛ ..ɛ Watin-B */ - {0x0191, 0x0191, +1}, /* 1x Ƒ ..Ƒ → ƒ ..ƒ Watin-B */ - {0x0193, 0x0193, +205}, /* 1x Ɠ ..Ɠ → ɠ ..ɠ Watin-B */ - {0x0194, 0x0194, +207}, /* 1x Ɣ ..Ɣ → ɣ ..ɣ Watin-B */ - {0x0196, 0x0196, +211}, /* 1x Ɩ ..Ɩ → ɩ ..ɩ Watin-B */ - {0x0197, 0x0197, +209}, /* 1x Ɨ ..Ɨ → ɨ ..ɨ Watin-B */ - {0x0198, 0x0198, +1}, /* 1x Ƙ ..Ƙ → ƙ ..ƙ Watin-B */ - {0x019c, 0x019c, +211}, /* 1x Ɯ ..Ɯ → ɯ ..ɯ Watin-B */ - {0x019d, 0x019d, +213}, /* 1x Ɲ ..Ɲ → ɲ ..ɲ Watin-B */ - {0x019f, 0x019f, +214}, /* 1x Ɵ ..Ɵ → ɵ ..ɵ Watin-B */ - {0x01a0, 0x01a0, +1}, /* 1x Ơ ..Ơ → ơ ..ơ Watin-B */ - {0x01a2, 0x01a2, +1}, /* 1x Ƣ ..Ƣ → ƣ ..ƣ Watin-B */ - {0x01a4, 0x01a4, +1}, /* 1x Ƥ ..Ƥ → ƥ ..ƥ Watin-B */ - {0x01a6, 0x01a6, +218}, /* 1x Ʀ ..Ʀ → ʀ ..ʀ Watin-B */ - {0x01a7, 0x01a7, +1}, /* 1x Ƨ ..Ƨ → ƨ ..ƨ Watin-B */ - {0x01a9, 0x01a9, +218}, /* 1x Ʃ ..Ʃ → ʃ ..ʃ Watin-B */ - {0x01ac, 0x01ac, +1}, /* 1x Ƭ ..Ƭ → ƭ ..ƭ Watin-B */ - {0x01ae, 0x01ae, +218}, /* 1x Ʈ ..Ʈ → ʈ ..ʈ Watin-B */ - {0x01af, 0x01af, +1}, /* 1x Ư ..Ư → ư ..ư Watin-B */ - {0x01b1, 0x01b2, +217}, /* 2x Ʊ ..Ʋ → ʊ ..ʋ Watin-B */ - {0x01b3, 0x01b3, +1}, /* 1x Ƴ ..Ƴ → ƴ ..ƴ Watin-B */ - {0x01b5, 0x01b5, +1}, /* 1x Ƶ ..Ƶ → ƶ ..ƶ Watin-B */ - {0x01b7, 0x01b7, +219}, /* 1x Ʒ ..Ʒ → ʒ ..ʒ Watin-B */ - {0x01b8, 0x01b8, +1}, /* 1x Ƹ ..Ƹ → ƹ ..ƹ Watin-B */ - {0x01bc, 0x01bc, +1}, /* 1x Ƽ ..Ƽ → ƽ ..ƽ Watin-B */ - {0x01c4, 0x01c4, +2}, /* 1x DŽ ..DŽ → dž ..dž Watin-B */ - {0x01c5, 0x01c5, +1}, /* 1x Dž ..Dž → dž ..dž Watin-B */ - {0x01c7, 0x01c7, +2}, /* 1x LJ ..LJ → lj ..lj Watin-B */ - {0x01c8, 0x01c8, +1}, /* 1x Lj ..Lj → lj ..lj Watin-B */ - {0x01ca, 0x01ca, +2}, /* 1x NJ ..NJ → nj ..nj Watin-B */ - {0x01cb, 0x01cb, +1}, /* 1x Nj ..Nj → nj ..nj Watin-B */ - {0x01cd, 0x01cd, +1}, /* 1x Ǎ ..Ǎ → ǎ ..ǎ Watin-B */ - {0x01f1, 0x01f1, +2}, /* 1x DZ ..DZ → dz ..dz Watin-B */ - {0x01f2, 0x01f2, +1}, /* 1x Dz ..Dz → dz ..dz Watin-B */ - {0x01f4, 0x01f4, +1}, /* 1x Ǵ ..Ǵ → ǵ ..ǵ Watin-B */ - {0x01f6, 0x01f6, -97}, /* 1x Ƕ ..Ƕ → ƕ ..ƕ Watin-B */ - {0x01f7, 0x01f7, -56}, /* 1x Ƿ ..Ƿ → ƿ ..ƿ Watin-B */ - {0x0220, 0x0220, -130}, /* 1x Ƞ ..Ƞ → ƞ ..ƞ Watin-B */ - {0x023b, 0x023b, +1}, /* 1x Ȼ ..Ȼ → ȼ ..ȼ Watin-B */ - {0x023d, 0x023d, -163}, /* 1x Ƚ ..Ƚ → ƚ ..ƚ Watin-B */ - {0x0241, 0x0241, +1}, /* 1x Ɂ ..Ɂ → ɂ ..ɂ Watin-B */ - {0x0243, 0x0243, -195}, /* 1x Ƀ ..Ƀ → ƀ ..ƀ Watin-B */ - {0x0244, 0x0244, +69}, /* 1x Ʉ ..Ʉ → ʉ ..ʉ Watin-B */ - {0x0245, 0x0245, +71}, /* 1x Ʌ ..Ʌ → ʌ ..ʌ Watin-B */ - {0x0246, 0x0246, +1}, /* 1x Ɇ ..Ɇ → ɇ ..ɇ Watin-B */ - {0x0248, 0x0248, +1}, /* 1x Ɉ ..Ɉ → ɉ ..ɉ Watin-B */ - {0x024a, 0x024a, +1}, /* 1x Ɋ ..Ɋ → ɋ ..ɋ Watin-B */ - {0x024c, 0x024c, +1}, /* 1x Ɍ ..Ɍ → ɍ ..ɍ Watin-B */ - {0x024e, 0x024e, +1}, /* 1x Ɏ ..Ɏ → ɏ ..ɏ Watin-B */ - {0x0386, 0x0386, +38}, /* 1x Ά ..Ά → ά ..ά Greek */ - {0x0388, 0x038a, +37}, /* 3x Έ ..Ί → έ ..ί Greek */ - {0x038c, 0x038c, +64}, /* 1x Ό ..Ό → ό ..ό Greek */ - {0x038e, 0x038f, +63}, /* 2x Ύ ..Ώ → ύ ..ώ Greek */ - {0x0391, 0x03a1, +32}, /* 17x Α ..Ρ → α ..ρ Greek */ - {0x03a3, 0x03ab, +32}, /* 9x Σ ..Ϋ → σ ..ϋ Greek */ - {0x03dc, 0x03dc, +1}, /* 1x Ϝ ..Ϝ → ϝ ..ϝ Greek */ - {0x03f4, 0x03f4, -60}, /* 1x ϴ ..ϴ → θ ..θ Greek */ - {0x0400, 0x040f, +80}, /* 16x Ѐ ..Џ → ѐ ..џ Cyrillic */ - {0x0410, 0x042f, +32}, /* 32x А ..Я → а ..я Cyrillic */ - {0x0460, 0x0460, +1}, /* 1x Ѡ ..Ѡ → ѡ ..ѡ Cyrillic */ - {0x0462, 0x0462, +1}, /* 1x Ѣ ..Ѣ → ѣ ..ѣ Cyrillic */ - {0x0464, 0x0464, +1}, /* 1x Ѥ ..Ѥ → ѥ ..ѥ Cyrillic */ - {0x0472, 0x0472, +1}, /* 1x Ѳ ..Ѳ → ѳ ..ѳ Cyrillic */ - {0x0490, 0x0490, +1}, /* 1x Ґ ..Ґ → ґ ..ґ Cyrillic */ - {0x0498, 0x0498, +1}, /* 1x Ҙ ..Ҙ → ҙ ..ҙ Cyrillic */ - {0x049a, 0x049a, +1}, /* 1x Қ ..Қ → қ ..қ Cyrillic */ - {0x0531, 0x0556, +48}, /* 38x Ա ..Ֆ → ա ..ֆ Armenian */ - {0x10a0, 0x10c5, +7264}, /* 38x Ⴀ ..Ⴥ → ⴀ ..ⴥ Georgian */ - {0x10c7, 0x10c7, +7264}, /* 1x Ⴧ ..Ⴧ → ⴧ ..ⴧ Georgian */ - {0x10cd, 0x10cd, +7264}, /* 1x Ⴭ ..Ⴭ → ⴭ ..ⴭ Georgian */ - {0x13f0, 0x13f5, +8}, /* 6x Ᏸ ..Ᏽ → ᏸ ..ᏽ Cherokee */ - {0x1c90, 0x1cba, -3008}, /* 43x Ა ..Ჺ → ა ..ჺ Georgian2 */ - {0x1cbd, 0x1cbf, -3008}, /* 3x Ჽ ..Ჿ → ჽ ..ჿ Georgian2 */ - {0x1f08, 0x1f0f, -8}, /* 8x Ἀ ..Ἇ → ἀ ..ἇ Greek2 */ - {0x1f18, 0x1f1d, -8}, /* 6x Ἐ ..Ἕ → ἐ ..ἕ Greek2 */ - {0x1f28, 0x1f2f, -8}, /* 8x Ἠ ..Ἧ → ἠ ..ἧ Greek2 */ - {0x1f38, 0x1f3f, -8}, /* 8x Ἰ ..Ἷ → ἰ ..ἷ Greek2 */ - {0x1f48, 0x1f4d, -8}, /* 6x Ὀ ..Ὅ → ὀ ..ὅ Greek2 */ - {0x1f59, 0x1f59, -8}, /* 1x Ὑ ..Ὑ → ὑ ..ὑ Greek2 */ - {0x1f5b, 0x1f5b, -8}, /* 1x Ὓ ..Ὓ → ὓ ..ὓ Greek2 */ - {0x1f5d, 0x1f5d, -8}, /* 1x Ὕ ..Ὕ → ὕ ..ὕ Greek2 */ - {0x1f5f, 0x1f5f, -8}, /* 1x Ὗ ..Ὗ → ὗ ..ὗ Greek2 */ - {0x1f68, 0x1f6f, -8}, /* 8x Ὠ ..Ὧ → ὠ ..ὧ Greek2 */ - {0x1f88, 0x1f8f, -8}, /* 8x ᾈ ..ᾏ → ᾀ ..ᾇ Greek2 */ - {0x1f98, 0x1f9f, -8}, /* 8x ᾘ ..ᾟ → ᾐ ..ᾗ Greek2 */ - {0x1fa8, 0x1faf, -8}, /* 8x ᾨ ..ᾯ → ᾠ ..ᾧ Greek2 */ - {0x1fb8, 0x1fb9, -8}, /* 2x Ᾰ ..Ᾱ → ᾰ ..ᾱ Greek2 */ - {0x1fba, 0x1fbb, -74}, /* 2x Ὰ ..Ά → ὰ ..ά Greek2 */ - {0x1fbc, 0x1fbc, -9}, /* 1x ᾼ ..ᾼ → ᾳ ..ᾳ Greek2 */ - {0x1fc8, 0x1fcb, -86}, /* 4x Ὲ ..Ή → ὲ ..ή Greek2 */ - {0x1fcc, 0x1fcc, -9}, /* 1x ῌ ..ῌ → ῃ ..ῃ Greek2 */ - {0x1fd8, 0x1fd9, -8}, /* 2x Ῐ ..Ῑ → ῐ ..ῑ Greek2 */ - {0x1fda, 0x1fdb, -100}, /* 2x Ὶ ..Ί → ὶ ..ί Greek2 */ - {0x1fe8, 0x1fe9, -8}, /* 2x Ῠ ..Ῡ → ῠ ..ῡ Greek2 */ - {0x1fea, 0x1feb, -112}, /* 2x Ὺ ..Ύ → ὺ ..ύ Greek2 */ - {0x1fec, 0x1fec, -7}, /* 1x Ῥ ..Ῥ → ῥ ..ῥ Greek2 */ - {0x1ff8, 0x1ff9, -128}, /* 2x Ὸ ..Ό → ὸ ..ό Greek2 */ - {0x1ffa, 0x1ffb, -126}, /* 2x Ὼ ..Ώ → ὼ ..ώ Greek2 */ - {0x1ffc, 0x1ffc, -9}, /* 1x ῼ ..ῼ → ῳ ..ῳ Greek2 */ - {0x2126, 0x2126, -7517}, /* 1x Ω ..Ω → ω ..ω Letterlike */ - {0x212a, 0x212a, -8383}, /* 1x K ..K → k ..k Letterlike */ - {0x212b, 0x212b, -8262}, /* 1x Å ..Å → å ..å Letterlike */ - {0x2132, 0x2132, +28}, /* 1x Ⅎ ..Ⅎ → ⅎ ..ⅎ Letterlike */ - {0x2160, 0x216f, +16}, /* 16x Ⅰ ..Ⅿ → ⅰ ..ⅿ Numbery */ - {0x2183, 0x2183, +1}, /* 1x Ↄ ..Ↄ → ↄ ..ↄ Numbery */ - {0x24b6, 0x24cf, +26}, /* 26x Ⓐ ..Ⓩ → ⓐ ..ⓩ Enclosed */ - {0x2c00, 0x2c2e, +48}, /* 47x Ⰰ ..Ⱞ → ⰰ ..ⱞ Glagolitic */ - {0xff21, 0xff3a, +32}, /* 26x A..Z → a..z Dubs */ - }; - l = 0; - r = n = sizeof(kLower) / sizeof(kLower[0]); - while (l < r) { - m = (l + r) >> 1; - if (kLower[m].b < c) { - l = m + 1; - } else { - r = m; - } - } - if (l < n && kLower[l].a <= c && c <= kLower[l].b) { - return c + kLower[l].d; - } else { - return c; - } - } - } else { - static struct { - unsigned a; - unsigned b; - short d; - } kAstralLower[] = { - {0x10400, 0x10427, +40}, /* 40x 𐐀 ..𐐧 → 𐐨 ..𐑏 Deseret */ - {0x104b0, 0x104d3, +40}, /* 36x 𐒰 ..𐓓 → 𐓘 ..𐓻 Osage */ - {0x1d400, 0x1d419, +26}, /* 26x 𝐀 ..𝐙 → 𝐚 ..𝐳 Math */ - {0x1d43c, 0x1d44d, +26}, /* 18x 𝐼 ..𝑍 → 𝑖 ..𝑧 Math */ - {0x1d468, 0x1d481, +26}, /* 26x 𝑨 ..𝒁 → 𝒂 ..𝒛 Math */ - {0x1d4ae, 0x1d4b5, +26}, /* 8x 𝒮 ..𝒵 → 𝓈 ..𝓏 Math */ - {0x1d4d0, 0x1d4e9, +26}, /* 26x 𝓐 ..𝓩 → 𝓪 ..𝔃 Math */ - {0x1d50d, 0x1d514, +26}, /* 8x 𝔍 ..𝔔 → 𝔧 ..𝔮 Math */ - {0x1d56c, 0x1d585, +26}, /* 26x 𝕬 ..𝖅 → 𝖆 ..𝖟 Math */ - {0x1d5a0, 0x1d5b9, +26}, /* 26x 𝖠 ..𝖹 → 𝖺 ..𝗓 Math */ - {0x1d5d4, 0x1d5ed, +26}, /* 26x 𝗔 ..𝗭 → 𝗮 ..𝘇 Math */ - {0x1d608, 0x1d621, +26}, /* 26x 𝘈 ..𝘡 → 𝘢 ..𝘻 Math */ - {0x1d63c, 0x1d655, -442}, /* 26x 𝘼 ..𝙕 → 𝒂 ..𝒛 Math */ - {0x1d670, 0x1d689, +26}, /* 26x 𝙰 ..𝚉 → 𝚊 ..𝚣 Math */ - {0x1d6a8, 0x1d6b8, +26}, /* 17x 𝚨 ..𝚸 → 𝛂 ..𝛒 Math */ - {0x1d6e2, 0x1d6f2, +26}, /* 17x 𝛢 ..𝛲 → 𝛼 ..𝜌 Math */ - {0x1d71c, 0x1d72c, +26}, /* 17x 𝜜 ..𝜬 → 𝜶 ..𝝆 Math */ - {0x1d756, 0x1d766, +26}, /* 17x 𝝖 ..𝝦 → 𝝰 ..𝞀 Math */ - {0x1d790, 0x1d7a0, -90}, /* 17x 𝞐 ..𝞠 → 𝜶 ..𝝆 Math */ - }; - l = 0; - r = n = sizeof(kAstralLower) / sizeof(kAstralLower[0]); - while (l < r) { - m = (l + r) >> 1; - if (kAstralLower[m].b < c) { - l = m + 1; - } else { - r = m; - } - } - if (l < n && kAstralLower[l].a <= c && c <= kAstralLower[l].b) { - return c + kAstralLower[l].d; - } else { - return c; - } - } -} - -unsigned bestlineUppercase(unsigned c) { - int m, l, r, n; - if (c < 0200) { - if ('a' <= c && c <= 'z') { - return c - 32; - } else { - return c; - } - } else if (c <= 0xffff) { - if ((0x0101 <= c && c <= 0x0177) || /* 60x ā..ŵ → Ā..ā Watin-A */ - (0x01df <= c && c <= 0x01ef) || /* 9x ǟ..ǯ → Ǟ..Ǯ Watin-B */ - (0x01f8 <= c && c <= 0x021e) || /* 20x ǹ..ȟ → Ǹ..Ȟ Watin-B */ - (0x0222 <= c && c <= 0x0232) || /* 9x ȣ..ȳ → Ȣ..Ȳ Watin-B */ - (0x1e01 <= c && c <= 0x1eff)) { /*256x ḁ..ỿ → Ḁ..Ỿ Watin-C */ - if (c == 0x0131) return c + 232; - if (c == 0x1e9e) return c; - return c - (c & 1); - } else if (0x01d0 <= c && c <= 0x01dc) { - return c - (~c & 1); /* 7x ǐ..ǜ → Ǐ..Ǜ Watin-B */ - } else if (0xab70 <= c && c <= 0xabbf) { - return c - 38864; /* 80x ꭰ ..ꮿ → Ꭰ ..Ꮿ Cherokee Supplement */ - } else { - static const struct { - unsigned short a; - unsigned short b; - short d; - } kUpper[] = { - {0x00b5, 0x00b5, +743}, /* 1x µ ..µ → Μ ..Μ Watin */ - {0x00e0, 0x00f6, -32}, /* 23x à ..ö → À ..Ö Watin */ - {0x00f8, 0x00fe, -32}, /* 7x ø ..þ → Ø ..Þ Watin */ - {0x00ff, 0x00ff, +121}, /* 1x ÿ ..ÿ → Ÿ ..Ÿ Watin */ - {0x017a, 0x017a, -1}, /* 1x ź ..ź → Ź ..Ź Watin-A */ - {0x017c, 0x017c, -1}, /* 1x ż ..ż → Ż ..Ż Watin-A */ - {0x017e, 0x017e, -1}, /* 1x ž ..ž → Ž ..Ž Watin-A */ - {0x017f, 0x017f, -300}, /* 1x ſ ..ſ → S ..S Watin-A */ - {0x0180, 0x0180, +195}, /* 1x ƀ ..ƀ → Ƀ ..Ƀ Watin-B */ - {0x0183, 0x0183, -1}, /* 1x ƃ ..ƃ → Ƃ ..Ƃ Watin-B */ - {0x0185, 0x0185, -1}, /* 1x ƅ ..ƅ → Ƅ ..Ƅ Watin-B */ - {0x0188, 0x0188, -1}, /* 1x ƈ ..ƈ → Ƈ ..Ƈ Watin-B */ - {0x018c, 0x018c, -1}, /* 1x ƌ ..ƌ → Ƌ ..Ƌ Watin-B */ - {0x0192, 0x0192, -1}, /* 1x ƒ ..ƒ → Ƒ ..Ƒ Watin-B */ - {0x0195, 0x0195, +97}, /* 1x ƕ ..ƕ → Ƕ ..Ƕ Watin-B */ - {0x0199, 0x0199, -1}, /* 1x ƙ ..ƙ → Ƙ ..Ƙ Watin-B */ - {0x019a, 0x019a, +163}, /* 1x ƚ ..ƚ → Ƚ ..Ƚ Watin-B */ - {0x019e, 0x019e, +130}, /* 1x ƞ ..ƞ → Ƞ ..Ƞ Watin-B */ - {0x01a1, 0x01a1, -1}, /* 1x ơ ..ơ → Ơ ..Ơ Watin-B */ - {0x01a3, 0x01a3, -1}, /* 1x ƣ ..ƣ → Ƣ ..Ƣ Watin-B */ - {0x01a5, 0x01a5, -1}, /* 1x ƥ ..ƥ → Ƥ ..Ƥ Watin-B */ - {0x01a8, 0x01a8, -1}, /* 1x ƨ ..ƨ → Ƨ ..Ƨ Watin-B */ - {0x01ad, 0x01ad, -1}, /* 1x ƭ ..ƭ → Ƭ ..Ƭ Watin-B */ - {0x01b0, 0x01b0, -1}, /* 1x ư ..ư → Ư ..Ư Watin-B */ - {0x01b4, 0x01b4, -1}, /* 1x ƴ ..ƴ → Ƴ ..Ƴ Watin-B */ - {0x01b6, 0x01b6, -1}, /* 1x ƶ ..ƶ → Ƶ ..Ƶ Watin-B */ - {0x01b9, 0x01b9, -1}, /* 1x ƹ ..ƹ → Ƹ ..Ƹ Watin-B */ - {0x01bd, 0x01bd, -1}, /* 1x ƽ ..ƽ → Ƽ ..Ƽ Watin-B */ - {0x01bf, 0x01bf, +56}, /* 1x ƿ ..ƿ → Ƿ ..Ƿ Watin-B */ - {0x01c5, 0x01c5, -1}, /* 1x Dž ..Dž → DŽ ..DŽ Watin-B */ - {0x01c6, 0x01c6, -2}, /* 1x dž ..dž → DŽ ..DŽ Watin-B */ - {0x01c8, 0x01c8, -1}, /* 1x Lj ..Lj → LJ ..LJ Watin-B */ - {0x01c9, 0x01c9, -2}, /* 1x lj ..lj → LJ ..LJ Watin-B */ - {0x01cb, 0x01cb, -1}, /* 1x Nj ..Nj → NJ ..NJ Watin-B */ - {0x01cc, 0x01cc, -2}, /* 1x nj ..nj → NJ ..NJ Watin-B */ - {0x01ce, 0x01ce, -1}, /* 1x ǎ ..ǎ → Ǎ ..Ǎ Watin-B */ - {0x01dd, 0x01dd, -79}, /* 1x ǝ ..ǝ → Ǝ ..Ǝ Watin-B */ - {0x01f2, 0x01f2, -1}, /* 1x Dz ..Dz → DZ ..DZ Watin-B */ - {0x01f3, 0x01f3, -2}, /* 1x dz ..dz → DZ ..DZ Watin-B */ - {0x01f5, 0x01f5, -1}, /* 1x ǵ ..ǵ → Ǵ ..Ǵ Watin-B */ - {0x023c, 0x023c, -1}, /* 1x ȼ ..ȼ → Ȼ ..Ȼ Watin-B */ - {0x023f, 0x0240,+10815}, /* 2x ȿ ..ɀ → Ȿ ..Ɀ Watin-B */ - {0x0242, 0x0242, -1}, /* 1x ɂ ..ɂ → Ɂ ..Ɂ Watin-B */ - {0x0247, 0x0247, -1}, /* 1x ɇ ..ɇ → Ɇ ..Ɇ Watin-B */ - {0x0249, 0x0249, -1}, /* 1x ɉ ..ɉ → Ɉ ..Ɉ Watin-B */ - {0x024b, 0x024b, -1}, /* 1x ɋ ..ɋ → Ɋ ..Ɋ Watin-B */ - {0x024d, 0x024d, -1}, /* 1x ɍ ..ɍ → Ɍ ..Ɍ Watin-B */ - {0x024f, 0x024f, -1}, /* 1x ɏ ..ɏ → Ɏ ..Ɏ Watin-B */ - {0x037b, 0x037d, +130}, /* 3x ͻ ..ͽ → Ͻ ..Ͽ Greek */ - {0x03ac, 0x03ac, -38}, /* 1x ά ..ά → Ά ..Ά Greek */ - {0x03ad, 0x03af, -37}, /* 3x έ ..ί → Έ ..Ί Greek */ - {0x03b1, 0x03c1, -32}, /* 17x α ..ρ → Α ..Ρ Greek */ - {0x03c2, 0x03c2, -31}, /* 1x ς ..ς → Σ ..Σ Greek */ - {0x03c3, 0x03cb, -32}, /* 9x σ ..ϋ → Σ ..Ϋ Greek */ - {0x03cc, 0x03cc, -64}, /* 1x ό ..ό → Ό ..Ό Greek */ - {0x03cd, 0x03ce, -63}, /* 2x ύ ..ώ → Ύ ..Ώ Greek */ - {0x03d0, 0x03d0, -62}, /* 1x ϐ ..ϐ → Β ..Β Greek */ - {0x03d1, 0x03d1, -57}, /* 1x ϑ ..ϑ → Θ ..Θ Greek */ - {0x03d5, 0x03d5, -47}, /* 1x ϕ ..ϕ → Φ ..Φ Greek */ - {0x03d6, 0x03d6, -54}, /* 1x ϖ ..ϖ → Π ..Π Greek */ - {0x03dd, 0x03dd, -1}, /* 1x ϝ ..ϝ → Ϝ ..Ϝ Greek */ - {0x03f0, 0x03f0, -86}, /* 1x ϰ ..ϰ → Κ ..Κ Greek */ - {0x03f1, 0x03f1, -80}, /* 1x ϱ ..ϱ → Ρ ..Ρ Greek */ - {0x03f5, 0x03f5, -96}, /* 1x ϵ ..ϵ → Ε ..Ε Greek */ - {0x0430, 0x044f, -32}, /* 32x а ..я → А ..Я Cyrillic */ - {0x0450, 0x045f, -80}, /* 16x ѐ ..џ → Ѐ ..Џ Cyrillic */ - {0x0461, 0x0461, -1}, /* 1x ѡ ..ѡ → Ѡ ..Ѡ Cyrillic */ - {0x0463, 0x0463, -1}, /* 1x ѣ ..ѣ → Ѣ ..Ѣ Cyrillic */ - {0x0465, 0x0465, -1}, /* 1x ѥ ..ѥ → Ѥ ..Ѥ Cyrillic */ - {0x0473, 0x0473, -1}, /* 1x ѳ ..ѳ → Ѳ ..Ѳ Cyrillic */ - {0x0491, 0x0491, -1}, /* 1x ґ ..ґ → Ґ ..Ґ Cyrillic */ - {0x0499, 0x0499, -1}, /* 1x ҙ ..ҙ → Ҙ ..Ҙ Cyrillic */ - {0x049b, 0x049b, -1}, /* 1x қ ..қ → Қ ..Қ Cyrillic */ - {0x0561, 0x0586, -48}, /* 38x ա ..ֆ → Ա ..Ֆ Armenian */ - {0x10d0, 0x10fa, +3008}, /* 43x ა ..ჺ → Ა ..Ჺ Georgian */ - {0x10fd, 0x10ff, +3008}, /* 3x ჽ ..ჿ → Ჽ ..Ჿ Georgian */ - {0x13f8, 0x13fd, -8}, /* 6x ᏸ ..ᏽ → Ᏸ ..Ᏽ Cherokee */ - {0x214e, 0x214e, -28}, /* 1x ⅎ ..ⅎ → Ⅎ ..Ⅎ Letterlike */ - {0x2170, 0x217f, -16}, /* 16x ⅰ ..ⅿ → Ⅰ ..Ⅿ Numbery */ - {0x2184, 0x2184, -1}, /* 1x ↄ ..ↄ → Ↄ ..Ↄ Numbery */ - {0x24d0, 0x24e9, -26}, /* 26x ⓐ ..ⓩ → Ⓐ ..Ⓩ Enclosed */ - {0x2c30, 0x2c5e, -48}, /* 47x ⰰ ..ⱞ → Ⰰ ..Ⱞ Glagolitic */ - {0x2d00, 0x2d25, -7264}, /* 38x ⴀ ..ⴥ → Ⴀ ..Ⴥ Georgian2 */ - {0x2d27, 0x2d27, -7264}, /* 1x ⴧ ..ⴧ → Ⴧ ..Ⴧ Georgian2 */ - {0x2d2d, 0x2d2d, -7264}, /* 1x ⴭ ..ⴭ → Ⴭ ..Ⴭ Georgian2 */ - {0xff41, 0xff5a, -32}, /* 26x a..z → A..Z Dubs */ - }; - l = 0; - r = n = sizeof(kUpper) / sizeof(kUpper[0]); - while (l < r) { - m = (l + r) >> 1; - if (kUpper[m].b < c) { - l = m + 1; - } else { - r = m; - } - } - if (l < n && kUpper[l].a <= c && c <= kUpper[l].b) { - return c + kUpper[l].d; - } else { - return c; - } - } - } else { - static const struct { - unsigned a; - unsigned b; - short d; - } kAstralUpper[] = { - {0x10428, 0x1044f, -40}, /* 40x 𐐨..𐑏 → 𐐀..𐐧 Deseret */ - {0x104d8, 0x104fb, -40}, /* 36x 𐓘..𐓻 → 𐒰..𐓓 Osage */ - {0x1d41a, 0x1d433, -26}, /* 26x 𝐚..𝐳 → 𝐀..𝐙 Math */ - {0x1d456, 0x1d467, -26}, /* 18x 𝑖..𝑧 → 𝐼..𝑍 Math */ - {0x1d482, 0x1d49b, -26}, /* 26x 𝒂..𝒛 → 𝑨..𝒁 Math */ - {0x1d4c8, 0x1d4cf, -26}, /* 8x 𝓈..𝓏 → 𝒮..𝒵 Math */ - {0x1d4ea, 0x1d503, -26}, /* 26x 𝓪..𝔃 → 𝓐..𝓩 Math */ - {0x1d527, 0x1d52e, -26}, /* 8x 𝔧..𝔮 → 𝔍..𝔔 Math */ - {0x1d586, 0x1d59f, -26}, /* 26x 𝖆..𝖟 → 𝕬..𝖅 Math */ - {0x1d5ba, 0x1d5d3, -26}, /* 26x 𝖺..𝗓 → 𝖠..𝖹 Math */ - {0x1d5ee, 0x1d607, -26}, /* 26x 𝗮..𝘇 → 𝗔..𝗭 Math */ - {0x1d622, 0x1d63b, -26}, /* 26x 𝘢..𝘻 → 𝘈..𝘡 Math */ - {0x1d68a, 0x1d6a3, +442}, /* 26x 𝒂..𝒛 → 𝘼..𝙕 Math */ - {0x1d6c2, 0x1d6d2, -26}, /* 26x 𝚊..𝚣 → 𝙰..𝚉 Math */ - {0x1d6fc, 0x1d70c, -26}, /* 17x 𝛂..𝛒 → 𝚨..𝚸 Math */ - {0x1d736, 0x1d746, -26}, /* 17x 𝛼..𝜌 → 𝛢..𝛲 Math */ - {0x1d770, 0x1d780, -26}, /* 17x 𝜶..𝝆 → 𝜜..𝜬 Math */ - {0x1d770, 0x1d756, -26}, /* 17x 𝝰..𝞀 → 𝝖..𝝦 Math */ - {0x1d736, 0x1d790, -90}, /* 17x 𝜶..𝝆 → 𝞐..𝞠 Math */ - }; - l = 0; - r = n = sizeof(kAstralUpper) / sizeof(kAstralUpper[0]); - while (l < r) { - m = (l + r) >> 1; - if (kAstralUpper[m].b < c) { - l = m + 1; - } else { - r = m; - } - } - if (l < n && kAstralUpper[l].a <= c && c <= kAstralUpper[l].b) { - return c + kAstralUpper[l].d; - } else { - return c; - } - } -} - -char bestlineNotSeparator(unsigned c) { - return !bestlineIsSeparator(c); -} - -static unsigned GetMirror(const unsigned short A[][2], size_t n, unsigned c) { - int l, m, r; - l = 0; - r = n - 1; - while (l <= r) { - m = (l + r) >> 1; - if (A[m][0] < c) { - l = m + 1; - } else if (A[m][0] > c) { - r = m - 1; - } else { - return A[m][1]; - } - } - return 0; -} - -unsigned bestlineMirrorLeft(unsigned c) { - static const unsigned short kMirrorRight[][2] = { - {L')', L'('}, {L']', L'['}, {L'}', L'{'}, {L'⁆', L'⁅'}, - {L'⁾', L'⁽'}, {L'₎', L'₍'}, {L'⌉', L'⌈'}, {L'⌋', L'⌊'}, - {L'〉', L'〈'}, {L'❩', L'❨'}, {L'❫', L'❪'}, {L'❭', L'❬'}, - {L'❯', L'❮'}, {L'❱', L'❰'}, {L'❳', L'❲'}, {L'❵', L'❴'}, - {L'⟆', L'⟅'}, {L'⟧', L'⟦'}, {L'⟩', L'⟨'}, {L'⟫', L'⟪'}, - {L'⟭', L'⟬'}, {L'⟯', L'⟮'}, {L'⦄', L'⦃'}, {L'⦆', L'⦅'}, - {L'⦈', L'⦇'}, {L'⦊', L'⦉'}, {L'⦌', L'⦋'}, {L'⦎', L'⦏'}, - {L'⦐', L'⦍'}, {L'⦒', L'⦑'}, {L'⦔', L'⦓'}, {L'⦘', L'⦗'}, - {L'⧙', L'⧘'}, {L'⧛', L'⧚'}, {L'⧽', L'⧼'}, {L'﹚', L'﹙'}, - {L'﹜', L'﹛'}, {L'﹞', L'﹝'}, {L')', L'('}, {L']', L'['}, - {L'}', L'{'}, {L'」', L'「'}, - }; - return GetMirror(kMirrorRight, - sizeof(kMirrorRight) / sizeof(kMirrorRight[0]), - c); -} - -unsigned bestlineMirrorRight(unsigned c) { - static const unsigned short kMirrorLeft[][2] = { - {L'(', L')'}, {L'[', L']'}, {L'{', L'}'}, {L'⁅', L'⁆'}, - {L'⁽', L'⁾'}, {L'₍', L'₎'}, {L'⌈', L'⌉'}, {L'⌊', L'⌋'}, - {L'〈', L'〉'}, {L'❨', L'❩'}, {L'❪', L'❫'}, {L'❬', L'❭'}, - {L'❮', L'❯'}, {L'❰', L'❱'}, {L'❲', L'❳'}, {L'❴', L'❵'}, - {L'⟅', L'⟆'}, {L'⟦', L'⟧'}, {L'⟨', L'⟩'}, {L'⟪', L'⟫'}, - {L'⟬', L'⟭'}, {L'⟮', L'⟯'}, {L'⦃', L'⦄'}, {L'⦅', L'⦆'}, - {L'⦇', L'⦈'}, {L'⦉', L'⦊'}, {L'⦋', L'⦌'}, {L'⦍', L'⦐'}, - {L'⦏', L'⦎'}, {L'⦑', L'⦒'}, {L'⦓', L'⦔'}, {L'⦗', L'⦘'}, - {L'⧘', L'⧙'}, {L'⧚', L'⧛'}, {L'⧼', L'⧽'}, {L'﹙', L'﹚'}, - {L'﹛', L'﹜'}, {L'﹝', L'﹞'}, {L'(', L')'}, {L'[', L']'}, - {L'{', L'}'}, {L'「', L'」'}, - }; - return GetMirror(kMirrorLeft, - sizeof(kMirrorLeft) / sizeof(kMirrorLeft[0]), - c); -} - -char bestlineIsXeparator(unsigned c) { - return (bestlineIsSeparator(c) && - !bestlineMirrorLeft(c) && - !bestlineMirrorRight(c)); -} - -static unsigned Capitalize(unsigned c) { - if (!iscapital) { - c = bestlineUppercase(c); - iscapital = 1; - } - return c; -} - -static inline int Bsr(unsigned long long x) { -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) - int b; - b = __builtin_clzll(x); - b ^= sizeof(unsigned long long) * CHAR_BIT - 1; - return b; -#else - static const char kDebruijn[64] = { - 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, - 54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, - 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45, - 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63, - }; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - x |= x >> 32; - return kDebruijn[(x * 0x03f79d71b4cb0a89) >> 58]; -#endif -} - -static struct rune DecodeUtf8(int c) { - struct rune r; - if (c < 252) { - r.n = Bsr(255 & ~c); - r.c = c & (((1 << r.n) - 1) | 3); - r.n = 6 - r.n; - } else { - r.c = c & 3; - r.n = 5; - } - return r; -} - -static unsigned long long EncodeUtf8(unsigned c) { - static const unsigned short kTpEnc[32 - 7] = { - 1|0300<<8, 1|0300<<8, 1|0300<<8, 1|0300<<8, 2|0340<<8, - 2|0340<<8, 2|0340<<8, 2|0340<<8, 2|0340<<8, 3|0360<<8, - 3|0360<<8, 3|0360<<8, 3|0360<<8, 3|0360<<8, 4|0370<<8, - 4|0370<<8, 4|0370<<8, 4|0370<<8, 4|0370<<8, 5|0374<<8, - 5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, - }; - int e, n; - unsigned long long w; - if (c < 0200) return c; - e = kTpEnc[Bsr(c) - 7]; - n = e & 0xff; - w = 0; - do { - w |= 0200 | (c & 077); - w <<= 8; - c >>= 6; - } while (--n); - return c | w | e >> 8; -} - -static struct rune GetUtf8(const char *p, size_t n) { - struct rune r; - if ((r.n = r.c = 0) < n && (r.c = p[r.n++] & 255) >= 0300) { - r.c = DecodeUtf8(r.c).c; - while (r.n < n && (p[r.n] & 0300) == 0200) { - r.c = r.c << 6 | (p[r.n++] & 077); - } - } - return r; -} - -static char *FormatUnsigned(char *p, unsigned x) { - char t; - size_t i, a, b; - i = 0; - do { - p[i++] = x % 10 + '0'; - x = x / 10; - } while (x > 0); - p[i] = '\0'; - if (i) { - for (a = 0, b = i - 1; a < b; ++a, --b) { - t = p[a]; - p[a] = p[b]; - p[b] = t; - } - } - return p + i; -} - -static void abInit(struct abuf *a) { - a->len = 0; - a->cap = 16; - a->b = (char *)malloc(a->cap); - a->b[0] = 0; -} - -static char abGrow(struct abuf *a, int need) { - int cap; - char *b; - cap = a->cap; - do cap += cap / 2; - while (cap < need); - if (!(b = (char *)realloc(a->b, cap * sizeof(*a->b)))) return 0; - a->cap = cap; - a->b = b; - return 1; -} - -static void abAppendw(struct abuf *a, unsigned long long w) { - char *p; - if (a->len + 8 > a->cap && !abGrow(a, a->len + 8)) return; - p = a->b + a->len; - p[0] = (0x00000000000000FF & w) >> 000; - p[1] = (0x000000000000FF00 & w) >> 010; - p[2] = (0x0000000000FF0000 & w) >> 020; - p[3] = (0x00000000FF000000 & w) >> 030; - p[4] = (0x000000FF00000000 & w) >> 040; - p[5] = (0x0000FF0000000000 & w) >> 050; - p[6] = (0x00FF000000000000 & w) >> 060; - p[7] = (0xFF00000000000000 & w) >> 070; - a->len += w ? (Bsr(w) >> 3) + 1 : 1; -} - -static void abAppend(struct abuf *a, const char *s, int len) { - if (a->len + len + 1 > a->cap && !abGrow(a, a->len + len + 1)) return; - memcpy(a->b + a->len, s, len); - a->b[a->len + len] = 0; - a->len += len; -} - -static void abAppends(struct abuf *a, const char *s) { - abAppend(a, s, strlen(s)); -} - -static void abAppendu(struct abuf *a, unsigned u) { - char b[11]; - abAppend(a, b, FormatUnsigned(b, u) - b); -} - -static void abFree(struct abuf *a) { - free(a->b); - a->b = 0; -} - -static size_t GetFdSize(int fd) { - struct stat st; - st.st_size = 0; - fstat(fd, &st); - return st.st_size; -} - -static char IsCharDev(int fd) { - struct stat st; - st.st_mode = 0; - fstat(fd, &st); - return (st.st_mode & S_IFMT) == S_IFCHR; -} - -static int WaitUntilReady(int fd, int events) { - struct pollfd p[1]; - p[0].fd = fd; - p[0].events = events; - return poll(p, 1, -1); -} - -static char HasPendingInput(int fd) { - struct pollfd p[1]; - p[0].fd = fd; - p[0].events = POLLIN; - return poll(p, 1, 0) == 1; -} - -static char *GetLineBlock(FILE *f) { - ssize_t rc; - char *p = 0; - size_t n, c = 0; - if ((rc = getdelim(&p, &c, '\n', f)) != EOF) { - for (n = rc; n; --n) { - if (p[n - 1] == '\r' || p[n - 1] == '\n') { - p[n - 1] = 0; - } else { - break; - } - } - return p; - } else { - free(p); - return 0; - } -} - -long bestlineReadCharacter(int fd, char *p, unsigned long n) { - int e; - size_t i; - ssize_t rc; - struct rune r; - unsigned char c; - enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2, kSs, kNf, kStr, kStr2, kDone } t; - i = 0; - r.c = 0; - r.n = 0; - e = errno; - t = kAscii; - if (n) p[0] = 0; - do { - for (;;) { - if (gotint) { - errno = EINTR; - return -1; - } - if (n) { - rc = read(fd,&c,1); - } else { - rc = read(fd,0,0); - } - if (rc == -1 && errno == EINTR) { - if (!i) { - return -1; - } - } else if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - if (WaitUntilReady(fd, POLLIN) == -1) { - if (rc == -1 && errno == EINTR) { - if (!i) { - return -1; - } - } else { - return -1; - } - } - } else if (rc == -1) { - return -1; - } else if (!rc) { - if (!i) { - errno = e; - return 0; - } else { - errno = EILSEQ; - return -1; - } - } else { - break; - } - } - if (i + 1 < n) { - p[i] = c; - p[i+1] = 0; - } else if (i < n) { - p[i] = 0; - } - ++i; - switch (t) { - Whoopsie: - if (n) p[0] = c; - t = kAscii; - i = 1; - /* fallthrough */ - case kAscii: - if (c < 0200) { - if (c == 033) { - t = kEsc; - } else { - t = kDone; - } - } else if (c >= 0300) { - t = kUtf8; - r = DecodeUtf8(c); - } else { - /* ignore overlong sequences */ - } - break; - case kUtf8: - if ((c & 0300) == 0200) { - r.c <<= 6; - r.c |= c & 077; - if (!--r.n) { - switch (r.c) { - case 033: - t = kEsc; /* parsed but not canonicalized */ - break; - case 0x9b: - t = kCsi1; /* unusual but legal */ - break; - case 0x8e: /* SS2 (Single Shift Two) */ - case 0x8f: /* SS3 (Single Shift Three) */ - t = kSs; - break; - case 0x90: /* DCS (Device Control String) */ - case 0x98: /* SOS (Start of String) */ - case 0x9d: /* OSC (Operating System Command) */ - case 0x9e: /* PM (Privacy Message) */ - case 0x9f: /* APC (Application Program Command) */ - t = kStr; - break; - default: - t = kDone; - break; - } - } - } else { - goto Whoopsie; /* ignore underlong sequences if not eof */ - } - break; - case kEsc: - if (0x20 <= c && c <= 0x2f) { /* Nf */ - /* - * Almost no one uses ANSI Nf sequences - * They overlaps with alt+graphic keystrokes - * We care more about being able to type alt-/ - */ - if (c == ' ' || c == '#') { - t = kNf; - } else { - t = kDone; - } - } else if (0x30 <= c && c <= 0x3f) { /* Fp */ - t = kDone; - } else if (0x20 <= c && c <= 0x5F) { /* Fe */ - switch (c) { - case '[': - t = kCsi1; - break; - case 'N': /* SS2 (Single Shift Two) */ - case 'O': /* SS3 (Single Shift Three) */ - t = kSs; - break; - case 'P': /* DCS (Device Control String) */ - case 'X': /* SOS (Start of String) */ - case ']': /* OSC (Operating System Command) */ - case '^': /* PM (Privacy Message) */ - case '_': /* APC (Application Program Command) */ - t = kStr; - break; - default: - t = kDone; - break; - } - } else if (0x60 <= c && c <= 0x7e) { /* Fs */ - t = kDone; - } else if (c == 033) { - if (i < 3) { - /* alt chording */ - } else { - t = kDone; /* esc mashing */ - i = 1; - } - } else { - t = kDone; - } - break; - case kSs: - t = kDone; - break; - case kNf: - if (0x30 <= c && c <= 0x7e) { - t = kDone; - } else if (!(0x20 <= c && c <= 0x2f)) { - goto Whoopsie; - } - break; - case kCsi1: - if (0x20 <= c && c <= 0x2f) { - t = kCsi2; - } else if (c == '[' && ((i == 3) || - (i == 4 && p[1] == 033))) { - /* linux function keys */ - } else if (0x40 <= c && c <= 0x7e) { - t = kDone; - } else if (!(0x30 <= c && c <= 0x3f)) { - goto Whoopsie; - } - break; - case kCsi2: - if (0x40 <= c && c <= 0x7e) { - t = kDone; - } else if (!(0x20 <= c && c <= 0x2f)) { - goto Whoopsie; - } - break; - case kStr: - switch (c) { - case '\a': - t = kDone; - break; - case 0033: /* ESC */ - case 0302: /* C1 (UTF-8) */ - t = kStr2; - break; - default: - break; - } - break; - case kStr2: - switch (c) { - case '\a': - case '\\': /* ST (ASCII) */ - case 0234: /* ST (UTF-8) */ - t = kDone; - break; - default: - t = kStr; - break; - } - break; - default: - assert(0); - } - } while (t != kDone); - errno = e; - return i; -} - -static char *GetLineChar(int fin, int fout) { - size_t got; - ssize_t rc; - char seq[16]; - struct abuf a; - struct sigaction sa[3]; - abInit(&a); - gotint = 0; - sigemptyset(&sa->sa_mask); - sa->sa_flags = 0; - sa->sa_handler = bestlineOnInt; - sigaction(SIGINT,sa,sa+1); - sigaction(SIGQUIT,sa,sa+2); - for (;;) { - if (gotint) { - rc = -1; - break; - } - if ((rc = bestlineReadCharacter(fin, seq, sizeof(seq))) == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - if (WaitUntilReady(fin, POLLIN) > 0) { - continue; - } - } - if (errno == EINTR) { - continue; - } else { - break; - } - } - if (!(got = rc)) { - if (a.len) { - break; - } else { - rc = -1; - break; - } - } - if (seq[0] == '\r') { - if (HasPendingInput(fin)) { - if ((rc = bestlineReadCharacter(fin, seq + 1, sizeof(seq) - 1)) > 0) { - if (seq[0] == '\n') { - break; - } - } else { - rc = -1; - break; - } - } else { - write(fout, "\n", 1); - break; - } - } else if (seq[0] == Ctrl('D')) { - break; - } else if (seq[0] == '\n') { - break; - } else if (seq[0] == '\b') { - while (a.len && (a.b[a.len - 1] & 0300) == 0200) --a.len; - if (a.len) --a.len; - } - if (!IsControl(seq[0])) { - abAppend(&a, seq, got); - } - } - sigaction(SIGQUIT,sa+2,0); - sigaction(SIGINT,sa+1,0); - if (gotint) { - abFree(&a); - raise(gotint); - errno = EINTR; - rc = -1; - } - if (rc != -1) { - return a.b; - } else { - abFree(&a); - return 0; - } -} - -static char *GetLine(FILE *in, FILE *out) { - if (!IsCharDev(fileno(in))) { - return GetLineBlock(in); - } else { - return GetLineChar(fileno(in), fileno(out)); - } -} - -static char *Copy(char *d, const char *s, size_t n) { - memcpy(d, s, n); - return d + n; -} - -static int CompareStrings(const char *a, const char *b) { - size_t i; - int x, y, c; - for (i = 0;; ++i) { - x = bestlineLowercase(a[i] & 255); - y = bestlineLowercase(b[i] & 255); - if ((c = x - y) || !x) { - return c; - } - } -} - -static const char *FindSubstringReverse(const char *p, size_t n, - const char *q, size_t m) { - size_t i; - if (m <= n) { - n -= m; - do { - for (i = 0; i < m; ++i) { - if (p[n + i] != q[i]) { - break; - } - } - if (i == m) { - return p + n; - } - } while (n--); - } - return 0; -} - -static int ParseUnsigned(const char *s, void *e) { - int c, x; - for (x = 0; (c = *s++);) { - if ('0' <= c && c <= '9') { - x = Min(c - '0' + x * 10, 32767); - } else { - break; - } - } - if (e) *(const char **)e = s; - return x; -} - -/** - * Returns UNICODE CJK Monospace Width of string. - * - * Control codes and ANSI sequences have a width of zero. We only parse - * a limited subset of ANSI here since we don't store ANSI codes in the - * linenoiseState::buf, but we do encourage CSI color codes in prompts. - */ -static size_t GetMonospaceWidth(const char *p, size_t n, char *out_haswides) { - int c, d; - size_t i, w; - struct rune r; - char haswides; - enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2 } t; - for (haswides = r.c = r.n = w = i = 0, t = kAscii; i < n; ++i) { - c = p[i] & 255; - switch (t) { - Whoopsie: - t = kAscii; - /* fallthrough */ - case kAscii: - if (c < 0200) { - if (c == 033) { - t = kEsc; - } else { - ++w; - } - } else if (c >= 0300) { - t = kUtf8; - r = DecodeUtf8(c); - } - break; - case kUtf8: - if ((c & 0300) == 0200) { - r.c <<= 6; - r.c |= c & 077; - if (!--r.n) { - d = GetMonospaceCharacterWidth(r.c); - d = Max(0, d); - w += d; - haswides |= d > 1; - t = kAscii; - break; - } - } else { - goto Whoopsie; - } - break; - case kEsc: - if (c == '[') { - t = kCsi1; - } else { - t = kAscii; - } - break; - case kCsi1: - if (0x20 <= c && c <= 0x2f) { - t = kCsi2; - } else if (0x40 <= c && c <= 0x7e) { - t = kAscii; - } else if (!(0x30 <= c && c <= 0x3f)) { - goto Whoopsie; - } - break; - case kCsi2: - if (0x40 <= c && c <= 0x7e) { - t = kAscii; - } else if (!(0x20 <= c && c <= 0x2f)) { - goto Whoopsie; - } - break; - default: - assert(0); - } - } - if (out_haswides) { - *out_haswides = haswides; - } - return w; -} - -static int bestlineIsUnsupportedTerm(void) { - size_t i; - char *term; - static char once, res; - if (!once) { - if ((term = getenv("TERM"))) { - for (i = 0; i < sizeof(kUnsupported) / sizeof(*kUnsupported); i++) { - if (!CompareStrings(term,kUnsupported[i])) { - res = 1; - break; - } - } - } - once = 1; - } - return res; -} - -static int enableRawMode(int fd) { - struct termios raw; - struct sigaction sa; - if (tcgetattr(fd,&orig_termios) != -1) { - raw = orig_termios; - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - raw.c_oflag &= ~OPOST; - raw.c_iflag |= IUTF8; - raw.c_cflag |= CS8; - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; - if (tcsetattr(fd,TCSANOW,&raw) != -1) { - sa.sa_flags = 0; - sa.sa_handler = bestlineOnCont; - sigemptyset(&sa.sa_mask); - sigaction(SIGCONT,&sa,&orig_cont); - sa.sa_handler = bestlineOnWinch; - sigaction(SIGWINCH,&sa,&orig_winch); - rawmode = fd; - gotwinch = 0; - gotcont = 0; - return 0; - } - } - errno = ENOTTY; - return -1; -} - -static void bestlineUnpause(int fd) { - if (ispaused) { - tcflow(fd, TCOON); - ispaused = 0; - } -} - -void bestlineDisableRawMode(void) { - if (rawmode != -1) { - bestlineUnpause(rawmode); - sigaction(SIGCONT,&orig_cont,0); - sigaction(SIGWINCH,&orig_winch,0); - tcsetattr(rawmode,TCSANOW,&orig_termios); - rawmode = -1; - } -} - -static int bestlineWrite(int fd, const void *p, size_t n) { - ssize_t rc; - size_t wrote; - do { - for (;;) { - if (gotint) { - errno = EINTR; - return -1; - } - if (ispaused) { - return 0; - } - rc = write(fd, p, n); - if (rc == -1 && errno == EINTR) { - continue; - } else if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - if (WaitUntilReady(fd, POLLOUT) == -1) { - if (errno == EINTR) { - continue; - } else { - return -1; - } - } - } else { - break; - } - } - if (rc != -1) { - wrote = rc; - n -= wrote; - p = (char *)p + wrote; - } else { - return -1; - } - } while (n); - return 0; -} - -static int bestlineWriteStr(int fd, const char *p) { - return bestlineWrite(fd, p, strlen(p)); -} - -static ssize_t bestlineRead(int fd, char *buf, size_t size, - struct bestlineState *l) { - size_t got; - ssize_t rc; - int refreshme; - do { - refreshme = 0; - if (gotint) { - errno = EINTR; - return -1; - } - if (gotcont && rawmode != -1) { - enableRawMode(rawmode); - if (l) refreshme = 1; - } - if (gotwinch && l) { - refreshme = 1; - } - if (refreshme) bestlineRefreshLine(l); - rc = bestlineReadCharacter(fd, buf, size); - } while (rc == -1 && errno == EINTR); - if (rc != -1) { - got = rc; - if (got > 0 && l) { - memcpy(l->seq[1], l->seq[0], sizeof(l->seq[0])); - memset(l->seq[0], 0, sizeof(l->seq[0])); - memcpy(l->seq[0], buf, Min(Min(size, got), sizeof(l->seq[0]) - 1)); - } - } - return rc; -} - -/** - * Returns number of columns in current terminal. - * - * 1. Checks COLUMNS environment variable (set by Emacs) - * 2. Tries asking termios (works for pseudoteletypewriters) - * 3. Falls back to inband signalling (works w/ pipe or serial) - * 4. Otherwise we conservatively assume 80 columns - * - * @param ws should be initialized by caller to zero before first call - * @param ifd is input file descriptor - * @param ofd is output file descriptor - * @return window size - */ -static struct winsize GetTerminalSize(struct winsize ws, int ifd, int ofd) { - int x; - ssize_t n; - char *p, *s, b[16]; - ioctl(ofd, TIOCGWINSZ, &ws); - if ((!ws.ws_row && - (s = getenv("ROWS")) && - (x = ParseUnsigned(s, 0)))) { - ws.ws_row = x; - } - if ((!ws.ws_col && - (s = getenv("COLUMNS")) && - (x = ParseUnsigned(s, 0)))) { - ws.ws_col = x; - } - if (((!ws.ws_col || !ws.ws_row) && - bestlineRead(ifd,0,0,0) != -1 && - bestlineWriteStr(ofd, - "\0337" /* save position */ - "\033[9979;9979H" /* move cursor to bottom right corner */ - "\033[6n" /* report position */ - "\0338") != -1 && /* restore position */ - (n = bestlineRead(ifd,b,sizeof(b),0)) != -1 && - n && b[0] == 033 && b[1] == '[' && b[n - 1] == 'R')) { - p = b+2; - if ((x = ParseUnsigned(p,&p))) ws.ws_row = x; - if (*p++ == ';' && (x = ParseUnsigned(p,0))) ws.ws_col = x; - } - if (!ws.ws_col) ws.ws_col = 80; - if (!ws.ws_row) ws.ws_row = 24; - return ws; -} - -/* Clear the screen. Used to handle ctrl+l */ -void bestlineClearScreen(int fd) { - bestlineWriteStr(fd, - "\033[H" /* move cursor to top left corner */ - "\033[2J"); /* erase display */ -} - -static void bestlineBeep(void) { - /* THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT */ -} - -static char bestlineGrow(struct bestlineState *ls, size_t n) { - char *p; - size_t m; - m = ls->buflen; - if (m >= n) return 1; - do m += m >> 1; - while (m < n); - if (!(p = (char *)realloc(ls->buf, m * sizeof(*ls->buf)))) return 0; - ls->buf = p; - ls->buflen = m; - return 1; -} - -/* This is an helper function for bestlineEdit() and is called when the - * user types the key in order to complete the string currently in the - * input. - * - * The state of the editing is encapsulated into the pointed bestlineState - * structure as described in the structure definition. */ -static ssize_t bestlineCompleteLine(struct bestlineState *ls, char *seq, int size) { - ssize_t nread; - size_t i, n, stop; - bestlineCompletions lc; - struct bestlineState saved; - nread=0; - memset(&lc,0,sizeof(lc)); - completionCallback(ls->buf,&lc); - if (!lc.len) { - bestlineBeep(); - } else { - i = 0; - stop = 0; - while (!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - saved = *ls; - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - bestlineRefreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - bestlineRefreshLine(ls); - } - if ((nread = bestlineRead(ls->ifd,seq,size,ls)) <= 0) { - bestlineFreeCompletions(&lc); - return -1; - } - switch (seq[0]) { - case '\t': - i = (i+1) % (lc.len+1); - if (i == lc.len) { - bestlineBeep(); - } - break; - default: - if (i < lc.len) { - n = strlen(lc.cvec[i]); - if (bestlineGrow(ls, n + 1)) { - memcpy(ls->buf, lc.cvec[i], n + 1); - ls->len = ls->pos = n; - } - } - stop = 1; - break; - } - } - } - bestlineFreeCompletions(&lc); - return nread; -} - -static void bestlineEditHistoryGoto(struct bestlineState *l, unsigned i) { - size_t n; - if (historylen <= 1) return; - i = Max(Min(i,historylen-1),0); - free(history[historylen - 1 - l->hindex]); - history[historylen - 1 - l->hindex] = strdup(l->buf); - l->hindex = i; - n = strlen(history[historylen - 1 - l->hindex]); - bestlineGrow(l, n + 1); - n = Min(n, l->buflen - 1); - memcpy(l->buf, history[historylen - 1 - l->hindex], n); - l->buf[n] = 0; - l->len = l->pos = n; - bestlineRefreshLine(l); -} - -static void bestlineEditHistoryMove(struct bestlineState *l, int dx) { - bestlineEditHistoryGoto(l,l->hindex+dx); -} - -static char *bestlineMakeSearchPrompt(struct abuf *ab, int fail, const char *s, int n) { - ab->len=0; - abAppendw(ab,'('); - if (fail) abAppends(ab,"failed "); - abAppends(ab,"reverse-i-search `\033[4m"); - abAppend(ab,s,n); - abAppends(ab,"\033[24m"); - abAppends(ab,s+n); - abAppendw(ab,Read32le("') ")); - return ab->b; -} - -static int bestlineSearch(struct bestlineState *l, char *seq, int size) { - char *p; - char isstale; - struct abuf ab; - struct abuf prompt; - unsigned i, j, k, matlen; - const char *oldprompt, *q; - int rc, fail, added, oldpos, oldindex; - if (historylen <= 1) return 0; - abInit(&ab); - abInit(&prompt); - oldpos = l->pos; - oldprompt = l->prompt; - oldindex = l->hindex; - for (fail=matlen=0;;) { - l->prompt = bestlineMakeSearchPrompt(&prompt,fail,ab.b,matlen); - bestlineRefreshLine(l); - fail = 1; - added = 0; - j = l->pos; - i = l->hindex; - rc = bestlineRead(l->ifd,seq,size,l); - if (rc > 0) { - if (seq[0] == Ctrl('?') || seq[0] == Ctrl('H')) { - if (ab.len) { - --ab.len; - matlen = Min(matlen, ab.len); - } - } else if (seq[0] == Ctrl('R')) { - if (j) { - --j; - } else if (i + 1 < historylen) { - ++i; - j = strlen(history[historylen - 1 - i]); - } - } else if (seq[0] == Ctrl('G')) { - bestlineEditHistoryGoto(l,oldindex); - l->pos = oldpos; - rc = 0; - break; - } else if (IsControl(seq[0])) { /* only sees canonical c0 */ - break; - } else { - abAppend(&ab,seq,rc); - added = rc; - } - } else { - break; - } - isstale = 0; - while (i < historylen) { - p = history[historylen - 1 - i]; - k = strlen(p); - if (!isstale) { - j = Min(k, j + ab.len); - } else { - isstale = 0; - j = k; - } - if ((q = FindSubstringReverse(p, j, ab.b, ab.len))) { - bestlineEditHistoryGoto(l,i); - l->pos = q - p; - fail = 0; - if (added) { - matlen += added; - added = 0; - } - break; - } else { - isstale = 1; - ++i; - } - } - } - l->prompt = oldprompt; - bestlineRefreshLine(l); - abFree(&prompt); - abFree(&ab); - bestlineRefreshLine(l); - return rc; -} - -static void bestlineRingFree(void) { - size_t i; - for (i = 0; i < BESTLINE_MAX_RING; ++i) { - if (ring.p[i]) { - free(ring.p[i]); - ring.p[i] = 0; - } - } -} - -static void bestlineRingPush(const char *p, size_t n) { - char *q; - if (!n) return; - if (!(q = (char *)malloc(n + 1))) return; - ring.i = (ring.i + 1) % BESTLINE_MAX_RING; - free(ring.p[ring.i]); - ring.p[ring.i] = (char *)memcpy(q, p, n); - ring.p[ring.i][n] = 0; -} - -static void bestlineRingRotate(void) { - size_t i; - for (i = 0; i < BESTLINE_MAX_RING; ++i) { - ring.i = (ring.i - 1) % BESTLINE_MAX_RING; - if (ring.p[ring.i]) break; - } -} - -static char *bestlineRefreshHints(struct bestlineState *l) { - char *hint; - struct abuf ab; - const char *ansi1 = "\033[90m", *ansi2 = "\033[39m"; - if (!hintsCallback) return 0; - if (!(hint = hintsCallback(l->buf, &ansi1, &ansi2))) return 0; - abInit(&ab); - if (ansi1) abAppends(&ab, ansi1); - abAppends(&ab, hint); - if (ansi2) abAppends(&ab, ansi2); - if (freeHintsCallback) freeHintsCallback(hint); - return ab.b; -} - -static size_t Backward(struct bestlineState *l, size_t pos) { - if (pos) { - do --pos; - while (pos && (l->buf[pos] & 0300) == 0200); - } - return pos; -} - -static int bestlineEditMirrorLeft(struct bestlineState *l, int res[2]) { - unsigned c, pos, left, right, depth, index; - if ((pos = Backward(l, l->pos))) { - right = GetUtf8(l->buf + pos, l->len - pos).c; - if ((left = bestlineMirrorLeft(right))) { - depth = 0; - index = pos; - do { - pos = Backward(l, pos); - c = GetUtf8(l->buf + pos, l->len - pos).c; - if (c == right) { - ++depth; - } else if (c == left) { - if (depth) { - --depth; - } else { - res[0] = pos; - res[1] = index; - return 0; - } - } - } while (pos); - } - } - return -1; -} - -static int bestlineEditMirrorRight(struct bestlineState *l, int res[2]) { - struct rune rune; - unsigned pos, left, right, depth, index; - pos = l->pos; - rune = GetUtf8(l->buf + pos, l->len - pos); - left = rune.c; - if ((right = bestlineMirrorRight(left))) { - depth = 0; - index = pos; - do { - pos += rune.n; - rune = GetUtf8(l->buf + pos, l->len - pos); - if (rune.c == left) { - ++depth; - } else if (rune.c == right) { - if (depth) { - --depth; - } else { - res[0] = index; - res[1] = pos; - return 0; - } - } - } while (pos + rune.n < l->len); - } - return -1; -} - -static int bestlineEditMirror(struct bestlineState *l, int res[2]) { - int rc; - rc = bestlineEditMirrorLeft(l, res); - if (rc == -1) rc = bestlineEditMirrorRight(l, res); - return rc; -} - -static void bestlineRefreshLineImpl(struct bestlineState *l, int force) { - char *hint; - char flipit; - char hasflip; - char haswides; - struct abuf ab; - const char *buf; - struct rune rune; - struct winsize oldsize; - int fd, plen, rows, len, pos; - unsigned x, xn, yn, width, pwidth; - int i, t, cx, cy, tn, resized, flip[2]; - - /* - * synchonize the i/o state - */ - if (ispaused) { - if (force) { - bestlineUnpause(l->ofd); - } else { - return; - } - } - if (!force && HasPendingInput(l->ifd)) { - l->dirty = 1; - return; - } - oldsize = l->ws; - if ((resized = gotwinch) && rawmode != -1) { - gotwinch = 0; - l->ws = GetTerminalSize(l->ws, l->ifd, l->ofd); - } - hasflip = !l->final && !bestlineEditMirror(l, flip); - -StartOver: - fd = l->ofd; - buf = l->buf; - pos = l->pos; - len = l->len; - xn = l->ws.ws_col; - yn = l->ws.ws_row; - plen = strlen(l->prompt); - pwidth = GetMonospaceWidth(l->prompt, plen, 0); - width = GetMonospaceWidth(buf, len, &haswides); - - /* - * handle the case where the line is larger than the whole display - * gnu readline actually isn't able to deal with this situation!!! - * we kludge xn to address the edge case of wide chars on the edge - */ - for (tn = xn - haswides * 2;;) { - if (pwidth + width + 1 < tn * yn) break; /* we're fine */ - if (!len || width < 2) break; /* we can't do anything */ - if (pwidth + 2 > tn * yn) break; /* we can't do anything */ - if (pos > len / 2) { - /* hide content on the left if we're editing on the right */ - rune = GetUtf8(buf, len); - buf += rune.n; - len -= rune.n; - pos -= rune.n; - } else { - /* hide content on the right if we're editing on left */ - t = len; - while (len && (buf[len - 1] & 0300) == 0200) --len; - if (len) --len; - rune = GetUtf8(buf + len, t - len); - } - if ((t = GetMonospaceCharacterWidth(rune.c)) > 0) { - width -= t; - } - } - pos = Max(0, Min(pos, len)); - - /* - * now generate the terminal codes to update the line - * - * since we support unlimited lines it's important that we don't - * clear the screen before we draw the screen. doing that causes - * flickering. the key with terminals is to overwrite cells, and - * then use \e[K and \e[J to clear everything else. - * - * we make the assumption that prompts and hints may contain ansi - * sequences, but the buffer does not. - * - * we need to handle the edge case where a wide character like 度 - * might be at the edge of the window, when there's one cell left. - * so we can't use division based on string width to compute the - * coordinates and have to track it as we go. - */ - cy = -1; - cx = -1; - rows = 1; - abInit(&ab); - abAppendw(&ab, '\r'); /* start of line */ - if (l->rows - l->oldpos - 1 > 0) { - abAppends(&ab, "\033["); - abAppendu(&ab, l->rows - l->oldpos - 1); - abAppendw(&ab, 'A'); /* cursor up clamped */ - } - abAppends(&ab, l->prompt); - x = pwidth; - for (i = 0; i < len; i += rune.n) { - rune = GetUtf8(buf + i, len - i); - if (x && x + rune.n > xn) { - if (cy >= 0) ++cy; - if (x < xn) { - abAppends(&ab, "\033[K"); /* clear line forward */ - } - abAppends(&ab, "\r" /* start of line */ - "\n"); /* cursor down unclamped */ - ++rows; - x = 0; - } - if (i == pos) { - cy = 0; - cx = x; - } - if (maskmode) { - abAppendw(&ab, '*'); - } else { - flipit = hasflip && (i == flip[0] || i == flip[1]); - if (flipit) abAppends(&ab, "\033[1m"); - abAppendw(&ab, EncodeUtf8(rune.c)); - if (flipit) abAppends(&ab, "\033[22m"); - } - t = GetMonospaceCharacterWidth(rune.c); - t = Max(0, t); - x += t; - } - if (!l->final && (hint = bestlineRefreshHints(l))) { - if (GetMonospaceWidth(hint, strlen(hint), 0) < xn - x) { - if (cx < 0) { - cx = x; - } - abAppends(&ab, hint); - } - free(hint); - } - abAppendw(&ab, Read32le("\033[J")); /* erase display forwards */ - - /* - * 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 == len && x >= xn) { - abAppendw(&ab, Read32le("\n\r\0")); - ++rows; - } - - /* - * move cursor to right position - */ - if (cy > 0) { - abAppends(&ab, "\033["); - abAppendu(&ab, cy); - abAppendw(&ab, 'A'); /* cursor up */ - } - if (cx > 0) { - abAppendw(&ab, Read32le("\r\033[")); - abAppendu(&ab, cx); - abAppendw(&ab, 'C'); /* cursor right */ - } else if (!cx) { - abAppendw(&ab, '\r'); /* start */ - } - - /* - * now get ready to progress state - * we use a mostly correct kludge when the tty resizes - */ - l->rows = rows; - if (resized && oldsize.ws_col > l->ws.ws_col) { - resized = 0; - abFree(&ab); - goto StartOver; - } - l->dirty = 0; - l->oldpos = Max(0, cy); - - /* - * send codes to terminal - */ - bestlineWrite(fd, ab.b, ab.len); - abFree(&ab); -} - -static void bestlineRefreshLine(struct bestlineState *l) { - bestlineRefreshLineImpl(l, 0); -} - -static void bestlineRefreshLineForce(struct bestlineState *l) { - bestlineRefreshLineImpl(l, 1); -} - -static void bestlineEditInsert(struct bestlineState *l, - const char *p, size_t n) { - if (!bestlineGrow(l, l->len + n + 1)) return; - memmove(l->buf + l->pos + n, l->buf + l->pos, l->len - l->pos); - memcpy(l->buf + l->pos, p, n); - l->pos += n; - l->len += n; - l->buf[l->len] = 0; - bestlineRefreshLine(l); -} - -static void bestlineEditHome(struct bestlineState *l) { - l->pos = 0; - bestlineRefreshLine(l); -} - -static void bestlineEditEnd(struct bestlineState *l) { - l->pos = l->len; - bestlineRefreshLine(l); -} - -static void bestlineEditUp(struct bestlineState *l) { - bestlineEditHistoryMove(l,BESTLINE_HISTORY_PREV); -} - -static void bestlineEditDown(struct bestlineState *l) { - bestlineEditHistoryMove(l,BESTLINE_HISTORY_NEXT); -} - -static void bestlineEditBof(struct bestlineState *l) { - bestlineEditHistoryMove(l,BESTLINE_HISTORY_FIRST); -} - -static void bestlineEditEof(struct bestlineState *l) { - bestlineEditHistoryMove(l,BESTLINE_HISTORY_LAST); -} - -static void bestlineEditRefresh(struct bestlineState *l) { - bestlineClearScreen(l->ofd); - bestlineRefreshLine(l); -} - -static size_t Forward(struct bestlineState *l, size_t pos) { - return pos + GetUtf8(l->buf + pos, l->len - pos).n; -} - -static size_t Backwards(struct bestlineState *l, size_t pos, char pred(unsigned)) { - size_t i; - struct rune r; - while (pos) { - i = Backward(l, pos); - r = GetUtf8(l->buf + i, l->len - i); - if (pred(r.c)) { - pos = i; - } else { - break; - } - } - return pos; -} - -static size_t Forwards(struct bestlineState *l, size_t pos, char pred(unsigned)) { - struct rune r; - while (pos < l->len) { - r = GetUtf8(l->buf + pos, l->len - pos); - if (pred(r.c)) { - pos += r.n; - } else { - break; - } - } - return pos; -} - -static size_t ForwardWord(struct bestlineState *l, size_t pos) { - pos = Forwards(l, pos, bestlineIsSeparator); - pos = Forwards(l, pos, bestlineNotSeparator); - return pos; -} - -static size_t BackwardWord(struct bestlineState *l, size_t pos) { - pos = Backwards(l, pos, bestlineIsSeparator); - pos = Backwards(l, pos, bestlineNotSeparator); - return pos; -} - -static size_t EscapeWord(struct bestlineState *l, size_t i) { - size_t j; - struct rune r; - for (; i && i < l->len; i += r.n) { - if (i < l->len) { - r = GetUtf8(l->buf + i, l->len - i); - if (bestlineIsSeparator(r.c)) break; - } - if ((j = i)) { - do --j; - while (j && (l->buf[j] & 0300) == 0200); - r = GetUtf8(l->buf + j, l->len - j); - if (bestlineIsSeparator(r.c)) break; - } - } - return i; -} - -static void bestlineEditLeft(struct bestlineState *l) { - l->pos = Backward(l, l->pos); - bestlineRefreshLine(l); -} - -static void bestlineEditRight(struct bestlineState *l) { - if (l->pos == l->len) return; - do l->pos++; - while (l->pos < l->len && (l->buf[l->pos] & 0300) == 0200); - bestlineRefreshLine(l); -} - -static void bestlineEditLeftWord(struct bestlineState *l) { - l->pos = BackwardWord(l, l->pos); - bestlineRefreshLine(l); -} - -static void bestlineEditRightWord(struct bestlineState *l) { - l->pos = ForwardWord(l, l->pos); - bestlineRefreshLine(l); -} - -static void bestlineEditLeftExpr(struct bestlineState *l) { - int mark[2]; - l->pos = Backwards(l, l->pos, bestlineIsXeparator); - if (!bestlineEditMirrorLeft(l, mark)) { - l->pos = mark[0]; - } else { - l->pos = Backwards(l, l->pos, bestlineNotSeparator); - } - bestlineRefreshLine(l); -} - -static void bestlineEditRightExpr(struct bestlineState *l) { - int mark[2]; - l->pos = Forwards(l, l->pos, bestlineIsXeparator); - if (!bestlineEditMirrorRight(l, mark)) { - l->pos = Forward(l, mark[1]); - } else { - l->pos = Forwards(l, l->pos, bestlineNotSeparator); - } - bestlineRefreshLine(l); -} - -static void bestlineEditDelete(struct bestlineState *l) { - size_t i; - if (l->pos == l->len) return; - i = Forward(l, l->pos); - memmove(l->buf+l->pos, l->buf+i, l->len-i+1); - l->len -= i - l->pos; - bestlineRefreshLine(l); -} - -static void bestlineEditRubout(struct bestlineState *l) { - size_t i; - if (!l->pos) return; - i = Backward(l, l->pos); - memmove(l->buf+i, l->buf+l->pos, l->len-l->pos+1); - l->len -= l->pos - i; - l->pos = i; - bestlineRefreshLine(l); -} - -static void bestlineEditDeleteWord(struct bestlineState *l) { - size_t i; - if (l->pos == l->len) return; - i = ForwardWord(l, l->pos); - bestlineRingPush(l->buf + l->pos, i - l->pos); - memmove(l->buf + l->pos, l->buf + i, l->len - i + 1); - l->len -= i - l->pos; - bestlineRefreshLine(l); -} - -static void bestlineEditRuboutWord(struct bestlineState *l) { - size_t i; - if (!l->pos) return; - i = BackwardWord(l, l->pos); - bestlineRingPush(l->buf + i, l->pos - i); - memmove(l->buf + i, l->buf + l->pos, l->len - l->pos + 1); - l->len -= l->pos - i; - l->pos = i; - bestlineRefreshLine(l); -} - -static void bestlineEditXlatWord(struct bestlineState *l, unsigned xlat(unsigned)) { - unsigned c; - size_t i, j; - struct rune r; - struct abuf ab; - abInit(&ab); - i = Forwards(l, l->pos, bestlineIsSeparator); - for (j = i; j < l->len; j += r.n) { - r = GetUtf8(l->buf + j, l->len - j); - if (bestlineIsSeparator(r.c)) break; - if ((c = xlat(r.c)) != r.c) { - abAppendw(&ab, EncodeUtf8(c)); - } else { /* avoid canonicalization */ - abAppend(&ab, l->buf + j, r.n); - } - } - if (ab.len && bestlineGrow(l, i + ab.len + l->len - j + 1)) { - l->pos = i + ab.len; - abAppend(&ab, l->buf + j, l->len - j); - l->len = i + ab.len; - memcpy(l->buf + i, ab.b, ab.len + 1); - bestlineRefreshLine(l); - } - abFree(&ab); -} - -static void bestlineEditLowercaseWord(struct bestlineState *l) { - bestlineEditXlatWord(l, bestlineLowercase); -} - -static void bestlineEditUppercaseWord(struct bestlineState *l) { - bestlineEditXlatWord(l, bestlineUppercase); -} - -static void bestlineEditCapitalizeWord(struct bestlineState *l) { - iscapital = 0; - bestlineEditXlatWord(l, Capitalize); -} - -static void bestlineEditKillLeft(struct bestlineState *l) { - size_t diff, old_pos; - bestlineRingPush(l->buf, l->pos); - old_pos = l->pos; - l->pos = 0; - diff = old_pos - l->pos; - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); - l->len -= diff; - bestlineRefreshLine(l); -} - -static void bestlineEditKillRight(struct bestlineState *l) { - bestlineRingPush(l->buf + l->pos, l->len - l->pos); - l->buf[l->pos] = '\0'; - l->len = l->pos; - bestlineRefreshLine(l); -} - -static void bestlineEditYank(struct bestlineState *l) { - char *p; - size_t n; - if (!ring.p[ring.i]) return; - n = strlen(ring.p[ring.i]); - if (!bestlineGrow(l, l->len + n + 1)) return; - if (!(p = (char *)malloc(l->len - l->pos + 1))) return; - memcpy(p, l->buf + l->pos, l->len - l->pos + 1); - memcpy(l->buf + l->pos, ring.p[ring.i], n); - memcpy(l->buf + l->pos + n, p, l->len - l->pos + 1); - free(p); - l->yi = l->pos; - l->yj = l->pos + n; - l->pos += n; - l->len += n; - bestlineRefreshLine(l); -} - -static void bestlineEditRotate(struct bestlineState *l) { - if ((l->seq[1][0] == Ctrl('Y') || - (l->seq[1][0] == 033 && l->seq[1][1] == 'y'))) { - if (l->yi < l->len && l->yj <= l->len) { - memmove(l->buf + l->yi, l->buf + l->yj, l->len - l->yj + 1); - l->len -= l->yj - l->yi; - l->pos -= l->yj - l->yi; - } - bestlineRingRotate(); - bestlineEditYank(l); - } -} - -static void bestlineEditTranspose(struct bestlineState *l) { - char *q, *p; - size_t a, b, c; - b = l->pos; - if (b == l->len) --b; - a = Backward(l, b); - c = Forward(l, b); - if (!(a < b && b < c)) return; - p = q = (char *)malloc(c - a); - p = Copy(p, l->buf + b, c - b); - p = Copy(p, l->buf + a, b - a); - assert((size_t)(p - q) == c - a); - memcpy(l->buf + a, q, p - q); - l->pos = c; - free(q); - bestlineRefreshLine(l); -} - -static void bestlineEditTransposeWords(struct bestlineState *l) { - char *q, *p; - size_t i, pi, xi, xj, yi, yj; - i = l->pos; - if (i == l->len) { - i = Backwards(l, i, bestlineIsSeparator); - i = Backwards(l, i, bestlineNotSeparator); - } - pi = EscapeWord(l, i); - xj = Backwards(l, pi, bestlineIsSeparator); - xi = Backwards(l, xj, bestlineNotSeparator); - yi = Forwards(l, pi, bestlineIsSeparator); - yj = Forwards(l, yi, bestlineNotSeparator); - if (!(xi < xj && xj < yi && yi < yj)) return; - p = q = (char *)malloc(yj - xi); - p = Copy(p, l->buf + yi, yj - yi); - p = Copy(p, l->buf + xj, yi - xj); - p = Copy(p, l->buf + xi, xj - xi); - assert((size_t)(p - q) == yj - xi); - memcpy(l->buf + xi, q, p - q); - l->pos = yj; - free(q); - bestlineRefreshLine(l); -} - -static void bestlineEditSqueeze(struct bestlineState *l) { - size_t i, j; - i = Backwards(l, l->pos, bestlineIsSeparator); - j = Forwards(l, l->pos, bestlineIsSeparator); - if (!(i < j)) return; - memmove(l->buf + i, l->buf + j, l->len - j + 1); - l->len -= j - i; - l->pos = i; - bestlineRefreshLine(l); -} - -static void bestlineEditMark(struct bestlineState *l) { - l->mark = l->pos; -} - -static void bestlineEditGoto(struct bestlineState *l) { - if (l->mark > l->len) return; - l->pos = Min(l->mark, l->len); - bestlineRefreshLine(l); -} - -static size_t bestlineEscape(char *d, const char *s, size_t n) { - char *p; - size_t i; - unsigned c, w, l; - for (p = d, l = i = 0; i < n; ++i) { - switch ((c = s[i] & 255)) { - Case('\a', w = Read16le("\\a")); - Case('\b', w = Read16le("\\b")); - Case('\t', w = Read16le("\\t")); - Case('\n', w = Read16le("\\n")); - Case('\v', w = Read16le("\\v")); - Case('\f', w = Read16le("\\f")); - Case('\r', w = Read16le("\\r")); - Case('"', w = Read16le("\\\"")); - Case('\'', w = Read16le("\\\'")); - Case('\\', w = Read16le("\\\\")); - default: - if (c <= 0x1F || c == 0x7F || - (c == '?' && l == '?')) { - w = Read16le("\\x"); - w |= "0123456789abcdef"[(c & 0xF0) >> 4] << 020; - w |= "0123456789abcdef"[(c & 0x0F) >> 0] << 030; - } else { - w = c; - } - break; - } - p[0] = (w & 0x000000ff) >> 000; - p[1] = (w & 0x0000ff00) >> 010; - p[2] = (w & 0x00ff0000) >> 020; - p[3] = (w & 0xff000000) >> 030; - p += (Bsr(w) >> 3) + 1; - l = w; - } - return p - d; -} - -static void bestlineEditInsertEscape(struct bestlineState *l) { - size_t m; - ssize_t n; - char seq[16]; - char esc[sizeof(seq) * 4]; - if ((n = bestlineRead(l->ifd, seq, sizeof(seq), l)) > 0) { - m = bestlineEscape(esc, seq, n); - bestlineEditInsert(l, esc, m); - } -} - -static void bestlineEditInterrupt(void) { - gotint = SIGINT; -} - -static void bestlineEditQuit(void) { - gotint = SIGQUIT; -} - -static void bestlineEditSuspend(void) { - raise(SIGSTOP); -} - -static void bestlineEditPause(struct bestlineState *l) { - tcflow(l->ofd, TCOOFF); - ispaused = 1; -} - -static void bestlineEditCtrlq(struct bestlineState *l) { - if (ispaused) { - bestlineUnpause(l->ofd); - bestlineRefreshLineForce(l); - } else { - bestlineEditInsertEscape(l); - } -} - -/** - * Moves last item inside current s-expression to outside, e.g. - * - * (a| b c) - * (a| b) c - * - * The cursor position changes only if a paren is moved before it: - * - * (a b c |) - * (a b) c | - * - * To accommodate non-LISP languages we connect unspaced outer symbols: - * - * f(a,| b, g()) - * f(a,| b), g() - * - * Our standard keybinding is ALT-SHIFT-B. - */ -static void bestlineEditBarf(struct bestlineState *l) { - struct rune r; - unsigned long w; - size_t i, pos, depth = 0; - unsigned lhs, rhs, end, *stack = 0; - /* go as far right within current s-expr as possible */ - for (pos = l->pos;; pos += r.n) { - if (pos == l->len) goto Finish; - r = GetUtf8(l->buf + pos, l->len - pos); - if (depth) { - if (r.c == stack[depth - 1]) { - --depth; - } - } else { - if ((rhs = bestlineMirrorRight(r.c))) { - stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); - stack[depth - 1] = rhs; - } else if (bestlineMirrorLeft(r.c)) { - end = pos; - break; - } - } - } - /* go back one item */ - pos = Backwards(l, pos, bestlineIsXeparator); - for (;; pos = i) { - if (!pos) goto Finish; - i = Backward(l, pos); - r = GetUtf8(l->buf + i, l->len - i); - if (depth) { - if (r.c == stack[depth - 1]) { - --depth; - } - } else { - if ((lhs = bestlineMirrorLeft(r.c))) { - stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); - stack[depth - 1] = lhs; - } else if (bestlineIsSeparator(r.c)) { - break; - } - } - } - pos = Backwards(l, pos, bestlineIsXeparator); - /* now move the text */ - r = GetUtf8(l->buf + end, l->len - end); - memmove(l->buf + pos + r.n, l->buf + pos, end - pos); - w = EncodeUtf8(r.c); - for (i = 0; i < r.n; ++i) { - l->buf[pos + i] = w; - w >>= 8; - } - if (l->pos > pos) { - l->pos += r.n; - } - bestlineRefreshLine(l); -Finish: - free(stack); -} - -/** - * Moves first item outside current s-expression to inside, e.g. - * - * (a| b) c d - * (a| b c) d - * - * To accommodate non-LISP languages we connect unspaced outer symbols: - * - * f(a,| b), g() - * f(a,| b, g()) - * - * Our standard keybinding is ALT-SHIFT-S. - */ -static void bestlineEditSlurp(struct bestlineState *l) { - char rp[6]; - struct rune r; - size_t pos, depth = 0; - unsigned rhs, point = 0, start = 0, *stack = 0; - /* go to outside edge of current s-expr */ - for (pos = l->pos; pos < l->len; pos += r.n) { - r = GetUtf8(l->buf + pos, l->len - pos); - if (depth) { - if (r.c == stack[depth - 1]) { - --depth; - } - } else { - if ((rhs = bestlineMirrorRight(r.c))) { - stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); - stack[depth - 1] = rhs; - } else if (bestlineMirrorLeft(r.c)) { - point = pos; - pos += r.n; - start = pos; - break; - } - } - } - /* go forward one item */ - pos = Forwards(l, pos, bestlineIsXeparator); - for (; pos < l->len ; pos += r.n) { - r = GetUtf8(l->buf + pos, l->len - pos); - if (depth) { - if (r.c == stack[depth - 1]) { - --depth; - } - } else { - if ((rhs = bestlineMirrorRight(r.c))) { - stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); - stack[depth - 1] = rhs; - } else if (bestlineIsSeparator(r.c)) { - break; - } - } - } - /* now move the text */ - memcpy(rp, l->buf + point, start - point); - memmove(l->buf + point, l->buf + start, pos - start); - memcpy(l->buf + pos - (start - point), rp, start - point); - bestlineRefreshLine(l); - free(stack); -} - -static void bestlineEditRaise(struct bestlineState *l) { - (void)l; -} - -/** - * Runs bestline engine. - * - * This function is the core of the line editing capability of bestline. - * 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. - * - * Returns chomped character count in buf >=0 or -1 on eof / error - */ -static ssize_t bestlineEdit(int stdin_fd, int stdout_fd, const char *prompt, - char **obuf) { - ssize_t rc; - size_t nread; - struct rune rune; - char *p, seq[16]; - unsigned long long w; - struct bestlineState l; - memset(&l,0,sizeof(l)); - if (!(l.buf = (char *)malloc((l.buflen = 32)))) return -1; - l.buf[0] = 0; - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.prompt = prompt ? prompt : ""; - l.ws = GetTerminalSize(l.ws,l.ifd,l.ofd); - bestlineHistoryAdd(""); - bestlineWriteStr(l.ofd,l.prompt); - while (1) { - if (l.dirty) bestlineRefreshLineForce(&l); - rc = bestlineRead(l.ifd,seq,sizeof(seq),&l); - if (rc > 0) { - if (seq[0] == Ctrl('R')) { - rc = bestlineSearch(&l,seq,sizeof(seq)); - if (!rc) continue; - } else if (seq[0] == '\t' && completionCallback) { - rc = bestlineCompleteLine(&l,seq,sizeof(seq)); - if (!rc) continue; - } - } - if (rc > 0) { - nread = rc; - } else if (!rc && l.len) { - nread = 1; - seq[0] = '\r'; - seq[1] = 0; - } else { - free(history[--historylen]); - history[historylen] = 0; - free(l.buf); - return -1; - } - switch (seq[0]) { - Case(Ctrl('P'), bestlineEditUp(&l)); - Case(Ctrl('E'), bestlineEditEnd(&l)); - Case(Ctrl('N'), bestlineEditDown(&l)); - Case(Ctrl('A'), bestlineEditHome(&l)); - Case(Ctrl('B'), bestlineEditLeft(&l)); - Case(Ctrl('@'), bestlineEditMark(&l)); - Case(Ctrl('Y'), bestlineEditYank(&l)); - Case(Ctrl('Q'), bestlineEditCtrlq(&l)); - Case(Ctrl('F'), bestlineEditRight(&l)); - Case(Ctrl('\\'), bestlineEditQuit()); - Case(Ctrl('S'), bestlineEditPause(&l)); - Case(Ctrl('?'), bestlineEditRubout(&l)); - Case(Ctrl('H'), bestlineEditRubout(&l)); - Case(Ctrl('L'), bestlineEditRefresh(&l)); - Case(Ctrl('Z'), bestlineEditSuspend()); - Case(Ctrl('U'), bestlineEditKillLeft(&l)); - Case(Ctrl('T'), bestlineEditTranspose(&l)); - Case(Ctrl('K'), bestlineEditKillRight(&l)); - Case(Ctrl('W'), bestlineEditRuboutWord(&l)); - case Ctrl('C'): - if (bestlineRead(l.ifd,seq,sizeof(seq),&l) != 1) break; - switch (seq[0]) { - Case(Ctrl('C'), bestlineEditInterrupt()); - Case(Ctrl('B'), bestlineEditBarf(&l)); - Case(Ctrl('S'), bestlineEditSlurp(&l)); - Case(Ctrl('R'), bestlineEditRaise(&l)); - default: - break; - } - break; - case Ctrl('X'): - if (l.seq[1][0] == Ctrl('X')) { - bestlineEditGoto(&l); - } - break; - case Ctrl('D'): - if (l.len) { - bestlineEditDelete(&l); - } else { - free(history[--historylen]); - history[historylen] = 0; - free(l.buf); - return -1; - } - break; - case '\r': - l.final = 1; - free(history[--historylen]); - history[historylen] = 0; - bestlineEditEnd(&l); - bestlineRefreshLineForce(&l); - if ((p = (char *)realloc(l.buf, l.len + 1))) l.buf = p; - *obuf = l.buf; - return l.len; - case 033: - if (nread < 2) break; - switch (seq[1]) { - Case('<', bestlineEditBof(&l)); - Case('>', bestlineEditEof(&l)); - Case('B', bestlineEditBarf(&l)); - Case('S', bestlineEditSlurp(&l)); - Case('R', bestlineEditRaise(&l)); - Case('y', bestlineEditRotate(&l)); - Case('\\', bestlineEditSqueeze(&l)); - Case('b', bestlineEditLeftWord(&l)); - Case('f', bestlineEditRightWord(&l)); - Case('h', bestlineEditRuboutWord(&l)); - Case('d', bestlineEditDeleteWord(&l)); - Case('l', bestlineEditLowercaseWord(&l)); - Case('u', bestlineEditUppercaseWord(&l)); - Case('c', bestlineEditCapitalizeWord(&l)); - Case('t', bestlineEditTransposeWords(&l)); - Case(Ctrl('B'), bestlineEditLeftExpr(&l)); - Case(Ctrl('F'), bestlineEditRightExpr(&l)); - Case(Ctrl('H'), bestlineEditRuboutWord(&l)); - case '[': - if (nread < 3) break; - if (seq[2] >= '0' && seq[2] <= '9') { - if (nread < 4) break; - if (seq[3] == '~') { - switch (seq[2]) { - Case('1', bestlineEditHome(&l)); /* \e[1~ */ - Case('3', bestlineEditDelete(&l)); /* \e[3~ */ - Case('4', bestlineEditEnd(&l)); /* \e[4~ */ - default: - break; - } - } - } else { - switch (seq[2]) { - Case('A', bestlineEditUp(&l)); - Case('B', bestlineEditDown(&l)); - Case('C', bestlineEditRight(&l)); - Case('D', bestlineEditLeft(&l)); - Case('H', bestlineEditHome(&l)); - Case('F', bestlineEditEnd(&l)); - default: - break; - } - } - break; - case 'O': - if (nread < 3) break; - switch (seq[2]) { - Case('A', bestlineEditUp(&l)); - Case('B', bestlineEditDown(&l)); - Case('C', bestlineEditRight(&l)); - Case('D', bestlineEditLeft(&l)); - Case('H', bestlineEditHome(&l)); - Case('F', bestlineEditEnd(&l)); - default: - break; - } - break; - case 033: - if (nread < 3) break; - switch (seq[2]) { - case '[': - if (nread < 4) break; - switch (seq[3]) { - Case('C', bestlineEditRightExpr(&l)); /* \e\e[C alt-right */ - Case('D', bestlineEditLeftExpr(&l)); /* \e\e[D alt-left */ - default: - break; - } - break; - case 'O': - if (nread < 4) break; - switch (seq[3]) { - Case('C', bestlineEditRightExpr(&l)); /* \e\eOC alt-right */ - Case('D', bestlineEditLeftExpr(&l)); /* \e\eOD alt-left */ - default: - break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - default: - if (!IsControl(seq[0])) { /* only sees canonical c0 */ - if (xlatCallback) { - rune = GetUtf8(seq,nread); - w = EncodeUtf8(xlatCallback(rune.c)); - nread = 0; - do { - seq[nread++] = w; - } while ((w >>= 8)); - } - bestlineEditInsert(&l,seq,nread); - } - break; - } - } -} - -void bestlineFree(void *ptr) { - free(ptr); -} - -void bestlineHistoryFree(void) { - size_t i; - for (i = 0; i < BESTLINE_MAX_HISTORY; i++) { - if (history[i]) { - free(history[i]); - history[i] = 0; - } - } - historylen = 0; -} - -static void bestlineAtExit(void) { - bestlineDisableRawMode(); - bestlineHistoryFree(); - bestlineRingFree(); -} - -int bestlineHistoryAdd(const char *line) { - char *linecopy; - if (!BESTLINE_MAX_HISTORY) return 0; - if (historylen && !strcmp(history[historylen-1], line)) return 0; - if (!(linecopy = strdup(line))) return 0; - if (historylen == BESTLINE_MAX_HISTORY) { - free(history[0]); - memmove(history,history+1,sizeof(char*)*(BESTLINE_MAX_HISTORY-1)); - historylen--; - } - history[historylen++] = linecopy; - return 1; -} - -/** - * Saves line editing history to file. - * - * @return 0 on success, or -1 w/ errno - */ -int bestlineHistorySave(const char *filename) { - FILE *fp; - unsigned j; - mode_t old_umask; - old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); - fp = fopen(filename,"w"); - umask(old_umask); - if (!fp) return -1; - chmod(filename,S_IRUSR|S_IWUSR); - for (j = 0; j < historylen; j++) { - fputs(history[j],fp); - fputc('\n',fp); - } - fclose(fp); - return 0; -} - -/** - * Loads history from the specified file. - * - * If the file doesn't exist, zero is returned and this will do nothing. - * If the file does exists and the operation succeeded zero is returned - * otherwise on error -1 is returned. - * - * @return 0 on success, or -1 w/ errno - */ -int bestlineHistoryLoad(const char *filename) { - char **h; - int rc, fd, err; - size_t i, j, k, n, t; - char *m, *e, *p, *q, *f, *s; - err = errno, rc = 0; - if (!BESTLINE_MAX_HISTORY) return 0; - if (!(h = (char**)calloc(2*BESTLINE_MAX_HISTORY,sizeof(char*)))) return -1; - if ((fd = open(filename,O_RDONLY)) != -1) { - if ((n = GetFdSize(fd))) { - if ((m = (char *)mmap(0,n,PROT_READ,MAP_SHARED,fd,0))!=MAP_FAILED) { - for (i = 0, e = (p = m) + n; p < e; p = f + 1) { - if (!(q = (char *)memchr(p, '\n', e - p))) q = e; - for (f = q; q > p; --q) { - if (q[-1] != '\n' && q[-1] != '\r') break; - } - if (q > p) { - h[i * 2 + 0] = p; - h[i * 2 + 1] = q; - i = (i + 1) % BESTLINE_MAX_HISTORY; - } - } - bestlineHistoryFree(); - for (j = 0; j < BESTLINE_MAX_HISTORY; ++j) { - if (h[(k = (i + j) % BESTLINE_MAX_HISTORY) * 2]) { - if ((s = (char *)malloc((t=h[k*2+1]-h[k*2])+1))) { - memcpy(s,h[k*2],t),s[t]=0; - history[historylen++] = s; - } - } - } - munmap(m,n); - } else { - rc = -1; - } - } - close(fd); - } else if (errno == ENOENT) { - errno = err; - } else { - rc = -1; - } - free(h); - return rc; -} - -/** - * Reads line interactively. - * - * This function can be used instead of bestline() in cases where we - * know for certain we're dealing with a terminal, which means we can - * avoid linking any stdio code. - * - * @return chomped allocated string of read line or null on eof/error - */ -char *bestlineRaw(const char *prompt, int infd, int outfd) { - char *buf; - ssize_t rc; - static char once; - struct sigaction sa[3]; - if (!once) atexit(bestlineAtExit), once = 1; - if (enableRawMode(infd) == -1) return 0; - buf = 0; - gotint = 0; - sigemptyset(&sa->sa_mask); - sa->sa_flags = 0; - sa->sa_handler = bestlineOnInt; - sigaction(SIGINT,sa,sa+1); - sigaction(SIGQUIT,sa,sa+2); - rc = bestlineEdit(infd,outfd,prompt,&buf); - bestlineDisableRawMode(); - sigaction(SIGQUIT,sa+2,0); - sigaction(SIGINT,sa+1,0); - if (gotint) { - free(buf); - buf = 0; - raise(gotint); - errno = EINTR; - rc = -1; - } - if (rc != -1) { - bestlineWriteStr(outfd,"\n"); - return buf; - } else { - free(buf); - return 0; - } -} - -/** - * Reads line intelligently. - * - * The high level function that is the main API of the bestline library. - * This function checks if the terminal has basic capabilities, just checking - * for a blacklist of inarticulate 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. - * - * @param prompt is printed before asking for input if we have a term - * and this may be set to empty or null to disable and prompt may - * contain ansi escape sequences, color, utf8, etc. - * @return chomped allocated string of read line or null on eof/error - */ -char *bestlineFile(const char *prompt, FILE *fin, FILE *fout) { - if (prompt && *prompt && - (strchr(prompt, '\n') || strchr(prompt, '\t') || - strchr(prompt + 1, '\r'))) { - errno = EINVAL; - return 0; - } - if ((!isatty(fileno(fin)) || - !isatty(fileno(fout)))) { - if (prompt && *prompt && (IsCharDev(fileno(fin)) && - IsCharDev(fileno(fout)))) { - fputs(prompt,fout); - fflush(fout); - } - return GetLine(fin, fout); - } else if (bestlineIsUnsupportedTerm()) { - if (prompt && *prompt) { - fputs(prompt,fout); - fflush(fout); - } - return GetLine(fin, fout); - } else { - fflush(fout); - return bestlineRaw(prompt,fileno(fin),fileno(fout)); - } -} - -char *bestline(const char *prompt) { - return bestlineFile(prompt, stdin, stdout); -} -/** - * Reads line intelligently w/ history, e.g. - * - * // see ~/.foo_history - * main() { - * char *line; - * while ((line = bestlineWithHistory("IN> ", "foo"))) { - * printf("OUT> %s\n", line); - * free(line); - * } - * } - * - * @param prompt is printed before asking for input if we have a term - * and this may be set to empty or null to disable and prompt may - * contain ansi escape sequences, color, utf8, etc. - * @param prog is name of your app, used to generate history filename - * however if it contains a slash / dot then we'll assume prog is - * the history filename which as determined by the caller - * @return chomped allocated string of read line or null on eof/error - */ -char *bestlineWithHistory(const char *prompt, const char *prog) { - char *line; - struct abuf path; - const char *a, *b; - abInit(&path); - if (prog) { - if (strchr(prog, '/') || strchr(prog, '.')) { - abAppends(&path, prog); - } else { - b = ""; - if (!(a = getenv("HOME"))) { - if (!(a = getenv("HOMEDRIVE")) || - !(b = getenv("HOMEPATH"))) { - a = ""; - } - } - if (*a) { - abAppends(&path, a); - abAppends(&path, b); - abAppendw(&path, '/'); - } - abAppendw(&path, '.'); - abAppends(&path, prog); - abAppends(&path, "_history"); - } - } - if (path.len) { - bestlineHistoryLoad(path.b); - } - line = bestline(prompt); - if (path.len && line && *line) { - /* history here is inefficient but helpful when the user has multiple - * repls open at the same time, so history propagates between them */ - bestlineHistoryLoad(path.b); - bestlineHistoryAdd(line); - bestlineHistorySave(path.b); - } - abFree(&path); - return line; -} - -/** - * Registers tab completion callback. - */ -void bestlineSetCompletionCallback(bestlineCompletionCallback *fn) { - completionCallback = fn; -} - -/** - * Registers hints callback. - * - * Register a hits function to be called to show hits to the user at the - * right of the prompt. - */ -void bestlineSetHintsCallback(bestlineHintsCallback *fn) { - hintsCallback = fn; -} - -/** - * Sets free hints callback. - * - * This registers a function to free the hints returned by the hints - * callback registered with bestlineSetHintsCallback(). - */ -void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *fn) { - freeHintsCallback = fn; -} - -/** - * Sets character translation callback. - */ -void bestlineSetXlatCallback(bestlineXlatCallback *fn) { - xlatCallback = fn; -} - -/** - * Adds completion. - * - * 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 . See the example.c source code for a very easy to - * understand example. - */ -void bestlineAddCompletion(bestlineCompletions *lc, const char *str) { - size_t len; - char *copy, **cvec; - if ((copy = (char *)malloc((len = strlen(str))+1))) { - memcpy(copy,str,len+1); - if ((cvec = (char **)realloc(lc->cvec,(lc->len+1)*sizeof(*lc->cvec)))) { - lc->cvec = cvec; - lc->cvec[lc->len++] = copy; - } else { - free(copy); - } - } -} - -/** - * Frees list of completion option populated by bestlineAddCompletion(). - */ -void bestlineFreeCompletions(bestlineCompletions *lc) { - size_t i; - for (i = 0; i < lc->len; i++) - free(lc->cvec[i]); - if (lc->cvec) - free(lc->cvec); -} - -/** - * Enables "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. - * - * @see bestlineMaskModeDisable() - */ -void bestlineMaskModeEnable(void) { - maskmode = 1; -} - -/** - * Disables "mask mode". - */ -void bestlineMaskModeDisable(void) { - maskmode = 0; -} diff --git a/deps/bestline/bestline.h b/deps/bestline/bestline.h deleted file mode 100644 index 8ccf7cf..0000000 --- a/deps/bestline/bestline.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include - -typedef struct bestlineCompletions { - unsigned long len; - char **cvec; -} bestlineCompletions; - -typedef void(bestlineCompletionCallback)(const char *, bestlineCompletions *); -typedef char *(bestlineHintsCallback)(const char *, const char **, - const char **); -typedef void(bestlineFreeHintsCallback)(void *); -typedef unsigned(bestlineXlatCallback)(unsigned); - -void bestlineSetCompletionCallback(bestlineCompletionCallback *); -void bestlineSetHintsCallback(bestlineHintsCallback *); -void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *); -void bestlineAddCompletion(bestlineCompletions *, const char *); -void bestlineSetXlatCallback(bestlineXlatCallback *); - -char *bestlineFile(const char *, FILE *, FILE *); -char *bestline(const char *); -char *bestlineRaw(const char *, int, int); -char *bestlineWithHistory(const char *, const char *); -int bestlineHistoryAdd(const char *); -int bestlineHistorySave(const char *); -int bestlineHistoryLoad(const char *); -void bestlineFreeCompletions(bestlineCompletions *); -void bestlineHistoryFree(void); -void bestlineClearScreen(int); -void bestlineMaskModeEnable(void); -void bestlineMaskModeDisable(void); -void bestlineDisableRawMode(void); -void bestlineFree(void *); - -char bestlineIsSeparator(unsigned); -char bestlineNotSeparator(unsigned); -char bestlineIsXeparator(unsigned); -unsigned bestlineUppercase(unsigned); -unsigned bestlineLowercase(unsigned); -long bestlineReadCharacter(int, char *, unsigned long); diff --git a/deps/isocline/LICENSE b/deps/isocline/LICENSE new file mode 100644 index 0000000..7ac3104 --- /dev/null +++ b/deps/isocline/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Daan Leijen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/deps/isocline/include/isocline.h b/deps/isocline/include/isocline.h new file mode 100644 index 0000000..0d46cf3 --- /dev/null +++ b/deps/isocline/include/isocline.h @@ -0,0 +1,627 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_ISOCLINE_H +#define IC_ISOCLINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include // size_t +#include // bool +#include // uint32_t +#include // term_vprintf + + +/*! \mainpage +Isocline C API reference. + +Isocline is a pure C library that can be used as an alternative to the GNU readline library. + +See the [Github repository](https://github.com/daanx/isocline#readme) +for general information and building the library. + +Contents: +- \ref readline +- \ref bbcode +- \ref history +- \ref completion +- \ref highlight +- \ref options +- \ref helper +- \ref completex +- \ref term +- \ref async +- \ref alloc +*/ + +/// \defgroup readline Readline +/// The basic readline interface. +/// \{ + +/// Isocline version: 102 = 1.0.2. +#define IC_VERSION (104) + + +/// Read input from the user using rich editing abilities. +/// @param prompt_text The prompt text, can be NULL for the default (""). +/// The displayed prompt becomes `prompt_text` followed by the `prompt_marker` ("> "). +/// @returns the heap allocated input on succes, which should be `free`d by the caller. +/// Returns NULL on error, or if the user typed ctrl+d or ctrl+c. +/// +/// If the standard input (`stdin`) has no editing capability +/// (like a dumb terminal (e.g. `TERM`=`dumb`), running in a debuggen, a pipe or redirected file, etc.) +/// the input is read directly from the input stream up to the +/// next line without editing capability. +/// See also \a ic_set_prompt_marker(), \a ic_style_def() +/// +/// @see ic_set_prompt_marker(), ic_style_def() +char* ic_readline(const char* prompt_text); + +/// \} + + +//-------------------------------------------------------------- +/// \defgroup bbcode Formatted Text +/// Formatted text using [bbcode markup](https://github.com/daanx/isocline#bbcode-format). +/// \{ + +/// Print to the terminal while respection bbcode markup. +/// Any unclosed tags are closed automatically at the end of the print. +/// For example: +/// ``` +/// ic_print("[b]bold, [i]bold and italic[/i], [red]red and bold[/][/b] default."); +/// ic_print("[b]bold[/], [i b]bold and italic[/], [yellow on blue]yellow on blue background"); +/// ic_style_add("em","i color=#888800"); +/// ic_print("[em]emphasis"); +/// ``` +/// Properties that can be assigned are: +/// * `color=` _clr_, `bgcolor=` _clr_: where _clr_ is either a hex value `#`RRGGBB or `#`RGB, a +/// standard HTML color name, or an ANSI palette name, like `ansi-maroon`, `ansi-default`, etc. +/// * `bold`,`italic`,`reverse`,`underline`: can be `on` or `off`. +/// * everything else is a style; all HTML and ANSI color names are also a style (so we can just use `red` +/// instead of `color=red`, or `on red` instead of `bgcolor=red`), and there are +/// the `b`, `i`, `u`, and `r` styles for bold, italic, underline, and reverse. +/// +/// See [here](https://github.com/daanx/isocline#bbcode-format) for a description of the full bbcode format. +void ic_print( const char* s ); + +/// Print with bbcode markup ending with a newline. +/// @see ic_print() +void ic_println( const char* s ); + +/// Print formatted with bbcode markup. +/// @see ic_print() +void ic_printf(const char* fmt, ...); + +/// Print formatted with bbcode markup. +/// @see ic_print +void ic_vprintf(const char* fmt, va_list args); + +/// Define or redefine a style. +/// @param style_name The name of the style. +/// @param fmt The `fmt` string is the content of a tag and can contain +/// other styles. This is very useful to theme the output of a program +/// by assigning standard styles like `em` or `warning` etc. +void ic_style_def( const char* style_name, const char* fmt ); + +/// Start a global style that is only reset when calling a matching ic_style_close(). +void ic_style_open( const char* fmt ); + +/// End a global style. +void ic_style_close(void); + +/// \} + + +//-------------------------------------------------------------- +// History +//-------------------------------------------------------------- +/// \defgroup history History +/// Readline input history. +/// \{ + +/// Enable history. +/// Use a \a NULL filename to not persist the history. Use -1 for max_entries to get the default (200). +void ic_set_history(const char* fname, long max_entries ); + +/// Remove the last entry in the history. +/// The last returned input from ic_readline() is automatically added to the history; this function removes it. +void ic_history_remove_last(void); + +/// Clear the history. +void ic_history_clear(void); + +/// Add an entry to the history +void ic_history_add( const char* entry ); + +/// \} + +//-------------------------------------------------------------- +// Basic Completion +//-------------------------------------------------------------- + +/// \defgroup completion Completion +/// Basic word completion. +/// \{ + +/// A completion environment +struct ic_completion_env_s; + +/// A completion environment +typedef struct ic_completion_env_s ic_completion_env_t; + +/// A completion callback that is called by isocline when tab is pressed. +/// It is passed a completion environment (containing the current input and the current cursor position), +/// the current input up-to the cursor (`prefix`) +/// and the user given argument when the callback was set. +/// When using completion transformers, like `ic_complete_quoted_word` the `prefix` contains the +/// the word to be completed without escape characters or quotes. +typedef void (ic_completer_fun_t)(ic_completion_env_t* cenv, const char* prefix ); + +/// Set the default completion handler. +/// @param completer The completion function +/// @param arg Argument passed to the \a completer. +/// There can only be one default completion function, setting it again disables the previous one. +/// The initial completer use `ic_complete_filename`. +void ic_set_default_completer( ic_completer_fun_t* completer, void* arg); + + +/// In a completion callback (usually from ic_complete_word()), use this function to add a completion. +/// (the completion string is copied by isocline and do not need to be preserved or allocated). +/// +/// Returns `true` if the callback should continue trying to find more possible completions. +/// If `false` is returned, the callback should try to return and not add more completions (for improved latency). +bool ic_add_completion(ic_completion_env_t* cenv, const char* completion); + +/// In a completion callback (usually from ic_complete_word()), use this function to add a completion. +/// The `display` is used to display the completion in the completion menu, and `help` is +/// displayed for hints for example. Both can be `NULL` for the default. +/// (all are copied by isocline and do not need to be preserved or allocated). +/// +/// Returns `true` if the callback should continue trying to find more possible completions. +/// If `false` is returned, the callback should try to return and not add more completions (for improved latency). +bool ic_add_completion_ex( ic_completion_env_t* cenv, const char* completion, const char* display, const char* help ); + +/// In a completion callback (usually from ic_complete_word()), use this function to add completions. +/// The `completions` array should be terminated with a NULL element, and all elements +/// are added as completions if they start with `prefix`. +/// +/// Returns `true` if the callback should continue trying to find more possible completions. +/// If `false` is returned, the callback should try to return and not add more completions (for improved latency). +bool ic_add_completions(ic_completion_env_t* cenv, const char* prefix, const char** completions); + +/// Complete a filename. +/// Complete a filename given a semi-colon separated list of root directories `roots` and +/// semi-colon separated list of possible extensions (excluding directories). +/// If `roots` is NULL, the current directory is the root ("."). +/// If `extensions` is NULL, any extension will match. +/// Each root directory should _not_ end with a directory separator. +/// If a directory is completed, the `dir_separator` is added at the end if it is not `0`. +/// Usually the `dir_separator` is `/` but it can be set to `\\` on Windows systems. +/// For example: +/// ``` +/// /ho --> /home/ +/// /home/.ba --> /home/.bashrc +/// ``` +/// (This already uses ic_complete_quoted_word() so do not call it from inside a word handler). +void ic_complete_filename( ic_completion_env_t* cenv, const char* prefix, char dir_separator, const char* roots, const char* extensions ); + + + +/// Function that returns whether a (utf8) character (of length `len`) is in a certain character class +/// @see ic_char_is_separator() etc. +typedef bool (ic_is_char_class_fun_t)(const char* s, long len); + + +/// Complete a _word_ (i.e. _token_). +/// Calls the user provided function `fun` to complete on the +/// current _word_. Almost all user provided completers should use this function. +/// If `is_word_char` is NULL, the default `&ic_char_is_nonseparator` is used. +/// The `prefix` passed to `fun` is modified to only contain the current word, and +/// any results from `ic_add_completion` are automatically adjusted to replace that part. +/// For example, on the input "hello w", a the user `fun` only gets `w` and can just complete +/// with "world" resulting in "hello world" without needing to consider `delete_before` etc. +/// @see ic_complete_qword() for completing quoted and escaped tokens. +void ic_complete_word(ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char); + + +/// Complete a quoted _word_. +/// Calls the user provided function `fun` to complete while taking +/// care of quotes and escape characters. Almost all user provided completers should use +/// this function. The `prefix` passed to `fun` is modified to be unquoted and unescaped, and +/// any results from `ic_add_completion` are automatically quoted and escaped again. +/// For example, completing `hello world`, the `fun` always just completes `hel` or `hello w` to `hello world`, +/// but depending on user input, it will complete as: +/// ``` +/// hel --> hello\ world +/// hello\ w --> hello\ world +/// hello w --> # no completion, the word is just 'w'> +/// "hel --> "hello world" +/// "hello w --> "hello world" +/// ``` +/// with proper quotes and escapes. +/// If `is_word_char` is NULL, the default `&ic_char_is_nonseparator` is used. +/// @see ic_complete_quoted_word() to customize the word boundary, quotes etc. +void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char ); + + + +/// Complete a _word_. +/// Calls the user provided function `fun` to complete while taking +/// care of quotes and escape characters. Almost all user provided completers should use this function. +/// The `is_word_char` is a set of characters that are part of a "word". Use NULL for the default (`&ic_char_is_nonseparator`). +/// The `escape_char` is the escaping character, usually `\` but use 0 to not have escape characters. +/// The `quote_chars` define the quotes, use NULL for the default `"\'\""` quotes. +/// @see ic_complete_word() which uses the default values for `non_word_chars`, `quote_chars` and `\` for escape characters. +void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t fun, + ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars ); + +/// \} + +//-------------------------------------------------------------- +/// \defgroup highlight Syntax Highlighting +/// Basic syntax highlighting. +/// \{ + +/// A syntax highlight environment +struct ic_highlight_env_s; +typedef struct ic_highlight_env_s ic_highlight_env_t; + +/// A syntax highlighter callback that is called by readline to syntax highlight user input. +typedef void (ic_highlight_fun_t)(ic_highlight_env_t* henv, const char* input, void* arg); + +/// Set a syntax highlighter. +/// There can only be one highlight function, setting it again disables the previous one. +void ic_set_default_highlighter(ic_highlight_fun_t* highlighter, void* arg); + +/// Set the style of characters starting at position `pos`. +void ic_highlight(ic_highlight_env_t* henv, long pos, long count, const char* style ); + +/// Experimental: Convenience callback for a function that highlights `s` using bbcode's. +/// The returned string should be allocated and is free'd by the caller. +typedef char* (ic_highlight_format_fun_t)(const char* s, void* arg); + +/// Experimental: Convenience function for highlighting with bbcodes. +/// Can be called in a `ic_highlight_fun_t` callback to colorize the `input` using the +/// the provided `formatted` input that is the styled `input` with bbcodes. The +/// content of `formatted` without bbcode tags should match `input` exactly. +void ic_highlight_formatted(ic_highlight_env_t* henv, const char* input, const char* formatted); + +/// \} + +//-------------------------------------------------------------- +// Readline with a specific completer and highlighter +//-------------------------------------------------------------- + +/// \defgroup readline +/// \{ + +/// Read input from the user using rich editing abilities, +/// using a particular completion function and highlighter for this call only. +/// both can be NULL in which case the defaults are used. +/// @see ic_readline(), ic_set_prompt_marker(), ic_set_default_completer(), ic_set_default_highlighter(). +char* ic_readline_ex(const char* prompt_text, ic_completer_fun_t* completer, void* completer_arg, + ic_highlight_fun_t* highlighter, void* highlighter_arg); + +/// \} + + +//-------------------------------------------------------------- +// Options +//-------------------------------------------------------------- + +/// \defgroup options Options +/// \{ + +/// Set a prompt marker and a potential marker for extra lines with multiline input. +/// Pass \a NULL for the `prompt_marker` for the default marker (`"> "`). +/// Pass \a NULL for continuation prompt marker to make it equal to the `prompt_marker`. +void ic_set_prompt_marker( const char* prompt_marker, const char* continuation_prompt_marker ); + +/// Get the current prompt marker. +const char* ic_get_prompt_marker(void); + +/// Get the current continuation prompt marker. +const char* ic_get_continuation_prompt_marker(void); + +/// Disable or enable multi-line input (enabled by default). +/// Returns the previous setting. +bool ic_enable_multiline( bool enable ); + +/// Disable or enable sound (enabled by default). +/// A beep is used when tab cannot find any completion for example. +/// Returns the previous setting. +bool ic_enable_beep( bool enable ); + +/// Disable or enable color output (enabled by default). +/// Returns the previous setting. +bool ic_enable_color( bool enable ); + +/// Disable or enable duplicate entries in the history (disabled by default). +/// Returns the previous setting. +bool ic_enable_history_duplicates( bool enable ); + +/// Disable or enable automatic tab completion after a completion +/// to expand as far as possible if the completions are unique. (disabled by default). +/// Returns the previous setting. +bool ic_enable_auto_tab( bool enable ); + +/// Disable or enable preview of a completion selection (enabled by default) +/// Returns the previous setting. +bool ic_enable_completion_preview( bool enable ); + +/// Disable or enable automatic identation of continuation lines in multiline +/// input so it aligns with the initial prompt. +/// Returns the previous setting. +bool ic_enable_multiline_indent(bool enable); + +/// Disable or enable display of short help messages for history search etc. +/// (full help is always dispayed when pressing F1 regardless of this setting) +/// @returns the previous setting. +bool ic_enable_inline_help(bool enable); + +/// Disable or enable hinting (enabled by default) +/// Shows a hint inline when there is a single possible completion. +/// @returns the previous setting. +bool ic_enable_hint(bool enable); + +/// Set millisecond delay before a hint is displayed. Can be zero. (500ms by default). +long ic_set_hint_delay(long delay_ms); + +/// Disable or enable syntax highlighting (enabled by default). +/// This applies regardless whether a syntax highlighter callback was set (`ic_set_highlighter`) +/// Returns the previous setting. +bool ic_enable_highlight(bool enable); + + +/// Set millisecond delay for reading escape sequences in order to distinguish +/// a lone ESC from the start of a escape sequence. The defaults are 100ms and 10ms, +/// but it may be increased if working with very slow terminals. +void ic_set_tty_esc_delay(long initial_delay_ms, long followup_delay_ms); + +/// Enable highlighting of matching braces (and error highlight unmatched braces).` +bool ic_enable_brace_matching(bool enable); + +/// Set matching brace pairs. +/// Pass \a NULL for the default `"()[]{}"`. +void ic_set_matching_braces(const char* brace_pairs); + +/// Enable automatic brace insertion (enabled by default). +bool ic_enable_brace_insertion(bool enable); + +/// Set matching brace pairs for automatic insertion. +/// Pass \a NULL for the default `()[]{}\"\"''` +void ic_set_insertion_braces(const char* brace_pairs); + +/// \} + + +//-------------------------------------------------------------- +// Advanced Completion +//-------------------------------------------------------------- + +/// \defgroup completex Advanced Completion +/// \{ + +/// Get the raw current input (and cursor position if `cursor` != NULL) for the completion. +/// Usually completer functions should look at their `prefix` though as transformers +/// like `ic_complete_word` may modify the prefix (for example, unescape it). +const char* ic_completion_input( ic_completion_env_t* cenv, long* cursor ); + +/// Get the completion argument passed to `ic_set_completer`. +void* ic_completion_arg( const ic_completion_env_t* cenv ); + +/// Do we have already some completions? +bool ic_has_completions( const ic_completion_env_t* cenv ); + +/// Do we already have enough completions and should we return if possible? (for improved latency) +bool ic_stop_completing( const ic_completion_env_t* cenv); + + +/// Primitive completion, cannot be used with most transformers (like `ic_complete_word` and `ic_complete_qword`). +/// When completed, `delete_before` _bytes_ are deleted before the cursor position, +/// `delete_after` _bytes_ are deleted after the cursor, and finally `completion` is inserted. +/// The `display` is used to display the completion in the completion menu, and `help` is displayed +/// with hinting. Both `display` and `help` can be NULL. +/// (all are copied by isocline and do not need to be preserved or allocated). +/// +/// Returns `true` if the callback should continue trying to find more possible completions. +/// If `false` is returned, the callback should try to return and not add more completions (for improved latency). +bool ic_add_completion_prim( ic_completion_env_t* cenv, const char* completion, + const char* display, const char* help, + long delete_before, long delete_after); + +/// \} + +//-------------------------------------------------------------- +/// \defgroup helper Character Classes. +/// Convenience functions for character classes, highlighting and completion. +/// \{ + +/// Convenience: return the position of a previous code point in a UTF-8 string `s` from postion `pos`. +/// Returns `-1` if `pos <= 0` or `pos > strlen(s)` (or other errors). +long ic_prev_char( const char* s, long pos ); + +/// Convenience: return the position of the next code point in a UTF-8 string `s` from postion `pos`. +/// Returns `-1` if `pos < 0` or `pos >= strlen(s)` (or other errors). +long ic_next_char( const char* s, long pos ); + +/// Convenience: does a string `s` starts with a given `prefix` ? +bool ic_starts_with( const char* s, const char* prefix ); + +/// Convenience: does a string `s` starts with a given `prefix` ignoring (ascii) case? +bool ic_istarts_with( const char* s, const char* prefix ); + + +/// Convenience: character class for whitespace `[ \t\r\n]`. +bool ic_char_is_white(const char* s, long len); + +/// Convenience: character class for non-whitespace `[^ \t\r\n]`. +bool ic_char_is_nonwhite(const char* s, long len); + +/// Convenience: character class for separators. +/// (``[ \t\r\n,.;:/\\(){}\[\]]``.) +/// This is used for word boundaries in isocline. +bool ic_char_is_separator(const char* s, long len); + +/// Convenience: character class for non-separators. +bool ic_char_is_nonseparator(const char* s, long len); + +/// Convenience: character class for letters (`[A-Za-z]` and any unicode > 0x80). +bool ic_char_is_letter(const char* s, long len); + +/// Convenience: character class for digits (`[0-9]`). +bool ic_char_is_digit(const char* s, long len); + +/// Convenience: character class for hexadecimal digits (`[A-Fa-f0-9]`). +bool ic_char_is_hexdigit(const char* s, long len); + +/// Convenience: character class for identifier letters (`[A-Za-z0-9_-]` and any unicode > 0x80). +bool ic_char_is_idletter(const char* s, long len); + +/// Convenience: character class for filename letters (_not in_ " \t\r\n`@$><=;|&\{\}\(\)\[\]]"). +bool ic_char_is_filename_letter(const char* s, long len); + + +/// Convenience: If this is a token start, return the length. Otherwise return 0. +long ic_is_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char); + +/// Convenience: Does this match the specified token? +/// Ensures not to match prefixes or suffixes, and returns the length of the match (in bytes). +/// E.g. `ic_match_token("function",0,&ic_char_is_letter,"fun")` returns 0. +/// while `ic_match_token("fun x",0,&ic_char_is_letter,"fun"})` returns 3. +long ic_match_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char, const char* token); + + +/// Convenience: Do any of the specified tokens match? +/// Ensures not to match prefixes or suffixes, and returns the length of the match (in bytes). +/// E.g. `ic_match_any_token("function",0,&ic_char_is_letter,{"fun","func",NULL})` returns 0. +/// while `ic_match_any_token("func x",0,&ic_char_is_letter,{"fun","func",NULL})` returns 4. +long ic_match_any_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char, const char** tokens); + +/// \} + +//-------------------------------------------------------------- +/// \defgroup term Terminal +/// +/// Experimental: Low level terminal output. +/// Ensures basic ANSI SGR escape sequences are processed +/// in a portable way (e.g. on Windows) +/// \{ + +/// Initialize for terminal output. +/// Call this before using the terminal write functions (`ic_term_write`) +/// Does nothing on most platforms but on Windows it sets the console to UTF8 output and possible +/// enables virtual terminal processing. +void ic_term_init(void); + +/// Call this when done with the terminal functions. +void ic_term_done(void); + +/// Flush the terminal output. +/// (happens automatically on newline characters ('\n') as well). +void ic_term_flush(void); + +/// Write a string to the console (and process CSI escape sequences). +void ic_term_write(const char* s); + +/// Write a string to the console and end with a newline +/// (and process CSI escape sequences). +void ic_term_writeln(const char* s); + +/// Write a formatted string to the console. +/// (and process CSI escape sequences) +void ic_term_writef(const char* fmt, ...); + +/// Write a formatted string to the console. +void ic_term_vwritef(const char* fmt, va_list args); + +/// Set text attributes from a style. +void ic_term_style( const char* style ); + +/// Set text attribute to bold. +void ic_term_bold(bool enable); + +/// Set text attribute to underline. +void ic_term_underline(bool enable); + +/// Set text attribute to italic. +void ic_term_italic(bool enable); + +/// Set text attribute to reverse video. +void ic_term_reverse(bool enable); + +/// Set text attribute to ansi color palette index between 0 and 255 (or 256 for the ANSI "default" color). +/// (auto matched to smaller palette if not supported) +void ic_term_color_ansi(bool foreground, int color); + +/// Set text attribute to 24-bit RGB color (between `0x000000` and `0xFFFFFF`). +/// (auto matched to smaller palette if not supported) +void ic_term_color_rgb(bool foreground, uint32_t color ); + +/// Reset the text attributes. +void ic_term_reset( void ); + +/// Get the palette used by the terminal: +/// This is usually initialized from the COLORTERM environment variable. The +/// possible values of COLORTERM for each palette are given in parenthesis. +/// +/// - 1: monochrome (`monochrome`) +/// - 3: old ANSI terminal with 8 colors, using bold for bright (`8color`/`3bit`) +/// - 4: regular ANSI terminal with 16 colors. (`16color`/`4bit`) +/// - 8: terminal with ANSI 256 color palette. (`256color`/`8bit`) +/// - 24: true-color terminal with full RGB colors. (`truecolor`/`24bit`/`direct`) +int ic_term_get_color_bits( void ); + +/// \} + +//-------------------------------------------------------------- +/// \defgroup async ASync +/// Async support +/// \{ + +/// Thread-safe way to asynchronously unblock a readline. +/// Behaves as if the user pressed the `ctrl-C` character +/// (resulting in returning NULL from `ic_readline`). +/// Returns `true` if the event was successfully delivered. +/// (This may not be supported on all platforms, but it is +/// functional on Linux, macOS and Windows). +bool ic_async_stop(void); + +/// \} + +//-------------------------------------------------------------- +/// \defgroup alloc Custom Allocation +/// Register allocation functions for custom allocators +/// \{ + +typedef void* (ic_malloc_fun_t)( size_t size ); +typedef void* (ic_realloc_fun_t)( void* p, size_t newsize ); +typedef void (ic_free_fun_t)( void* p ); + +/// Initialize with custom allocation functions. +/// This must be called as the first function in a program! +void ic_init_custom_alloc( ic_malloc_fun_t* _malloc, ic_realloc_fun_t* _realloc, ic_free_fun_t* _free ); + +/// Free a potentially custom alloc'd pointer (in particular, the result returned from `ic_readline`) +void ic_free( void* p ); + +/// Allocate using the current memory allocator. +void* ic_malloc(size_t sz); + +/// Duplicate a string using the current memory allocator. +const char* ic_strdup( const char* s ); + +/// \} + +#ifdef __cplusplus +} +#endif + +#endif /// IC_ISOCLINE_H diff --git a/deps/isocline/src/attr.c b/deps/isocline/src/attr.c new file mode 100644 index 0000000..b5ad78f --- /dev/null +++ b/deps/isocline/src/attr.c @@ -0,0 +1,294 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include + +#include "common.h" +#include "stringbuf.h" // str_next_ofs +#include "attr.h" +#include "term.h" // color_from_ansi256 + +//------------------------------------------------------------- +// Attributes +//------------------------------------------------------------- + +ic_private attr_t attr_none(void) { + attr_t attr; + attr.value = 0; + return attr; +} + +ic_private attr_t attr_default(void) { + attr_t attr = attr_none(); + attr.x.color = IC_ANSI_DEFAULT; + attr.x.bgcolor = IC_ANSI_DEFAULT; + attr.x.bold = IC_OFF; + attr.x.underline = IC_OFF; + attr.x.reverse = IC_OFF; + attr.x.italic = IC_OFF; + return attr; +} + +ic_private bool attr_is_none(attr_t attr) { + return (attr.value == 0); +} + +ic_private bool attr_is_eq(attr_t attr1, attr_t attr2) { + return (attr1.value == attr2.value); +} + +ic_private attr_t attr_from_color( ic_color_t color ) { + attr_t attr = attr_none(); + attr.x.color = color; + return attr; +} + + +ic_private attr_t attr_update_with( attr_t oldattr, attr_t newattr ) { + attr_t attr = oldattr; + if (newattr.x.color != IC_COLOR_NONE) { attr.x.color = newattr.x.color; } + if (newattr.x.bgcolor != IC_COLOR_NONE) { attr.x.bgcolor = newattr.x.bgcolor; } + if (newattr.x.bold != IC_NONE) { attr.x.bold = newattr.x.bold; } + if (newattr.x.italic != IC_NONE) { attr.x.italic = newattr.x.italic; } + if (newattr.x.reverse != IC_NONE) { attr.x.reverse = newattr.x.reverse; } + if (newattr.x.underline != IC_NONE) { attr.x.underline = newattr.x.underline; } + return attr; +} + +static bool sgr_is_digit(char c) { + return (c >= '0' && c <= '9'); +} + +static bool sgr_is_sep( char c ) { + return (c==';' || c==':'); +} + +static bool sgr_next_par(const char* s, ssize_t* pi, ssize_t* par) { + const ssize_t i = *pi; + ssize_t n = 0; + while( sgr_is_digit(s[i+n])) { + n++; + } + if (n==0) { + *par = 0; + return true; + } + else { + *pi = i+n; + return ic_atoz(s+i, par); + } +} + +static bool sgr_next_par3(const char* s, ssize_t* pi, ssize_t* p1, ssize_t* p2, ssize_t* p3) { + bool ok = false; + ssize_t i = *pi; + if (sgr_next_par(s,&i,p1) && sgr_is_sep(s[i])) { + i++; + if (sgr_next_par(s,&i,p2) && sgr_is_sep(s[i])) { + i++; + if (sgr_next_par(s,&i,p3)) { + ok = true; + }; + } + } + *pi = i; + return ok; +} + +ic_private attr_t attr_from_sgr( const char* s, ssize_t len) { + attr_t attr = attr_none(); + for( ssize_t i = 0; i < len && s[i] != 0; i++) { + ssize_t cmd = 0; + if (!sgr_next_par(s,&i,&cmd)) continue; + switch(cmd) { + case 0: attr = attr_default(); break; + case 1: attr.x.bold = IC_ON; break; + case 3: attr.x.italic = IC_ON; break; + case 4: attr.x.underline = IC_ON; break; + case 7: attr.x.reverse = IC_ON; break; + case 22: attr.x.bold = IC_OFF; break; + case 23: attr.x.italic = IC_OFF; break; + case 24: attr.x.underline = IC_OFF; break; + case 27: attr.x.reverse = IC_OFF; break; + case 39: attr.x.color = IC_ANSI_DEFAULT; break; + case 49: attr.x.bgcolor = IC_ANSI_DEFAULT; break; + default: { + if (cmd >= 30 && cmd <= 37) { + attr.x.color = IC_ANSI_BLACK + (unsigned)(cmd - 30); + } + else if (cmd >= 40 && cmd <= 47) { + attr.x.bgcolor = IC_ANSI_BLACK + (unsigned)(cmd - 40); + } + else if (cmd >= 90 && cmd <= 97) { + attr.x.color = IC_ANSI_DARKGRAY + (unsigned)(cmd - 90); + } + else if (cmd >= 100 && cmd <= 107) { + attr.x.bgcolor = IC_ANSI_DARKGRAY + (unsigned)(cmd - 100); + } + else if ((cmd == 38 || cmd == 48) && sgr_is_sep(s[i])) { + // non-associative SGR :-( + ssize_t par = 0; + i++; + if (sgr_next_par(s, &i, &par)) { + if (par==5 && sgr_is_sep(s[i])) { + // ansi 256 index + i++; + if (sgr_next_par(s, &i, &par) && par >= 0 && par <= 0xFF) { + ic_color_t color = color_from_ansi256(par); + if (cmd==38) { attr.x.color = color; } + else { attr.x.bgcolor = color; } + } + } + else if (par == 2 && sgr_is_sep(s[i])) { + // rgb value + i++; + ssize_t r,g,b; + if (sgr_next_par3(s, &i, &r,&g,&b)) { + ic_color_t color = ic_rgbx(r,g,b); + if (cmd==38) { attr.x.color = color; } + else { attr.x.bgcolor = color; } + } + } + } + } + else { + debug_msg("attr: unknow ANSI SGR code: %zd\n", cmd ); + } + } + } + } + return attr; +} + +ic_private attr_t attr_from_esc_sgr( const char* s, ssize_t len) { + if (len <= 2 || s[0] != '\x1B' || s[1] != '[' || s[len-1] != 'm') return attr_none(); + return attr_from_sgr(s+2, len-2); +} + + +//------------------------------------------------------------- +// Attribute buffer +//------------------------------------------------------------- +struct attrbuf_s { + attr_t* attrs; + ssize_t capacity; + ssize_t count; + alloc_t* mem; +}; + +static bool attrbuf_ensure_capacity( attrbuf_t* ab, ssize_t needed ) { + if (needed <= ab->capacity) return true; + ssize_t newcap = (ab->capacity <= 0 ? 240 : (ab->capacity > 1000 ? ab->capacity + 1000 : 2*ab->capacity)); + if (needed > newcap) { newcap = needed; } + attr_t* newattrs = mem_realloc_tp( ab->mem, attr_t, ab->attrs, newcap ); + if (newattrs == NULL) return false; + ab->attrs = newattrs; + ab->capacity = newcap; + assert(needed <= ab->capacity); + return true; +} + +static bool attrbuf_ensure_extra( attrbuf_t* ab, ssize_t extra ) { + const ssize_t needed = ab->count + extra; + return attrbuf_ensure_capacity( ab, needed ); +} + + +ic_private attrbuf_t* attrbuf_new( alloc_t* mem ) { + attrbuf_t* ab = mem_zalloc_tp(mem,attrbuf_t); + if (ab == NULL) return NULL; + ab->mem = mem; + attrbuf_ensure_extra(ab,1); + return ab; +} + +ic_private void attrbuf_free( attrbuf_t* ab ) { + if (ab==NULL) return; + mem_free(ab->mem, ab->attrs); + mem_free(ab->mem, ab); +} + +ic_private void attrbuf_clear(attrbuf_t* ab) { + if (ab == NULL) return; + ab->count = 0; +} + +ic_private ssize_t attrbuf_len( attrbuf_t* ab ) { + return (ab==NULL ? 0 : ab->count); +} + +ic_private const attr_t* attrbuf_attrs( attrbuf_t* ab, ssize_t expected_len ) { + assert(expected_len <= ab->count ); + // expand if needed + if (ab->count < expected_len) { + if (!attrbuf_ensure_capacity(ab,expected_len)) return NULL; + for(ssize_t i = ab->count; i < expected_len; i++) { + ab->attrs[i] = attr_none(); + } + ab->count = expected_len; + } + return ab->attrs; +} + + + +static void attrbuf_update_set_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr, bool update ) { + const ssize_t end = pos + count; + if (!attrbuf_ensure_capacity(ab, end)) return; + ssize_t i; + // initialize if end is beyond the count (todo: avoid duplicate init and set if update==false?) + if (ab->count < end) { + for(i = ab->count; i < end; i++) { + ab->attrs[i] = attr_none(); + } + ab->count = end; + } + // fill pos to end with attr + for(i = pos; i < end; i++) { + ab->attrs[i] = (update ? attr_update_with(ab->attrs[i],attr) : attr); + } +} + +ic_private void attrbuf_set_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ) { + attrbuf_update_set_at(ab, pos, count, attr, false); +} + +ic_private void attrbuf_update_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ) { + attrbuf_update_set_at(ab, pos, count, attr, true); +} + +ic_private void attrbuf_insert_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ) { + if (pos < 0 || pos > ab->count || count <= 0) return; + if (!attrbuf_ensure_extra(ab,count)) return; + ic_memmove( ab->attrs + pos + count, ab->attrs + pos, (ab->count - pos)*ssizeof(attr_t) ); + ab->count += count; + attrbuf_set_at( ab, pos, count, attr ); +} + + +// note: must allow ab == NULL! +ic_private ssize_t attrbuf_append_n( stringbuf_t* sb, attrbuf_t* ab, const char* s, ssize_t len, attr_t attr ) { + if (s == NULL || len == 0) return sbuf_len(sb); + if (ab != NULL) { + if (!attrbuf_ensure_extra(ab,len)) return sbuf_len(sb); + attrbuf_set_at(ab, ab->count, len, attr); + } + return sbuf_append_n(sb,s,len); +} + +ic_private attr_t attrbuf_attr_at( attrbuf_t* ab, ssize_t pos ) { + if (ab==NULL || pos < 0 || pos > ab->count) return attr_none(); + return ab->attrs[pos]; +} + +ic_private void attrbuf_delete_at( attrbuf_t* ab, ssize_t pos, ssize_t count ) { + if (ab==NULL || pos < 0 || pos > ab->count) return; + if (pos + count > ab->count) { count = ab->count - pos; } + if (count == 0) return; + assert(pos + count <= ab->count); + ic_memmove( ab->attrs + pos, ab->attrs + pos + count, ab->count - (pos + count) ); + ab->count -= count; +} diff --git a/deps/isocline/src/attr.h b/deps/isocline/src/attr.h new file mode 100644 index 0000000..8f37d05 --- /dev/null +++ b/deps/isocline/src/attr.h @@ -0,0 +1,70 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_ATTR_H +#define IC_ATTR_H + +#include "common.h" +#include "stringbuf.h" + +//------------------------------------------------------------- +// text attributes +//------------------------------------------------------------- + +#define IC_ON (1) +#define IC_OFF (-1) +#define IC_NONE (0) + +// try to fit in 64 bits +// note: order is important for some compilers +// note: each color can actually be 25 bits +typedef union attr_s { + struct { + unsigned int color:28; + signed int bold:2; + signed int reverse:2; + unsigned int bgcolor:28; + signed int underline:2; + signed int italic:2; + } x; + uint64_t value; +} attr_t; + +ic_private attr_t attr_none(void); +ic_private attr_t attr_default(void); +ic_private attr_t attr_from_color( ic_color_t color ); + +ic_private bool attr_is_none(attr_t attr); +ic_private bool attr_is_eq(attr_t attr1, attr_t attr2); + +ic_private attr_t attr_update_with( attr_t attr, attr_t newattr ); + +ic_private attr_t attr_from_sgr( const char* s, ssize_t len); +ic_private attr_t attr_from_esc_sgr( const char* s, ssize_t len); + +//------------------------------------------------------------- +// attribute buffer used for rich rendering +//------------------------------------------------------------- + +struct attrbuf_s; +typedef struct attrbuf_s attrbuf_t; + +ic_private attrbuf_t* attrbuf_new( alloc_t* mem ); +ic_private void attrbuf_free( attrbuf_t* ab ); // ab can be NULL +ic_private void attrbuf_clear( attrbuf_t* ab ); // ab can be NULL +ic_private ssize_t attrbuf_len( attrbuf_t* ab); // ab can be NULL +ic_private const attr_t* attrbuf_attrs( attrbuf_t* ab, ssize_t expected_len ); +ic_private ssize_t attrbuf_append_n( stringbuf_t* sb, attrbuf_t* ab, const char* s, ssize_t len, attr_t attr ); + +ic_private void attrbuf_set_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ); +ic_private void attrbuf_update_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ); +ic_private void attrbuf_insert_at( attrbuf_t* ab, ssize_t pos, ssize_t count, attr_t attr ); + +ic_private attr_t attrbuf_attr_at( attrbuf_t* ab, ssize_t pos ); +ic_private void attrbuf_delete_at( attrbuf_t* ab, ssize_t pos, ssize_t count ); + +#endif // IC_ATTR_H diff --git a/deps/isocline/src/bbcode.c b/deps/isocline/src/bbcode.c new file mode 100644 index 0000000..4d11ac3 --- /dev/null +++ b/deps/isocline/src/bbcode.c @@ -0,0 +1,842 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include +#include +#include + +#include "common.h" +#include "attr.h" +#include "term.h" +#include "bbcode.h" + +//------------------------------------------------------------- +// HTML color table +//------------------------------------------------------------- + +#include "bbcode_colors.c" + +//------------------------------------------------------------- +// Types +//------------------------------------------------------------- + +typedef struct style_s { + const char* name; // name of the style + attr_t attr; // attribute to apply +} style_t; + +typedef enum align_e { + IC_ALIGN_LEFT, + IC_ALIGN_CENTER, + IC_ALIGN_RIGHT +} align_t; + +typedef struct width_s { + ssize_t w; // > 0 + align_t align; + bool dots; // "..." (e.g. "sentence...") + char fill; // " " (e.g. "hello ") +} width_t; + +typedef struct tag_s { + const char* name; // tag open name + attr_t attr; // the saved attribute before applying the style + width_t width; // start sequence of at most "width" columns + ssize_t pos; // start position in the output (used for width restriction) +} tag_t; + + +static void tag_init(tag_t* tag) { + memset(tag,0,sizeof(*tag)); +} + +struct bbcode_s { + tag_t* tags; // stack of tags; one entry for each open tag + ssize_t tags_capacity; + ssize_t tags_nesting; + style_t* styles; // list of used defined styles + ssize_t styles_capacity; + ssize_t styles_count; + term_t* term; // terminal + alloc_t* mem; // allocator + // caches + stringbuf_t* out; // print buffer + attrbuf_t* out_attrs; + stringbuf_t* vout; // vprintf buffer +}; + + +//------------------------------------------------------------- +// Create, helpers +//------------------------------------------------------------- + +ic_private bbcode_t* bbcode_new( alloc_t* mem, term_t* term ) { + bbcode_t* bb = mem_zalloc_tp(mem,bbcode_t); + if (bb==NULL) return NULL; + bb->mem = mem; + bb->term = term; + bb->out = sbuf_new(mem); + bb->out_attrs = attrbuf_new(mem); + bb->vout = sbuf_new(mem); + return bb; +} + +ic_private void bbcode_free( bbcode_t* bb ) { + for(ssize_t i = 0; i < bb->styles_count; i++) { + mem_free(bb->mem, bb->styles[i].name); + } + mem_free(bb->mem, bb->tags); + mem_free(bb->mem, bb->styles); + sbuf_free(bb->vout); + sbuf_free(bb->out); + attrbuf_free(bb->out_attrs); + mem_free(bb->mem, bb); +} + +ic_private void bbcode_style_add( bbcode_t* bb, const char* style_name, attr_t attr ) { + if (bb->styles_count >= bb->styles_capacity) { + ssize_t newlen = bb->styles_capacity + 32; + style_t* p = mem_realloc_tp( bb->mem, style_t, bb->styles, newlen ); + if (p == NULL) return; + bb->styles = p; + bb->styles_capacity = newlen; + } + assert(bb->styles_count < bb->styles_capacity); + bb->styles[bb->styles_count].name = mem_strdup( bb->mem, style_name ); + bb->styles[bb->styles_count].attr = attr; + bb->styles_count++; +} + +static ssize_t bbcode_tag_push( bbcode_t* bb, const tag_t* tag ) { + if (bb->tags_nesting >= bb->tags_capacity) { + ssize_t newcap = bb->tags_capacity + 32; + tag_t* p = mem_realloc_tp( bb->mem, tag_t, bb->tags, newcap ); + if (p == NULL) return -1; + bb->tags = p; + bb->tags_capacity = newcap; + } + assert(bb->tags_nesting < bb->tags_capacity); + bb->tags[bb->tags_nesting] = *tag; + bb->tags_nesting++; + return (bb->tags_nesting-1); +} + +static void bbcode_tag_pop( bbcode_t* bb, tag_t* tag ) { + if (bb->tags_nesting <= 0) { + if (tag != NULL) { tag_init(tag); } + } + else { + bb->tags_nesting--; + if (tag != NULL) { + *tag = bb->tags[bb->tags_nesting]; + } + } +} + +//------------------------------------------------------------- +// Invalid parse/values/balance +//------------------------------------------------------------- + +static void bbcode_invalid(const char* fmt, ... ) { + if (getenv("ISOCLINE_BBCODE_DEBUG") != NULL) { + va_list args; + va_start(args,fmt); + vfprintf(stderr,fmt,args); + va_end(args); + } +} + +//------------------------------------------------------------- +// Set attributes +//------------------------------------------------------------- + + +static attr_t bbcode_open( bbcode_t* bb, ssize_t out_pos, const tag_t* tag, attr_t current ) { + // save current and set + tag_t cur; + tag_init(&cur); + cur.name = tag->name; + cur.attr = current; + cur.width = tag->width; + cur.pos = out_pos; + bbcode_tag_push(bb,&cur); + return attr_update_with( current, tag->attr ); +} + +static bool bbcode_close( bbcode_t* bb, ssize_t base, const char* name, tag_t* pprev ) { + // pop until match + while (bb->tags_nesting > base) { + tag_t prev; + bbcode_tag_pop(bb,&prev); + if (name==NULL || prev.name==NULL || ic_stricmp(prev.name,name) == 0) { + // matched + if (pprev != NULL) { *pprev = prev; } + return true; + } + else { + // unbalanced: we either continue popping or restore the tags depending if there is a matching open tag in our tags. + bool has_open_tag = false; + if (name != NULL) { + for( ssize_t i = bb->tags_nesting - 1; i > base; i--) { + if (bb->tags[i].name != NULL && ic_stricmp(bb->tags[i].name, name) == 0) { + has_open_tag = true; + break; + } + } + } + bbcode_invalid("bbcode: unbalanced tags: open [%s], close [/%s]\n", prev.name, name); + if (!has_open_tag) { + bbcode_tag_push( bb, &prev ); // restore the tags and ignore this close tag + break; + } + else { + // continue until we hit our open tag + } + } + } + if (pprev != NULL) { memset(pprev,0,sizeof(*pprev)); } + return false; +} + +//------------------------------------------------------------- +// Update attributes +//------------------------------------------------------------- + +static const char* attr_update_bool( const char* fname, signed int* field, const char* value ) { + if (value == NULL || value[0] == 0 || strcmp(value,"on") || strcmp(value,"true") || strcmp(value,"1")) { + *field = IC_ON; + } + else if (strcmp(value,"off") || strcmp(value,"false") || strcmp(value,"0")) { + *field = IC_OFF; + } + else { + bbcode_invalid("bbcode: invalid %s value: %s\n", fname, value ); + } + return fname; +} + +static const char* attr_update_color( const char* fname, ic_color_t* field, const char* value ) { + if (value == NULL || value[0] == 0 || strcmp(value,"none") == 0) { + *field = IC_COLOR_NONE; + return fname; + } + + // hex value + if (value[0] == '#') { + uint32_t rgb = 0; + if (sscanf(value,"#%x",&rgb) == 1) { + *field = ic_rgb(rgb); + } + else { + bbcode_invalid("bbcode: invalid color code: %s\n", value); + } + return fname; + } + + // search color names + ssize_t lo = 0; + ssize_t hi = IC_HTML_COLOR_COUNT-1; + while( lo <= hi ) { + ssize_t mid = (lo + hi) / 2; + style_color_t* info = &html_colors[mid]; + int cmp = strcmp(info->name,value); + if (cmp < 0) { + lo = mid+1; + } + else if (cmp > 0) { + hi = mid-1; + } + else { + *field = info->color; + return fname; + } + } + bbcode_invalid("bbcode: unknown %s: %s\n", fname, value); + *field = IC_COLOR_NONE; + return fname; +} + +static const char* attr_update_sgr( const char* fname, attr_t* attr, const char* value ) { + *attr = attr_update_with(*attr, attr_from_sgr(value, ic_strlen(value))); + return fname; +} + +static void attr_update_width( width_t* pwidth, char default_fill, const char* value ) { + // parse width value: ;;; + width_t width; + memset(&width, 0, sizeof(width)); + width.fill = default_fill; // use 0 for no-fill (as for max-width) + if (ic_atoz(value, &width.w)) { + ssize_t i = 0; + while( value[i] != ';' && value[i] != 0 ) { i++; } + if (value[i] == ';') { + i++; + ssize_t len = 0; + while( value[i+len] != ';' && value[i+len] != 0 ) { len++; } + if (len == 4 && ic_istarts_with(value+i,"left")) { + width.align = IC_ALIGN_LEFT; + } + else if (len == 5 && ic_istarts_with(value+i,"right")) { + width.align = IC_ALIGN_RIGHT; + } + else if (len == 6 && ic_istarts_with(value+i,"center")) { + width.align = IC_ALIGN_CENTER; + } + i += len; + if (value[i] == ';') { + i++; len = 0; + while( value[i+len] != ';' && value[i+len] != 0 ) { len++; } + if (len == 1) { width.fill = value[i]; } + i+= len; + if (value[i] == ';') { + i++; len = 0; + while( value[i+len] != ';' && value[i+len] != 0 ) { len++; } + if ((len == 2 && ic_istarts_with(value+i,"on")) || (len == 1 && value[i] == '1')) { width.dots = true; } + i += len; + } + } + } + } + else { + bbcode_invalid("bbcode: illegal width: %s\n", value); + } + *pwidth = width; +} + +static const char* attr_update_ansi_color( const char* fname, ic_color_t* color, const char* value ) { + ssize_t num = 0; + if (ic_atoz(value, &num) && num >= 0 && num <= 256) { + *color = color_from_ansi256(num); + } + return fname; +} + + +static const char* attr_update_property( tag_t* tag, const char* attr_name, const char* value ) { + const char* fname = NULL; + fname = "bold"; + if (strcmp(attr_name,fname) == 0) { + signed int b = IC_NONE; + attr_update_bool(fname,&b, value); + if (b != IC_NONE) { tag->attr.x.bold = b; } + return fname; + } + fname = "italic"; + if (strcmp(attr_name,fname) == 0) { + signed int b = IC_NONE; + attr_update_bool(fname,&b, value); + if (b != IC_NONE) { tag->attr.x.italic = b; } + return fname; + } + fname = "underline"; + if (strcmp(attr_name,fname) == 0) { + signed int b = IC_NONE; + attr_update_bool(fname,&b, value); + if (b != IC_NONE) { tag->attr.x.underline = b; } + return fname; + } + fname = "reverse"; + if (strcmp(attr_name,fname) == 0) { + signed int b = IC_NONE; + attr_update_bool(fname,&b, value); + if (b != IC_NONE) { tag->attr.x.reverse = b; } + return fname; + } + fname = "color"; + if (strcmp(attr_name,fname) == 0) { + unsigned int color = IC_COLOR_NONE; + attr_update_color(fname, &color, value); + if (color != IC_COLOR_NONE) { tag->attr.x.color = color; } + return fname; + } + fname = "bgcolor"; + if (strcmp(attr_name,fname) == 0) { + unsigned int color = IC_COLOR_NONE; + attr_update_color(fname, &color, value); + if (color != IC_COLOR_NONE) { tag->attr.x.bgcolor = color; } + return fname; + } + fname = "ansi-sgr"; + if (strcmp(attr_name,fname) == 0) { + attr_update_sgr(fname, &tag->attr, value); + return fname; + } + fname = "ansi-color"; + if (strcmp(attr_name,fname) == 0) { + ic_color_t color = IC_COLOR_NONE;; + attr_update_ansi_color(fname, &color, value); + if (color != IC_COLOR_NONE) { tag->attr.x.color = color; } + return fname; + } + fname = "ansi-bgcolor"; + if (strcmp(attr_name,fname) == 0) { + ic_color_t color = IC_COLOR_NONE;; + attr_update_ansi_color(fname, &color, value); + if (color != IC_COLOR_NONE) { tag->attr.x.bgcolor = color; } + return fname; + } + fname = "width"; + if (strcmp(attr_name,fname) == 0) { + attr_update_width(&tag->width, ' ', value); + return fname; + } + fname = "max-width"; + if (strcmp(attr_name,fname) == 0) { + attr_update_width(&tag->width, 0, value); + return "width"; + } + else { + return NULL; + } +} + +static const style_t builtin_styles[] = { + { "b", { { IC_COLOR_NONE, IC_ON , IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } }, + { "r", { { IC_COLOR_NONE, IC_NONE, IC_ON , IC_COLOR_NONE, IC_NONE, IC_NONE } } }, + { "u", { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_ON , IC_NONE } } }, + { "i", { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_NONE, IC_ON } } }, + { "em", { { IC_COLOR_NONE, IC_ON , IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } }, // bold + { "url",{ { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_ON, IC_NONE } } }, // underline + { NULL, { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } } +}; + +static void attr_update_with_styles( tag_t* tag, const char* attr_name, const char* value, + bool usebgcolor, const style_t* styles, ssize_t count ) +{ + // direct hex color? + if (attr_name[0] == '#' && (value == NULL || value[0]==0)) { + value = attr_name; + attr_name = (usebgcolor ? "bgcolor" : "color"); + } + // first try if it is a builtin property + const char* name; + if ((name = attr_update_property(tag,attr_name,value)) != NULL) { + if (tag->name != NULL) tag->name = name; + return; + } + // then check all styles + while( count-- > 0 ) { + const style_t* style = styles + count; + if (strcmp(style->name,attr_name) == 0) { + tag->attr = attr_update_with(tag->attr,style->attr); + if (tag->name != NULL) tag->name = style->name; + return; + } + } + // check builtin styles; todo: binary search? + for( const style_t* style = builtin_styles; style->name != NULL; style++) { + if (strcmp(style->name,attr_name) == 0) { + tag->attr = attr_update_with(tag->attr,style->attr); + if (tag->name != NULL) tag->name = style->name; + return; + } + } + // check colors as a style + ssize_t lo = 0; + ssize_t hi = IC_HTML_COLOR_COUNT-1; + while( lo <= hi ) { + ssize_t mid = (lo + hi) / 2; + style_color_t* info = &html_colors[mid]; + int cmp = strcmp(info->name,attr_name); + if (cmp < 0) { + lo = mid+1; + } + else if (cmp > 0) { + hi = mid-1; + } + else { + attr_t cattr = attr_none(); + if (usebgcolor) { cattr.x.bgcolor = info->color; } + else { cattr.x.color = info->color; } + tag->attr = attr_update_with(tag->attr,cattr); + if (tag->name != NULL) tag->name = info->name; + return; + } + } + // not found + bbcode_invalid("bbcode: unknown style: %s\n", attr_name); +} + + +ic_private attr_t bbcode_style( bbcode_t* bb, const char* style_name ) { + tag_t tag; + tag_init(&tag); + attr_update_with_styles( &tag, style_name, NULL, false, bb->styles, bb->styles_count ); + return tag.attr; +} + +//------------------------------------------------------------- +// Parse tags +//------------------------------------------------------------- + +ic_private const char* parse_skip_white(const char* s) { + while( *s != 0 && *s != ']') { + if (!(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) break; + s++; + } + return s; +} + +ic_private const char* parse_skip_to_white(const char* s) { + while( *s != 0 && *s != ']') { + if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') break; + s++; + } + return parse_skip_white(s); +} + +ic_private const char* parse_skip_to_end(const char* s) { + while( *s != 0 && *s != ']' ) { s++; } + return s; +} + +ic_private const char* parse_attr_name(const char* s) { + if (*s == '#') { + s++; // hex rgb color as id + while( *s != 0 && *s != ']') { + if (!((*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9'))) break; + s++; + } + } + else { + while( *s != 0 && *s != ']') { + if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || *s == '_' || *s == '-')) break; + s++; + } + } + return s; +} + +ic_private const char* parse_value(const char* s, const char** start, const char** end) { + if (*s == '"') { + s++; + *start = s; + while( *s != 0 ) { + if (*s == '"') break; + s++; + } + *end = s; + if (*s == '"') { s++; } + } + else if (*s == '#') { + *start = s; + s++; + while( *s != 0 ) { + if (!((*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9'))) break; + s++; + } + *end = s; + } + else { + *start = s; + while( *s != 0 ) { + if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'F') || (*s >= '0' && *s <= '9') || *s == '-' || *s == '_')) break; + s++; + } + *end = s; + } + return s; +} + +ic_private const char* parse_tag_value( tag_t* tag, char* idbuf, const char* s, const style_t* styles, ssize_t scount ) { + // parse: \s*[\w-]+\s*(=\s*) + bool usebgcolor = false; + const char* id = s; + const char* idend = parse_attr_name(id); + const char* val = NULL; + const char* valend = NULL; + if (id == idend) { + bbcode_invalid("bbcode: empty identifier? %.10s...\n", id ); + return parse_skip_to_white(id); + } + // "on" bgcolor? + s = parse_skip_white(idend); + if (idend - id == 2 && ic_strnicmp(id,"on",2) == 0 && *s != '=') { + usebgcolor = true; + id = s; + idend = parse_attr_name(id); + if (id == idend) { + bbcode_invalid("bbcode: empty identifier follows 'on'? %.10s...\n", id ); + return parse_skip_to_white(id); + } + s = parse_skip_white(idend); + } + // value + if (*s == '=') { + s++; + s = parse_skip_white(s); + s = parse_value(s, &val, &valend); + s = parse_skip_white(s); + } + // limit name and attr to 128 bytes + char valbuf[128]; + ic_strncpy( idbuf, 128, id, idend - id); + ic_strncpy( valbuf, 128, val, valend - val); + ic_str_tolower(idbuf); + ic_str_tolower(valbuf); + attr_update_with_styles( tag, idbuf, valbuf, usebgcolor, styles, scount ); + return s; +} + +static const char* parse_tag_values( tag_t* tag, char* idbuf, const char* s, const style_t* styles, ssize_t scount ) { + s = parse_skip_white(s); + idbuf[0] = 0; + ssize_t count = 0; + while( *s != 0 && *s != ']') { + char idbuf_next[128]; + s = parse_tag_value(tag, (count==0 ? idbuf : idbuf_next), s, styles, scount); + count++; + } + if (*s == ']') { s++; } + return s; +} + +static const char* parse_tag( tag_t* tag, char* idbuf, bool* open, bool* pre, const char* s, const style_t* styles, ssize_t scount ) { + *open = true; + *pre = false; + if (*s != '[') return s; + s = parse_skip_white(s+1); + if (*s == '!') { // pre + *pre = true; + s = parse_skip_white(s+1); + } + else if (*s == '/') { + *open = false; + s = parse_skip_white(s+1); + }; + s = parse_tag_values( tag, idbuf, s, styles, scount); + return s; +} + + +//--------------------------------------------------------- +// Styles +//--------------------------------------------------------- + +static void bbcode_parse_tag_content( bbcode_t* bb, const char* s, tag_t* tag ) { + tag_init(tag); + if (s != NULL) { + char idbuf[128]; + parse_tag_values(tag, idbuf, s, bb->styles, bb->styles_count); + } +} + +ic_private void bbcode_style_def( bbcode_t* bb, const char* style_name, const char* s ) { + tag_t tag; + bbcode_parse_tag_content( bb, s, &tag); + bbcode_style_add(bb, style_name, tag.attr); +} + +ic_private void bbcode_style_open( bbcode_t* bb, const char* fmt ) { + tag_t tag; + bbcode_parse_tag_content(bb, fmt, &tag); + term_set_attr( bb->term, bbcode_open(bb, 0, &tag, term_get_attr(bb->term)) ); +} + +ic_private void bbcode_style_close( bbcode_t* bb, const char* fmt ) { + const ssize_t base = bb->tags_nesting - 1; // as we end a style + tag_t tag; + bbcode_parse_tag_content(bb, fmt, &tag); + tag_t prev; + if (bbcode_close(bb, base, tag.name, &prev)) { + term_set_attr( bb->term, prev.attr ); + } +} + +//--------------------------------------------------------- +// Restrict to width +//--------------------------------------------------------- + +static void bbcode_restrict_width( ssize_t start, width_t width, stringbuf_t* out, attrbuf_t* attr_out ) { + if (width.w <= 0) return; + assert(start <= sbuf_len(out)); + assert(attr_out == NULL || sbuf_len(out) == attrbuf_len(attr_out)); + const char* s = sbuf_string(out) + start; + const ssize_t len = sbuf_len(out) - start; + const ssize_t w = str_column_width(s); + if (w == width.w) return; // fits exactly + if (w > width.w) { + // too large + ssize_t innerw = (width.dots && width.w > 3 ? width.w-3 : width.w); + if (width.align == IC_ALIGN_RIGHT) { + // right align + const ssize_t ndel = str_skip_until_fit( s, innerw ); + sbuf_delete_at( out, start, ndel ); + attrbuf_delete_at( attr_out, start, ndel ); + if (innerw < width.w) { + // add dots + sbuf_insert_at( out, "...", start ); + attr_t attr = attrbuf_attr_at(attr_out, start); + attrbuf_insert_at( attr_out, start, 3, attr); + } + } + else { + // left or center align + ssize_t count = str_take_while_fit( s, innerw ); + sbuf_delete_at( out, start + count, len - count ); + attrbuf_delete_at( attr_out, start + count, len - count ); + if (innerw < width.w) { + // add dots + attr_t attr = attrbuf_attr_at(attr_out,start); + attrbuf_append_n( out, attr_out, "...", 3, attr ); + } + } + } + else { + // too short, pad to width + const ssize_t diff = (width.w - w); + const ssize_t pad_left = (width.align == IC_ALIGN_RIGHT ? diff : (width.align == IC_ALIGN_LEFT ? 0 : diff / 2)); + const ssize_t pad_right = (width.align == IC_ALIGN_LEFT ? diff : (width.align == IC_ALIGN_RIGHT ? 0 : diff - pad_left)); + if (width.fill != 0 && pad_left > 0) { + const attr_t attr = attrbuf_attr_at(attr_out,start); + for( ssize_t i = 0; i < pad_left; i++) { // todo: optimize + sbuf_insert_char_at(out, width.fill, start); + } + attrbuf_insert_at( attr_out, start, pad_left, attr ); + } + if (width.fill != 0 && pad_right > 0) { + const attr_t attr = attrbuf_attr_at(attr_out,sbuf_len(out) - 1); + char buf[2]; + buf[0] = width.fill; + buf[1] = 0; + for( ssize_t i = 0; i < pad_right; i++) { // todo: optimize + attrbuf_append_n( out, attr_out, buf, 1, attr ); + } + } + } +} + +//--------------------------------------------------------- +// Print +//--------------------------------------------------------- + +ic_private ssize_t bbcode_process_tag( bbcode_t* bb, const char* s, const ssize_t nesting_base, + stringbuf_t* out, attrbuf_t* attr_out, attr_t* cur_attr ) { + assert(*s == '['); + tag_t tag; + tag_init(&tag); + bool open = true; + bool ispre = false; + char idbuf[128]; + const char* end = parse_tag( &tag, idbuf, &open, &ispre, s, bb->styles, bb->styles_count ); // todo: styles + assert(end > s); + if (open) { + if (!ispre) { + // open tag + *cur_attr = bbcode_open( bb, sbuf_len(out), &tag, *cur_attr ); + } + else { + // scan pre to end tag + attr_t attr = attr_update_with(*cur_attr, tag.attr); + char pre[132]; + if (snprintf(pre, 132, "[/%s]", idbuf) < ssizeof(pre)) { + const char* etag = strstr(end,pre); + if (etag == NULL) { + const ssize_t len = ic_strlen(end); + attrbuf_append_n(out, attr_out, end, len, attr); + end += len; + } + else { + attrbuf_append_n(out, attr_out, end, (etag - end), attr); + end = etag + ic_strlen(pre); + } + } + } + } + else { + // pop the tag + tag_t prev; + if (bbcode_close( bb, nesting_base, tag.name, &prev)) { + *cur_attr = prev.attr; + if (prev.width.w > 0) { + // closed a width tag; restrict the output to width + bbcode_restrict_width( prev.pos, prev.width, out, attr_out); + } + } + } + return (end - s); +} + +ic_private void bbcode_append( bbcode_t* bb, const char* s, stringbuf_t* out, attrbuf_t* attr_out ) { + if (bb == NULL || s == NULL) return; + attr_t attr = attr_none(); + const ssize_t base = bb->tags_nesting; // base; will not be popped + ssize_t i = 0; + while( s[i] != 0 ) { + // handle no tags in bulk + ssize_t nobb = 0; + char c; + while( (c = s[i+nobb]) != 0) { + if (c == '[' || c == '\\') { break; } + if (c == '\x1B' && s[i+nobb+1] == '[') { + nobb++; // don't count 'ESC[' as a tag opener + } + nobb++; + } + if (nobb > 0) { attrbuf_append_n(out, attr_out, s+i, nobb, attr); } + i += nobb; + // tag + if (s[i] == '[') { + i += bbcode_process_tag(bb, s+i, base, out, attr_out, &attr); + } + else if (s[i] == '\\') { + if (s[i+1] == '\\' || s[i+1] == '[') { + attrbuf_append_n(out, attr_out, s+i+1, 1, attr); // escape '\[' and '\\' + i += 2; + } + else { + attrbuf_append_n(out, attr_out, s+i, 1, attr); // pass '\\' as is + i++; + } + } + } + // pop unclosed openings + assert(bb->tags_nesting >= base); + while( bb->tags_nesting > base ) { + bbcode_tag_pop(bb,NULL); + }; +} + +ic_private void bbcode_print( bbcode_t* bb, const char* s ) { + if (bb->out == NULL || bb->out_attrs == NULL || s == NULL) return; + assert(sbuf_len(bb->out) == 0 && attrbuf_len(bb->out_attrs) == 0); + bbcode_append( bb, s, bb->out, bb->out_attrs ); + term_write_formatted( bb->term, sbuf_string(bb->out), attrbuf_attrs(bb->out_attrs,sbuf_len(bb->out)) ); + attrbuf_clear(bb->out_attrs); + sbuf_clear(bb->out); +} + +ic_private void bbcode_println( bbcode_t* bb, const char* s ) { + bbcode_print(bb,s); + term_writeln(bb->term, ""); +} + +ic_private void bbcode_vprintf( bbcode_t* bb, const char* fmt, va_list args ) { + if (bb->vout == NULL || fmt == NULL) return; + assert(sbuf_len(bb->vout) == 0); + sbuf_append_vprintf(bb->vout,fmt,args); + bbcode_print(bb, sbuf_string(bb->vout)); + sbuf_clear(bb->vout); +} + +ic_private void bbcode_printf( bbcode_t* bb, const char* fmt, ... ) { + va_list args; + va_start(args,fmt); + bbcode_vprintf(bb,fmt,args); + va_end(args); +} + +ic_private ssize_t bbcode_column_width( bbcode_t* bb, const char* s ) { + if (s==NULL || s[0] == 0) return 0; + if (bb->vout == NULL) { return str_column_width(s); } + assert(sbuf_len(bb->vout) == 0); + bbcode_append( bb, s, bb->vout, NULL); + const ssize_t w = str_column_width(sbuf_string(bb->vout)); + sbuf_clear(bb->vout); + return w; +} diff --git a/deps/isocline/src/bbcode.h b/deps/isocline/src/bbcode.h new file mode 100644 index 0000000..be96bfe --- /dev/null +++ b/deps/isocline/src/bbcode.h @@ -0,0 +1,37 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_BBCODE_H +#define IC_BBCODE_H + +#include +#include "common.h" +#include "term.h" + +struct bbcode_s; +typedef struct bbcode_s bbcode_t; + +ic_private bbcode_t* bbcode_new( alloc_t* mem, term_t* term ); +ic_private void bbcode_free( bbcode_t* bb ); + +ic_private void bbcode_style_add( bbcode_t* bb, const char* style_name, attr_t attr ); +ic_private void bbcode_style_def( bbcode_t* bb, const char* style_name, const char* s ); +ic_private void bbcode_style_open( bbcode_t* bb, const char* fmt ); +ic_private void bbcode_style_close( bbcode_t* bb, const char* fmt ); +ic_private attr_t bbcode_style( bbcode_t* bb, const char* style_name ); + +ic_private void bbcode_print( bbcode_t* bb, const char* s ); +ic_private void bbcode_println( bbcode_t* bb, const char* s ); +ic_private void bbcode_printf( bbcode_t* bb, const char* fmt, ... ); +ic_private void bbcode_vprintf( bbcode_t* bb, const char* fmt, va_list args ); + +ic_private ssize_t bbcode_column_width( bbcode_t* bb, const char* s ); + +// allows `attr_out == NULL`. +ic_private void bbcode_append( bbcode_t* bb, const char* s, stringbuf_t* out, attrbuf_t* attr_out ); + +#endif // IC_BBCODE_H diff --git a/deps/isocline/src/bbcode_colors.c b/deps/isocline/src/bbcode_colors.c new file mode 100644 index 0000000..245cd3d --- /dev/null +++ b/deps/isocline/src/bbcode_colors.c @@ -0,0 +1,194 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +// This file is included from "bbcode.c" and contains html color names + +#include "common.h" + +typedef struct style_color_s { + const char* name; + ic_color_t color; +} style_color_t; + +#define IC_HTML_COLOR_COUNT (172) + +// ordered list of HTML color names (so we can use binary search) +static style_color_t html_colors[IC_HTML_COLOR_COUNT+1] = { + { "aliceblue", IC_RGB(0xf0f8ff) }, + { "ansi-aqua", IC_ANSI_AQUA }, + { "ansi-black", IC_ANSI_BLACK }, + { "ansi-blue", IC_ANSI_BLUE }, + { "ansi-cyan", IC_ANSI_CYAN }, + { "ansi-darkgray", IC_ANSI_DARKGRAY }, + { "ansi-darkgrey", IC_ANSI_DARKGRAY }, + { "ansi-default", IC_ANSI_DEFAULT }, + { "ansi-fuchsia", IC_ANSI_FUCHSIA }, + { "ansi-gray", IC_ANSI_GRAY }, + { "ansi-green", IC_ANSI_GREEN }, + { "ansi-grey", IC_ANSI_GRAY }, + { "ansi-lightgray", IC_ANSI_LIGHTGRAY }, + { "ansi-lightgrey", IC_ANSI_LIGHTGRAY }, + { "ansi-lime" , IC_ANSI_LIME }, + { "ansi-magenta", IC_ANSI_MAGENTA }, + { "ansi-maroon", IC_ANSI_MAROON }, + { "ansi-navy", IC_ANSI_NAVY }, + { "ansi-olive", IC_ANSI_OLIVE }, + { "ansi-purple", IC_ANSI_PURPLE }, + { "ansi-red", IC_ANSI_RED }, + { "ansi-silver", IC_ANSI_SILVER }, + { "ansi-teal", IC_ANSI_TEAL }, + { "ansi-white", IC_ANSI_WHITE }, + { "ansi-yellow", IC_ANSI_YELLOW }, + { "antiquewhite", IC_RGB(0xfaebd7) }, + { "aqua", IC_RGB(0x00ffff) }, + { "aquamarine", IC_RGB(0x7fffd4) }, + { "azure", IC_RGB(0xf0ffff) }, + { "beige", IC_RGB(0xf5f5dc) }, + { "bisque", IC_RGB(0xffe4c4) }, + { "black", IC_RGB(0x000000) }, + { "blanchedalmond", IC_RGB(0xffebcd) }, + { "blue", IC_RGB(0x0000ff) }, + { "blueviolet", IC_RGB(0x8a2be2) }, + { "brown", IC_RGB(0xa52a2a) }, + { "burlywood", IC_RGB(0xdeb887) }, + { "cadetblue", IC_RGB(0x5f9ea0) }, + { "chartreuse", IC_RGB(0x7fff00) }, + { "chocolate", IC_RGB(0xd2691e) }, + { "coral", IC_RGB(0xff7f50) }, + { "cornflowerblue", IC_RGB(0x6495ed) }, + { "cornsilk", IC_RGB(0xfff8dc) }, + { "crimson", IC_RGB(0xdc143c) }, + { "cyan", IC_RGB(0x00ffff) }, + { "darkblue", IC_RGB(0x00008b) }, + { "darkcyan", IC_RGB(0x008b8b) }, + { "darkgoldenrod", IC_RGB(0xb8860b) }, + { "darkgray", IC_RGB(0xa9a9a9) }, + { "darkgreen", IC_RGB(0x006400) }, + { "darkgrey", IC_RGB(0xa9a9a9) }, + { "darkkhaki", IC_RGB(0xbdb76b) }, + { "darkmagenta", IC_RGB(0x8b008b) }, + { "darkolivegreen", IC_RGB(0x556b2f) }, + { "darkorange", IC_RGB(0xff8c00) }, + { "darkorchid", IC_RGB(0x9932cc) }, + { "darkred", IC_RGB(0x8b0000) }, + { "darksalmon", IC_RGB(0xe9967a) }, + { "darkseagreen", IC_RGB(0x8fbc8f) }, + { "darkslateblue", IC_RGB(0x483d8b) }, + { "darkslategray", IC_RGB(0x2f4f4f) }, + { "darkslategrey", IC_RGB(0x2f4f4f) }, + { "darkturquoise", IC_RGB(0x00ced1) }, + { "darkviolet", IC_RGB(0x9400d3) }, + { "deeppink", IC_RGB(0xff1493) }, + { "deepskyblue", IC_RGB(0x00bfff) }, + { "dimgray", IC_RGB(0x696969) }, + { "dimgrey", IC_RGB(0x696969) }, + { "dodgerblue", IC_RGB(0x1e90ff) }, + { "firebrick", IC_RGB(0xb22222) }, + { "floralwhite", IC_RGB(0xfffaf0) }, + { "forestgreen", IC_RGB(0x228b22) }, + { "fuchsia", IC_RGB(0xff00ff) }, + { "gainsboro", IC_RGB(0xdcdcdc) }, + { "ghostwhite", IC_RGB(0xf8f8ff) }, + { "gold", IC_RGB(0xffd700) }, + { "goldenrod", IC_RGB(0xdaa520) }, + { "gray", IC_RGB(0x808080) }, + { "green", IC_RGB(0x008000) }, + { "greenyellow", IC_RGB(0xadff2f) }, + { "grey", IC_RGB(0x808080) }, + { "honeydew", IC_RGB(0xf0fff0) }, + { "hotpink", IC_RGB(0xff69b4) }, + { "indianred", IC_RGB(0xcd5c5c) }, + { "indigo", IC_RGB(0x4b0082) }, + { "ivory", IC_RGB(0xfffff0) }, + { "khaki", IC_RGB(0xf0e68c) }, + { "lavender", IC_RGB(0xe6e6fa) }, + { "lavenderblush", IC_RGB(0xfff0f5) }, + { "lawngreen", IC_RGB(0x7cfc00) }, + { "lemonchiffon", IC_RGB(0xfffacd) }, + { "lightblue", IC_RGB(0xadd8e6) }, + { "lightcoral", IC_RGB(0xf08080) }, + { "lightcyan", IC_RGB(0xe0ffff) }, + { "lightgoldenrodyellow", IC_RGB(0xfafad2) }, + { "lightgray", IC_RGB(0xd3d3d3) }, + { "lightgreen", IC_RGB(0x90ee90) }, + { "lightgrey", IC_RGB(0xd3d3d3) }, + { "lightpink", IC_RGB(0xffb6c1) }, + { "lightsalmon", IC_RGB(0xffa07a) }, + { "lightseagreen", IC_RGB(0x20b2aa) }, + { "lightskyblue", IC_RGB(0x87cefa) }, + { "lightslategray", IC_RGB(0x778899) }, + { "lightslategrey", IC_RGB(0x778899) }, + { "lightsteelblue", IC_RGB(0xb0c4de) }, + { "lightyellow", IC_RGB(0xffffe0) }, + { "lime", IC_RGB(0x00ff00) }, + { "limegreen", IC_RGB(0x32cd32) }, + { "linen", IC_RGB(0xfaf0e6) }, + { "magenta", IC_RGB(0xff00ff) }, + { "maroon", IC_RGB(0x800000) }, + { "mediumaquamarine", IC_RGB(0x66cdaa) }, + { "mediumblue", IC_RGB(0x0000cd) }, + { "mediumorchid", IC_RGB(0xba55d3) }, + { "mediumpurple", IC_RGB(0x9370db) }, + { "mediumseagreen", IC_RGB(0x3cb371) }, + { "mediumslateblue", IC_RGB(0x7b68ee) }, + { "mediumspringgreen", IC_RGB(0x00fa9a) }, + { "mediumturquoise", IC_RGB(0x48d1cc) }, + { "mediumvioletred", IC_RGB(0xc71585) }, + { "midnightblue", IC_RGB(0x191970) }, + { "mintcream", IC_RGB(0xf5fffa) }, + { "mistyrose", IC_RGB(0xffe4e1) }, + { "moccasin", IC_RGB(0xffe4b5) }, + { "navajowhite", IC_RGB(0xffdead) }, + { "navy", IC_RGB(0x000080) }, + { "oldlace", IC_RGB(0xfdf5e6) }, + { "olive", IC_RGB(0x808000) }, + { "olivedrab", IC_RGB(0x6b8e23) }, + { "orange", IC_RGB(0xffa500) }, + { "orangered", IC_RGB(0xff4500) }, + { "orchid", IC_RGB(0xda70d6) }, + { "palegoldenrod", IC_RGB(0xeee8aa) }, + { "palegreen", IC_RGB(0x98fb98) }, + { "paleturquoise", IC_RGB(0xafeeee) }, + { "palevioletred", IC_RGB(0xdb7093) }, + { "papayawhip", IC_RGB(0xffefd5) }, + { "peachpuff", IC_RGB(0xffdab9) }, + { "peru", IC_RGB(0xcd853f) }, + { "pink", IC_RGB(0xffc0cb) }, + { "plum", IC_RGB(0xdda0dd) }, + { "powderblue", IC_RGB(0xb0e0e6) }, + { "purple", IC_RGB(0x800080) }, + { "rebeccapurple", IC_RGB(0x663399) }, + { "red", IC_RGB(0xff0000) }, + { "rosybrown", IC_RGB(0xbc8f8f) }, + { "royalblue", IC_RGB(0x4169e1) }, + { "saddlebrown", IC_RGB(0x8b4513) }, + { "salmon", IC_RGB(0xfa8072) }, + { "sandybrown", IC_RGB(0xf4a460) }, + { "seagreen", IC_RGB(0x2e8b57) }, + { "seashell", IC_RGB(0xfff5ee) }, + { "sienna", IC_RGB(0xa0522d) }, + { "silver", IC_RGB(0xc0c0c0) }, + { "skyblue", IC_RGB(0x87ceeb) }, + { "slateblue", IC_RGB(0x6a5acd) }, + { "slategray", IC_RGB(0x708090) }, + { "slategrey", IC_RGB(0x708090) }, + { "snow", IC_RGB(0xfffafa) }, + { "springgreen", IC_RGB(0x00ff7f) }, + { "steelblue", IC_RGB(0x4682b4) }, + { "tan", IC_RGB(0xd2b48c) }, + { "teal", IC_RGB(0x008080) }, + { "thistle", IC_RGB(0xd8bfd8) }, + { "tomato", IC_RGB(0xff6347) }, + { "turquoise", IC_RGB(0x40e0d0) }, + { "violet", IC_RGB(0xee82ee) }, + { "wheat", IC_RGB(0xf5deb3) }, + { "white", IC_RGB(0xffffff) }, + { "whitesmoke", IC_RGB(0xf5f5f5) }, + { "yellow", IC_RGB(0xffff00) }, + { "yellowgreen", IC_RGB(0x9acd32) }, + {NULL, 0} +}; diff --git a/deps/isocline/src/common.c b/deps/isocline/src/common.c new file mode 100644 index 0000000..1d9fb56 --- /dev/null +++ b/deps/isocline/src/common.c @@ -0,0 +1,347 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include "common.h" + + +//------------------------------------------------------------- +// String wrappers for ssize_t +//------------------------------------------------------------- + +ic_private ssize_t ic_strlen( const char* s ) { + if (s==NULL) return 0; + return to_ssize_t(strlen(s)); +} + +ic_private void ic_memmove( void* dest, const void* src, ssize_t n ) { + assert(dest!=NULL && src != NULL); + if (n <= 0) return; + memmove(dest,src,to_size_t(n)); +} + + +ic_private void ic_memcpy( void* dest, const void* src, ssize_t n ) { + assert(dest!=NULL && src != NULL); + if (dest == NULL || src == NULL || n <= 0) return; + memcpy(dest,src,to_size_t(n)); +} + +ic_private void ic_memset(void* dest, uint8_t value, ssize_t n) { + assert(dest!=NULL); + if (dest == NULL || n <= 0) return; + memset(dest,(int8_t)value,to_size_t(n)); +} + +ic_private bool ic_memnmove( void* dest, ssize_t dest_size, const void* src, ssize_t n ) { + assert(dest!=NULL && src != NULL); + if (n <= 0) return true; + if (dest_size < n) { assert(false); return false; } + memmove(dest,src,to_size_t(n)); + return true; +} + +ic_private bool ic_strcpy( char* dest, ssize_t dest_size /* including 0 */, const char* src) { + assert(dest!=NULL && src != NULL); + if (dest == NULL || dest_size <= 0) return false; + ssize_t slen = ic_strlen(src); + if (slen >= dest_size) return false; + strcpy(dest,src); + assert(dest[slen] == 0); + return true; +} + + +ic_private bool ic_strncpy( char* dest, ssize_t dest_size /* including 0 */, const char* src, ssize_t n) { + assert(dest!=NULL && n < dest_size); + if (dest == NULL || dest_size <= 0) return false; + if (n >= dest_size) return false; + if (src==NULL || n <= 0) { + dest[0] = 0; + } + else { + strncpy(dest,src,to_size_t(n)); + dest[n] = 0; + } + return true; +} + +//------------------------------------------------------------- +// String matching +//------------------------------------------------------------- + +ic_public bool ic_starts_with( const char* s, const char* prefix ) { + if (s==prefix) return true; + if (prefix==NULL) return true; + if (s==NULL) return false; + + ssize_t i; + for( i = 0; s[i] != 0 && prefix[i] != 0; i++) { + if (s[i] != prefix[i]) return false; + } + return (prefix[i] == 0); +} + +ic_private char ic_tolower( char c ) { + return (c >= 'A' && c <= 'Z' ? c - 'A' + 'a' : c); +} + +ic_private void ic_str_tolower(char* s) { + while(*s != 0) { + *s = ic_tolower(*s); + s++; + } +} + +ic_public bool ic_istarts_with( const char* s, const char* prefix ) { + if (s==prefix) return true; + if (prefix==NULL) return true; + if (s==NULL) return false; + + ssize_t i; + for( i = 0; s[i] != 0 && prefix[i] != 0; i++) { + if (ic_tolower(s[i]) != ic_tolower(prefix[i])) return false; + } + return (prefix[i] == 0); +} + + +ic_private int ic_strnicmp(const char* s1, const char* s2, ssize_t n) { + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL) return -1; + if (s2 == NULL) return 1; + ssize_t i; + for (i = 0; s1[i] != 0 && i < n; i++) { // note: if s2[i] == 0 the loop will stop as c1 != c2 + char c1 = ic_tolower(s1[i]); + char c2 = ic_tolower(s2[i]); + if (c1 < c2) return -1; + if (c1 > c2) return 1; + } + return ((i >= n || s2[i] == 0) ? 0 : -1); +} + +ic_private int ic_stricmp(const char* s1, const char* s2) { + ssize_t len1 = ic_strlen(s1); + ssize_t len2 = ic_strlen(s2); + if (len1 < len2) return -1; + if (len1 > len2) return 1; + return (ic_strnicmp(s1, s2, (len1 >= len2 ? len1 : len2))); +} + + +static const char* ic_stristr(const char* s, const char* pat) { + if (s==NULL) return NULL; + if (pat==NULL || pat[0] == 0) return s; + ssize_t patlen = ic_strlen(pat); + for (ssize_t i = 0; s[i] != 0; i++) { + if (ic_strnicmp(s + i, pat, patlen) == 0) return (s+i); + } + return NULL; +} + +ic_private bool ic_contains(const char* big, const char* s) { + if (big == NULL) return false; + if (s == NULL) return true; + return (strstr(big,s) != NULL); +} + +ic_private bool ic_icontains(const char* big, const char* s) { + if (big == NULL) return false; + if (s == NULL) return true; + return (ic_stristr(big,s) != NULL); +} + + +//------------------------------------------------------------- +// Unicode +// QUTF-8: See +// Raw bytes are code points 0xEE000 - 0xEE0FF +//------------------------------------------------------------- +#define IC_UNICODE_RAW ((unicode_t)(0xEE000U)) + +ic_private unicode_t unicode_from_raw(uint8_t c) { + return (IC_UNICODE_RAW + c); +} + +ic_private bool unicode_is_raw(unicode_t u, uint8_t* c) { + if (u >= IC_UNICODE_RAW && u <= IC_UNICODE_RAW + 0xFF) { + *c = (uint8_t)(u - IC_UNICODE_RAW); + return true; + } + else { + return false; + } +} + +ic_private void unicode_to_qutf8(unicode_t u, uint8_t buf[5]) { + memset(buf, 0, 5); + if (u <= 0x7F) { + buf[0] = (uint8_t)u; + } + else if (u <= 0x07FF) { + buf[0] = (0xC0 | ((uint8_t)(u >> 6))); + buf[1] = (0x80 | (((uint8_t)u) & 0x3F)); + } + else if (u <= 0xFFFF) { + buf[0] = (0xE0 | ((uint8_t)(u >> 12))); + buf[1] = (0x80 | (((uint8_t)(u >> 6)) & 0x3F)); + buf[2] = (0x80 | (((uint8_t)u) & 0x3F)); + } + else if (u <= 0x10FFFF) { + if (unicode_is_raw(u, &buf[0])) { + buf[1] = 0; + } + else { + buf[0] = (0xF0 | ((uint8_t)(u >> 18))); + buf[1] = (0x80 | (((uint8_t)(u >> 12)) & 0x3F)); + buf[2] = (0x80 | (((uint8_t)(u >> 6)) & 0x3F)); + buf[3] = (0x80 | (((uint8_t)u) & 0x3F)); + } + } +} + +// is this a utf8 continuation byte? +ic_private bool utf8_is_cont(uint8_t c) { + return ((c & 0xC0) == 0x80); +} + +ic_private unicode_t unicode_from_qutf8(const uint8_t* s, ssize_t len, ssize_t* count) { + unicode_t c0 = 0; + if (len <= 0 || s == NULL) { + goto fail; + } + // 1 byte + c0 = s[0]; + if (c0 <= 0x7F && len >= 1) { + if (count != NULL) *count = 1; + return c0; + } + else if (c0 <= 0xC1) { // invalid continuation byte or invalid 0xC0, 0xC1 + goto fail; + } + // 2 bytes + else if (c0 <= 0xDF && len >= 2 && utf8_is_cont(s[1])) { + if (count != NULL) *count = 2; + return (((c0 & 0x1F) << 6) | (s[1] & 0x3F)); + } + // 3 bytes: reject overlong and surrogate halves + else if (len >= 3 && + ((c0 == 0xE0 && s[1] >= 0xA0 && s[1] <= 0xBF && utf8_is_cont(s[2])) || + (c0 >= 0xE1 && c0 <= 0xEC && utf8_is_cont(s[1]) && utf8_is_cont(s[2])) + )) + { + if (count != NULL) *count = 3; + return (((c0 & 0x0F) << 12) | ((unicode_t)(s[1] & 0x3F) << 6) | (s[2] & 0x3F)); + } + // 4 bytes: reject overlong + else if (len >= 4 && + (((c0 == 0xF0 && s[1] >= 0x90 && s[1] <= 0xBF && utf8_is_cont(s[2]) && utf8_is_cont(s[3])) || + (c0 >= 0xF1 && c0 <= 0xF3 && utf8_is_cont(s[1]) && utf8_is_cont(s[2]) && utf8_is_cont(s[3])) || + (c0 == 0xF4 && s[1] >= 0x80 && s[1] <= 0x8F && utf8_is_cont(s[2]) && utf8_is_cont(s[3]))) + )) + { + if (count != NULL) *count = 4; + return (((c0 & 0x07) << 18) | ((unicode_t)(s[1] & 0x3F) << 12) | ((unicode_t)(s[2] & 0x3F) << 6) | (s[3] & 0x3F)); + } +fail: + if (count != NULL) *count = 1; + return unicode_from_raw(s[0]); +} + + +//------------------------------------------------------------- +// Debug +//------------------------------------------------------------- + +#if defined(IC_NO_DEBUG_MSG) +// nothing +#elif !defined(IC_DEBUG_TO_FILE) +ic_private void debug_msg(const char* fmt, ...) { + if (getenv("ISOCLINE_DEBUG")) { + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + } +} +#else +ic_private void debug_msg(const char* fmt, ...) { + static int debug_init; + static const char* debug_fname = "isocline.debug.txt"; + // initialize? + if (debug_init==0) { + debug_init = -1; + const char* rdebug = getenv("ISOCLINE_DEBUG"); + if (rdebug!=NULL && strcmp(rdebug,"1") == 0) { + FILE* fdbg = fopen(debug_fname, "w"); + if (fdbg!=NULL) { + debug_init = 1; + fclose(fdbg); + } + } + } + if (debug_init <= 0) return; + + // write debug messages + FILE* fdbg = fopen(debug_fname, "a"); + if (fdbg==NULL) return; + va_list args; + va_start(args, fmt); + vfprintf(fdbg, fmt, args); + fclose(fdbg); + va_end(args); +} +#endif + + +//------------------------------------------------------------- +// Allocation +//------------------------------------------------------------- + +ic_private void* mem_malloc(alloc_t* mem, ssize_t sz) { + return mem->malloc(to_size_t(sz)); +} + +ic_private void* mem_zalloc(alloc_t* mem, ssize_t sz) { + void* p = mem_malloc(mem, sz); + if (p != NULL) memset(p, 0, to_size_t(sz)); + return p; +} + +ic_private void* mem_realloc(alloc_t* mem, void* p, ssize_t newsz) { + return mem->realloc(p, to_size_t(newsz)); +} + +ic_private void mem_free(alloc_t* mem, const void* p) { + mem->free((void*)p); +} + +ic_private char* mem_strdup(alloc_t* mem, const char* s) { + if (s==NULL) return NULL; + ssize_t n = ic_strlen(s); + char* p = mem_malloc_tp_n(mem, char, n+1); + if (p == NULL) return NULL; + ic_memcpy(p, s, n+1); + return p; +} + +ic_private char* mem_strndup(alloc_t* mem, const char* s, ssize_t n) { + if (s==NULL || n < 0) return NULL; + char* p = mem_malloc_tp_n(mem, char, n+1); + if (p == NULL) return NULL; + ssize_t i; + for (i = 0; i < n && s[i] != 0; i++) { + p[i] = s[i]; + } + assert(i <= n); + p[i] = 0; + return p; +} + diff --git a/deps/isocline/src/common.h b/deps/isocline/src/common.h new file mode 100644 index 0000000..dd5b256 --- /dev/null +++ b/deps/isocline/src/common.h @@ -0,0 +1,187 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#pragma once +#ifndef IC_COMMON_H +#define IC_COMMON_H + +//------------------------------------------------------------- +// Headers and defines +//------------------------------------------------------------- + +#include // ssize_t +#include +#include +#include +#include +#include +#include "../include/isocline.h" // ic_malloc_fun_t, ic_color_t etc. + +# ifdef __cplusplus +# define ic_extern_c extern "C" +# else +# define ic_extern_c +# endif + +#if defined(IC_SEPARATE_OBJS) +# define ic_public ic_extern_c +# if defined(__GNUC__) // includes clang and icc +# define ic_private __attribute__((visibility("hidden"))) +# else +# define ic_private +# endif +#else +# define ic_private static +# define ic_public ic_extern_c +#endif + +#define ic_unused(x) (void)(x) + + +//------------------------------------------------------------- +// ssize_t +//------------------------------------------------------------- + +#if defined(_MSC_VER) +typedef intptr_t ssize_t; +#endif + +#define ssizeof(tp) (ssize_t)(sizeof(tp)) +static inline size_t to_size_t(ssize_t sz) { return (sz >= 0 ? (size_t)sz : 0); } +static inline ssize_t to_ssize_t(size_t sz) { return (sz <= SIZE_MAX/2 ? (ssize_t)sz : 0); } + +ic_private void ic_memmove(void* dest, const void* src, ssize_t n); +ic_private void ic_memcpy(void* dest, const void* src, ssize_t n); +ic_private void ic_memset(void* dest, uint8_t value, ssize_t n); +ic_private bool ic_memnmove(void* dest, ssize_t dest_size, const void* src, ssize_t n); + +ic_private ssize_t ic_strlen(const char* s); +ic_private bool ic_strcpy(char* dest, ssize_t dest_size /* including 0 */, const char* src); +ic_private bool ic_strncpy(char* dest, ssize_t dest_size /* including 0 */, const char* src, ssize_t n); + +ic_private bool ic_contains(const char* big, const char* s); +ic_private bool ic_icontains(const char* big, const char* s); +ic_private char ic_tolower(char c); +ic_private void ic_str_tolower(char* s); +ic_private int ic_stricmp(const char* s1, const char* s2); +ic_private int ic_strnicmp(const char* s1, const char* s2, ssize_t n); + + + +//--------------------------------------------------------------------- +// Unicode +// +// We use "qutf-8" (quite like utf-8) encoding and decoding. +// Internally we always use valid utf-8. If we encounter invalid +// utf-8 bytes (or bytes >= 0x80 from any other encoding) we encode +// these as special code points in the "raw plane" (0xEE000 - 0xEE0FF). +// When decoding we are then able to restore such raw bytes as-is. +// See +//--------------------------------------------------------------------- + +typedef uint32_t unicode_t; + +ic_private void unicode_to_qutf8(unicode_t u, uint8_t buf[5]); +ic_private unicode_t unicode_from_qutf8(const uint8_t* s, ssize_t len, ssize_t* nread); // validating + +ic_private unicode_t unicode_from_raw(uint8_t c); +ic_private bool unicode_is_raw(unicode_t u, uint8_t* c); + +ic_private bool utf8_is_cont(uint8_t c); + + +//------------------------------------------------------------- +// Colors +//------------------------------------------------------------- + +// A color is either RGB or an ANSI code. +// (RGB colors have bit 24 set to distinguish them from the ANSI color palette colors.) +// (Isocline will automatically convert from RGB on terminals that do not support full colors) +typedef uint32_t ic_color_t; + +// Create a color from a 24-bit color value. +ic_private ic_color_t ic_rgb(uint32_t hex); + +// Create a color from a 8-bit red/green/blue components. +// The value of each component is capped between 0 and 255. +ic_private ic_color_t ic_rgbx(ssize_t r, ssize_t g, ssize_t b); + +#define IC_COLOR_NONE (0) +#define IC_RGB(rgb) (0x1000000 | (uint32_t)(rgb)) // ic_rgb(rgb) // define to it can be used as a constant + +// ANSI colors. +// The actual colors used is usually determined by the terminal theme +// See +#define IC_ANSI_BLACK (30) +#define IC_ANSI_MAROON (31) +#define IC_ANSI_GREEN (32) +#define IC_ANSI_OLIVE (33) +#define IC_ANSI_NAVY (34) +#define IC_ANSI_PURPLE (35) +#define IC_ANSI_TEAL (36) +#define IC_ANSI_SILVER (37) +#define IC_ANSI_DEFAULT (39) + +#define IC_ANSI_GRAY (90) +#define IC_ANSI_RED (91) +#define IC_ANSI_LIME (92) +#define IC_ANSI_YELLOW (93) +#define IC_ANSI_BLUE (94) +#define IC_ANSI_FUCHSIA (95) +#define IC_ANSI_AQUA (96) +#define IC_ANSI_WHITE (97) + +#define IC_ANSI_DARKGRAY IC_ANSI_GRAY +#define IC_ANSI_LIGHTGRAY IC_ANSI_SILVER +#define IC_ANSI_MAGENTA IC_ANSI_FUCHSIA +#define IC_ANSI_CYAN IC_ANSI_AQUA + + + +//------------------------------------------------------------- +// Debug +//------------------------------------------------------------- + +#if defined(IC_NO_DEBUG_MSG) +#define debug_msg(fmt,...) (void)(0) +#else +ic_private void debug_msg( const char* fmt, ... ); +#endif + + +//------------------------------------------------------------- +// Abstract environment +//------------------------------------------------------------- +struct ic_env_s; +typedef struct ic_env_s ic_env_t; + + +//------------------------------------------------------------- +// Allocation +//------------------------------------------------------------- + +typedef struct alloc_s { + ic_malloc_fun_t* malloc; + ic_realloc_fun_t* realloc; + ic_free_fun_t* free; +} alloc_t; + + +ic_private void* mem_malloc( alloc_t* mem, ssize_t sz ); +ic_private void* mem_zalloc( alloc_t* mem, ssize_t sz ); +ic_private void* mem_realloc( alloc_t* mem, void* p, ssize_t newsz ); +ic_private void mem_free( alloc_t* mem, const void* p ); +ic_private char* mem_strdup( alloc_t* mem, const char* s); +ic_private char* mem_strndup( alloc_t* mem, const char* s, ssize_t n); + +#define mem_zalloc_tp(mem,tp) (tp*)mem_zalloc(mem,ssizeof(tp)) +#define mem_malloc_tp_n(mem,tp,n) (tp*)mem_malloc(mem,(n)*ssizeof(tp)) +#define mem_zalloc_tp_n(mem,tp,n) (tp*)mem_zalloc(mem,(n)*ssizeof(tp)) +#define mem_realloc_tp(mem,tp,p,n) (tp*)mem_realloc(mem,p,(n)*ssizeof(tp)) + + +#endif // IC_COMMON_H diff --git a/deps/isocline/src/completers.c b/deps/isocline/src/completers.c new file mode 100644 index 0000000..e9701c1 --- /dev/null +++ b/deps/isocline/src/completers.c @@ -0,0 +1,675 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include + +#include "../include/isocline.h" +#include "common.h" +#include "env.h" +#include "stringbuf.h" +#include "completions.h" + + + +//------------------------------------------------------------- +// Word completion +//------------------------------------------------------------- + +// free variables for word completion +typedef struct word_closure_s { + long delete_before_adjust; + void* prev_env; + ic_completion_fun_t* prev_complete; +} word_closure_t; + + +// word completion callback +static bool token_add_completion_ex(ic_env_t* env, void* closure, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) { + word_closure_t* wenv = (word_closure_t*)(closure); + // call the previous completer with an adjusted delete-before + return (*wenv->prev_complete)(env, wenv->prev_env, replacement, display, help, wenv->delete_before_adjust + delete_before, delete_after); +} + + +ic_public void ic_complete_word(ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, + ic_is_char_class_fun_t* is_word_char) +{ + if (is_word_char == NULL) is_word_char = &ic_char_is_nonseparator; + + ssize_t len = ic_strlen(prefix); + ssize_t pos = len; // will be start of the 'word' (excluding a potential start quote) + while (pos > 0) { + // go back one code point + ssize_t ofs = str_prev_ofs(prefix, pos, NULL); + if (ofs <= 0) break; + if (!(*is_word_char)(prefix + (pos - ofs), (long)ofs)) { + break; + } + pos -= ofs; + } + if (pos < 0) { pos = 0; } + + // stop if empty word + // if (len == pos) return; + + // set up the closure + word_closure_t wenv; + wenv.delete_before_adjust = (long)(len - pos); + wenv.prev_complete = cenv->complete; + wenv.prev_env = cenv->env; + cenv->complete = &token_add_completion_ex; + cenv->closure = &wenv; + + // and call the user completion routine + (*fun)(cenv, prefix + pos); + + // restore the original environment + cenv->complete = wenv.prev_complete; + cenv->closure = wenv.prev_env; +} + + +//------------------------------------------------------------- +// Quoted word completion (with escape characters) +//------------------------------------------------------------- + +// free variables for word completion +typedef struct qword_closure_s { + char escape_char; + char quote; + long delete_before_adjust; + stringbuf_t* sbuf; + void* prev_env; + ic_is_char_class_fun_t* is_word_char; + ic_completion_fun_t* prev_complete; +} qword_closure_t; + + +// word completion callback +static bool qword_add_completion_ex(ic_env_t* env, void* closure, const char* replacement, const char* display, const char* help, + long delete_before, long delete_after) { + qword_closure_t* wenv = (qword_closure_t*)(closure); + sbuf_replace( wenv->sbuf, replacement ); + if (wenv->quote != 0) { + // add end quote + sbuf_append_char( wenv->sbuf, wenv->quote); + } + else { + // escape non-word characters if it was not quoted + ssize_t pos = 0; + ssize_t next; + while ( (next = sbuf_next_ofs(wenv->sbuf, pos, NULL)) > 0 ) + { + if (!(*wenv->is_word_char)(sbuf_string(wenv->sbuf) + pos, (long)next)) { // strchr(wenv->non_word_char, sbuf_char_at( wenv->sbuf, pos )) != NULL) { + sbuf_insert_char_at( wenv->sbuf, wenv->escape_char, pos); + pos++; + } + pos += next; + } + } + // and call the previous completion function + return (*wenv->prev_complete)( env, wenv->prev_env, sbuf_string(wenv->sbuf), display, help, wenv->delete_before_adjust + delete_before, delete_after ); +} + + +ic_public void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char ) { + ic_complete_qword_ex( cenv, prefix, fun, is_word_char, '\\', NULL); +} + + +ic_public void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, + ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars ) { + if (is_word_char == NULL) is_word_char = &ic_char_is_nonseparator ; + if (quote_chars == NULL) quote_chars = "'\""; + + ssize_t len = ic_strlen(prefix); + ssize_t pos; // will be start of the 'word' (excluding a potential start quote) + char quote = 0; + ssize_t quote_len = 0; + + // 1. look for a starting quote + if (quote_chars[0] != 0) { + // we go forward and count all quotes; if it is uneven, we need to complete quoted. + ssize_t qpos_open = -1; + ssize_t qpos_close = -1; + ssize_t qcount = 0; + pos = 0; + while(pos < len) { + if (prefix[pos] == escape_char && prefix[pos+1] != 0 && + !(*is_word_char)(prefix + pos + 1, 1)) // strchr(non_word_char, prefix[pos+1]) != NULL + { + pos++; // skip escape and next char + } + else if (qcount % 2 == 0 && strchr(quote_chars, prefix[pos]) != NULL) { + // open quote + qpos_open = pos; + quote = prefix[pos]; + qcount++; + } + else if (qcount % 2 == 1 && prefix[pos] == quote) { + // close quote + qpos_close = pos; + qcount++; + } + else if (!(*is_word_char)(prefix + pos, 1)) { // strchr(non_word_char, prefix[pos]) != NULL) { + qpos_close = -1; + } + ssize_t ofs = str_next_ofs( prefix, len, pos, NULL ); + if (ofs <= 0) break; + pos += ofs; + } + if ((qcount % 2 == 0 && qpos_close >= 0) || // if the last quote is only followed by word chars, we still complete it + (qcount % 2 == 1)) // opening quote found + { + quote_len = (len - qpos_open - 1); + pos = qpos_open + 1; // pos points to the word start just after the quote. + } + else { + quote = 0; + } + } + + // 2. if we did not find a quoted word, look for non-word-chars + if (quote == 0) { + pos = len; + while(pos > 0) { + // go back one code point + ssize_t ofs = str_prev_ofs(prefix, pos, NULL ); + if (ofs <= 0) break; + if (!(*is_word_char)(prefix + (pos - ofs), (long)ofs)) { // strchr(non_word_char, prefix[pos - ofs]) != NULL) { + // non word char, break if it is not escaped + if (pos <= ofs || prefix[pos - ofs - 1] != escape_char) break; + // otherwise go on + pos--; // skip escaped char + } + pos -= ofs; + } + } + + // stop if empty word + // if (len == pos) return; + + // allocate new unescaped word prefix + char* word = mem_strndup( cenv->env->mem, prefix + pos, (quote==0 ? len - pos : quote_len)); + if (word == NULL) return; + + if (quote == 0) { + // unescape prefix + ssize_t wlen = len - pos; + ssize_t wpos = 0; + while (wpos < wlen) { + ssize_t ofs = str_next_ofs(word, wlen, wpos, NULL); + if (ofs <= 0) break; + if (word[wpos] == escape_char && word[wpos+1] != 0 && + !(*is_word_char)(word + wpos + 1, (long)ofs)) // strchr(non_word_char, word[wpos+1]) != NULL) { + { + ic_memmove(word + wpos, word + wpos + 1, wlen - wpos /* including 0 */); + } + wpos += ofs; + } + } + #ifdef _WIN32 + else { + // remove inner quote: "c:\Program Files\"Win + ssize_t wlen = len - pos; + ssize_t wpos = 0; + while (wpos < wlen) { + ssize_t ofs = str_next_ofs(word, wlen, wpos, NULL); + if (ofs <= 0) break; + if (word[wpos] == escape_char && word[wpos+1] == quote) { + word[wpos+1] = escape_char; + ic_memmove(word + wpos, word + wpos + 1, wlen - wpos /* including 0 */); + } + wpos += ofs; + } + } + #endif + + // set up the closure + qword_closure_t wenv; + wenv.quote = quote; + wenv.is_word_char = is_word_char; + wenv.escape_char = escape_char; + wenv.delete_before_adjust = (long)(len - pos); + wenv.prev_complete = cenv->complete; + wenv.prev_env = cenv->env; + wenv.sbuf = sbuf_new(cenv->env->mem); + if (wenv.sbuf == NULL) { mem_free(cenv->env->mem, word); return; } + cenv->complete = &qword_add_completion_ex; + cenv->closure = &wenv; + + // and call the user completion routine + (*fun)( cenv, word ); + + // restore the original environment + cenv->complete = wenv.prev_complete; + cenv->closure = wenv.prev_env; + + sbuf_free(wenv.sbuf); + mem_free(cenv->env->mem, word); +} + + + + +//------------------------------------------------------------- +// Complete file names +// Listing files +//------------------------------------------------------------- +#include + +typedef enum file_type_e { + // must follow BSD style LSCOLORS order + FT_DEFAULT = 0, + FT_DIR, + FT_SYM, + FT_SOCK, + FT_PIPE, + FT_BLOCK, + FT_CHAR, + FT_SETUID, + FT_SETGID, + FT_DIR_OW_STICKY, + FT_DIR_OW, + FT_DIR_STICKY, + FT_EXE, + FT_LAST +} file_type_t; + +static int cli_color; // 1 enabled, 0 not initialized, -1 disabled +static const char* lscolors = "exfxcxdxbxegedabagacad"; // default BSD setting +static const char* ls_colors; +static const char* ls_colors_names[] = { "no=","di=","ln=","so=","pi=","bd=","cd=","su=","sg=","tw=","ow=","st=","ex=", NULL }; + +static bool ls_colors_init(void) { + if (cli_color != 0) return (cli_color >= 1); + // colors enabled? + const char* s = getenv("CLICOLOR"); + if (s==NULL || (strcmp(s, "1")!=0 && strcmp(s, "") != 0)) { + cli_color = -1; + return false; + } + cli_color = 1; + s = getenv("LS_COLORS"); + if (s != NULL) { ls_colors = s; } + s = getenv("LSCOLORS"); + if (s != NULL) { lscolors = s; } + return true; +} + +static bool ls_valid_esc(ssize_t c) { + return ((c==0 || c==1 || c==4 || c==7 || c==22 || c==24 || c==27) || + (c >= 30 && c <= 37) || (c >= 40 && c <= 47) || + (c >= 90 && c <= 97) || (c >= 100 && c <= 107)); +} + +static bool ls_colors_from_key(stringbuf_t* sb, const char* key) { + // find key + ssize_t keylen = ic_strlen(key); + if (keylen <= 0) return false; + const char* p = strstr(ls_colors, key); + if (p == NULL) return false; + p += keylen; + if (key[keylen-1] != '=') { + if (*p != '=') return false; + p++; + } + ssize_t len = 0; + while (p[len] != 0 && p[len] != ':') { + len++; + } + if (len <= 0) return false; + sbuf_append(sb, "[ansi-sgr=\"" ); + sbuf_append_n(sb, p, len ); + sbuf_append(sb, "\"]"); + return true; +} + +static int ls_colors_from_char(char c) { + if (c >= 'a' && c <= 'h') { return (c - 'a'); } + else if (c >= 'A' && c <= 'H') { return (c - 'A') + 8; } + else if (c == 'x') { return 256; } + else return 256; // default +} + +static bool ls_colors_append(stringbuf_t* sb, file_type_t ft, const char* ext) { + if (!ls_colors_init()) return false; + if (ls_colors != NULL) { + // GNU style + if (ft == FT_DEFAULT && ext != NULL) { + // first try extension match + if (ls_colors_from_key(sb, ext)) return true; + } + if (ft >= FT_DEFAULT && ft < FT_LAST) { + // then a filetype match + const char* key = ls_colors_names[ft]; + if (ls_colors_from_key(sb, key)) return true; + } + } + else if (lscolors != NULL) { + // BSD style + char fg = 'x'; + char bg = 'x'; + if (ic_strlen(lscolors) > (2*(ssize_t)ft)+1) { + fg = lscolors[2*ft]; + bg = lscolors[2*ft + 1]; + } + sbuf_appendf(sb, "[ansi-color=%d ansi-bgcolor=%d]", ls_colors_from_char(fg), ls_colors_from_char(bg) ); + return true; + } + return false; +} + +static void ls_colorize(bool no_lscolor, stringbuf_t* sb, file_type_t ft, const char* name, const char* ext, char dirsep) { + bool close = (no_lscolor ? false : ls_colors_append( sb, ft, ext)); + sbuf_append(sb, "[!pre]" ); + sbuf_append(sb, name); + if (dirsep != 0) sbuf_append_char(sb, dirsep); + sbuf_append(sb,"[/pre]" ); + if (close) { sbuf_append(sb, "[/]"); } +} + +#if defined(_WIN32) +#include +#include + +static bool os_is_dir(const char* cpath) { + struct _stat64 st = { 0 }; + _stat64(cpath, &st); + return ((st.st_mode & _S_IFDIR) != 0); +} + +static file_type_t os_get_filetype(const char* cpath) { + struct _stat64 st = { 0 }; + _stat64(cpath, &st); + if (((st.st_mode) & _S_IFDIR) != 0) return FT_DIR; + if (((st.st_mode) & _S_IFCHR) != 0) return FT_CHAR; + if (((st.st_mode) & _S_IFIFO) != 0) return FT_PIPE; + if (((st.st_mode) & _S_IEXEC) != 0) return FT_EXE; + return FT_DEFAULT; +} + + +#define dir_cursor intptr_t +#define dir_entry struct __finddata64_t + +static bool os_findfirst(alloc_t* mem, const char* path, dir_cursor* d, dir_entry* entry) { + stringbuf_t* spath = sbuf_new(mem); + if (spath == NULL) return false; + sbuf_append(spath, path); + sbuf_append(spath, "\\*"); + *d = _findfirsti64(sbuf_string(spath), entry); + mem_free(mem,spath); + return (*d != -1); +} + +static bool os_findnext(dir_cursor d, dir_entry* entry) { + return (_findnexti64(d, entry) == 0); +} + +static void os_findclose(dir_cursor d) { + _findclose(d); +} + +static const char* os_direntry_name(dir_entry* entry) { + return entry->name; +} + +static bool os_path_is_absolute( const char* path ) { + if (path != NULL && path[0] != 0 && path[1] == ':' && (path[2] == '\\' || path[2] == '/' || path[2] == 0)) { + char drive = path[0]; + return ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z')); + } + else return false; +} + +ic_private char ic_dirsep(void) { + return '\\'; +} +#else + +#include +#include +#include +#include + +static bool os_is_dir(const char* cpath) { + struct stat st; + memset(&st, 0, sizeof(st)); + stat(cpath, &st); + return (S_ISDIR(st.st_mode)); +} + +static file_type_t os_get_filetype(const char* cpath) { + struct stat st; + memset(&st, 0, sizeof(st)); + lstat(cpath, &st); + switch ((st.st_mode)&S_IFMT) { + case S_IFSOCK: return FT_SOCK; + case S_IFLNK: { + return FT_SYM; + } + case S_IFIFO: return FT_PIPE; + case S_IFCHR: return FT_CHAR; + case S_IFBLK: return FT_BLOCK; + case S_IFDIR: { + if ((st.st_mode & S_ISUID) != 0) return FT_SETUID; + if ((st.st_mode & S_ISGID) != 0) return FT_SETGID; + if ((st.st_mode & S_IWGRP) != 0 && (st.st_mode & S_ISVTX) != 0) return FT_DIR_OW_STICKY; + if ((st.st_mode & S_IWGRP)) return FT_DIR_OW; + if ((st.st_mode & S_ISVTX)) return FT_DIR_STICKY; + return FT_DIR; + } + case S_IFREG: + default: { + if ((st.st_mode & S_IXUSR) != 0) return FT_EXE; + return FT_DEFAULT; + } + } +} + + +#define dir_cursor DIR* +#define dir_entry struct dirent* + +static bool os_findnext(dir_cursor d, dir_entry* entry) { + *entry = readdir(d); + return (*entry != NULL); +} + +static bool os_findfirst(alloc_t* mem, const char* cpath, dir_cursor* d, dir_entry* entry) { + ic_unused(mem); + *d = opendir(cpath); + if (*d == NULL) { + return false; + } + else { + return os_findnext(*d, entry); + } +} + +static void os_findclose(dir_cursor d) { + closedir(d); +} + +static const char* os_direntry_name(dir_entry* entry) { + return (*entry)->d_name; +} + +static bool os_path_is_absolute( const char* path ) { + return (path != NULL && path[0] == '/'); +} + +ic_private char ic_dirsep(void) { + return '/'; +} +#endif + + + +//------------------------------------------------------------- +// File completion +//------------------------------------------------------------- + +static bool ends_with_n(const char* name, ssize_t name_len, const char* ending, ssize_t len) { + if (name_len < len) return false; + if (ending == NULL || len <= 0) return true; + for (ssize_t i = 1; i <= len; i++) { + char c1 = name[name_len - i]; + char c2 = ending[len - i]; + #ifdef _WIN32 + if (ic_tolower(c1) != ic_tolower(c2)) return false; + #else + if (c1 != c2) return false; + #endif + } + return true; +} + +static bool match_extension(const char* name, const char* extensions) { + if (extensions == NULL || extensions[0] == 0) return true; + if (name == NULL) return false; + ssize_t name_len = ic_strlen(name); + ssize_t len = ic_strlen(extensions); + ssize_t cur = 0; + //debug_msg("match extensions: %s ~ %s", name, extensions); + for (ssize_t end = 0; end <= len; end++) { + if (extensions[end] == ';' || extensions[end] == 0) { + if (ends_with_n(name, name_len, extensions+cur, (end - cur))) { + return true; + } + cur = end+1; + } + } + return false; +} + +static bool filename_complete_indir( ic_completion_env_t* cenv, stringbuf_t* dir, + stringbuf_t* dir_prefix, stringbuf_t* display, + const char* base_prefix, + char dir_sep, const char* extensions ) +{ + dir_cursor d = 0; + dir_entry entry; + bool cont = true; + if (os_findfirst(cenv->env->mem, sbuf_string(dir), &d, &entry)) { + do { + const char* name = os_direntry_name(&entry); + if (name != NULL && strcmp(name, ".") != 0 && strcmp(name, "..") != 0 && + ic_istarts_with(name, base_prefix)) + { + // possible match, first check if it is a directory + file_type_t ft; + bool isdir; + const ssize_t plen = sbuf_len(dir_prefix); + sbuf_append(dir_prefix, name); + { // check directory and potentially add a dirsep to the dir_prefix + const ssize_t dlen = sbuf_len(dir); + sbuf_append_char(dir,ic_dirsep()); + sbuf_append(dir,name); + ft = os_get_filetype(sbuf_string(dir)); + isdir = os_is_dir(sbuf_string(dir)); + if (isdir && dir_sep != 0) { + sbuf_append_char(dir_prefix,dir_sep); + } + sbuf_delete_from(dir,dlen); // restore dir + } + if (isdir || match_extension(name, extensions)) { + // add completion + sbuf_clear(display); + ls_colorize(cenv->env->no_lscolors, display, ft, name, NULL, (isdir ? dir_sep : 0)); + cont = ic_add_completion_ex(cenv, sbuf_string(dir_prefix), sbuf_string(display), NULL); + } + sbuf_delete_from( dir_prefix, plen ); // restore dir_prefix + } + } while (cont && os_findnext(d, &entry)); + os_findclose(d); + } + return cont; +} + +typedef struct filename_closure_s { + const char* roots; + const char* extensions; + char dir_sep; +} filename_closure_t; + +static void filename_completer( ic_completion_env_t* cenv, const char* prefix ) { + if (prefix == NULL) return; + filename_closure_t* fclosure = (filename_closure_t*)cenv->arg; + stringbuf_t* root_dir = sbuf_new(cenv->env->mem); + stringbuf_t* dir_prefix = sbuf_new(cenv->env->mem); + stringbuf_t* display = sbuf_new(cenv->env->mem); + if (root_dir!=NULL && dir_prefix != NULL && display != NULL) + { + // split prefix in dir_prefix / base. + const char* base = strrchr(prefix,'/'); + #ifdef _WIN32 + const char* base2 = strrchr(prefix,'\\'); + if (base == NULL || base2 > base) base = base2; + #endif + if (base != NULL) { + base++; + sbuf_append_n(dir_prefix, prefix, base - prefix ); // includes dir separator + } + + // absolute path + if (os_path_is_absolute(prefix)) { + // do not use roots but try to complete directly + if (base != NULL) { + sbuf_append_n( root_dir, prefix, (base - prefix)); // include dir separator + } + filename_complete_indir( cenv, root_dir, dir_prefix, display, + (base != NULL ? base : prefix), + fclosure->dir_sep, fclosure->extensions ); + } + else { + // relative path, complete with respect to every root. + const char* next; + const char* root = fclosure->roots; + while ( root != NULL ) { + // create full root in `root_dir` + sbuf_clear(root_dir); + next = strchr(root,';'); + if (next == NULL) { + sbuf_append( root_dir, root ); + root = NULL; + } + else { + sbuf_append_n( root_dir, root, next - root ); + root = next + 1; + } + sbuf_append_char( root_dir, ic_dirsep()); + + // add the dir_prefix to the root + if (base != NULL) { + sbuf_append_n( root_dir, prefix, (base - prefix) - 1); + } + + // and complete in this directory + filename_complete_indir( cenv, root_dir, dir_prefix, display, + (base != NULL ? base : prefix), + fclosure->dir_sep, fclosure->extensions); + } + } + } + sbuf_free(display); + sbuf_free(root_dir); + sbuf_free(dir_prefix); +} + +ic_public void ic_complete_filename( ic_completion_env_t* cenv, const char* prefix, char dir_sep, const char* roots, const char* extensions ) { + if (roots == NULL) roots = "."; + if (extensions == NULL) extensions = ""; + if (dir_sep == 0) dir_sep = ic_dirsep(); + filename_closure_t fclosure; + fclosure.dir_sep = dir_sep; + fclosure.roots = roots; + fclosure.extensions = extensions; + cenv->arg = &fclosure; + ic_complete_qword_ex( cenv, prefix, &filename_completer, &ic_char_is_filename_letter, '\\', "'\""); +} diff --git a/deps/isocline/src/completions.c b/deps/isocline/src/completions.c new file mode 100644 index 0000000..01453ef --- /dev/null +++ b/deps/isocline/src/completions.c @@ -0,0 +1,326 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include +#include + +#include "../include/isocline.h" +#include "common.h" +#include "env.h" +#include "stringbuf.h" +#include "completions.h" + + +//------------------------------------------------------------- +// Completions +//------------------------------------------------------------- + +typedef struct completion_s { + const char* replacement; + const char* display; + const char* help; + ssize_t delete_before; + ssize_t delete_after; +} completion_t; + +struct completions_s { + ic_completer_fun_t* completer; + void* completer_arg; + ssize_t completer_max; + ssize_t count; + ssize_t len; + completion_t* elems; + alloc_t* mem; +}; + +static void default_filename_completer( ic_completion_env_t* cenv, const char* prefix ); + +ic_private completions_t* completions_new(alloc_t* mem) { + completions_t* cms = mem_zalloc_tp(mem, completions_t); + if (cms == NULL) return NULL; + cms->mem = mem; + cms->completer = &default_filename_completer; + return cms; +} + +ic_private void completions_free(completions_t* cms) { + if (cms == NULL) return; + completions_clear(cms); + if (cms->elems != NULL) { + mem_free(cms->mem, cms->elems); + cms->elems = NULL; + cms->count = 0; + cms->len = 0; + } + mem_free(cms->mem, cms); // free ourselves +} + + +ic_private void completions_clear(completions_t* cms) { + while (cms->count > 0) { + completion_t* cm = cms->elems + cms->count - 1; + mem_free( cms->mem, cm->display); + mem_free( cms->mem, cm->replacement); + mem_free( cms->mem, cm->help); + memset(cm,0,sizeof(*cm)); + cms->count--; + } +} + +static void completions_push(completions_t* cms, const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after) +{ + if (cms->count >= cms->len) { + ssize_t newlen = (cms->len <= 0 ? 32 : cms->len*2); + completion_t* newelems = mem_realloc_tp(cms->mem, completion_t, cms->elems, newlen ); + if (newelems == NULL) return; + cms->elems = newelems; + cms->len = newlen; + } + assert(cms->count < cms->len); + completion_t* cm = cms->elems + cms->count; + cm->replacement = mem_strdup(cms->mem,replacement); + cm->display = mem_strdup(cms->mem,display); + cm->help = mem_strdup(cms->mem,help); + cm->delete_before = delete_before; + cm->delete_after = delete_after; + cms->count++; +} + +ic_private ssize_t completions_count(completions_t* cms) { + return cms->count; +} + +static bool completions_contains(completions_t* cms, const char* replacement) { + for( ssize_t i = 0; i < cms->count; i++ ) { + const completion_t* c = cms->elems + i; + if (strcmp(replacement,c->replacement) == 0) { return true; } + } + return false; +} + +ic_private bool completions_add(completions_t* cms, const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after) { + if (cms->completer_max <= 0) return false; + cms->completer_max--; + //debug_msg("completion: add: %d,%d, %s\n", delete_before, delete_after, replacement); + if (!completions_contains(cms,replacement)) { + completions_push(cms, replacement, display, help, delete_before, delete_after); + } + return true; +} + +static completion_t* completions_get(completions_t* cms, ssize_t index) { + if (index < 0 || cms->count <= 0 || index >= cms->count) return NULL; + return &cms->elems[index]; +} + +ic_private const char* completions_get_display( completions_t* cms, ssize_t index, const char** help ) { + if (help != NULL) { *help = NULL; } + completion_t* cm = completions_get(cms, index); + if (cm == NULL) return NULL; + if (help != NULL) { *help = cm->help; } + return (cm->display != NULL ? cm->display : cm->replacement); +} + +ic_private const char* completions_get_help( completions_t* cms, ssize_t index ) { + completion_t* cm = completions_get(cms, index); + if (cm == NULL) return NULL; + return cm->help; +} + +ic_private const char* completions_get_hint(completions_t* cms, ssize_t index, const char** help) { + if (help != NULL) { *help = NULL; } + completion_t* cm = completions_get(cms, index); + if (cm == NULL) return NULL; + ssize_t len = ic_strlen(cm->replacement); + if (len < cm->delete_before) return NULL; + const char* hint = (cm->replacement + cm->delete_before); + if (*hint == 0 || utf8_is_cont((uint8_t)(*hint))) return NULL; // utf8 boundary? + if (help != NULL) { *help = cm->help; } + return hint; +} + +ic_private void completions_set_completer(completions_t* cms, ic_completer_fun_t* completer, void* arg) { + cms->completer = completer; + cms->completer_arg = arg; +} + +ic_private void completions_get_completer(completions_t* cms, ic_completer_fun_t** completer, void** arg) { + *completer = cms->completer; + *arg = cms->completer_arg; +} + + +ic_public void* ic_completion_arg( const ic_completion_env_t* cenv ) { + return (cenv == NULL ? NULL : cenv->env->completions->completer_arg); +} + +ic_public bool ic_has_completions( const ic_completion_env_t* cenv ) { + return (cenv == NULL ? false : cenv->env->completions->count > 0); +} + +ic_public bool ic_stop_completing( const ic_completion_env_t* cenv) { + return (cenv == NULL ? true : cenv->env->completions->completer_max <= 0); +} + + +static ssize_t completion_apply( completion_t* cm, stringbuf_t* sbuf, ssize_t pos ) { + if (cm == NULL) return -1; + debug_msg( "completion: apply: %s at %zd\n", cm->replacement, pos); + ssize_t start = pos - cm->delete_before; + if (start < 0) start = 0; + ssize_t n = cm->delete_before + cm->delete_after; + if (ic_strlen(cm->replacement) == n && strncmp(sbuf_string_at(sbuf,start), cm->replacement, to_size_t(n)) == 0) { + // no changes + return -1; + } + else { + sbuf_delete_from_to( sbuf, start, pos + cm->delete_after ); + return sbuf_insert_at(sbuf, cm->replacement, start); + } +} + +ic_private ssize_t completions_apply( completions_t* cms, ssize_t index, stringbuf_t* sbuf, ssize_t pos ) { + completion_t* cm = completions_get(cms, index); + return completion_apply( cm, sbuf, pos ); +} + + +static int completion_compare(const void* p1, const void* p2) { + if (p1 == NULL || p2 == NULL) return 0; + const completion_t* cm1 = (const completion_t*)p1; + const completion_t* cm2 = (const completion_t*)p2; + return ic_stricmp(cm1->replacement, cm2->replacement); +} + +ic_private void completions_sort(completions_t* cms) { + if (cms->count <= 0) return; + qsort(cms->elems, to_size_t(cms->count), sizeof(cms->elems[0]), &completion_compare); +} + +#define IC_MAX_PREFIX (256) + +// find longest common prefix and complete with that. +ic_private ssize_t completions_apply_longest_prefix(completions_t* cms, stringbuf_t* sbuf, ssize_t pos) { + if (cms->count <= 1) { + return completions_apply(cms,0,sbuf,pos); + } + + // set initial prefix to the first entry + completion_t* cm = completions_get(cms, 0); + if (cm == NULL) return -1; + + char prefix[IC_MAX_PREFIX+1]; + ssize_t delete_before = cm->delete_before; + ic_strncpy( prefix, IC_MAX_PREFIX+1, cm->replacement, IC_MAX_PREFIX ); + prefix[IC_MAX_PREFIX] = 0; + + // and visit all others to find the longest common prefix + for(ssize_t i = 1; i < cms->count; i++) { + cm = completions_get(cms,i); + if (cm->delete_before != delete_before) { // deletions must match delete_before + prefix[0] = 0; + break; + } + // check if it is still a prefix + const char* r = cm->replacement; + ssize_t j; + for(j = 0; prefix[j] != 0 && r[j] != 0; j++) { + if (prefix[j] != r[j]) break; + } + prefix[j] = 0; + if (j <= 0) break; + } + + // check the length + ssize_t len = ic_strlen(prefix); + if (len <= 0 || len < delete_before) return -1; + + // we found a prefix :-) + completion_t cprefix; + memset(&cprefix,0,sizeof(cprefix)); + cprefix.delete_before = delete_before; + cprefix.replacement = prefix; + ssize_t newpos = completion_apply( &cprefix, sbuf, pos); + if (newpos < 0) return newpos; + + // adjust all delete_before for the new replacement + for( ssize_t i = 0; i < cms->count; i++) { + cm = completions_get(cms,i); + cm->delete_before = len; + } + + return newpos; +} + + +//------------------------------------------------------------- +// Completer functions +//------------------------------------------------------------- + +ic_public bool ic_add_completions(ic_completion_env_t* cenv, const char* prefix, const char** completions) { + for (const char** pc = completions; *pc != NULL; pc++) { + if (ic_istarts_with(*pc, prefix)) { + if (!ic_add_completion_ex(cenv, *pc, NULL, NULL)) return false; + } + } + return true; +} + +ic_public bool ic_add_completion(ic_completion_env_t* cenv, const char* replacement) { + return ic_add_completion_ex(cenv, replacement, NULL, NULL); +} + +ic_public bool ic_add_completion_ex( ic_completion_env_t* cenv, const char* replacement, const char* display, const char* help ) { + return ic_add_completion_prim(cenv,replacement,display,help,0,0); +} + +ic_public bool ic_add_completion_prim(ic_completion_env_t* cenv, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) { + return (*cenv->complete)(cenv->env, cenv->closure, replacement, display, help, delete_before, delete_after ); +} + +static bool prim_add_completion(ic_env_t* env, void* funenv, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) { + ic_unused(funenv); + return completions_add(env->completions, replacement, display, help, delete_before, delete_after); +} + +ic_public void ic_set_default_completer(ic_completer_fun_t* completer, void* arg) { + ic_env_t* env = ic_get_env(); if (env == NULL) return; + completions_set_completer(env->completions, completer, arg); +} + +ic_private ssize_t completions_generate(struct ic_env_s* env, completions_t* cms, const char* input, ssize_t pos, ssize_t max) { + completions_clear(cms); + if (cms->completer == NULL || input == NULL || ic_strlen(input) < pos) return 0; + + // set up env + ic_completion_env_t cenv; + cenv.env = env; + cenv.input = input, + cenv.cursor = (long)pos; + cenv.arg = cms->completer_arg; + cenv.complete = &prim_add_completion; + cenv.closure = NULL; + const char* prefix = mem_strndup(cms->mem, input, pos); + cms->completer_max = max; + + // and complete + cms->completer(&cenv,prefix); + + // restore + mem_free(cms->mem,prefix); + return completions_count(cms); +} + +// The default completer is no completion is set +static void default_filename_completer( ic_completion_env_t* cenv, const char* prefix ) { + #ifdef _WIN32 + const char sep = '\\'; + #else + const char sep = '/'; + #endif + ic_complete_filename( cenv, prefix, sep, ".", NULL); +} diff --git a/deps/isocline/src/completions.h b/deps/isocline/src/completions.h new file mode 100644 index 0000000..8361d50 --- /dev/null +++ b/deps/isocline/src/completions.h @@ -0,0 +1,52 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_COMPLETIONS_H +#define IC_COMPLETIONS_H + +#include "common.h" +#include "stringbuf.h" + + +//------------------------------------------------------------- +// Completions +//------------------------------------------------------------- +#define IC_MAX_COMPLETIONS_TO_SHOW (1000) +#define IC_MAX_COMPLETIONS_TO_TRY (IC_MAX_COMPLETIONS_TO_SHOW/4) + +typedef struct completions_s completions_t; + +ic_private completions_t* completions_new(alloc_t* mem); +ic_private void completions_free(completions_t* cms); +ic_private void completions_clear(completions_t* cms); +ic_private bool completions_add(completions_t* cms , const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after); +ic_private ssize_t completions_count(completions_t* cms); +ic_private ssize_t completions_generate(struct ic_env_s* env, completions_t* cms , const char* input, ssize_t pos, ssize_t max); +ic_private void completions_sort(completions_t* cms); +ic_private void completions_set_completer(completions_t* cms, ic_completer_fun_t* completer, void* arg); +ic_private const char* completions_get_display(completions_t* cms , ssize_t index, const char** help); +ic_private const char* completions_get_hint(completions_t* cms, ssize_t index, const char** help); +ic_private void completions_get_completer(completions_t* cms, ic_completer_fun_t** completer, void** arg); + +ic_private ssize_t completions_apply(completions_t* cms, ssize_t index, stringbuf_t* sbuf, ssize_t pos); +ic_private ssize_t completions_apply_longest_prefix(completions_t* cms, stringbuf_t* sbuf, ssize_t pos); + +//------------------------------------------------------------- +// Completion environment +//------------------------------------------------------------- +typedef bool (ic_completion_fun_t)( ic_env_t* env, void* funenv, const char* replacement, const char* display, const char* help, long delete_before, long delete_after ); + +struct ic_completion_env_s { + ic_env_t* env; // the isocline environment + const char* input; // current full input + long cursor; // current cursor position + void* arg; // argument given to `ic_set_completer` + void* closure; // free variables for function composition + ic_completion_fun_t* complete; // function that adds a completion +}; + +#endif // IC_COMPLETIONS_H diff --git a/deps/isocline/src/editline.c b/deps/isocline/src/editline.c new file mode 100644 index 0000000..270c42d --- /dev/null +++ b/deps/isocline/src/editline.c @@ -0,0 +1,1142 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include + +#include "common.h" +#include "term.h" +#include "tty.h" +#include "env.h" +#include "stringbuf.h" +#include "history.h" +#include "completions.h" +#include "undo.h" +#include "highlight.h" + +//------------------------------------------------------------- +// The editor state +//------------------------------------------------------------- + + + +// editor state +typedef struct editor_s { + stringbuf_t* input; // current user input + stringbuf_t* extra; // extra displayed info (for completion menu etc) + stringbuf_t* hint; // hint displayed as part of the input + stringbuf_t* hint_help; // help for a hint. + ssize_t pos; // current cursor position in the input + ssize_t cur_rows; // current used rows to display our content (including extra content) + ssize_t cur_row; // current row that has the cursor (0 based, relative to the prompt) + ssize_t termw; + bool modified; // has a modification happened? (used for history navigation for example) + bool disable_undo; // temporarily disable auto undo (for history search) + ssize_t history_idx; // current index in the history + editstate_t* undo; // undo buffer + editstate_t* redo; // redo buffer + const char* prompt_text; // text of the prompt before the prompt marker + alloc_t* mem; // allocator + // caches + attrbuf_t* attrs; // reuse attribute buffers + attrbuf_t* attrs_extra; +} editor_t; + + + + + +//------------------------------------------------------------- +// Main edit line +//------------------------------------------------------------- +static char* edit_line( ic_env_t* env, const char* prompt_text ); // defined at bottom +static void edit_refresh(ic_env_t* env, editor_t* eb); + +ic_private char* ic_editline(ic_env_t* env, const char* prompt_text) { + tty_start_raw(env->tty); + term_start_raw(env->term); + char* line = edit_line(env,prompt_text); + term_end_raw(env->term,false); + tty_end_raw(env->tty); + term_writeln(env->term,""); + term_flush(env->term); + return line; +} + + +//------------------------------------------------------------- +// Undo/Redo +//------------------------------------------------------------- + +// capture the current edit state +static void editor_capture(editor_t* eb, editstate_t** es ) { + if (!eb->disable_undo) { + editstate_capture( eb->mem, es, sbuf_string(eb->input), eb->pos ); + } +} + +static void editor_undo_capture(editor_t* eb ) { + editor_capture(eb, &eb->undo ); +} + +static void editor_undo_forget(editor_t* eb) { + if (eb->disable_undo) return; + const char* input = NULL; + ssize_t pos = 0; + editstate_restore(eb->mem, &eb->undo, &input, &pos); + mem_free(eb->mem, input); +} + +static void editor_restore(editor_t* eb, editstate_t** from, editstate_t** to ) { + if (eb->disable_undo) return; + if (*from == NULL) return; + const char* input; + if (to != NULL) { editor_capture( eb, to ); } + if (!editstate_restore( eb->mem, from, &input, &eb->pos )) return; + sbuf_replace( eb->input, input ); + mem_free(eb->mem, input); + eb->modified = false; +} + +static void editor_undo_restore(editor_t* eb, bool with_redo ) { + editor_restore(eb, &eb->undo, (with_redo ? &eb->redo : NULL)); +} + +static void editor_redo_restore(editor_t* eb ) { + editor_restore(eb, &eb->redo, &eb->undo); + eb->modified = false; +} + +static void editor_start_modify(editor_t* eb ) { + editor_undo_capture(eb); + editstate_done(eb->mem, &eb->redo); // clear redo + eb->modified = true; +} + + + +static bool editor_pos_is_at_end(editor_t* eb ) { + return (eb->pos == sbuf_len(eb->input)); +} + +//------------------------------------------------------------- +// Row/Column width and positioning +//------------------------------------------------------------- + + +static void edit_get_prompt_width( ic_env_t* env, editor_t* eb, bool in_extra, ssize_t* promptw, ssize_t* cpromptw ) { + if (in_extra) { + *promptw = 0; + *cpromptw = 0; + } + else { + // todo: cache prompt widths + ssize_t textw = bbcode_column_width(env->bbcode, eb->prompt_text); + ssize_t markerw = bbcode_column_width(env->bbcode, env->prompt_marker); + ssize_t cmarkerw = bbcode_column_width(env->bbcode, env->cprompt_marker); + *promptw = markerw + textw; + *cpromptw = (env->no_multiline_indent || *promptw < cmarkerw ? cmarkerw : *promptw); + } +} + +static ssize_t edit_get_rowcol( ic_env_t* env, editor_t* eb, rowcol_t* rc ) { + ssize_t promptw, cpromptw; + edit_get_prompt_width(env, eb, false, &promptw, &cpromptw); + return sbuf_get_rc_at_pos( eb->input, eb->termw, promptw, cpromptw, eb->pos, rc ); +} + +static void edit_set_pos_at_rowcol( ic_env_t* env, editor_t* eb, ssize_t row, ssize_t col ) { + ssize_t promptw, cpromptw; + edit_get_prompt_width(env, eb, false, &promptw, &cpromptw); + ssize_t pos = sbuf_get_pos_at_rc( eb->input, eb->termw, promptw, cpromptw, row, col ); + if (pos < 0) return; + eb->pos = pos; + edit_refresh(env, eb); +} + +static bool edit_pos_is_at_row_end( ic_env_t* env, editor_t* eb ) { + rowcol_t rc; + edit_get_rowcol( env, eb, &rc ); + return rc.last_on_row; +} + +static void edit_write_prompt( ic_env_t* env, editor_t* eb, ssize_t row, bool in_extra ) { + if (in_extra) return; + bbcode_style_open(env->bbcode, "ic-prompt"); + if (row==0) { + // regular prompt text + bbcode_print( env->bbcode, eb->prompt_text ); + } + else if (!env->no_multiline_indent) { + // multiline continuation indentation + // todo: cache prompt widths + ssize_t textw = bbcode_column_width(env->bbcode, eb->prompt_text ); + ssize_t markerw = bbcode_column_width(env->bbcode, env->prompt_marker); + ssize_t cmarkerw = bbcode_column_width(env->bbcode, env->cprompt_marker); + if (cmarkerw < markerw + textw) { + term_write_repeat(env->term, " ", markerw + textw - cmarkerw ); + } + } + // the marker + bbcode_print(env->bbcode, (row == 0 ? env->prompt_marker : env->cprompt_marker )); + bbcode_style_close(env->bbcode,NULL); +} + +//------------------------------------------------------------- +// Refresh +//------------------------------------------------------------- + +typedef struct refresh_info_s { + ic_env_t* env; + editor_t* eb; + attrbuf_t* attrs; + bool in_extra; + ssize_t first_row; + ssize_t last_row; +} refresh_info_t; + +static bool edit_refresh_rows_iter( + const char* s, + ssize_t row, ssize_t row_start, ssize_t row_len, + ssize_t startw, bool is_wrap, const void* arg, void* res) +{ + ic_unused(res); ic_unused(startw); + const refresh_info_t* info = (const refresh_info_t*)(arg); + term_t* term = info->env->term; + + // debug_msg("edit: line refresh: row %zd, len: %zd\n", row, row_len); + if (row < info->first_row) return false; + if (row > info->last_row) return true; // should not occur + + // term_clear_line(term); + edit_write_prompt(info->env, info->eb, row, info->in_extra); + + //' write output + if (info->attrs == NULL || (info->env->no_highlight && info->env->no_bracematch)) { + term_write_n( term, s + row_start, row_len ); + } + else { + term_write_formatted_n( term, s + row_start, attrbuf_attrs(info->attrs, row_start + row_len) + row_start, row_len ); + } + + // write line ending + if (row < info->last_row) { + if (is_wrap && tty_is_utf8(info->env->tty)) { + #ifndef __APPLE__ + bbcode_print( info->env->bbcode, "[ic-dim]\xE2\x86\x90"); // left arrow + #else + bbcode_print( info->env->bbcode, "[ic-dim]\xE2\x86\xB5" ); // return symbol + #endif + } + term_clear_to_end_of_line(term); + term_writeln(term, ""); + } + else { + term_clear_to_end_of_line(term); + } + return (row >= info->last_row); +} + +static void edit_refresh_rows(ic_env_t* env, editor_t* eb, stringbuf_t* input, attrbuf_t* attrs, + ssize_t promptw, ssize_t cpromptw, bool in_extra, + ssize_t first_row, ssize_t last_row) +{ + if (input == NULL) return; + refresh_info_t info; + info.env = env; + info.eb = eb; + info.attrs = attrs; + info.in_extra = in_extra; + info.first_row = first_row; + info.last_row = last_row; + sbuf_for_each_row( input, eb->termw, promptw, cpromptw, &edit_refresh_rows_iter, &info, NULL); +} + + +static void edit_refresh(ic_env_t* env, editor_t* eb) +{ + // calculate the new cursor row and total rows needed + ssize_t promptw, cpromptw; + edit_get_prompt_width( env, eb, false, &promptw, &cpromptw ); + + if (eb->attrs != NULL) { + highlight( env->mem, env->bbcode, sbuf_string(eb->input), eb->attrs, + (env->no_highlight ? NULL : env->highlighter), env->highlighter_arg ); + } + + // highlight matching braces + if (eb->attrs != NULL && !env->no_bracematch) { + highlight_match_braces(sbuf_string(eb->input), eb->attrs, eb->pos, ic_env_get_match_braces(env), + bbcode_style(env->bbcode,"ic-bracematch"), bbcode_style(env->bbcode,"ic-error")); + } + + // insert hint + if (sbuf_len(eb->hint) > 0) { + if (eb->attrs != NULL) { + attrbuf_insert_at( eb->attrs, eb->pos, sbuf_len(eb->hint), bbcode_style(env->bbcode, "ic-hint") ); + } + sbuf_insert_at(eb->input, sbuf_string(eb->hint), eb->pos ); + } + + // render extra (like a completion menu) + stringbuf_t* extra = NULL; + if (sbuf_len(eb->extra) > 0) { + extra = sbuf_new(eb->mem); + if (extra != NULL) { + if (sbuf_len(eb->hint_help) > 0) { + bbcode_append(env->bbcode, sbuf_string(eb->hint_help), extra, eb->attrs_extra); + } + bbcode_append(env->bbcode, sbuf_string(eb->extra), extra, eb->attrs_extra); + } + } + + // calculate rows and row/col position + rowcol_t rc = { 0 }; + const ssize_t rows_input = sbuf_get_rc_at_pos( eb->input, eb->termw, promptw, cpromptw, eb->pos, &rc ); + rowcol_t rc_extra = { 0 }; + ssize_t rows_extra = 0; + if (extra != NULL) { + rows_extra = sbuf_get_rc_at_pos( extra, eb->termw, 0, 0, 0 /*pos*/, &rc_extra ); + } + const ssize_t rows = rows_input + rows_extra; + debug_msg("edit: refresh: rows %zd, cursor: %zd,%zd (previous rows %zd, cursor row %zd)\n", rows, rc.row, rc.col, eb->cur_rows, eb->cur_row); + + // only render at most terminal height rows + const ssize_t termh = term_get_height(env->term); + ssize_t first_row = 0; // first visible row + ssize_t last_row = rows - 1; // last visible row + if (rows > termh) { + first_row = rc.row - termh + 1; // ensure cursor is visible + if (first_row < 0) first_row = 0; + last_row = first_row + termh - 1; + } + assert(last_row - first_row < termh); + + // reduce flicker + buffer_mode_t bmode = term_set_buffer_mode(env->term, BUFFERED); + + // back up to the first line + term_start_of_line(env->term); + term_up(env->term, (eb->cur_row >= termh ? termh-1 : eb->cur_row) ); + // term_clear_lines_to_end(env->term); // gives flicker in old Windows cmd prompt + + // render rows + edit_refresh_rows( env, eb, eb->input, eb->attrs, promptw, cpromptw, false, first_row, last_row ); + if (rows_extra > 0) { + assert(extra != NULL); + const ssize_t first_rowx = (first_row > rows_input ? first_row - rows_input : 0); + const ssize_t last_rowx = last_row - rows_input; assert(last_rowx >= 0); + edit_refresh_rows(env, eb, extra, eb->attrs_extra, 0, 0, true, first_rowx, last_rowx); + } + + // overwrite trailing rows we do not use anymore + ssize_t rrows = last_row - first_row + 1; // rendered rows + if (rrows < termh && rows < eb->cur_rows) { + ssize_t clear = eb->cur_rows - rows; + while (rrows < termh && clear > 0) { + clear--; + rrows++; + term_writeln(env->term,""); + term_clear_line(env->term); + } + } + + // move cursor back to edit position + term_start_of_line(env->term); + term_up(env->term, first_row + rrows - 1 - rc.row ); + term_right(env->term, rc.col + (rc.row == 0 ? promptw : cpromptw)); + + // and refresh + term_flush(env->term); + + // stop buffering + term_set_buffer_mode(env->term, bmode); + + // restore input by removing the hint + sbuf_delete_at(eb->input, eb->pos, sbuf_len(eb->hint)); + sbuf_delete_at(eb->extra, 0, sbuf_len(eb->hint_help)); + attrbuf_clear(eb->attrs); + attrbuf_clear(eb->attrs_extra); + sbuf_free(extra); + + // update previous + eb->cur_rows = rows; + eb->cur_row = rc.row; +} + +// clear current output +static void edit_clear(ic_env_t* env, editor_t* eb ) { + term_attr_reset(env->term); + term_up(env->term, eb->cur_row); + + // overwrite all rows + for( ssize_t i = 0; i < eb->cur_rows; i++) { + term_clear_line(env->term); + term_writeln(env->term, ""); + } + + // move cursor back + term_up(env->term, eb->cur_rows - eb->cur_row ); +} + + +// clear screen and refresh +static void edit_clear_screen(ic_env_t* env, editor_t* eb ) { + ssize_t cur_rows = eb->cur_rows; + eb->cur_rows = term_get_height(env->term) - 1; + edit_clear(env,eb); + eb->cur_rows = cur_rows; + edit_refresh(env,eb); +} + + +// refresh after a terminal window resized (but before doing further edit operations!) +static bool edit_resize(ic_env_t* env, editor_t* eb ) { + // update dimensions + term_update_dim(env->term); + ssize_t newtermw = term_get_width(env->term); + if (eb->termw == newtermw) return false; + + // recalculate the row layout assuming the hardwrapping for the new terminal width + ssize_t promptw, cpromptw; + edit_get_prompt_width( env, eb, false, &promptw, &cpromptw ); + sbuf_insert_at(eb->input, sbuf_string(eb->hint), eb->pos); // insert used hint + + // render extra (like a completion menu) + stringbuf_t* extra = NULL; + if (sbuf_len(eb->extra) > 0) { + extra = sbuf_new(eb->mem); + if (extra != NULL) { + if (sbuf_len(eb->hint_help) > 0) { + bbcode_append(env->bbcode, sbuf_string(eb->hint_help), extra, NULL); + } + bbcode_append(env->bbcode, sbuf_string(eb->extra), extra, NULL); + } + } + rowcol_t rc = { 0 }; + const ssize_t rows_input = sbuf_get_wrapped_rc_at_pos( eb->input, eb->termw, newtermw, promptw, cpromptw, eb->pos, &rc ); + rowcol_t rc_extra = { 0 }; + ssize_t rows_extra = 0; + if (extra != NULL) { + rows_extra = sbuf_get_wrapped_rc_at_pos(extra, eb->termw, newtermw, 0, 0, 0 /*pos*/, &rc_extra); + } + ssize_t rows = rows_input + rows_extra; + debug_msg("edit: resize: new rows: %zd, cursor row: %zd (previous: rows: %zd, cursor row %zd)\n", rows, rc.row, eb->cur_rows, eb->cur_row); + + // update the newly calculated row and rows + eb->cur_row = rc.row; + if (rows > eb->cur_rows) { + eb->cur_rows = rows; + } + eb->termw = newtermw; + edit_refresh(env,eb); + + // remove hint again + sbuf_delete_at(eb->input, eb->pos, sbuf_len(eb->hint)); + sbuf_free(extra); + return true; +} + +static void editor_append_hint_help(editor_t* eb, const char* help) { + sbuf_clear(eb->hint_help); + if (help != NULL) { + sbuf_replace(eb->hint_help, "[ic-info]"); + sbuf_append(eb->hint_help, help); + sbuf_append(eb->hint_help, "[/ic-info]\n"); + } +} + +// refresh with possible hint +static void edit_refresh_hint(ic_env_t* env, editor_t* eb) { + if (env->no_hint || env->hint_delay > 0) { + // refresh without hint first + edit_refresh(env, eb); + if (env->no_hint) return; + } + + // and see if we can construct a hint (displayed after a delay) + ssize_t count = completions_generate(env, env->completions, sbuf_string(eb->input), eb->pos, 2); + if (count == 1) { + const char* help = NULL; + const char* hint = completions_get_hint(env->completions, 0, &help); + if (hint != NULL) { + sbuf_replace(eb->hint, hint); + editor_append_hint_help(eb, help); + // do auto-tabbing? + if (env->complete_autotab) { + stringbuf_t* sb = sbuf_new(env->mem); // temporary buffer for completion + if (sb != NULL) { + sbuf_replace( sb, sbuf_string(eb->input) ); + ssize_t pos = eb->pos; + const char* extra_hint = hint; + do { + ssize_t newpos = sbuf_insert_at( sb, extra_hint, pos ); + if (newpos <= pos) break; + pos = newpos; + count = completions_generate(env, env->completions, sbuf_string(sb), pos, 2); + if (count == 1) { + const char* extra_help = NULL; + extra_hint = completions_get_hint(env->completions, 0, &extra_help); + if (extra_hint != NULL) { + editor_append_hint_help(eb, extra_help); + sbuf_append(eb->hint, extra_hint); + } + } + } + while(count == 1); + sbuf_free(sb); + } + } + } + } + + if (env->hint_delay <= 0) { + // refresh with hint directly + edit_refresh(env, eb); + } +} + +//------------------------------------------------------------- +// Edit operations +//------------------------------------------------------------- + +static void edit_history_prev(ic_env_t* env, editor_t* eb); +static void edit_history_next(ic_env_t* env, editor_t* eb); + +static void edit_undo_restore(ic_env_t* env, editor_t* eb) { + editor_undo_restore(eb, true); + edit_refresh(env,eb); +} + +static void edit_redo_restore(ic_env_t* env, editor_t* eb) { + editor_redo_restore(eb); + edit_refresh(env,eb); +} + +static void edit_cursor_left(ic_env_t* env, editor_t* eb) { + ssize_t cwidth = 1; + ssize_t prev = sbuf_prev(eb->input,eb->pos,&cwidth); + if (prev < 0) return; + rowcol_t rc; + edit_get_rowcol( env, eb, &rc); + eb->pos = prev; + edit_refresh(env,eb); +} + +static void edit_cursor_right(ic_env_t* env, editor_t* eb) { + ssize_t cwidth = 1; + ssize_t next = sbuf_next(eb->input,eb->pos,&cwidth); + if (next < 0) return; + rowcol_t rc; + edit_get_rowcol( env, eb, &rc); + eb->pos = next; + edit_refresh(env,eb); +} + +static void edit_cursor_line_end(ic_env_t* env, editor_t* eb) { + ssize_t end = sbuf_find_line_end(eb->input,eb->pos); + if (end < 0) return; + eb->pos = end; + edit_refresh(env,eb); +} + +static void edit_cursor_line_start(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_line_start(eb->input,eb->pos); + if (start < 0) return; + eb->pos = start; + edit_refresh(env,eb); +} + +static void edit_cursor_next_word(ic_env_t* env, editor_t* eb) { + ssize_t end = sbuf_find_word_end(eb->input,eb->pos); + if (end < 0) return; + eb->pos = end; + edit_refresh(env,eb); +} + +static void edit_cursor_prev_word(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_word_start(eb->input,eb->pos); + if (start < 0) return; + eb->pos = start; + edit_refresh(env,eb); +} + +static void edit_cursor_next_ws_word(ic_env_t* env, editor_t* eb) { + ssize_t end = sbuf_find_ws_word_end(eb->input, eb->pos); + if (end < 0) return; + eb->pos = end; + edit_refresh(env, eb); +} + +static void edit_cursor_prev_ws_word(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_ws_word_start(eb->input, eb->pos); + if (start < 0) return; + eb->pos = start; + edit_refresh(env, eb); +} + +static void edit_cursor_to_start(ic_env_t* env, editor_t* eb) { + eb->pos = 0; + edit_refresh(env,eb); +} + +static void edit_cursor_to_end(ic_env_t* env, editor_t* eb) { + eb->pos = sbuf_len(eb->input); + edit_refresh(env,eb); +} + + +static void edit_cursor_row_up(ic_env_t* env, editor_t* eb) { + rowcol_t rc; + edit_get_rowcol( env, eb, &rc); + if (rc.row == 0) { + edit_history_prev(env,eb); + } + else { + edit_set_pos_at_rowcol( env, eb, rc.row - 1, rc.col ); + } +} + +static void edit_cursor_row_down(ic_env_t* env, editor_t* eb) { + rowcol_t rc; + ssize_t rows = edit_get_rowcol( env, eb, &rc); + if (rc.row + 1 >= rows) { + edit_history_next(env,eb); + } + else { + edit_set_pos_at_rowcol( env, eb, rc.row + 1, rc.col ); + } +} + + +static void edit_cursor_match_brace(ic_env_t* env, editor_t* eb) { + ssize_t match = find_matching_brace( sbuf_string(eb->input), eb->pos, ic_env_get_match_braces(env), NULL ); + if (match < 0) return; + eb->pos = match; + edit_refresh(env,eb); +} + +static void edit_backspace(ic_env_t* env, editor_t* eb) { + if (eb->pos <= 0) return; + editor_start_modify(eb); + eb->pos = sbuf_delete_char_before(eb->input,eb->pos); + edit_refresh(env,eb); +} + +static void edit_delete_char(ic_env_t* env, editor_t* eb) { + if (eb->pos >= sbuf_len(eb->input)) return; + editor_start_modify(eb); + sbuf_delete_char_at(eb->input,eb->pos); + edit_refresh(env,eb); +} + +static void edit_delete_all(ic_env_t* env, editor_t* eb) { + if (sbuf_len(eb->input) <= 0) return; + editor_start_modify(eb); + sbuf_clear(eb->input); + eb->pos = 0; + edit_refresh(env,eb); +} + +static void edit_delete_to_end_of_line(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_line_start(eb->input,eb->pos); + if (start < 0) return; + ssize_t end = sbuf_find_line_end(eb->input,eb->pos); + if (end < 0) return; + editor_start_modify(eb); + // if on an empty line, remove it completely + if (start == end && sbuf_char_at(eb->input,end) == '\n') { + end++; + } + else if (start == end && sbuf_char_at(eb->input,start - 1) == '\n') { + eb->pos--; + } + sbuf_delete_from_to( eb->input, eb->pos, end ); + edit_refresh(env,eb); +} + +static void edit_delete_to_start_of_line(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_line_start(eb->input,eb->pos); + if (start < 0) return; + ssize_t end = sbuf_find_line_end(eb->input,eb->pos); + if (end < 0) return; + editor_start_modify(eb); + // delete start newline if it was an empty line + bool goright = false; + if (start > 0 && sbuf_char_at(eb->input,start-1) == '\n' && start == end) { + // if it is an empty line remove it + start--; + // afterwards, move to start of next line if it exists (so the cursor stays on the same row) + goright = true; + } + sbuf_delete_from_to( eb->input, start, eb->pos ); + eb->pos = start; + if (goright) edit_cursor_right(env,eb); + edit_refresh(env,eb); +} + +static void edit_delete_line(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_line_start(eb->input,eb->pos); + if (start < 0) return; + ssize_t end = sbuf_find_line_end(eb->input,eb->pos); + if (end < 0) return; + editor_start_modify(eb); + // delete newline as well so no empty line is left; + bool goright = false; + if (start > 0 && sbuf_char_at(eb->input,start-1) == '\n') { + start--; + // afterwards, move to start of next line if it exists (so the cursor stays on the same row) + goright = true; + } + else if (sbuf_char_at(eb->input,end) == '\n') { + end++; + } + sbuf_delete_from_to(eb->input,start,end); + eb->pos = start; + if (goright) edit_cursor_right(env,eb); + edit_refresh(env,eb); +} + +static void edit_delete_to_start_of_word(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_word_start(eb->input,eb->pos); + if (start < 0) return; + editor_start_modify(eb); + sbuf_delete_from_to( eb->input, start, eb->pos ); + eb->pos = start; + edit_refresh(env,eb); +} + +static void edit_delete_to_end_of_word(ic_env_t* env, editor_t* eb) { + ssize_t end = sbuf_find_word_end(eb->input,eb->pos); + if (end < 0) return; + editor_start_modify(eb); + sbuf_delete_from_to( eb->input, eb->pos, end ); + edit_refresh(env,eb); +} + +static void edit_delete_to_start_of_ws_word(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_ws_word_start(eb->input, eb->pos); + if (start < 0) return; + editor_start_modify(eb); + sbuf_delete_from_to(eb->input, start, eb->pos); + eb->pos = start; + edit_refresh(env, eb); +} + +static void edit_delete_to_end_of_ws_word(ic_env_t* env, editor_t* eb) { + ssize_t end = sbuf_find_ws_word_end(eb->input, eb->pos); + if (end < 0) return; + editor_start_modify(eb); + sbuf_delete_from_to(eb->input, eb->pos, end); + edit_refresh(env, eb); +} + + +static void edit_delete_word(ic_env_t* env, editor_t* eb) { + ssize_t start = sbuf_find_word_start(eb->input,eb->pos); + if (start < 0) return; + ssize_t end = sbuf_find_word_end(eb->input,eb->pos); + if (end < 0) return; + editor_start_modify(eb); + sbuf_delete_from_to(eb->input,start,end); + eb->pos = start; + edit_refresh(env,eb); +} + +static void edit_swap_char( ic_env_t* env, editor_t* eb ) { + if (eb->pos <= 0 || eb->pos == sbuf_len(eb->input)) return; + editor_start_modify(eb); + eb->pos = sbuf_swap_char(eb->input,eb->pos); + edit_refresh(env,eb); +} + +static void edit_multiline_eol(ic_env_t* env, editor_t* eb) { + if (eb->pos <= 0) return; + if (sbuf_string(eb->input)[eb->pos-1] != env->multiline_eol) return; + editor_start_modify(eb); + // replace line continuation with a real newline + sbuf_delete_at( eb->input, eb->pos-1, 1); + sbuf_insert_at( eb->input, "\n", eb->pos-1); + edit_refresh(env,eb); +} + +static void edit_insert_unicode(ic_env_t* env, editor_t* eb, unicode_t u) { + editor_start_modify(eb); + ssize_t nextpos = sbuf_insert_unicode_at(eb->input, u, eb->pos); + if (nextpos >= 0) eb->pos = nextpos; + edit_refresh_hint(env, eb); +} + +static void edit_auto_brace(ic_env_t* env, editor_t* eb, char c) { + if (env->no_autobrace) return; + const char* braces = ic_env_get_auto_braces(env); + for (const char* b = braces; *b != 0; b += 2) { + if (*b == c) { + const char close = b[1]; + //if (sbuf_char_at(eb->input, eb->pos) != close) { + sbuf_insert_char_at(eb->input, close, eb->pos); + bool balanced = false; + find_matching_brace(sbuf_string(eb->input), eb->pos, braces, &balanced ); + if (!balanced) { + // don't insert if it leads to an unbalanced expression. + sbuf_delete_char_at(eb->input, eb->pos); + } + //} + return; + } + else if (b[1] == c) { + // close brace, check if there we don't overwrite to the right + if (sbuf_char_at(eb->input, eb->pos) == c) { + sbuf_delete_char_at(eb->input, eb->pos); + } + return; + } + } +} + +static void editor_auto_indent(editor_t* eb, const char* pre, const char* post ) { + assert(eb->pos > 0 && sbuf_char_at(eb->input,eb->pos-1) == '\n'); + ssize_t prelen = ic_strlen(pre); + if (prelen > 0) { + if (eb->pos - 1 < prelen) return; + if (!ic_starts_with(sbuf_string(eb->input) + eb->pos - 1 - prelen, pre)) return; + if (!ic_starts_with(sbuf_string(eb->input) + eb->pos, post)) return; + eb->pos = sbuf_insert_at(eb->input, " ", eb->pos); + sbuf_insert_char_at(eb->input, '\n', eb->pos); + } +} + +static void edit_insert_char(ic_env_t* env, editor_t* eb, char c) { + editor_start_modify(eb); + ssize_t nextpos = sbuf_insert_char_at( eb->input, c, eb->pos ); + if (nextpos >= 0) eb->pos = nextpos; + edit_auto_brace(env, eb, c); + if (c=='\n') { + editor_auto_indent(eb, "{", "}"); // todo: custom auto indent tokens? + } + edit_refresh_hint(env,eb); +} + +//------------------------------------------------------------- +// Help +//------------------------------------------------------------- + +#include "editline_help.c" + +//------------------------------------------------------------- +// History +//------------------------------------------------------------- + +#include "editline_history.c" + +//------------------------------------------------------------- +// Completion +//------------------------------------------------------------- + +#include "editline_completion.c" + + +//------------------------------------------------------------- +// Edit line: main edit loop +//------------------------------------------------------------- + +static char* edit_line( ic_env_t* env, const char* prompt_text ) +{ + // set up an edit buffer + editor_t eb; + memset(&eb, 0, sizeof(eb)); + eb.mem = env->mem; + eb.input = sbuf_new(env->mem); + eb.extra = sbuf_new(env->mem); + eb.hint = sbuf_new(env->mem); + eb.hint_help= sbuf_new(env->mem); + eb.termw = term_get_width(env->term); + eb.pos = 0; + eb.cur_rows = 1; + eb.cur_row = 0; + eb.modified = false; + eb.prompt_text = (prompt_text != NULL ? prompt_text : ""); + eb.history_idx = 0; + editstate_init(&eb.undo); + editstate_init(&eb.redo); + if (eb.input==NULL || eb.extra==NULL || eb.hint==NULL || eb.hint_help==NULL) { + return NULL; + } + + // caching + if (!(env->no_highlight && env->no_bracematch)) { + eb.attrs = attrbuf_new(env->mem); + eb.attrs_extra = attrbuf_new(env->mem); + } + + // show prompt + edit_write_prompt(env, &eb, 0, false); + + // always a history entry for the current input + history_push(env->history, ""); + + // process keys + code_t c; // current key code + while(true) { + // read a character + term_flush(env->term); + if (env->hint_delay <= 0 || sbuf_len(eb.hint) == 0) { + // blocking read + c = tty_read(env->tty); + } + else { + // timeout to display hint + if (!tty_read_timeout(env->tty, env->hint_delay, &c)) { + // timed-out + if (sbuf_len(eb.hint) > 0) { + // display hint + edit_refresh(env, &eb); + } + c = tty_read(env->tty); + } + else { + // clear the pending hint if we got input before the delay expired + sbuf_clear(eb.hint); + sbuf_clear(eb.hint_help); + } + } + + // update terminal in case of a resize + if (tty_term_resize_event(env->tty)) { + edit_resize(env,&eb); + } + + // clear hint only after a potential resize (so resize row calculations are correct) + const bool had_hint = (sbuf_len(eb.hint) > 0); + sbuf_clear(eb.hint); + sbuf_clear(eb.hint_help); + + // if the user tries to move into a hint with left-cursor or end, we complete it first + if ((c == KEY_RIGHT || c == KEY_END) && had_hint) { + edit_generate_completions(env, &eb, true); + c = KEY_NONE; + } + + // Operations that may return + if (c == KEY_ENTER) { + if (!env->singleline_only && eb.pos > 0 && + sbuf_string(eb.input)[eb.pos-1] == env->multiline_eol && + edit_pos_is_at_row_end(env,&eb)) + { + // replace line-continuation with newline + edit_multiline_eol(env,&eb); + } + else { + // otherwise done + break; + } + } + else if (c == KEY_CTRL_D) { + if (eb.pos == 0 && editor_pos_is_at_end(&eb)) break; // ctrl+D on empty quits with NULL + edit_delete_char(env,&eb); // otherwise it is like delete + } + else if (c == KEY_CTRL_C || c == KEY_EVENT_STOP) { + break; // ctrl+C or STOP event quits with NULL + } + else if (c == KEY_ESC) { + if (eb.pos == 0 && editor_pos_is_at_end(&eb)) break; // ESC on empty input returns with empty input + edit_delete_all(env,&eb); // otherwise delete the current input + // edit_delete_line(env,&eb); // otherwise delete the current line + } + else if (c == KEY_BELL /* ^G */) { + edit_delete_all(env,&eb); + break; // ctrl+G cancels (and returns empty input) + } + + // Editing Operations + else switch(c) { + // events + case KEY_EVENT_RESIZE: // not used + edit_resize(env,&eb); + break; + case KEY_EVENT_AUTOTAB: + edit_generate_completions(env, &eb, true); + break; + + // completion, history, help, undo + case KEY_TAB: + case WITH_ALT('?'): + edit_generate_completions(env,&eb,false); + break; + case KEY_CTRL_R: + case KEY_CTRL_S: + edit_history_search_with_current_word(env,&eb); + break; + case KEY_CTRL_P: + edit_history_prev(env, &eb); + break; + case KEY_CTRL_N: + edit_history_next(env, &eb); + break; + case KEY_CTRL_L: + edit_clear_screen(env, &eb); + break; + case KEY_CTRL_Z: + case WITH_CTRL('_'): + edit_undo_restore(env, &eb); + break; + case KEY_CTRL_Y: + edit_redo_restore(env, &eb); + break; + case KEY_F1: + edit_show_help(env, &eb); + break; + + // navigation + case KEY_LEFT: + case KEY_CTRL_B: + edit_cursor_left(env,&eb); + break; + case KEY_RIGHT: + case KEY_CTRL_F: + if (eb.pos == sbuf_len(eb.input)) { + edit_generate_completions( env, &eb, false ); + } + else { + edit_cursor_right(env,&eb); + } + break; + case KEY_UP: + edit_cursor_row_up(env,&eb); + break; + case KEY_DOWN: + edit_cursor_row_down(env,&eb); + break; + case KEY_HOME: + case KEY_CTRL_A: + edit_cursor_line_start(env,&eb); + break; + case KEY_END: + case KEY_CTRL_E: + edit_cursor_line_end(env,&eb); + break; + case KEY_CTRL_LEFT: + case WITH_SHIFT(KEY_LEFT): + case WITH_ALT('b'): + edit_cursor_prev_word(env,&eb); + break; + case KEY_CTRL_RIGHT: + case WITH_SHIFT(KEY_RIGHT): + case WITH_ALT('f'): + if (eb.pos == sbuf_len(eb.input)) { + edit_generate_completions( env, &eb, false ); + } + else { + edit_cursor_next_word(env,&eb); + } + break; + case KEY_CTRL_HOME: + case WITH_SHIFT(KEY_HOME): + case KEY_PAGEUP: + case WITH_ALT('<'): + edit_cursor_to_start(env,&eb); + break; + case KEY_CTRL_END: + case WITH_SHIFT(KEY_END): + case KEY_PAGEDOWN: + case WITH_ALT('>'): + edit_cursor_to_end(env,&eb); + break; + case WITH_ALT('m'): + edit_cursor_match_brace(env,&eb); + break; + + // deletion + case KEY_BACKSP: + edit_backspace(env,&eb); + break; + case KEY_DEL: + edit_delete_char(env,&eb); + break; + case WITH_ALT('d'): + edit_delete_to_end_of_word(env,&eb); + break; + case KEY_CTRL_W: + edit_delete_to_start_of_ws_word(env, &eb); + break; + case WITH_ALT(KEY_DEL): + case WITH_ALT(KEY_BACKSP): + edit_delete_to_start_of_word(env,&eb); + break; + case KEY_CTRL_U: + edit_delete_to_start_of_line(env,&eb); + break; + case KEY_CTRL_K: + edit_delete_to_end_of_line(env,&eb); + break; + case KEY_CTRL_T: + edit_swap_char(env,&eb); + break; + + // Editing + case KEY_SHIFT_TAB: + case KEY_LINEFEED: // '\n' (ctrl+J, shift+enter) + if (!env->singleline_only) { + edit_insert_char(env, &eb, '\n'); + } + break; + default: { + char chr; + unicode_t uchr; + if (code_is_ascii_char(c,&chr)) { + edit_insert_char(env,&eb,chr); + } + else if (code_is_unicode(c, &uchr)) { + edit_insert_unicode(env,&eb, uchr); + } + else { + debug_msg( "edit: ignore code: 0x%04x\n", c); + } + break; + } + } + + } + + // goto end + eb.pos = sbuf_len(eb.input); + + // refresh once more but without brace matching + bool bm = env->no_bracematch; + env->no_bracematch = true; + edit_refresh(env,&eb); + env->no_bracematch = bm; + + // save result + char* res; + if ((c == KEY_CTRL_D && sbuf_len(eb.input) == 0) || c == KEY_CTRL_C || c == KEY_EVENT_STOP) { + res = NULL; + } + else if (!tty_is_utf8(env->tty)) { + res = sbuf_strdup_from_utf8(eb.input); + } + else { + res = sbuf_strdup(eb.input); + } + + // update history + history_update(env->history, sbuf_string(eb.input)); + if (res == NULL || sbuf_len(eb.input) <= 1) { ic_history_remove_last(); } // no empty or single-char entries + history_save(env->history); + + // free resources + editstate_done(env->mem, &eb.undo); + editstate_done(env->mem, &eb.redo); + attrbuf_free(eb.attrs); + attrbuf_free(eb.attrs_extra); + sbuf_free(eb.input); + sbuf_free(eb.extra); + sbuf_free(eb.hint); + sbuf_free(eb.hint_help); + + return res; +} + diff --git a/deps/isocline/src/editline_completion.c b/deps/isocline/src/editline_completion.c new file mode 100644 index 0000000..1734ef3 --- /dev/null +++ b/deps/isocline/src/editline_completion.c @@ -0,0 +1,277 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +//------------------------------------------------------------- +// Completion menu: this file is included in editline.c +//------------------------------------------------------------- + +// return true if anything changed +static bool edit_complete(ic_env_t* env, editor_t* eb, ssize_t idx) { + editor_start_modify(eb); + ssize_t newpos = completions_apply(env->completions, idx, eb->input, eb->pos); + if (newpos < 0) { + editor_undo_restore(eb,false); + return false; + } + eb->pos = newpos; + edit_refresh(env,eb); + return true; +} + +static bool edit_complete_longest_prefix(ic_env_t* env, editor_t* eb ) { + editor_start_modify(eb); + ssize_t newpos = completions_apply_longest_prefix( env->completions, eb->input, eb->pos ); + if (newpos < 0) { + editor_undo_restore(eb,false); + return false; + } + eb->pos = newpos; + edit_refresh(env,eb); + return true; +} + +ic_private void sbuf_append_tagged( stringbuf_t* sb, const char* tag, const char* content ) { + sbuf_appendf(sb, "[%s]", tag); + sbuf_append(sb,content); + sbuf_append(sb,"[/]"); +} + +static void editor_append_completion(ic_env_t* env, editor_t* eb, ssize_t idx, ssize_t width, bool numbered, bool selected ) { + const char* help = NULL; + const char* display = completions_get_display(env->completions, idx, &help); + if (display == NULL) return; + if (numbered) { + sbuf_appendf(eb->extra, "[ic-info]%s%zd [/]", (selected ? (tty_is_utf8(env->tty) ? "\xE2\x86\x92" : "*") : " "), 1 + idx); + width -= 3; + } + + if (width > 0) { + sbuf_appendf(eb->extra, "[width=\"%zd;left; ;on\"]", width ); + } + if (selected) { + sbuf_append(eb->extra, "[ic-emphasis]"); + } + sbuf_append(eb->extra, display); + if (selected) { sbuf_append(eb->extra,"[/ic-emphasis]"); } + if (help != NULL) { + sbuf_append(eb->extra, " "); + sbuf_append_tagged(eb->extra, "ic-info", help ); + } + if (width > 0) { sbuf_append(eb->extra,"[/width]"); } +} + +// 2 and 3 column output up to 80 wide +#define IC_DISPLAY2_MAX 34 +#define IC_DISPLAY2_COL (3+IC_DISPLAY2_MAX) +#define IC_DISPLAY2_WIDTH (2*IC_DISPLAY2_COL + 2) // 75 + +#define IC_DISPLAY3_MAX 21 +#define IC_DISPLAY3_COL (3+IC_DISPLAY3_MAX) +#define IC_DISPLAY3_WIDTH (3*IC_DISPLAY3_COL + 2*2) // 76 + +static void editor_append_completion2(ic_env_t* env, editor_t* eb, ssize_t col_width, ssize_t idx1, ssize_t idx2, ssize_t selected ) { + editor_append_completion(env, eb, idx1, col_width, true, (idx1 == selected) ); + sbuf_append( eb->extra, " "); + editor_append_completion(env, eb, idx2, col_width, true, (idx2 == selected) ); +} + +static void editor_append_completion3(ic_env_t* env, editor_t* eb, ssize_t col_width, ssize_t idx1, ssize_t idx2, ssize_t idx3, ssize_t selected ) { + editor_append_completion(env, eb, idx1, col_width, true, (idx1 == selected) ); + sbuf_append( eb->extra, " "); + editor_append_completion(env, eb, idx2, col_width, true, (idx2 == selected)); + sbuf_append( eb->extra, " "); + editor_append_completion(env, eb, idx3, col_width, true, (idx3 == selected) ); +} + +static ssize_t edit_completions_max_width( ic_env_t* env, ssize_t count ) { + ssize_t max_width = 0; + for( ssize_t i = 0; i < count; i++) { + const char* help = NULL; + ssize_t w = bbcode_column_width(env->bbcode, completions_get_display(env->completions, i, &help)); + if (help != NULL) { + w += 2 + bbcode_column_width(env->bbcode, help); + } + if (w > max_width) { + max_width = w; + } + } + return max_width; +} + +static void edit_completion_menu(ic_env_t* env, editor_t* eb, bool more_available) { + ssize_t count = completions_count(env->completions); + ssize_t count_displayed = count; + assert(count > 1); + ssize_t selected = (env->complete_nopreview ? 0 : -1); // select first or none + ssize_t percolumn = count; + +again: + // show first 9 (or 8) completions + sbuf_clear(eb->extra); + ssize_t twidth = term_get_width(env->term) - 1; + ssize_t colwidth; + if (count > 3 && ((colwidth = 3 + edit_completions_max_width(env, 9))*3 + 2*2) < twidth) { + // display as a 3 column block + count_displayed = (count > 9 ? 9 : count); + percolumn = 3; + for (ssize_t rw = 0; rw < percolumn; rw++) { + if (rw > 0) sbuf_append(eb->extra, "\n"); + editor_append_completion3(env, eb, colwidth, rw, percolumn+rw, (2*percolumn)+rw, selected); + } + } + else if (count > 4 && ((colwidth = 3 + edit_completions_max_width(env, 8))*2 + 2) < twidth) { + // display as a 2 column block if some entries are too wide for three columns + count_displayed = (count > 8 ? 8 : count); + percolumn = (count_displayed <= 6 ? 3 : 4); + for (ssize_t rw = 0; rw < percolumn; rw++) { + if (rw > 0) sbuf_append(eb->extra, "\n"); + editor_append_completion2(env, eb, colwidth, rw, percolumn+rw, selected); + } + } + else { + // display as a list + count_displayed = (count > 9 ? 9 : count); + percolumn = count_displayed; + for (ssize_t i = 0; i < count_displayed; i++) { + if (i > 0) sbuf_append(eb->extra, "\n"); + editor_append_completion(env, eb, i, -1, true /* numbered */, selected == i); + } + } + if (count > count_displayed) { + if (more_available) { + sbuf_append(eb->extra, "\n[ic-info](press page-down (or ctrl-j) to see all further completions)[/]"); + } + else { + sbuf_appendf(eb->extra, "\n[ic-info](press page-down (or ctrl-j) to see all %zd completions)[/]", count ); + } + } + if (!env->complete_nopreview && selected >= 0 && selected <= count_displayed) { + edit_complete(env,eb,selected); + editor_undo_restore(eb,false); + } + else { + edit_refresh(env, eb); + } + + // read here; if not a valid key, push it back and return to main event loop + code_t c = tty_read(env->tty); + if (tty_term_resize_event(env->tty)) { + edit_resize(env, eb); + } + sbuf_clear(eb->extra); + + // direct selection? + if (c >= '1' && c <= '9') { + ssize_t i = (c - '1'); + if (i < count) { + selected = i; + c = KEY_ENTER; + } + } + + // process commands + if (c == KEY_DOWN || c == KEY_TAB) { + selected++; + if (selected >= count_displayed) { + //term_beep(env->term); + selected = 0; + } + goto again; + } + else if (c == KEY_UP || c == KEY_SHIFT_TAB) { + selected--; + if (selected < 0) { + selected = count_displayed - 1; + //term_beep(env->term); + } + goto again; + } + else if (c == KEY_F1) { + edit_show_help(env, eb); + goto again; + } + else if (c == KEY_ESC) { + completions_clear(env->completions); + edit_refresh(env,eb); + c = 0; // ignore and return + } + else if (selected >= 0 && (c == KEY_ENTER || c == KEY_RIGHT || c == KEY_END)) /* || c == KEY_TAB*/ { + // select the current entry + assert(selected < count); + c = 0; + edit_complete(env, eb, selected); + if (env->complete_autotab) { + tty_code_pushback(env->tty,KEY_EVENT_AUTOTAB); // immediately try to complete again + } + } + else if (!env->complete_nopreview && !code_is_virt_key(c)) { + // if in preview mode, select the current entry and exit the menu + assert(selected < count); + edit_complete(env, eb, selected); + } + else if ((c == KEY_PAGEDOWN || c == KEY_LINEFEED) && count > 9) { + // show all completions + c = 0; + if (more_available) { + // generate all entries (up to the max (= 1000)) + count = completions_generate(env, env->completions, sbuf_string(eb->input), eb->pos, IC_MAX_COMPLETIONS_TO_SHOW); + } + rowcol_t rc; + edit_get_rowcol(env,eb,&rc); + edit_clear(env,eb); + edit_write_prompt(env,eb,0,false); + term_writeln(env->term, ""); + for(ssize_t i = 0; i < count; i++) { + const char* display = completions_get_display(env->completions, i, NULL); + if (display != NULL) { + bbcode_println(env->bbcode, display); + } + } + if (count >= IC_MAX_COMPLETIONS_TO_SHOW) { + bbcode_println(env->bbcode, "[ic-info]... and more.[/]"); + } + else { + bbcode_printf(env->bbcode, "[ic-info](%zd possible completions)[/]\n", count ); + } + for(ssize_t i = 0; i < rc.row+1; i++) { + term_write(env->term, " \n"); + } + eb->cur_rows = 0; + edit_refresh(env,eb); + } + else { + edit_refresh(env,eb); + } + // done + completions_clear(env->completions); + if (c != 0) tty_code_pushback(env->tty,c); +} + +static void edit_generate_completions(ic_env_t* env, editor_t* eb, bool autotab) { + debug_msg( "edit: complete: %zd: %s\n", eb->pos, sbuf_string(eb->input) ); + if (eb->pos < 0) return; + ssize_t count = completions_generate(env, env->completions, sbuf_string(eb->input), eb->pos, IC_MAX_COMPLETIONS_TO_TRY); + bool more_available = (count >= IC_MAX_COMPLETIONS_TO_TRY); + if (count <= 0) { + // no completions + if (!autotab) { term_beep(env->term); } + } + else if (count == 1) { + // complete if only one match + if (edit_complete(env,eb,0 /*idx*/) && env->complete_autotab) { + tty_code_pushback(env->tty,KEY_EVENT_AUTOTAB); + } + } + else { + //term_beep(env->term); + if (!more_available) { + edit_complete_longest_prefix(env,eb); + } + completions_sort(env->completions); + edit_completion_menu( env, eb, more_available); + } +} diff --git a/deps/isocline/src/editline_help.c b/deps/isocline/src/editline_help.c new file mode 100644 index 0000000..fa07d1d --- /dev/null +++ b/deps/isocline/src/editline_help.c @@ -0,0 +1,140 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +//------------------------------------------------------------- +// Help: this is included into editline.c +//------------------------------------------------------------- + +static const char* help[] = { + "","Navigation:", + "left," + "^b", "go one character to the left", + "right," + "^f", "go one character to the right", + "up", "go one row up, or back in the history", + "down", "go one row down, or forward in the history", + #ifdef __APPLE__ + "shift-left", + #else + "^left", + #endif + "go to the start of the previous word", + #ifdef __APPLE__ + "shift-right", + #else + "^right", + #endif + "go to the end the current word", + "home," + "^a", "go to the start of the current line", + "end," + "^e", "go to the end of the current line", + "pgup," + "^home", "go to the start of the current input", + "pgdn," + "^end", "go to the end of the current input", + "alt-m", "jump to matching brace", + "^p", "go back in the history", + "^n", "go forward in the history", + "^r,^s", "search the history starting with the current word", + "","", + + "", "Deletion:", + "del,^d", "delete the current character", + "backsp,^h", "delete the previous character", + "^w", "delete to preceding white space", + "alt-backsp", "delete to the start of the current word", + "alt-d", "delete to the end of the current word", + "^u", "delete to the start of the current line", + "^k", "delete to the end of the current line", + "esc", "delete the current input, or done with empty input", + "","", + + "", "Editing:", + "enter", "accept current input", + #ifndef __APPLE__ + "^enter, ^j", "", + "shift-tab", + #else + "shift-tab,^j", + #endif + "create a new line for multi-line input", + //" ", "(or type '\\' followed by enter)", + "^l", "clear screen", + "^t", "swap with previous character (move character backward)", + "^z,^_", "undo", + "^y", "redo", + //"^C", "done with empty input", + //"F1", "show this help", + "tab", "try to complete the current input", + "","", + "","In the completion menu:", + "enter,left", "use the currently selected completion", + "1 - 9", "use completion N from the menu", + "tab,down", "select the next completion", + "shift-tab,up","select the previous completion", + "esc", "exit menu without completing", + "pgdn,^j", "show all further possible completions", + "","", + "","In incremental history search:", + "enter", "use the currently found history entry", + "backsp," + "^z", "go back to the previous match (undo)", + "tab," + "^r", "find the next match", + "shift-tab," + "^s", "find an earlier match", + "esc", "exit search", + " ","", + NULL, NULL +}; + +static const char* help_initial = + "[ic-info]" + "Isocline v1.0, copyright (c) 2021 Daan Leijen.\n" + "This is free software; you can redistribute it and/or\n" + "modify it under the terms of the MIT License.\n" + "See <[url]https://github.com/daanx/isocline[/url]> for further information.\n" + "We use ^ as a shorthand for ctrl-.\n" + "\n" + "Overview:\n" + "\n[ansi-lightgray]" + " home,ctrl-a cursor end,ctrl-e\n" + " ┌────────────────┼───────────────┐ (navigate)\n" + //" │ │ │\n" + #ifndef __APPLE__ + " │ ctrl-left │ ctrl-right │\n" + #else + " │ alt-left │ alt-right │\n" + #endif + " │ ┌───────┼──────┐ │ ctrl-r : search history\n" + " ▼ ▼ ▼ ▼ ▼ tab : complete word\n" + " prompt> [ansi-darkgray]it's the quintessential language[/] shift-tab: insert new line\n" + " ▲ ▲ ▲ ▲ esc : delete input, done\n" + " │ └──────────────┘ │ ctrl-z : undo\n" + " │ alt-backsp alt-d │\n" + //" │ │ │\n" + " └────────────────────────────────┘ (delete)\n" + " ctrl-u ctrl-k\n" + "[/ansi-lightgray][/ic-info]\n"; + +static void edit_show_help(ic_env_t* env, editor_t* eb) { + edit_clear(env, eb); + bbcode_println(env->bbcode, help_initial); + for (ssize_t i = 0; help[i] != NULL && help[i+1] != NULL; i += 2) { + if (help[i][0] == 0) { + bbcode_printf(env->bbcode, "[ic-info]%s[/]\n", help[i+1]); + } + else { + bbcode_printf(env->bbcode, " [ic-emphasis]%-13s[/][ansi-lightgray]%s%s[/]\n", help[i], (help[i+1][0] == 0 ? "" : ": "), help[i+1]); + } + } + + eb->cur_rows = 0; + eb->cur_row = 0; + edit_refresh(env, eb); +} diff --git a/deps/isocline/src/editline_history.c b/deps/isocline/src/editline_history.c new file mode 100644 index 0000000..2a0afa1 --- /dev/null +++ b/deps/isocline/src/editline_history.c @@ -0,0 +1,260 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +//------------------------------------------------------------- +// History search: this file is included in editline.c +//------------------------------------------------------------- + +static void edit_history_at(ic_env_t* env, editor_t* eb, int ofs ) +{ + if (eb->modified) { + history_update(env->history, sbuf_string(eb->input)); // update first entry if modified + eb->history_idx = 0; // and start again + eb->modified = false; + } + const char* entry = history_get(env->history,eb->history_idx + ofs); + // debug_msg( "edit: history: at: %d + %d, found: %s\n", eb->history_idx, ofs, entry); + if (entry == NULL) { + term_beep(env->term); + } + else { + eb->history_idx += ofs; + sbuf_replace(eb->input, entry); + if (ofs > 0) { + // at end of first line when scrolling up + ssize_t end = sbuf_find_line_end(eb->input,0); + eb->pos = (end < 0 ? 0 : end); + } + else { + eb->pos = sbuf_len(eb->input); // at end of last line when scrolling down + } + edit_refresh(env, eb); + } +} + +static void edit_history_prev(ic_env_t* env, editor_t* eb) { + edit_history_at(env,eb, 1 ); +} + +static void edit_history_next(ic_env_t* env, editor_t* eb) { + edit_history_at(env,eb, -1 ); +} + +typedef struct hsearch_s { + struct hsearch_s* next; + ssize_t hidx; + ssize_t match_pos; + ssize_t match_len; + bool cinsert; +} hsearch_t; + +static void hsearch_push( alloc_t* mem, hsearch_t** hs, ssize_t hidx, ssize_t mpos, ssize_t mlen, bool cinsert ) { + hsearch_t* h = mem_zalloc_tp( mem, hsearch_t ); + if (h == NULL) return; + h->hidx = hidx; + h->match_pos = mpos; + h->match_len = mlen; + h->cinsert = cinsert; + h->next = *hs; + *hs = h; +} + +static bool hsearch_pop( alloc_t* mem, hsearch_t** hs, ssize_t* hidx, ssize_t* match_pos, ssize_t* match_len, bool* cinsert ) { + hsearch_t* h = *hs; + if (h == NULL) return false; + *hs = h->next; + if (hidx != NULL) *hidx = h->hidx; + if (match_pos != NULL) *match_pos = h->match_pos; + if (match_len != NULL) *match_len = h->match_len; + if (cinsert != NULL) *cinsert = h->cinsert; + mem_free(mem, h); + return true; +} + +static void hsearch_done( alloc_t* mem, hsearch_t* hs ) { + while (hs != NULL) { + hsearch_t* next = hs->next; + mem_free(mem, hs); + hs = next; + } +} + +static void edit_history_search(ic_env_t* env, editor_t* eb, char* initial ) { + if (history_count( env->history ) <= 0) { + term_beep(env->term); + return; + } + + // update history + if (eb->modified) { + history_update(env->history, sbuf_string(eb->input)); // update first entry if modified + eb->history_idx = 0; // and start again + eb->modified = false; + } + + // set a search prompt and remember the previous state + editor_undo_capture(eb); + eb->disable_undo = true; + bool old_hint = ic_enable_hint(false); + const char* prompt_text = eb->prompt_text; + eb->prompt_text = "history search"; + + // search state + hsearch_t* hs = NULL; // search undo + ssize_t hidx = 1; // current history entry + ssize_t match_pos = 0; // current matched position + ssize_t match_len = 0; // length of the match + const char* hentry = NULL; // current history entry + + // Simulate per character searches for each letter in `initial` (so backspace works) + if (initial != NULL) { + const ssize_t initial_len = ic_strlen(initial); + ssize_t ipos = 0; + while( ipos < initial_len ) { + ssize_t next = str_next_ofs( initial, initial_len, ipos, NULL ); + if (next < 0) break; + hsearch_push( eb->mem, &hs, hidx, match_pos, match_len, true); + char c = initial[ipos + next]; // terminate temporarily + initial[ipos + next] = 0; + if (history_search( env->history, hidx, initial, true, &hidx, &match_pos )) { + match_len = ipos + next; + } + else if (ipos + next >= initial_len) { + term_beep(env->term); + } + initial[ipos + next] = c; // restore + ipos += next; + } + sbuf_replace( eb->input, initial); + eb->pos = ipos; + } + else { + sbuf_clear( eb->input ); + eb->pos = 0; + } + + // Incremental search +again: + hentry = history_get(env->history,hidx); + if (hentry != NULL) { + sbuf_appendf(eb->extra, "[ic-info]%zd. [/][ic-diminish][!pre]", hidx); + sbuf_append_n( eb->extra, hentry, match_pos ); + sbuf_append(eb->extra, "[/pre][u ic-emphasis][!pre]" ); + sbuf_append_n( eb->extra, hentry + match_pos, match_len ); + sbuf_append(eb->extra, "[/pre][/u][!pre]" ); + sbuf_append(eb->extra, hentry + match_pos + match_len ); + sbuf_append(eb->extra, "[/pre][/ic-diminish]"); + if (!env->no_help) { + sbuf_append(eb->extra, "\n[ic-info](use tab for the next match)[/]"); + } + sbuf_append(eb->extra, "\n" ); + } + edit_refresh(env, eb); + + // Wait for input + code_t c = (hentry == NULL ? KEY_ESC : tty_read(env->tty)); + if (tty_term_resize_event(env->tty)) { + edit_resize(env, eb); + } + sbuf_clear(eb->extra); + + // Process commands + if (c == KEY_ESC || c == KEY_BELL /* ^G */ || c == KEY_CTRL_C) { + c = 0; + eb->disable_undo = false; + editor_undo_restore(eb, false); + } + else if (c == KEY_ENTER) { + c = 0; + editor_undo_forget(eb); + sbuf_replace( eb->input, hentry ); + eb->pos = sbuf_len(eb->input); + eb->modified = false; + eb->history_idx = hidx; + } + else if (c == KEY_BACKSP || c == KEY_CTRL_Z) { + // undo last search action + bool cinsert; + if (hsearch_pop(env->mem,&hs, &hidx, &match_pos, &match_len, &cinsert)) { + if (cinsert) edit_backspace(env,eb); + } + goto again; + } + else if (c == KEY_CTRL_R || c == KEY_TAB || c == KEY_UP) { + // search backward + hsearch_push(env->mem, &hs, hidx, match_pos, match_len, false); + if (!history_search( env->history, hidx+1, sbuf_string(eb->input), true, &hidx, &match_pos )) { + hsearch_pop(env->mem,&hs,NULL,NULL,NULL,NULL); + term_beep(env->term); + }; + goto again; + } + else if (c == KEY_CTRL_S || c == KEY_SHIFT_TAB || c == KEY_DOWN) { + // search forward + hsearch_push(env->mem, &hs, hidx, match_pos, match_len, false); + if (!history_search( env->history, hidx-1, sbuf_string(eb->input), false, &hidx, &match_pos )) { + hsearch_pop(env->mem, &hs,NULL,NULL,NULL,NULL); + term_beep(env->term); + }; + goto again; + } + else if (c == KEY_F1) { + edit_show_help(env, eb); + goto again; + } + else { + // insert character and search further backward + char chr; + unicode_t uchr; + if (code_is_ascii_char(c,&chr)) { + hsearch_push(env->mem, &hs, hidx, match_pos, match_len, true); + edit_insert_char(env,eb,chr); + } + else if (code_is_unicode(c,&uchr)) { + hsearch_push(env->mem, &hs, hidx, match_pos, match_len, true); + edit_insert_unicode(env,eb,uchr); + } + else { + // ignore command + term_beep(env->term); + goto again; + } + // search for the new input + if (history_search( env->history, hidx, sbuf_string(eb->input), true, &hidx, &match_pos )) { + match_len = sbuf_len(eb->input); + } + else { + term_beep(env->term); + }; + goto again; + } + + // done + eb->disable_undo = false; + hsearch_done(env->mem,hs); + eb->prompt_text = prompt_text; + ic_enable_hint(old_hint); + edit_refresh(env,eb); + if (c != 0) tty_code_pushback(env->tty, c); +} + +// Start an incremental search with the current word +static void edit_history_search_with_current_word(ic_env_t* env, editor_t* eb) { + char* initial = NULL; + ssize_t start = sbuf_find_word_start( eb->input, eb->pos ); + if (start >= 0) { + const ssize_t next = sbuf_next(eb->input, start, NULL); + if (!ic_char_is_idletter(sbuf_string(eb->input) + start, (long)(next - start))) { + start = next; + } + if (start >= 0 && start < eb->pos) { + initial = mem_strndup(eb->mem, sbuf_string(eb->input) + start, eb->pos - start); + } + } + edit_history_search( env, eb, initial); + mem_free(env->mem, initial); +} diff --git a/deps/isocline/src/env.h b/deps/isocline/src/env.h new file mode 100644 index 0000000..edfc100 --- /dev/null +++ b/deps/isocline/src/env.h @@ -0,0 +1,60 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_ENV_H +#define IC_ENV_H + +#include "../include/isocline.h" +#include "common.h" +#include "term.h" +#include "tty.h" +#include "stringbuf.h" +#include "history.h" +#include "completions.h" +#include "bbcode.h" + +//------------------------------------------------------------- +// Environment +//------------------------------------------------------------- + +struct ic_env_s { + alloc_t* mem; // potential custom allocator + ic_env_t* next; // next environment (used for proper deallocation) + term_t* term; // terminal + tty_t* tty; // keyboard (NULL if stdin is a pipe, file, etc) + completions_t* completions; // current completions + history_t* history; // edit history + bbcode_t* bbcode; // print with bbcodes + const char* prompt_marker; // the prompt marker (defaults to "> ") + const char* cprompt_marker; // prompt marker for continuation lines (defaults to `prompt_marker`) + ic_highlight_fun_t* highlighter; // highlight callback + void* highlighter_arg; // user state for the highlighter. + const char* match_braces; // matching braces, e.g "()[]{}" + const char* auto_braces; // auto insertion braces, e.g "()[]{}\"\"''" + char multiline_eol; // character used for multiline input ("\") (set to 0 to disable) + bool initialized; // are we initialized? + bool noedit; // is rich editing possible (tty != NULL) + bool singleline_only; // allow only single line editing? + bool complete_nopreview; // do not show completion preview for each selection in the completion menu? + bool complete_autotab; // try to keep completing after a completion? + bool no_multiline_indent; // indent continuation lines to line up under the initial prompt + bool no_help; // show short help line for history search etc. + bool no_hint; // allow hinting? + bool no_highlight; // enable highlighting? + bool no_bracematch; // enable brace matching? + bool no_autobrace; // enable automatic brace insertion? + bool no_lscolors; // use LSCOLORS/LS_COLORS to colorize file name completions? + long hint_delay; // delay before displaying a hint in milliseconds +}; + +ic_private char* ic_editline(ic_env_t* env, const char* prompt_text); + +ic_private ic_env_t* ic_get_env(void); +ic_private const char* ic_env_get_auto_braces(ic_env_t* env); +ic_private const char* ic_env_get_match_braces(ic_env_t* env); + +#endif // IC_ENV_H diff --git a/deps/isocline/src/highlight.c b/deps/isocline/src/highlight.c new file mode 100644 index 0000000..59c7255 --- /dev/null +++ b/deps/isocline/src/highlight.c @@ -0,0 +1,259 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include +#include "common.h" +#include "term.h" +#include "stringbuf.h" +#include "attr.h" +#include "bbcode.h" + +//------------------------------------------------------------- +// Syntax highlighting +//------------------------------------------------------------- + +struct ic_highlight_env_s { + attrbuf_t* attrs; + const char* input; + ssize_t input_len; + bbcode_t* bbcode; + alloc_t* mem; + ssize_t cached_upos; // cached unicode position + ssize_t cached_cpos; // corresponding utf-8 byte position +}; + + +ic_private void highlight( alloc_t* mem, bbcode_t* bb, const char* s, attrbuf_t* attrs, ic_highlight_fun_t* highlighter, void* arg ) { + const ssize_t len = ic_strlen(s); + if (len <= 0) return; + attrbuf_set_at(attrs,0,len,attr_none()); // fill to length of s + if (highlighter != NULL) { + ic_highlight_env_t henv; + henv.attrs = attrs; + henv.input = s; + henv.input_len = len; + henv.bbcode = bb; + henv.mem = mem; + henv.cached_cpos = 0; + henv.cached_upos = 0; + (*highlighter)( &henv, s, arg ); + } +} + + +//------------------------------------------------------------- +// Client interface +//------------------------------------------------------------- + +static void pos_adjust( ic_highlight_env_t* henv, ssize_t* ppos, ssize_t* plen ) { + ssize_t pos = *ppos; + ssize_t len = *plen; + if (pos >= henv->input_len) return; + if (pos >= 0 && len >= 0) return; // already character positions + if (henv->input == NULL) return; + + if (pos < 0) { + // negative `pos` is used as the unicode character position (for easy interfacing with Haskell) + ssize_t upos = -pos; + ssize_t cpos = 0; + ssize_t ucount = 0; + if (henv->cached_upos <= upos) { // if we have a cached position, start from there + ucount = henv->cached_upos; + cpos = henv->cached_cpos; + } + while ( ucount < upos ) { + ssize_t next = str_next_ofs(henv->input, henv->input_len, cpos, NULL); + if (next <= 0) return; + ucount++; + cpos += next; + } + *ppos = pos = cpos; + // and cache it to avoid quadratic behavior + henv->cached_upos = upos; + henv->cached_cpos = cpos; + } + if (len < 0) { + // negative `len` is used as a unicode character length + len = -len; + ssize_t ucount = 0; + ssize_t clen = 0; + while (ucount < len) { + ssize_t next = str_next_ofs(henv->input, henv->input_len, pos + clen, NULL); + if (next <= 0) return; + ucount++; + clen += next; + } + *plen = len = clen; + // and update cache if possible + if (henv->cached_cpos == pos) { + henv->cached_upos += ucount; + henv->cached_cpos += clen; + } + } +} + +static void highlight_attr(ic_highlight_env_t* henv, ssize_t pos, ssize_t count, attr_t attr ) { + if (henv==NULL) return; + pos_adjust(henv,&pos,&count); + if (pos < 0 || count <= 0) return; + attrbuf_update_at(henv->attrs, pos, count, attr); +} + +ic_public void ic_highlight(ic_highlight_env_t* henv, long pos, long count, const char* style ) { + if (henv == NULL || style==NULL || style[0]==0 || pos < 0) return; + highlight_attr(henv,pos,count,bbcode_style( henv->bbcode, style )); +} + +ic_public void ic_highlight_formatted(ic_highlight_env_t* henv, const char* s, const char* fmt) { + if (s==NULL || s[0] == 0 || fmt==NULL) return; + attrbuf_t* attrs = attrbuf_new(henv->mem); + stringbuf_t* out = sbuf_new(henv->mem); // todo: avoid allocating out? + if (attrs!=NULL && out != NULL) { + bbcode_append( henv->bbcode, fmt, out, attrs); + const ssize_t len = ic_strlen(s); + if (sbuf_len(out) != len) { + debug_msg("highlight: formatted string content differs from the original input:\n original: %s\n formatted: %s\n", s, fmt); + } + for( ssize_t i = 0; i < len; i++) { + attrbuf_update_at(henv->attrs, i, 1, attrbuf_attr_at(attrs,i)); + } + } + sbuf_free(out); + attrbuf_free(attrs); +} + +//------------------------------------------------------------- +// Brace matching +//------------------------------------------------------------- +#define MAX_NESTING (64) + +typedef struct brace_s { + char close; + bool at_cursor; + ssize_t pos; +} brace_t; + +ic_private void highlight_match_braces(const char* s, attrbuf_t* attrs, ssize_t cursor_pos, const char* braces, attr_t match_attr, attr_t error_attr) +{ + brace_t open[MAX_NESTING+1]; + ssize_t nesting = 0; + const ssize_t brace_len = ic_strlen(braces); + for (long i = 0; i < ic_strlen(s); i++) { + const char c = s[i]; + // push open brace + bool found_open = false; + for (ssize_t b = 0; b < brace_len; b += 2) { + if (c == braces[b]) { + // open brace + if (nesting >= MAX_NESTING) return; // give up + open[nesting].close = braces[b+1]; + open[nesting].pos = i; + open[nesting].at_cursor = (i == cursor_pos - 1); + nesting++; + found_open = true; + break; + } + } + if (found_open) continue; + + // pop to closing brace and potentially highlight + for (ssize_t b = 1; b < brace_len; b += 2) { + if (c == braces[b]) { + // close brace + if (nesting <= 0) { + // unmatched close brace + attrbuf_update_at( attrs, i, 1, error_attr); + } + else { + // can we fix an unmatched brace where we can match by popping just one? + if (open[nesting-1].close != c && nesting > 1 && open[nesting-2].close == c) { + // assume previous open brace was wrong + attrbuf_update_at(attrs, open[nesting-1].pos, 1, error_attr); + nesting--; + } + if (open[nesting-1].close != c) { + // unmatched open brace + attrbuf_update_at( attrs, i, 1, error_attr); + } + else { + // matching brace + nesting--; + if (i == cursor_pos - 1 || (open[nesting].at_cursor && open[nesting].pos != i - 1)) { + // highlight matching brace + attrbuf_update_at(attrs, open[nesting].pos, 1, match_attr); + attrbuf_update_at(attrs, i, 1, match_attr); + } + } + } + break; + } + } + } + // note: don't mark further unmatched open braces as in error +} + + +ic_private ssize_t find_matching_brace(const char* s, ssize_t cursor_pos, const char* braces, bool* is_balanced) +{ + if (is_balanced != NULL) { *is_balanced = false; } + bool balanced = true; + ssize_t match = -1; + brace_t open[MAX_NESTING+1]; + ssize_t nesting = 0; + const ssize_t brace_len = ic_strlen(braces); + for (long i = 0; i < ic_strlen(s); i++) { + const char c = s[i]; + // push open brace + bool found_open = false; + for (ssize_t b = 0; b < brace_len; b += 2) { + if (c == braces[b]) { + // open brace + if (nesting >= MAX_NESTING) return -1; // give up + open[nesting].close = braces[b+1]; + open[nesting].pos = i; + open[nesting].at_cursor = (i == cursor_pos - 1); + nesting++; + found_open = true; + break; + } + } + if (found_open) continue; + + // pop to closing brace + for (ssize_t b = 1; b < brace_len; b += 2) { + if (c == braces[b]) { + // close brace + if (nesting <= 0) { + // unmatched close brace + balanced = false; + } + else { + if (open[nesting-1].close != c) { + // unmatched open brace + balanced = false; + } + else { + // matching brace + nesting--; + if (i == cursor_pos - 1) { + // found matching open brace + match = open[nesting].pos + 1; + } + else if (open[nesting].at_cursor) { + // found matching close brace + match = i + 1; + } + } + } + break; + } + } + } + if (nesting != 0) { balanced = false; } + if (is_balanced != NULL) { *is_balanced = balanced; } + return match; +} diff --git a/deps/isocline/src/highlight.h b/deps/isocline/src/highlight.h new file mode 100644 index 0000000..67da02f --- /dev/null +++ b/deps/isocline/src/highlight.h @@ -0,0 +1,24 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_HIGHLIGHT_H +#define IC_HIGHLIGHT_H + +#include "common.h" +#include "attr.h" +#include "term.h" +#include "bbcode.h" + +//------------------------------------------------------------- +// Syntax highlighting +//------------------------------------------------------------- + +ic_private void highlight( alloc_t* mem, bbcode_t* bb, const char* s, attrbuf_t* attrs, ic_highlight_fun_t* highlighter, void* arg ); +ic_private void highlight_match_braces(const char* s, attrbuf_t* attrs, ssize_t cursor_pos, const char* braces, attr_t match_attr, attr_t error_attr); +ic_private ssize_t find_matching_brace(const char* s, ssize_t cursor_pos, const char* braces, bool* is_balanced); + +#endif // IC_HIGHLIGHT_H diff --git a/deps/isocline/src/history.c b/deps/isocline/src/history.c new file mode 100644 index 0000000..440976a --- /dev/null +++ b/deps/isocline/src/history.c @@ -0,0 +1,269 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include +#include + +#include "../include/isocline.h" +#include "common.h" +#include "history.h" +#include "stringbuf.h" + +#define IC_MAX_HISTORY (200) + +struct history_s { + ssize_t count; // current number of entries in use + ssize_t len; // size of elems + const char** elems; // history items (up to count) + const char* fname; // history file + alloc_t* mem; + bool allow_duplicates; // allow duplicate entries? +}; + +ic_private history_t* history_new(alloc_t* mem) { + history_t* h = mem_zalloc_tp(mem,history_t); + h->mem = mem; + return h; +} + +ic_private void history_free(history_t* h) { + if (h == NULL) return; + history_clear(h); + if (h->len > 0) { + mem_free( h->mem, h->elems ); + h->elems = NULL; + h->len = 0; + } + mem_free(h->mem, h->fname); + h->fname = NULL; + mem_free(h->mem, h); // free ourselves +} + +ic_private bool history_enable_duplicates( history_t* h, bool enable ) { + bool prev = h->allow_duplicates; + h->allow_duplicates = enable; + return prev; +} + +ic_private ssize_t history_count(const history_t* h) { + return h->count; +} + +//------------------------------------------------------------- +// push/clear +//------------------------------------------------------------- + +ic_private bool history_update( history_t* h, const char* entry ) { + if (entry==NULL) return false; + history_remove_last(h); + history_push(h,entry); + //debug_msg("history: update: with %s; now at %s\n", entry, history_get(h,0)); + return true; +} + +static void history_delete_at( history_t* h, ssize_t idx ) { + if (idx < 0 || idx >= h->count) return; + mem_free(h->mem, h->elems[idx]); + for(ssize_t i = idx+1; i < h->count; i++) { + h->elems[i-1] = h->elems[i]; + } + h->count--; +} + +ic_private bool history_push( history_t* h, const char* entry ) { + if (h->len <= 0 || entry==NULL) return false; + // remove any older duplicate + if (!h->allow_duplicates) { + for( int i = 0; i < h->count; i++) { + if (strcmp(h->elems[i],entry) == 0) { + history_delete_at(h,i); + } + } + } + // insert at front + if (h->count == h->len) { + // delete oldest entry + history_delete_at(h,0); + } + assert(h->count < h->len); + h->elems[h->count] = mem_strdup(h->mem,entry); + h->count++; + return true; +} + + +static void history_remove_last_n( history_t* h, ssize_t n ) { + if (n <= 0) return; + if (n > h->count) n = h->count; + for( ssize_t i = h->count - n; i < h->count; i++) { + mem_free( h->mem, h->elems[i] ); + } + h->count -= n; + assert(h->count >= 0); +} + +ic_private void history_remove_last(history_t* h) { + history_remove_last_n(h,1); +} + +ic_private void history_clear(history_t* h) { + history_remove_last_n( h, h->count ); +} + +ic_private const char* history_get( const history_t* h, ssize_t n ) { + if (n < 0 || n >= h->count) return NULL; + return h->elems[h->count - n - 1]; +} + +ic_private bool history_search( const history_t* h, ssize_t from /*including*/, const char* search, bool backward, ssize_t* hidx, ssize_t* hpos ) { + const char* p = NULL; + ssize_t i; + if (backward) { + for( i = from; i < h->count; i++ ) { + p = strstr( history_get(h,i), search); + if (p != NULL) break; + } + } + else { + for( i = from; i >= 0; i-- ) { + p = strstr( history_get(h,i), search); + if (p != NULL) break; + } + } + if (p == NULL) return false; + if (hidx != NULL) *hidx = i; + if (hpos != NULL) *hpos = (p - history_get(h,i)); + return true; +} + +//------------------------------------------------------------- +// +//------------------------------------------------------------- + +ic_private void history_load_from(history_t* h, const char* fname, long max_entries ) { + history_clear(h); + h->fname = mem_strdup(h->mem,fname); + if (max_entries == 0) { + assert(h->elems == NULL); + return; + } + if (max_entries < 0 || max_entries > IC_MAX_HISTORY) max_entries = IC_MAX_HISTORY; + h->elems = (const char**)mem_zalloc_tp_n(h->mem, char*, max_entries ); + if (h->elems == NULL) return; + h->len = max_entries; + history_load(h); +} + + + + +//------------------------------------------------------------- +// save/load history to file +//------------------------------------------------------------- + +static char from_xdigit( int c ) { + if (c >= '0' && c <= '9') return (char)(c - '0'); + if (c >= 'A' && c <= 'F') return (char)(10 + (c - 'A')); + if (c >= 'a' && c <= 'f') return (char)(10 + (c - 'a')); + return 0; +} + +static char to_xdigit( uint8_t c ) { + if (c <= 9) return ((char)c + '0'); + if (c >= 10 && c <= 15) return ((char)c - 10 + 'A'); + return '0'; +} + +static bool ic_isxdigit( int c ) { + return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9')); +} + +static bool history_read_entry( history_t* h, FILE* f, stringbuf_t* sbuf ) { + sbuf_clear(sbuf); + while( !feof(f)) { + int c = fgetc(f); + if (c == EOF || c == '\n') break; + if (c == '\\') { + c = fgetc(f); + if (c == 'n') { sbuf_append(sbuf,"\n"); } + else if (c == 'r') { /* ignore */ } // sbuf_append(sbuf,"\r"); + else if (c == 't') { sbuf_append(sbuf,"\t"); } + else if (c == '\\') { sbuf_append(sbuf,"\\"); } + else if (c == 'x') { + int c1 = fgetc(f); + int c2 = fgetc(f); + if (ic_isxdigit(c1) && ic_isxdigit(c2)) { + char chr = from_xdigit(c1)*16 + from_xdigit(c2); + sbuf_append_char(sbuf,chr); + } + else return false; + } + else return false; + } + else sbuf_append_char(sbuf,(char)c); + } + if (sbuf_len(sbuf)==0 || sbuf_string(sbuf)[0] == '#') return true; + return history_push(h, sbuf_string(sbuf)); +} + +static bool history_write_entry( const char* entry, FILE* f, stringbuf_t* sbuf ) { + sbuf_clear(sbuf); + //debug_msg("history: write: %s\n", entry); + while( entry != NULL && *entry != 0 ) { + char c = *entry++; + if (c == '\\') { sbuf_append(sbuf,"\\\\"); } + else if (c == '\n') { sbuf_append(sbuf,"\\n"); } + else if (c == '\r') { /* ignore */ } // sbuf_append(sbuf,"\\r"); } + else if (c == '\t') { sbuf_append(sbuf,"\\t"); } + else if (c < ' ' || c > '~' || c == '#') { + char c1 = to_xdigit( (uint8_t)c / 16 ); + char c2 = to_xdigit( (uint8_t)c % 16 ); + sbuf_append(sbuf,"\\x"); + sbuf_append_char(sbuf,c1); + sbuf_append_char(sbuf,c2); + } + else sbuf_append_char(sbuf,c); + } + //debug_msg("history: write buf: %s\n", sbuf_string(sbuf)); + + if (sbuf_len(sbuf) > 0) { + sbuf_append(sbuf,"\n"); + fputs(sbuf_string(sbuf),f); + } + return true; +} + +ic_private void history_load( history_t* h ) { + if (h->fname == NULL) return; + FILE* f = fopen(h->fname, "r"); + if (f == NULL) return; + stringbuf_t* sbuf = sbuf_new(h->mem); + if (sbuf != NULL) { + while (!feof(f)) { + if (!history_read_entry(h,f,sbuf)) break; // error + } + sbuf_free(sbuf); + } + fclose(f); +} + +ic_private void history_save( const history_t* h ) { + if (h->fname == NULL) return; + FILE* f = fopen(h->fname, "w"); + if (f == NULL) return; + #ifndef _WIN32 + chmod(h->fname,S_IRUSR|S_IWUSR); + #endif + stringbuf_t* sbuf = sbuf_new(h->mem); + if (sbuf != NULL) { + for( int i = 0; i < h->count; i++ ) { + if (!history_write_entry(h->elems[i],f,sbuf)) break; // error + } + sbuf_free(sbuf); + } + fclose(f); +} diff --git a/deps/isocline/src/history.h b/deps/isocline/src/history.h new file mode 100644 index 0000000..76a3716 --- /dev/null +++ b/deps/isocline/src/history.h @@ -0,0 +1,38 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_HISTORY_H +#define IC_HISTORY_H + +#include "common.h" + +//------------------------------------------------------------- +// History +//------------------------------------------------------------- + +struct history_s; +typedef struct history_s history_t; + +ic_private history_t* history_new(alloc_t* mem); +ic_private void history_free(history_t* h); +ic_private void history_clear(history_t* h); +ic_private bool history_enable_duplicates( history_t* h, bool enable ); +ic_private ssize_t history_count(const history_t* h); + +ic_private void history_load_from(history_t* h, const char* fname, long max_entries); +ic_private void history_load( history_t* h ); +ic_private void history_save( const history_t* h ); + +ic_private bool history_push( history_t* h, const char* entry ); +ic_private bool history_update( history_t* h, const char* entry ); +ic_private const char* history_get( const history_t* h, ssize_t n ); +ic_private void history_remove_last(history_t* h); + +ic_private bool history_search( const history_t* h, ssize_t from, const char* search, bool backward, ssize_t* hidx, ssize_t* hpos); + + +#endif // IC_HISTORY_H diff --git a/deps/isocline/src/isocline.c b/deps/isocline/src/isocline.c new file mode 100644 index 0000000..8b6055c --- /dev/null +++ b/deps/isocline/src/isocline.c @@ -0,0 +1,594 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +//------------------------------------------------------------- +// Usually we include all sources one file so no internal +// symbols are public in the libray. +// +// You can compile the entire library just as: +// $ gcc -c src/isocline.c +//------------------------------------------------------------- +#if !defined(IC_SEPARATE_OBJS) +# ifndef _CRT_NONSTDC_NO_WARNINGS +# define _CRT_NONSTDC_NO_WARNINGS // for msvc +# endif +# ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS // for msvc +# endif +# define _XOPEN_SOURCE 700 // for wcwidth +# define _DEFAULT_SOURCE // ensure usleep stays visible with _XOPEN_SOURCE >= 700 +# include "attr.c" +# include "bbcode.c" +# include "editline.c" +# include "highlight.c" +# include "undo.c" +# include "history.c" +# include "completers.c" +# include "completions.c" +# include "term.c" +# include "tty_esc.c" +# include "tty.c" +# include "stringbuf.c" +# include "common.c" +#endif + +//------------------------------------------------------------- +// includes +//------------------------------------------------------------- +#include +#include +#include +#include + +#include "../include/isocline.h" +#include "common.h" +#include "env.h" + + +//------------------------------------------------------------- +// Readline +//------------------------------------------------------------- + +static char* ic_getline( alloc_t* mem ); + +ic_public char* ic_readline(const char* prompt_text) +{ + ic_env_t* env = ic_get_env(); + if (env == NULL) return NULL; + if (!env->noedit) { + // terminal editing enabled + return ic_editline(env, prompt_text); // in editline.c + } + else { + // no editing capability (pipe, dumb terminal, etc) + if (env->tty != NULL && env->term != NULL) { + // if the terminal is not interactive, but we are reading from the tty (keyboard), we display a prompt + term_start_raw(env->term); // set utf8 mode on windows + if (prompt_text != NULL) { + term_write(env->term, prompt_text); + } + term_write(env->term, env->prompt_marker); + term_end_raw(env->term, false); + } + // read directly from stdin + return ic_getline(env->mem); + } +} + + +//------------------------------------------------------------- +// Read a line from the stdin stream if there is no editing +// support (like from a pipe, file, or dumb terminal). +//------------------------------------------------------------- + +static char* ic_getline(alloc_t* mem) +{ + // read until eof or newline + stringbuf_t* sb = sbuf_new(mem); + int c; + while (true) { + c = fgetc(stdin); + if (c==EOF || c=='\n') { + break; + } + else { + sbuf_append_char(sb, (char)c); + } + } + return sbuf_free_dup(sb); +} + + +//------------------------------------------------------------- +// Formatted output +//------------------------------------------------------------- + + +ic_public void ic_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + ic_vprintf(fmt, ap); + va_end(ap); +} + +ic_public void ic_vprintf(const char* fmt, va_list args) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode == NULL) return; + bbcode_vprintf(env->bbcode, fmt, args); +} + +ic_public void ic_print(const char* s) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return; + bbcode_print(env->bbcode, s); +} + +ic_public void ic_println(const char* s) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return; + bbcode_println(env->bbcode, s); +} + +void ic_style_def(const char* name, const char* fmt) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return; + bbcode_style_def(env->bbcode, name, fmt); +} + +void ic_style_open(const char* fmt) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return; + bbcode_style_open(env->bbcode, fmt); +} + +void ic_style_close(void) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->bbcode==NULL) return; + bbcode_style_close(env->bbcode, NULL); +} + + +//------------------------------------------------------------- +// Interface +//------------------------------------------------------------- + +ic_public bool ic_async_stop(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + if (env->tty==NULL) return false; + return tty_async_stop(env->tty); +} + +static void set_prompt_marker(ic_env_t* env, const char* prompt_marker, const char* cprompt_marker) { + if (prompt_marker == NULL) prompt_marker = "> "; + if (cprompt_marker == NULL) cprompt_marker = prompt_marker; + mem_free(env->mem, env->prompt_marker); + mem_free(env->mem, env->cprompt_marker); + env->prompt_marker = mem_strdup(env->mem, prompt_marker); + env->cprompt_marker = mem_strdup(env->mem, cprompt_marker); +} + +ic_public const char* ic_get_prompt_marker(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return NULL; + return env->prompt_marker; +} + +ic_public const char* ic_get_continuation_prompt_marker(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return NULL; + return env->cprompt_marker; +} + +ic_public void ic_set_prompt_marker( const char* prompt_marker, const char* cprompt_marker ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + set_prompt_marker(env, prompt_marker, cprompt_marker); +} + +ic_public bool ic_enable_multiline( bool enable ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->singleline_only; + env->singleline_only = !enable; + return !prev; +} + +ic_public bool ic_enable_beep( bool enable ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + return term_enable_beep(env->term, enable); +} + +ic_public bool ic_enable_color( bool enable ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + return term_enable_color( env->term, enable ); +} + +ic_public bool ic_enable_history_duplicates( bool enable ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + return history_enable_duplicates(env->history, enable); +} + +ic_public void ic_set_history(const char* fname, long max_entries ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + history_load_from(env->history, fname, max_entries ); +} + +ic_public void ic_history_remove_last(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + history_remove_last(env->history); +} + +ic_public void ic_history_add( const char* entry ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + history_push( env->history, entry ); +} + +ic_public void ic_history_clear(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + history_clear(env->history); +} + +ic_public bool ic_enable_auto_tab( bool enable ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->complete_autotab; + env->complete_autotab = enable; + return prev; +} + +ic_public bool ic_enable_completion_preview( bool enable ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->complete_nopreview; + env->complete_nopreview = !enable; + return !prev; +} + +ic_public bool ic_enable_multiline_indent(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->no_multiline_indent; + env->no_multiline_indent = !enable; + return !prev; +} + +ic_public bool ic_enable_hint(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->no_hint; + env->no_hint = !enable; + return !prev; +} + +ic_public long ic_set_hint_delay(long delay_ms) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + long prev = env->hint_delay; + env->hint_delay = (delay_ms < 0 ? 0 : (delay_ms > 5000 ? 5000 : delay_ms)); + return prev; +} + +ic_public void ic_set_tty_esc_delay(long initial_delay_ms, long followup_delay_ms ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->tty == NULL) return; + tty_set_esc_delay(env->tty, initial_delay_ms, followup_delay_ms); +} + + +ic_public bool ic_enable_highlight(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->no_highlight; + env->no_highlight = !enable; + return !prev; +} + +ic_public bool ic_enable_inline_help(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->no_help; + env->no_help = !enable; + return !prev; +} + +ic_public bool ic_enable_brace_matching(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->no_bracematch; + env->no_bracematch = !enable; + return !prev; +} + +ic_public void ic_set_matching_braces(const char* brace_pairs) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + mem_free(env->mem, env->match_braces); + env->match_braces = NULL; + if (brace_pairs != NULL) { + ssize_t len = ic_strlen(brace_pairs); + if (len > 0 && (len % 2) == 0) { + env->match_braces = mem_strdup(env->mem, brace_pairs); + } + } +} + +ic_public bool ic_enable_brace_insertion(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL) return false; + bool prev = env->no_autobrace; + env->no_autobrace = !enable; + return !prev; +} + +ic_public void ic_set_insertion_braces(const char* brace_pairs) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + mem_free(env->mem, env->auto_braces); + env->auto_braces = NULL; + if (brace_pairs != NULL) { + ssize_t len = ic_strlen(brace_pairs); + if (len > 0 && (len % 2) == 0) { + env->auto_braces = mem_strdup(env->mem, brace_pairs); + } + } +} + +ic_private const char* ic_env_get_match_braces(ic_env_t* env) { + return (env->match_braces == NULL ? "()[]{}" : env->match_braces); +} + +ic_private const char* ic_env_get_auto_braces(ic_env_t* env) { + return (env->auto_braces == NULL ? "()[]{}\"\"''" : env->auto_braces); +} + +ic_public void ic_set_default_highlighter(ic_highlight_fun_t* highlighter, void* arg) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + env->highlighter = highlighter; + env->highlighter_arg = arg; +} + + +ic_public void ic_free( void* p ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + mem_free(env->mem, p); +} + +ic_public void* ic_malloc(size_t sz) { + ic_env_t* env = ic_get_env(); if (env==NULL) return NULL; + return mem_malloc(env->mem, to_ssize_t(sz)); +} + +ic_public const char* ic_strdup( const char* s ) { + if (s==NULL) return NULL; + ic_env_t* env = ic_get_env(); if (env==NULL) return NULL; + ssize_t len = ic_strlen(s); + char* p = mem_malloc_tp_n( env->mem, char, len + 1 ); + if (p == NULL) return NULL; + ic_memcpy( p, s, len ); + p[len] = 0; + return p; +} + +//------------------------------------------------------------- +// Terminal +//------------------------------------------------------------- + +ic_public void ic_term_init(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term==NULL) return; + term_start_raw(env->term); +} + +ic_public void ic_term_done(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term==NULL) return; + term_end_raw(env->term,false); +} + +ic_public void ic_term_flush(void) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term==NULL) return; + term_flush(env->term); +} + +ic_public void ic_term_write(const char* s) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term == NULL) return; + term_write(env->term, s); +} + +ic_public void ic_term_writeln(const char* s) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term == NULL) return; + term_writeln(env->term, s); +} + +ic_public void ic_term_writef(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + ic_term_vwritef(fmt, ap); + va_end(ap); +} + +ic_public void ic_term_vwritef(const char* fmt, va_list args) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term == NULL) return; + term_vwritef(env->term, fmt, args); +} + +ic_public void ic_term_reset( void ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term == NULL) return; + term_attr_reset(env->term); +} + +ic_public void ic_term_style( const char* style ) { + ic_env_t* env = ic_get_env(); if (env==NULL) return; + if (env->term == NULL || env->bbcode == NULL) return; + term_set_attr( env->term, bbcode_style(env->bbcode, style)); +} + +ic_public int ic_term_get_color_bits(void) { + ic_env_t* env = ic_get_env(); + if (env==NULL || env->term==NULL) return 4; + return term_get_color_bits(env->term); +} + +ic_public void ic_term_bold(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return; + term_bold(env->term, enable); +} + +ic_public void ic_term_underline(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return; + term_underline(env->term, enable); +} + +ic_public void ic_term_italic(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return; + term_italic(env->term, enable); +} + +ic_public void ic_term_reverse(bool enable) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return; + term_reverse(env->term, enable); +} + +ic_public void ic_term_color_ansi(bool foreground, int ansi_color) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return; + ic_color_t color = color_from_ansi256(ansi_color); + if (foreground) { term_color(env->term, color); } + else { term_bgcolor(env->term, color); } +} + +ic_public void ic_term_color_rgb(bool foreground, uint32_t hcolor) { + ic_env_t* env = ic_get_env(); if (env==NULL || env->term==NULL) return; + ic_color_t color = ic_rgb(hcolor); + if (foreground) { term_color(env->term, color); } + else { term_bgcolor(env->term, color); } +} + + +//------------------------------------------------------------- +// Readline with temporary completer and highlighter +//------------------------------------------------------------- + +ic_public char* ic_readline_ex(const char* prompt_text, + ic_completer_fun_t* completer, void* completer_arg, + ic_highlight_fun_t* highlighter, void* highlighter_arg ) +{ + ic_env_t* env = ic_get_env(); if (env == NULL) return NULL; + // save previous + ic_completer_fun_t* prev_completer; + void* prev_completer_arg; + completions_get_completer(env->completions, &prev_completer, &prev_completer_arg); + ic_highlight_fun_t* prev_highlighter = env->highlighter; + void* prev_highlighter_arg = env->highlighter_arg; + // call with current + if (completer != NULL) { ic_set_default_completer(completer, completer_arg); } + if (highlighter != NULL) { ic_set_default_highlighter(highlighter, highlighter_arg); } + char* res = ic_readline(prompt_text); + // restore previous + ic_set_default_completer(prev_completer, prev_completer_arg); + ic_set_default_highlighter(prev_highlighter, prev_highlighter_arg); + return res; +} + + +//------------------------------------------------------------- +// Initialize +//------------------------------------------------------------- + +static void ic_atexit(void); + +static void ic_env_free(ic_env_t* env) { + if (env == NULL) return; + history_save(env->history); + history_free(env->history); + completions_free(env->completions); + bbcode_free(env->bbcode); + term_free(env->term); + tty_free(env->tty); + mem_free(env->mem, env->cprompt_marker); + mem_free(env->mem,env->prompt_marker); + mem_free(env->mem, env->match_braces); + mem_free(env->mem, env->auto_braces); + env->prompt_marker = NULL; + + // and deallocate ourselves + alloc_t* mem = env->mem; + mem_free(mem, env); + + // and finally the custom memory allocation structure + mem_free(mem, mem); +} + + +static ic_env_t* ic_env_create( ic_malloc_fun_t* _malloc, ic_realloc_fun_t* _realloc, ic_free_fun_t* _free ) +{ + if (_malloc == NULL) _malloc = &malloc; + if (_realloc == NULL) _realloc = &realloc; + if (_free == NULL) _free = &free; + // allocate + alloc_t* mem = (alloc_t*)_malloc(sizeof(alloc_t)); + if (mem == NULL) return NULL; + mem->malloc = _malloc; + mem->realloc = _realloc; + mem->free = _free; + ic_env_t* env = mem_zalloc_tp(mem, ic_env_t); + if (env==NULL) { + mem->free(mem); + return NULL; + } + env->mem = mem; + + // Initialize + env->tty = tty_new(env->mem, -1); // can return NULL + env->term = term_new(env->mem, env->tty, false, false, -1 ); + env->history = history_new(env->mem); + env->completions = completions_new(env->mem); + env->bbcode = bbcode_new(env->mem, env->term); + env->hint_delay = 400; + + if (env->tty == NULL || env->term==NULL || + env->completions == NULL || env->history == NULL || env->bbcode == NULL || + !term_is_interactive(env->term)) + { + env->noedit = true; + } + env->multiline_eol = '\\'; + + bbcode_style_def(env->bbcode, "ic-prompt", "ansi-green" ); + bbcode_style_def(env->bbcode, "ic-info", "ansi-darkgray" ); + bbcode_style_def(env->bbcode, "ic-diminish", "ansi-lightgray" ); + bbcode_style_def(env->bbcode, "ic-emphasis", "#ffffd7" ); + bbcode_style_def(env->bbcode, "ic-hint", "ansi-darkgray" ); + bbcode_style_def(env->bbcode, "ic-error", "#d70000" ); + bbcode_style_def(env->bbcode, "ic-bracematch","ansi-white"); // color = #F7DC6F" ); + + bbcode_style_def(env->bbcode, "keyword", "#569cd6" ); + bbcode_style_def(env->bbcode, "control", "#c586c0" ); + bbcode_style_def(env->bbcode, "number", "#b5cea8" ); + bbcode_style_def(env->bbcode, "string", "#ce9178" ); + bbcode_style_def(env->bbcode, "comment", "#6A9955" ); + bbcode_style_def(env->bbcode, "type", "darkcyan" ); + bbcode_style_def(env->bbcode, "constant", "#569cd6" ); + + set_prompt_marker(env, NULL, NULL); + return env; +} + +static ic_env_t* rpenv; + +static void ic_atexit(void) { + if (rpenv != NULL) { + ic_env_free(rpenv); + rpenv = NULL; + } +} + +ic_private ic_env_t* ic_get_env(void) { + if (rpenv==NULL) { + rpenv = ic_env_create( NULL, NULL, NULL ); + if (rpenv != NULL) { atexit( &ic_atexit ); } + } + return rpenv; +} + +ic_public void ic_init_custom_malloc( ic_malloc_fun_t* _malloc, ic_realloc_fun_t* _realloc, ic_free_fun_t* _free ) { + assert(rpenv == NULL); + if (rpenv != NULL) { + ic_env_free(rpenv); + rpenv = ic_env_create( _malloc, _realloc, _free ); + } + else { + rpenv = ic_env_create( _malloc, _realloc, _free ); + if (rpenv != NULL) { + atexit( &ic_atexit ); + } + } +} + diff --git a/deps/isocline/src/stringbuf.c b/deps/isocline/src/stringbuf.c new file mode 100644 index 0000000..7bbfad0 --- /dev/null +++ b/deps/isocline/src/stringbuf.c @@ -0,0 +1,1038 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +// get `wcwidth` for the column width of unicode characters +// note: for now the OS provided one is unused as we see quite a bit of variation +// among platforms and including our own seems more reliable. +/* +#if defined(__linux__) || defined(__freebsd__) +// use the system supplied one +#if !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 700 // so wcwidth is visible +#endif +#include +#else +*/ +// use our own (also on APPLE as that fails within vscode) +#define wcwidth(c) mk_wcwidth(c) +#include "wcwidth.c" +// #endif + +#include +#include +#include + +#include "common.h" +#include "stringbuf.h" + +//------------------------------------------------------------- +// In place growable utf-8 strings +//------------------------------------------------------------- + +struct stringbuf_s { + char* buf; + ssize_t buflen; + ssize_t count; + alloc_t* mem; +}; + + +//------------------------------------------------------------- +// String column width +//------------------------------------------------------------- + +// column width of a utf8 single character sequence. +static ssize_t utf8_char_width( const char* s, ssize_t n ) { + if (n <= 0) return 0; + + uint8_t b = (uint8_t)s[0]; + int32_t c; + if (b < ' ') { + return 0; + } + else if (b <= 0x7F) { + return 1; + } + else if (b <= 0xC1) { // invalid continuation byte or invalid 0xC0, 0xC1 (check is strictly not necessary as we don't validate..) + return 1; + } + else if (b <= 0xDF && n >= 2) { // b >= 0xC2 // 2 bytes + c = (((b & 0x1F) << 6) | (s[1] & 0x3F)); + assert(c < 0xD800 || c > 0xDFFF); + int w = wcwidth(c); + return w; + } + else if (b <= 0xEF && n >= 3) { // b >= 0xE0 // 3 bytes + c = (((b & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F)); + return wcwidth(c); + } + else if (b <= 0xF4 && n >= 4) { // b >= 0xF0 // 4 bytes + c = (((b & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F)); + return wcwidth(c); + } + else { + // failed + return 1; + } +} + + +// The column width of a codepoint (0, 1, or 2) +static ssize_t char_column_width( const char* s, ssize_t n ) { + if (s == NULL || n <= 0) return 0; + else if ((uint8_t)(*s) < ' ') return 0; // also for CSI escape sequences + else { + ssize_t w = utf8_char_width(s, n); + #ifdef _WIN32 + return (w <= 0 ? 1 : w); // windows console seems to use at least one column + #else + return w; + #endif + } +} + +static ssize_t str_column_width_n( const char* s, ssize_t len ) { + if (s == NULL || len <= 0) return 0; + ssize_t pos = 0; + ssize_t cwidth = 0; + ssize_t cw; + ssize_t ofs; + while (s[pos] != 0 && (ofs = str_next_ofs(s, len, pos, &cw)) > 0) { + cwidth += cw; + pos += ofs; + } + return cwidth; +} + +ic_private ssize_t str_column_width( const char* s ) { + return str_column_width_n( s, ic_strlen(s) ); +} + +ic_private ssize_t str_skip_until_fit( const char* s, ssize_t max_width ) { + if (s == NULL) return 0; + ssize_t cwidth = str_column_width(s); + ssize_t len = ic_strlen(s); + ssize_t pos = 0; + ssize_t next; + ssize_t cw; + while (cwidth > max_width && (next = str_next_ofs(s, len, pos, &cw)) > 0) { + cwidth -= cw; + pos += next; + } + return pos; +} + +ic_private ssize_t str_take_while_fit( const char* s, ssize_t max_width) { + if (s == NULL) return 0; + const ssize_t len = ic_strlen(s); + ssize_t pos = 0; + ssize_t next; + ssize_t cw; + ssize_t cwidth = 0; + while ((next = str_next_ofs(s, len, pos, &cw)) > 0) { + if (cwidth + cw > max_width) break; + cwidth += cw; + pos += next; + } + return pos; +} + + +//------------------------------------------------------------- +// String navigation +//------------------------------------------------------------- + +// get offset of the previous codepoint. does not skip back over CSI sequences. +ic_private ssize_t str_prev_ofs( const char* s, ssize_t pos, ssize_t* width ) { + ssize_t ofs = 0; + if (s != NULL && pos > 0) { + ofs = 1; + while (pos > ofs) { + uint8_t u = (uint8_t)s[pos - ofs]; + if (u < 0x80 || u > 0xBF) break; // continue while follower + ofs++; + } + } + if (width != NULL) *width = char_column_width( s+(pos-ofs), ofs ); + return ofs; +} + +// skip an escape sequence +// +ic_private bool skip_esc( const char* s, ssize_t len, ssize_t* esclen ) { + if (s == NULL || len <= 1 || s[0] != '\x1B') return false; + if (esclen != NULL) *esclen = 0; + if (strchr("[PX^_]",s[1]) != NULL) { + // CSI (ESC [), DCS (ESC P), SOS (ESC X), PM (ESC ^), APC (ESC _), and OSC (ESC ]): terminated with a special sequence + bool finalCSI = (s[1] == '['); // CSI terminates with 0x40-0x7F; otherwise ST (bell or ESC \) + ssize_t n = 2; + while (len > n) { + char c = s[n++]; + if ((finalCSI && (uint8_t)c >= 0x40 && (uint8_t)c <= 0x7F) || // terminating byte: @A–Z[\]^_`a–z{|}~ + (!finalCSI && c == '\x07') || // bell + (c == '\x02')) // STX terminates as well + { + if (esclen != NULL) *esclen = n; + return true; + } + else if (!finalCSI && c == '\x1B' && len > n && s[n] == '\\') { // ST (ESC \) + n++; + if (esclen != NULL) *esclen = n; + return true; + } + } + } + if (strchr(" #%()*+",s[1]) != NULL) { + // assume escape sequence of length 3 (like ESC % G) + if (esclen != NULL) *esclen = 2; + return true; + } + else { + // assume single character escape code (like ESC 7) + if (esclen != NULL) *esclen = 2; + return true; + } + return false; +} + +// Offset to the next codepoint, treats CSI escape sequences as a single code point. +ic_private ssize_t str_next_ofs( const char* s, ssize_t len, ssize_t pos, ssize_t* cwidth ) { + ssize_t ofs = 0; + if (s != NULL && len > pos) { + if (skip_esc(s+pos,len-pos,&ofs)) { + // skip escape sequence + } + else { + ofs = 1; + // utf8 extended character? + while(len > pos + ofs) { + uint8_t u = (uint8_t)s[pos + ofs]; + if (u < 0x80 || u > 0xBF) break; // break if not a follower + ofs++; + } + } + } + if (cwidth != NULL) *cwidth = char_column_width( s+pos, ofs ); + return ofs; +} + +static ssize_t str_limit_to_length( const char* s, ssize_t n ) { + ssize_t i; + for(i = 0; i < n && s[i] != 0; i++) { /* nothing */ } + return i; +} + + +//------------------------------------------------------------- +// String searching prev/next word, line, ws_word +//------------------------------------------------------------- + + +static ssize_t str_find_backward( const char* s, ssize_t len, ssize_t pos, ic_is_char_class_fun_t* match, bool skip_immediate_matches ) { + if (pos > len) pos = len; + if (pos < 0) pos = 0; + ssize_t i = pos; + // skip matching first (say, whitespace in case of the previous start-of-word) + if (skip_immediate_matches) { + do { + ssize_t prev = str_prev_ofs(s, i, NULL); + if (prev <= 0) break; + assert(i - prev >= 0); + if (!match(s + i - prev, (long)prev)) break; + i -= prev; + } while (i > 0); + } + // find match + do { + ssize_t prev = str_prev_ofs(s, i, NULL); + if (prev <= 0) break; + assert(i - prev >= 0); + if (match(s + i - prev, (long)prev)) { + return i; // found; + } + i -= prev; + } while (i > 0); + return -1; // not found +} + +static ssize_t str_find_forward( const char* s, ssize_t len, ssize_t pos, ic_is_char_class_fun_t* match, bool skip_immediate_matches ) { + if (s == NULL || len < 0) return -1; + if (pos > len) pos = len; + if (pos < 0) pos = 0; + ssize_t i = pos; + ssize_t next; + // skip matching first (say, whitespace in case of the next end-of-word) + if (skip_immediate_matches) { + do { + next = str_next_ofs(s, len, i, NULL); + if (next <= 0) break; + assert( i + next <= len); + if (!match(s + i, (long)next)) break; + i += next; + } while (i < len); + } + // and then look + do { + next = str_next_ofs(s, len, i, NULL); + if (next <= 0) break; + assert( i + next <= len); + if (match(s + i, (long)next)) { + return i; // found + } + i += next; + } while (i < len); + return -1; +} + +static bool char_is_linefeed( const char* s, long n ) { + return (n == 1 && (*s == '\n' || *s == 0)); +} + +static ssize_t str_find_line_start( const char* s, ssize_t len, ssize_t pos) { + ssize_t start = str_find_backward(s,len,pos,&char_is_linefeed,false /* don't skip immediate matches */); + return (start < 0 ? 0 : start); +} + +static ssize_t str_find_line_end( const char* s, ssize_t len, ssize_t pos) { + ssize_t end = str_find_forward(s,len,pos, &char_is_linefeed, false); + return (end < 0 ? len : end); +} + +static ssize_t str_find_word_start( const char* s, ssize_t len, ssize_t pos) { + ssize_t start = str_find_backward(s,len,pos, &ic_char_is_idletter,true /* skip immediate matches */); + return (start < 0 ? 0 : start); +} + +static ssize_t str_find_word_end( const char* s, ssize_t len, ssize_t pos) { + ssize_t end = str_find_forward(s,len,pos,&ic_char_is_idletter,true /* skip immediate matches */); + return (end < 0 ? len : end); +} + +static ssize_t str_find_ws_word_start( const char* s, ssize_t len, ssize_t pos) { + ssize_t start = str_find_backward(s,len,pos,&ic_char_is_white,true /* skip immediate matches */); + return (start < 0 ? 0 : start); +} + +static ssize_t str_find_ws_word_end( const char* s, ssize_t len, ssize_t pos) { + ssize_t end = str_find_forward(s,len,pos,&ic_char_is_white,true /* skip immediate matches */); + return (end < 0 ? len : end); +} + + +//------------------------------------------------------------- +// String row/column iteration +//------------------------------------------------------------- + +// invoke a function for each terminal row; returns total row count. +static ssize_t str_for_each_row( const char* s, ssize_t len, ssize_t termw, ssize_t promptw, ssize_t cpromptw, + row_fun_t* fun, const void* arg, void* res ) +{ + if (s == NULL) s = ""; + ssize_t i; + ssize_t rcount = 0; + ssize_t rcol = 0; + ssize_t rstart = 0; + ssize_t startw = promptw; + for(i = 0; i < len; ) { + ssize_t w; + ssize_t next = str_next_ofs(s, len, i, &w); + if (next <= 0) { + debug_msg("str: foreach row: next<=0: len %zd, i %zd, w %zd, buf %s\n", len, i, w, s ); + assert(false); + break; + } + startw = (rcount == 0 ? promptw : cpromptw); + ssize_t termcol = rcol + w + startw + 1 /* for the cursor */; + if (termw != 0 && i != 0 && termcol >= termw) { + // wrap + if (fun != NULL) { + if (fun(s,rcount,rstart,i - rstart,startw,true,arg,res)) return rcount; + } + rcount++; + rstart = i; + rcol = 0; + } + if (s[i] == '\n') { + // newline + if (fun != NULL) { + if (fun(s,rcount,rstart,i - rstart,startw,false,arg,res)) return rcount; + } + rcount++; + rstart = i+1; + rcol = 0; + } + assert (s[i] != 0); + i += next; + rcol += w; + } + if (fun != NULL) { + if (fun(s,rcount,rstart,i - rstart,startw,false,arg,res)) return rcount; + } + return rcount+1; +} + +//------------------------------------------------------------- +// String: get row/column position +//------------------------------------------------------------- + + +static bool str_get_current_pos_iter( + const char* s, + ssize_t row, ssize_t row_start, ssize_t row_len, + ssize_t startw, bool is_wrap, const void* arg, void* res) +{ + ic_unused(is_wrap); ic_unused(startw); + rowcol_t* rc = (rowcol_t*)res; + ssize_t pos = *((ssize_t*)arg); + + if (pos >= row_start && pos <= (row_start + row_len)) { + // found the cursor row + rc->row_start = row_start; + rc->row_len = row_len; + rc->row = row; + rc->col = str_column_width_n( s + row_start, pos - row_start ); + rc->first_on_row = (pos == row_start); + if (is_wrap) { + // if wrapped, we check if the next character is at row_len + ssize_t next = str_next_ofs(s, row_start + row_len, pos, NULL); + rc->last_on_row = (pos + next >= row_start + row_len); + } + else { + // normal last position is right after the last character + rc->last_on_row = (pos >= row_start + row_len); + } + // debug_msg("edit; pos iter: pos: %zd (%c), row_start: %zd, rowlen: %zd\n", pos, s[pos], row_start, row_len); + } + return false; // always continue to count all rows +} + +static ssize_t str_get_rc_at_pos(const char* s, ssize_t len, ssize_t termw, ssize_t promptw, ssize_t cpromptw, ssize_t pos, rowcol_t* rc) { + memset(rc, 0, sizeof(*rc)); + ssize_t rows = str_for_each_row(s, len, termw, promptw, cpromptw, &str_get_current_pos_iter, &pos, rc); + // debug_msg("edit: current pos: (%d, %d) %s %s\n", rc->row, rc->col, rc->first_on_row ? "first" : "", rc->last_on_row ? "last" : ""); + return rows; +} + + + +//------------------------------------------------------------- +// String: get row/column position for a resized terminal +// with potentially "hard-wrapped" rows +//------------------------------------------------------------- +typedef struct wrapped_arg_s { + ssize_t pos; + ssize_t newtermw; +} wrapped_arg_t; + +typedef struct wrowcol_s { + rowcol_t rc; + ssize_t hrows; // count of hard-wrapped extra rows +} wrowcol_t; + +static bool str_get_current_wrapped_pos_iter( + const char* s, + ssize_t row, ssize_t row_start, ssize_t row_len, + ssize_t startw, bool is_wrap, const void* arg, void* res) +{ + ic_unused(is_wrap); + wrowcol_t* wrc = (wrowcol_t*)res; + const wrapped_arg_t* warg = (const wrapped_arg_t*)arg; + + // iterate through the row and record the postion and hard-wraps + ssize_t hwidth = startw; + ssize_t i = 0; + while( i <= row_len ) { // include rowlen as the cursor position can be just after the last character + // get next position and column width + ssize_t cw; + ssize_t next; + bool is_cursor = (warg->pos == row_start+i); + if (i < row_len) { + next = str_next_ofs(s + row_start, row_len, i, &cw); + } + else { + // end of row: take wrap or cursor into account + // (wrap has width 2 as it displays a back-arrow but also has an invisible newline that wraps) + cw = (is_wrap ? 2 : (is_cursor ? 1 : 0)); + next = 1; + } + + if (next > 0) { + if (hwidth + cw > warg->newtermw) { + // hardwrap + hwidth = 0; + wrc->hrows++; + debug_msg("str: found hardwrap: row: %zd, hrows: %zd\n", row, wrc->hrows); + } + } + else { + next++; // ensure we terminate (as we go up to rowlen) + } + + // did we find our position? + if (is_cursor) { + debug_msg("str: found position: row: %zd, hrows: %zd\n", row, wrc->hrows); + wrc->rc.row_start = row_start; + wrc->rc.row_len = row_len; + wrc->rc.row = wrc->hrows + row; + wrc->rc.col = hwidth; + wrc->rc.first_on_row = (i==0); + wrc->rc.last_on_row = (i+next >= row_len - (is_wrap ? 1 : 0)); + } + + // advance + hwidth += cw; + i += next; + } + return false; // always continue to count all rows +} + + +static ssize_t str_get_wrapped_rc_at_pos(const char* s, ssize_t len, ssize_t termw, ssize_t newtermw, ssize_t promptw, ssize_t cpromptw, ssize_t pos, rowcol_t* rc) { + wrapped_arg_t warg; + warg.pos = pos; + warg.newtermw = newtermw; + wrowcol_t wrc; + memset(&wrc,0,sizeof(wrc)); + ssize_t rows = str_for_each_row(s, len, termw, promptw, cpromptw, &str_get_current_wrapped_pos_iter, &warg, &wrc); + debug_msg("edit: wrapped pos: (%zd,%zd) rows %zd %s %s, hrows: %zd\n", wrc.rc.row, wrc.rc.col, rows, wrc.rc.first_on_row ? "first" : "", wrc.rc.last_on_row ? "last" : "", wrc.hrows); + *rc = wrc.rc; + return (rows + wrc.hrows); +} + + +//------------------------------------------------------------- +// Set position +//------------------------------------------------------------- + +static bool str_set_pos_iter( + const char* s, + ssize_t row, ssize_t row_start, ssize_t row_len, + ssize_t startw, bool is_wrap, const void* arg, void* res) +{ + ic_unused(arg); ic_unused(is_wrap); ic_unused(startw); + rowcol_t* rc = (rowcol_t*)arg; + if (rc->row != row) return false; // keep searching + // we found our row + ssize_t col = 0; + ssize_t i = row_start; + ssize_t end = row_start + row_len; + while (col < rc->col && i < end) { + ssize_t cw; + ssize_t next = str_next_ofs(s, row_start + row_len, i, &cw); + if (next <= 0) break; + i += next; + col += cw; + } + *((ssize_t*)res) = i; + return true; // stop iteration +} + +static ssize_t str_get_pos_at_rc(const char* s, ssize_t len, ssize_t termw, ssize_t promptw, ssize_t cpromptw, ssize_t row, ssize_t col /* without prompt */) { + rowcol_t rc; + memset(&rc,0,ssizeof(rc)); + rc.row = row; + rc.col = col; + ssize_t pos = -1; + str_for_each_row(s,len,termw,promptw,cpromptw,&str_set_pos_iter,&rc,&pos); + return pos; +} + + +//------------------------------------------------------------- +// String buffer +//------------------------------------------------------------- +static bool sbuf_ensure_extra(stringbuf_t* s, ssize_t extra) +{ + if (s->buflen >= s->count + extra) return true; + // reallocate; pick good initial size and multiples to increase reuse on allocation + ssize_t newlen = (s->buflen <= 0 ? 120 : (s->buflen > 1000 ? s->buflen + 1000 : 2*s->buflen)); + if (newlen < s->count + extra) newlen = s->count + extra; + if (s->buflen > 0) { + debug_msg("stringbuf: reallocate: old %zd, new %zd\n", s->buflen, newlen); + } + char* newbuf = mem_realloc_tp(s->mem, char, s->buf, newlen+1); // one more for terminating zero + if (newbuf == NULL) { + assert(false); + return false; + } + s->buf = newbuf; + s->buflen = newlen; + s->buf[s->count] = s->buf[s->buflen] = 0; + assert(s->buflen >= s->count + extra); + return true; +} + +static void sbuf_init( stringbuf_t* sbuf, alloc_t* mem ) { + sbuf->mem = mem; + sbuf->buf = NULL; + sbuf->buflen = 0; + sbuf->count = 0; +} + +static void sbuf_done( stringbuf_t* sbuf ) { + mem_free( sbuf->mem, sbuf->buf ); + sbuf->buf = NULL; + sbuf->buflen = 0; + sbuf->count = 0; +} + + +ic_private void sbuf_free( stringbuf_t* sbuf ) { + if (sbuf==NULL) return; + sbuf_done(sbuf); + mem_free(sbuf->mem, sbuf); +} + +ic_private stringbuf_t* sbuf_new( alloc_t* mem ) { + stringbuf_t* sbuf = mem_zalloc_tp(mem,stringbuf_t); + if (sbuf == NULL) return NULL; + sbuf_init(sbuf,mem); + return sbuf; +} + +// free the sbuf and return the current string buffer as the result +ic_private char* sbuf_free_dup(stringbuf_t* sbuf) { + if (sbuf == NULL) return NULL; + char* s = NULL; + if (sbuf->buf != NULL) { + s = mem_realloc_tp(sbuf->mem, char, sbuf->buf, sbuf_len(sbuf)+1); + if (s == NULL) { s = sbuf->buf; } + sbuf->buf = 0; + sbuf->buflen = 0; + sbuf->count = 0; + } + sbuf_free(sbuf); + return s; +} + +ic_private const char* sbuf_string_at( stringbuf_t* sbuf, ssize_t pos ) { + if (pos < 0 || sbuf->count < pos) return NULL; + if (sbuf->buf == NULL) return ""; + assert(sbuf->buf[sbuf->count] == 0); + return sbuf->buf + pos; +} + +ic_private const char* sbuf_string( stringbuf_t* sbuf ) { + return sbuf_string_at( sbuf, 0 ); +} + +ic_private char sbuf_char_at(stringbuf_t* sbuf, ssize_t pos) { + if (sbuf->buf == NULL || pos < 0 || sbuf->count < pos) return 0; + return sbuf->buf[pos]; +} + +ic_private char* sbuf_strdup_at( stringbuf_t* sbuf, ssize_t pos ) { + return mem_strdup(sbuf->mem, sbuf_string_at(sbuf,pos)); +} + +ic_private char* sbuf_strdup( stringbuf_t* sbuf ) { + return mem_strdup(sbuf->mem, sbuf_string(sbuf)); +} + +ic_private ssize_t sbuf_len(const stringbuf_t* s) { + if (s == NULL) return 0; + return s->count; +} + +ic_private ssize_t sbuf_append_vprintf(stringbuf_t* sb, const char* fmt, va_list args) { + const ssize_t min_needed = ic_strlen(fmt); + if (!sbuf_ensure_extra(sb,min_needed + 16)) return sb->count; + ssize_t avail = sb->buflen - sb->count; + va_list args0; + va_copy(args0, args); + ssize_t needed = vsnprintf(sb->buf + sb->count, to_size_t(avail), fmt, args0); + if (needed > avail) { + sb->buf[sb->count] = 0; + if (!sbuf_ensure_extra(sb, needed)) return sb->count; + avail = sb->buflen - sb->count; + needed = vsnprintf(sb->buf + sb->count, to_size_t(avail), fmt, args); + } + assert(needed <= avail); + sb->count += (needed > avail ? avail : (needed >= 0 ? needed : 0)); + assert(sb->count <= sb->buflen); + sb->buf[sb->count] = 0; + return sb->count; +} + +ic_private ssize_t sbuf_appendf(stringbuf_t* sb, const char* fmt, ...) { + va_list args; + va_start( args, fmt); + ssize_t res = sbuf_append_vprintf( sb, fmt, args ); + va_end(args); + return res; +} + + +ic_private ssize_t sbuf_insert_at_n(stringbuf_t* sbuf, const char* s, ssize_t n, ssize_t pos ) { + if (pos < 0 || pos > sbuf->count || s == NULL) return pos; + n = str_limit_to_length(s,n); + if (n <= 0 || !sbuf_ensure_extra(sbuf,n)) return pos; + ic_memmove(sbuf->buf + pos + n, sbuf->buf + pos, sbuf->count - pos); + ic_memcpy(sbuf->buf + pos, s, n); + sbuf->count += n; + sbuf->buf[sbuf->count] = 0; + return (pos + n); +} + +ic_private stringbuf_t* sbuf_split_at( stringbuf_t* sb, ssize_t pos ) { + stringbuf_t* res = sbuf_new(sb->mem); + if (res==NULL || pos < 0) return NULL; + if (pos < sb->count) { + sbuf_append_n(res, sb->buf + pos, sb->count - pos); + sb->count = pos; + } + return res; +} + +ic_private ssize_t sbuf_insert_at(stringbuf_t* sbuf, const char* s, ssize_t pos ) { + return sbuf_insert_at_n( sbuf, s, ic_strlen(s), pos ); +} + +ic_private ssize_t sbuf_insert_char_at(stringbuf_t* sbuf, char c, ssize_t pos ) { + char s[2]; + s[0] = c; + s[1] = 0; + return sbuf_insert_at_n( sbuf, s, 1, pos); +} + +ic_private ssize_t sbuf_insert_unicode_at(stringbuf_t* sbuf, unicode_t u, ssize_t pos) { + uint8_t s[5]; + unicode_to_qutf8(u, s); + return sbuf_insert_at(sbuf, (const char*)s, pos); +} + + + +ic_private void sbuf_delete_at( stringbuf_t* sbuf, ssize_t pos, ssize_t count ) { + if (pos < 0 || pos >= sbuf->count) return; + if (pos + count > sbuf->count) count = sbuf->count - pos; + ic_memmove(sbuf->buf + pos, sbuf->buf + pos + count, sbuf->count - pos - count); + sbuf->count -= count; + sbuf->buf[sbuf->count] = 0; +} + +ic_private void sbuf_delete_from_to( stringbuf_t* sbuf, ssize_t pos, ssize_t end ) { + if (end <= pos) return; + sbuf_delete_at( sbuf, pos, end - pos); +} + +ic_private void sbuf_delete_from(stringbuf_t* sbuf, ssize_t pos ) { + sbuf_delete_at(sbuf, pos, sbuf_len(sbuf) - pos ); +} + + +ic_private void sbuf_clear( stringbuf_t* sbuf ) { + sbuf_delete_at(sbuf, 0, sbuf_len(sbuf)); +} + +ic_private ssize_t sbuf_append_n( stringbuf_t* sbuf, const char* s, ssize_t n ) { + return sbuf_insert_at_n( sbuf, s, n, sbuf_len(sbuf)); +} + +ic_private ssize_t sbuf_append( stringbuf_t* sbuf, const char* s ) { + return sbuf_insert_at( sbuf, s, sbuf_len(sbuf)); +} + +ic_private ssize_t sbuf_append_char( stringbuf_t* sbuf, char c ) { + char buf[2]; + buf[0] = c; + buf[1] = 0; + return sbuf_append( sbuf, buf ); +} + +ic_private void sbuf_replace(stringbuf_t* sbuf, const char* s) { + sbuf_clear(sbuf); + sbuf_append(sbuf,s); +} + +ic_private ssize_t sbuf_next_ofs( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth ) { + return str_next_ofs( sbuf->buf, sbuf->count, pos, cwidth); +} + +ic_private ssize_t sbuf_prev_ofs( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth ) { + return str_prev_ofs( sbuf->buf, pos, cwidth); +} + +ic_private ssize_t sbuf_next( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth) { + ssize_t ofs = sbuf_next_ofs(sbuf,pos,cwidth); + if (ofs <= 0) return -1; + assert(pos + ofs <= sbuf->count); + return pos + ofs; +} + +ic_private ssize_t sbuf_prev( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth) { + ssize_t ofs = sbuf_prev_ofs(sbuf,pos,cwidth); + if (ofs <= 0) return -1; + assert(pos - ofs >= 0); + return pos - ofs; +} + +ic_private ssize_t sbuf_delete_char_before( stringbuf_t* sbuf, ssize_t pos ) { + ssize_t n = sbuf_prev_ofs(sbuf, pos, NULL); + if (n <= 0) return 0; + assert( pos - n >= 0 ); + sbuf_delete_at(sbuf, pos - n, n); + return pos - n; +} + +ic_private void sbuf_delete_char_at( stringbuf_t* sbuf, ssize_t pos ) { + ssize_t n = sbuf_next_ofs(sbuf, pos, NULL); + if (n <= 0) return; + assert( pos + n <= sbuf->count ); + sbuf_delete_at(sbuf, pos, n); + return; +} + +ic_private ssize_t sbuf_swap_char( stringbuf_t* sbuf, ssize_t pos ) { + ssize_t next = sbuf_next_ofs(sbuf, pos, NULL); + if (next <= 0) return 0; + ssize_t prev = sbuf_prev_ofs(sbuf, pos, NULL); + if (prev <= 0) return 0; + char buf[64]; + if (prev >= 63) return 0; + ic_memcpy(buf, sbuf->buf + pos - prev, prev ); + ic_memmove(sbuf->buf + pos - prev, sbuf->buf + pos, next); + ic_memmove(sbuf->buf + pos - prev + next, buf, prev); + return pos - prev; +} + +ic_private ssize_t sbuf_find_line_start( stringbuf_t* sbuf, ssize_t pos ) { + return str_find_line_start( sbuf->buf, sbuf->count, pos); +} + +ic_private ssize_t sbuf_find_line_end( stringbuf_t* sbuf, ssize_t pos ) { + return str_find_line_end( sbuf->buf, sbuf->count, pos); +} + +ic_private ssize_t sbuf_find_word_start( stringbuf_t* sbuf, ssize_t pos ) { + return str_find_word_start( sbuf->buf, sbuf->count, pos); +} + +ic_private ssize_t sbuf_find_word_end( stringbuf_t* sbuf, ssize_t pos ) { + return str_find_word_end( sbuf->buf, sbuf->count, pos); +} + +ic_private ssize_t sbuf_find_ws_word_start( stringbuf_t* sbuf, ssize_t pos ) { + return str_find_ws_word_start( sbuf->buf, sbuf->count, pos); +} + +ic_private ssize_t sbuf_find_ws_word_end( stringbuf_t* sbuf, ssize_t pos ) { + return str_find_ws_word_end( sbuf->buf, sbuf->count, pos); +} + +// find row/col position +ic_private ssize_t sbuf_get_pos_at_rc( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw, ssize_t row, ssize_t col ) { + return str_get_pos_at_rc( sbuf->buf, sbuf->count, termw, promptw, cpromptw, row, col); +} + +// get row/col for a given position +ic_private ssize_t sbuf_get_rc_at_pos( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw, ssize_t pos, rowcol_t* rc ) { + return str_get_rc_at_pos( sbuf->buf, sbuf->count, termw, promptw, cpromptw, pos, rc); +} + +ic_private ssize_t sbuf_get_wrapped_rc_at_pos( stringbuf_t* sbuf, ssize_t termw, ssize_t newtermw, ssize_t promptw, ssize_t cpromptw, ssize_t pos, rowcol_t* rc ) { + return str_get_wrapped_rc_at_pos( sbuf->buf, sbuf->count, termw, newtermw, promptw, cpromptw, pos, rc); +} + +ic_private ssize_t sbuf_for_each_row( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw, row_fun_t* fun, void* arg, void* res ) { + if (sbuf == NULL) return 0; + return str_for_each_row( sbuf->buf, sbuf->count, termw, promptw, cpromptw, fun, arg, res); +} + + +// Duplicate and decode from utf-8 (for non-utf8 terminals) +ic_private char* sbuf_strdup_from_utf8(stringbuf_t* sbuf) { + ssize_t len = sbuf_len(sbuf); + if (sbuf == NULL || len <= 0) return NULL; + char* s = mem_zalloc_tp_n(sbuf->mem, char, len); + if (s == NULL) return NULL; + ssize_t dest = 0; + for (ssize_t i = 0; i < len; ) { + ssize_t ofs = sbuf_next_ofs(sbuf, i, NULL); + if (ofs <= 0) { + // invalid input + break; + } + else if (ofs == 1) { + // regular character + s[dest++] = sbuf->buf[i]; + } + else if (sbuf->buf[i] == '\x1B') { + // skip escape sequences + } + else { + // decode unicode + ssize_t nread; + unicode_t uchr = unicode_from_qutf8( (const uint8_t*)(sbuf->buf + i), ofs, &nread); + uint8_t c; + if (unicode_is_raw(uchr, &c)) { + // raw byte, output as is (this will take care of locale specific input) + s[dest++] = (char)c; + } + else if (uchr <= 0x7F) { + // allow ascii + s[dest++] = (char)uchr; + } + else { + // skip unknown unicode characters.. + // todo: convert according to locale? + } + } + i += ofs; + } + assert(dest <= len); + s[dest] = 0; + return s; +} + +//------------------------------------------------------------- +// String helpers +//------------------------------------------------------------- + +ic_public long ic_prev_char( const char* s, long pos ) { + ssize_t len = ic_strlen(s); + if (pos < 0 || pos > len) return -1; + ssize_t ofs = str_prev_ofs( s, pos, NULL ); + if (ofs <= 0) return -1; + return (long)(pos - ofs); +} + +ic_public long ic_next_char( const char* s, long pos ) { + ssize_t len = ic_strlen(s); + if (pos < 0 || pos > len) return -1; + ssize_t ofs = str_next_ofs( s, len, pos, NULL ); + if (ofs <= 0) return -1; + return (long)(pos + ofs); +} + + +// parse a decimal (leave pi unchanged on error) +ic_private bool ic_atoz(const char* s, ssize_t* pi) { + return (sscanf(s, "%zd", pi) == 1); +} + +// parse two decimals separated by a semicolon +ic_private bool ic_atoz2(const char* s, ssize_t* pi, ssize_t* pj) { + return (sscanf(s, "%zd;%zd", pi, pj) == 2); +} + +// parse unsigned 32-bit (leave pu unchanged on error) +ic_private bool ic_atou32(const char* s, uint32_t* pu) { + return (sscanf(s, "%" SCNu32, pu) == 1); +} + + +// Convenience: character class for whitespace `[ \t\r\n]`. +ic_public bool ic_char_is_white(const char* s, long len) { + if (s == NULL || len != 1) return false; + const char c = *s; + return (c==' ' || c == '\t' || c == '\n' || c == '\r'); +} + +// Convenience: character class for non-whitespace `[^ \t\r\n]`. +ic_public bool ic_char_is_nonwhite(const char* s, long len) { + return !ic_char_is_white(s, len); +} + +// Convenience: character class for separators `[ \t\r\n,.;:/\\\(\)\{\}\[\]]`. +ic_public bool ic_char_is_separator(const char* s, long len) { + if (s == NULL || len != 1) return false; + const char c = *s; + return (strchr(" \t\r\n,.;:/\\(){}[]", c) != NULL); +} + +// Convenience: character class for non-separators. +ic_public bool ic_char_is_nonseparator(const char* s, long len) { + return !ic_char_is_separator(s, len); +} + + +// Convenience: character class for digits (`[0-9]`). +ic_public bool ic_char_is_digit(const char* s, long len) { + if (s == NULL || len != 1) return false; + const char c = *s; + return (c >= '0' && c <= '9'); +} + +// Convenience: character class for hexadecimal digits (`[A-Fa-f0-9]`). +ic_public bool ic_char_is_hexdigit(const char* s, long len) { + if (s == NULL || len != 1) return false; + const char c = *s; + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +// Convenience: character class for letters (`[A-Za-z]` and any unicode > 0x80). +ic_public bool ic_char_is_letter(const char* s, long len) { + if (s == NULL || len <= 0) return false; + const char c = *s; + return ((uint8_t)c >= 0x80 || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +// Convenience: character class for identifier letters (`[A-Za-z0-9_-]` and any unicode > 0x80). +ic_public bool ic_char_is_idletter(const char* s, long len) { + if (s == NULL || len <= 0) return false; + const char c = *s; + return ((uint8_t)c >= 0x80 || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_') || (c == '-')); +} + +// Convenience: character class for filename letters (`[^ \t\r\n`@$><=;|&{(]`). +ic_public bool ic_char_is_filename_letter(const char* s, long len) { + if (s == NULL || len <= 0) return false; + const char c = *s; + return ((uint8_t)c >= 0x80 || (strchr(" \t\r\n`@$><=;|&{}()[]", c) == NULL)); +} + +// Convenience: If this is a token start, returns the length (or <= 0 if not found). +ic_public long ic_is_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char) { + if (s == NULL || pos < 0 || is_token_char == NULL) return -1; + ssize_t len = ic_strlen(s); + if (pos >= len) return -1; + if (pos > 0 && is_token_char(s + pos -1, 1)) return -1; // token start? + ssize_t i = pos; + while ( i < len ) { + ssize_t next = str_next_ofs(s, len, i, NULL); + if (next <= 0) return -1; + if (!is_token_char(s + i, (long)next)) break; + i += next; + } + return (long)(i - pos); +} + + +static int ic_strncmp(const char* s1, const char* s2, ssize_t n) { + return strncmp(s1, s2, to_size_t(n)); +} + +// Convenience: Does this match the specified token? +// Ensures not to match prefixes or suffixes, and returns the length of the match (in bytes). +// E.g. `ic_match_token("function",0,&ic_char_is_letter,"fun")` returns 0. +ic_public long ic_match_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char, const char* token) { + long n = ic_is_token(s, pos, is_token_char); + if (n > 0 && token != NULL && n == ic_strlen(token) && ic_strncmp(s + pos, token, n) == 0) { + return n; + } + else { + return 0; + } +} + + +// Convenience: Do any of the specified tokens match? +// Ensures not to match prefixes or suffixes, and returns the length of the match (in bytes). +// Ensures not to match prefixes or suffixes. +// E.g. `ic_match_any_token("function",0,&ic_char_is_letter,{"fun","func",NULL})` returns 0. +ic_public long ic_match_any_token(const char* s, long pos, ic_is_char_class_fun_t* is_token_char, const char** tokens) { + long n = ic_is_token(s, pos, is_token_char); + if (n <= 0 || tokens == NULL) return 0; + for (const char** token = tokens; *token != NULL; token++) { + if (n == ic_strlen(*token) && ic_strncmp(s + pos, *token, n) == 0) { + return n; + } + } + return 0; +} + diff --git a/deps/isocline/src/stringbuf.h b/deps/isocline/src/stringbuf.h new file mode 100644 index 0000000..39b21ea --- /dev/null +++ b/deps/isocline/src/stringbuf.h @@ -0,0 +1,121 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_STRINGBUF_H +#define IC_STRINGBUF_H + +#include +#include "common.h" + +//------------------------------------------------------------- +// string buffer +// in-place modified buffer with edit operations +// that grows on demand. +//------------------------------------------------------------- + +// abstract string buffer +struct stringbuf_s; +typedef struct stringbuf_s stringbuf_t; + +ic_private stringbuf_t* sbuf_new( alloc_t* mem ); +ic_private void sbuf_free( stringbuf_t* sbuf ); +ic_private char* sbuf_free_dup(stringbuf_t* sbuf); +ic_private ssize_t sbuf_len(const stringbuf_t* s); + +ic_private const char* sbuf_string_at( stringbuf_t* sbuf, ssize_t pos ); +ic_private const char* sbuf_string( stringbuf_t* sbuf ); +ic_private char sbuf_char_at(stringbuf_t* sbuf, ssize_t pos); +ic_private char* sbuf_strdup_at( stringbuf_t* sbuf, ssize_t pos ); +ic_private char* sbuf_strdup( stringbuf_t* sbuf ); +ic_private char* sbuf_strdup_from_utf8(stringbuf_t* sbuf); // decode to locale + + +ic_private ssize_t sbuf_appendf(stringbuf_t* sb, const char* fmt, ...); +ic_private ssize_t sbuf_append_vprintf(stringbuf_t* sb, const char* fmt, va_list args); + +ic_private stringbuf_t* sbuf_split_at( stringbuf_t* sb, ssize_t pos ); + +// primitive edit operations (inserts return the new position) +ic_private void sbuf_clear(stringbuf_t* sbuf); +ic_private void sbuf_replace(stringbuf_t* sbuf, const char* s); +ic_private void sbuf_delete_at(stringbuf_t* sbuf, ssize_t pos, ssize_t count); +ic_private void sbuf_delete_from_to(stringbuf_t* sbuf, ssize_t pos, ssize_t end); +ic_private void sbuf_delete_from(stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_insert_at_n(stringbuf_t* sbuf, const char* s, ssize_t n, ssize_t pos ); +ic_private ssize_t sbuf_insert_at(stringbuf_t* sbuf, const char* s, ssize_t pos ); +ic_private ssize_t sbuf_insert_char_at(stringbuf_t* sbuf, char c, ssize_t pos ); +ic_private ssize_t sbuf_insert_unicode_at(stringbuf_t* sbuf, unicode_t u, ssize_t pos); +ic_private ssize_t sbuf_append_n(stringbuf_t* sbuf, const char* s, ssize_t n); +ic_private ssize_t sbuf_append(stringbuf_t* sbuf, const char* s); +ic_private ssize_t sbuf_append_char(stringbuf_t* sbuf, char c); + +// high level edit operations (return the new position) +ic_private ssize_t sbuf_next( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth ); +ic_private ssize_t sbuf_prev( stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth ); +ic_private ssize_t sbuf_next_ofs(stringbuf_t* sbuf, ssize_t pos, ssize_t* cwidth); + +ic_private ssize_t sbuf_delete_char_before( stringbuf_t* sbuf, ssize_t pos ); +ic_private void sbuf_delete_char_at( stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_swap_char( stringbuf_t* sbuf, ssize_t pos ); + +ic_private ssize_t sbuf_find_line_start( stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_find_line_end( stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_find_word_start( stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_find_word_end( stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_find_ws_word_start( stringbuf_t* sbuf, ssize_t pos ); +ic_private ssize_t sbuf_find_ws_word_end( stringbuf_t* sbuf, ssize_t pos ); + +// parse a decimal +ic_private bool ic_atoz(const char* s, ssize_t* i); +// parse two decimals separated by a semicolon +ic_private bool ic_atoz2(const char* s, ssize_t* i, ssize_t* j); +ic_private bool ic_atou32(const char* s, uint32_t* pu); + +// row/column info +typedef struct rowcol_s { + ssize_t row; + ssize_t col; + ssize_t row_start; + ssize_t row_len; + bool first_on_row; + bool last_on_row; +} rowcol_t; + +// find row/col position +ic_private ssize_t sbuf_get_pos_at_rc( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw, + ssize_t row, ssize_t col ); +// get row/col for a given position +ic_private ssize_t sbuf_get_rc_at_pos( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw, + ssize_t pos, rowcol_t* rc ); + +ic_private ssize_t sbuf_get_wrapped_rc_at_pos( stringbuf_t* sbuf, ssize_t termw, ssize_t newtermw, ssize_t promptw, ssize_t cpromptw, + ssize_t pos, rowcol_t* rc ); + +// row iteration +typedef bool (row_fun_t)(const char* s, + ssize_t row, ssize_t row_start, ssize_t row_len, + ssize_t startw, // prompt width + bool is_wrap, const void* arg, void* res); + +ic_private ssize_t sbuf_for_each_row( stringbuf_t* sbuf, ssize_t termw, ssize_t promptw, ssize_t cpromptw, + row_fun_t* fun, void* arg, void* res ); + + +//------------------------------------------------------------- +// Strings +//------------------------------------------------------------- + +// skip a single CSI sequence (ESC [ ...) +ic_private bool skip_csi_esc( const char* s, ssize_t len, ssize_t* esclen ); // used in term.c + +ic_private ssize_t str_column_width( const char* s ); +ic_private ssize_t str_prev_ofs( const char* s, ssize_t pos, ssize_t* cwidth ); +ic_private ssize_t str_next_ofs( const char* s, ssize_t len, ssize_t pos, ssize_t* cwidth ); +ic_private ssize_t str_skip_until_fit( const char* s, ssize_t max_width); // tail that fits +ic_private ssize_t str_take_while_fit( const char* s, ssize_t max_width); // prefix that fits + +#endif // IC_STRINGBUF_H diff --git a/deps/isocline/src/term.c b/deps/isocline/src/term.c new file mode 100644 index 0000000..c55d9ae --- /dev/null +++ b/deps/isocline/src/term.c @@ -0,0 +1,1124 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include +#include +#include // getenv +#include + +#include "common.h" +#include "tty.h" +#include "term.h" +#include "stringbuf.h" // str_next_ofs + +#if defined(_WIN32) +#include +#define STDOUT_FILENO 1 +#else +#include +#include +#include +#if defined(__linux__) +#include +#endif +#endif + +#define IC_CSI "\x1B[" + +// color support; colors are auto mapped smaller palettes if needed. (see `term_color.c`) +typedef enum palette_e { + MONOCHROME, // no color + ANSI8, // only basic 8 ANSI color (ESC[m, idx: 30-37, +10 for background) + ANSI16, // basic + bright ANSI colors (ESC[m, idx: 30-37, 90-97, +10 for background) + ANSI256, // ANSI 256 color palette (ESC[38;5;m, idx: 0-15 standard color, 16-231 6x6x6 rbg colors, 232-255 gray shades) + ANSIRGB // direct rgb colors supported (ESC[38;2;;;m) +} palette_t; + +// The terminal screen +struct term_s { + int fd_out; // output handle + ssize_t width; // screen column width + ssize_t height; // screen row height + ssize_t raw_enabled; // is raw mode active? counted by start/end pairs + bool nocolor; // show colors? + bool silent; // enable beep? + bool is_utf8; // utf-8 output? determined by the tty + attr_t attr; // current text attributes + palette_t palette; // color support + buffer_mode_t bufmode; // buffer mode + stringbuf_t* buf; // buffer for buffered output + tty_t* tty; // used on posix to get the cursor position + alloc_t* mem; // allocator + #ifdef _WIN32 + HANDLE hcon; // output console handler + WORD hcon_default_attr; // default text attributes + WORD hcon_orig_attr; // original text attributes + DWORD hcon_orig_mode; // original console mode + DWORD hcon_mode; // used console mode + UINT hcon_orig_cp; // original console code-page (locale) + COORD hcon_save_cursor; // saved cursor position (for escape sequence emulation) + #endif +}; + +static bool term_write_direct(term_t* term, const char* s, ssize_t n ); +static void term_append_buf(term_t* term, const char* s, ssize_t n); + +//------------------------------------------------------------- +// Colors +//------------------------------------------------------------- + +#include "term_color.c" + +//------------------------------------------------------------- +// Helpers +//------------------------------------------------------------- + +ic_private void term_left(term_t* term, ssize_t n) { + if (n <= 0) return; + term_writef( term, IC_CSI "%zdD", n ); +} + +ic_private void term_right(term_t* term, ssize_t n) { + if (n <= 0) return; + term_writef( term, IC_CSI "%zdC", n ); +} + +ic_private void term_up(term_t* term, ssize_t n) { + if (n <= 0) return; + term_writef( term, IC_CSI "%zdA", n ); +} + +ic_private void term_down(term_t* term, ssize_t n) { + if (n <= 0) return; + term_writef( term, IC_CSI "%zdB", n ); +} + +ic_private void term_clear_line(term_t* term) { + term_write( term, "\r" IC_CSI "K"); +} + +ic_private void term_clear_to_end_of_line(term_t* term) { + term_write(term, IC_CSI "K"); +} + +ic_private void term_start_of_line(term_t* term) { + term_write( term, "\r" ); +} + +ic_private ssize_t term_get_width(term_t* term) { + return term->width; +} + +ic_private ssize_t term_get_height(term_t* term) { + return term->height; +} + +ic_private void term_attr_reset(term_t* term) { + term_write(term, IC_CSI "m" ); +} + +ic_private void term_underline(term_t* term, bool on) { + term_write(term, on ? IC_CSI "4m" : IC_CSI "24m" ); +} + +ic_private void term_reverse(term_t* term, bool on) { + term_write(term, on ? IC_CSI "7m" : IC_CSI "27m"); +} + +ic_private void term_bold(term_t* term, bool on) { + term_write(term, on ? IC_CSI "1m" : IC_CSI "22m" ); +} + +ic_private void term_italic(term_t* term, bool on) { + term_write(term, on ? IC_CSI "3m" : IC_CSI "23m" ); +} + +ic_private void term_writeln(term_t* term, const char* s) { + term_write(term,s); + term_write(term,"\n"); +} + +ic_private void term_write_char(term_t* term, char c) { + char buf[2]; + buf[0] = c; + buf[1] = 0; + term_write_n(term, buf, 1 ); +} + +ic_private attr_t term_get_attr( const term_t* term ) { + return term->attr; +} + +ic_private void term_set_attr( term_t* term, attr_t attr ) { + if (term->nocolor) return; + if (attr.x.color != term->attr.x.color && attr.x.color != IC_COLOR_NONE) { + term_color(term,attr.x.color); + if (term->palette < ANSIRGB && color_is_rgb(attr.x.color)) { + term->attr.x.color = attr.x.color; // actual color may have been approximated but we keep the actual color to avoid updating every time + } + } + if (attr.x.bgcolor != term->attr.x.bgcolor && attr.x.bgcolor != IC_COLOR_NONE) { + term_bgcolor(term,attr.x.bgcolor); + if (term->palette < ANSIRGB && color_is_rgb(attr.x.bgcolor)) { + term->attr.x.bgcolor = attr.x.bgcolor; + } + } + if (attr.x.bold != term->attr.x.bold && attr.x.bold != IC_NONE) { + term_bold(term,attr.x.bold == IC_ON); + } + if (attr.x.underline != term->attr.x.underline && attr.x.underline != IC_NONE) { + term_underline(term,attr.x.underline == IC_ON); + } + if (attr.x.reverse != term->attr.x.reverse && attr.x.reverse != IC_NONE) { + term_reverse(term,attr.x.reverse == IC_ON); + } + if (attr.x.italic != term->attr.x.italic && attr.x.italic != IC_NONE) { + term_italic(term,attr.x.italic == IC_ON); + } + assert(attr.x.color == term->attr.x.color || attr.x.color == IC_COLOR_NONE); + assert(attr.x.bgcolor == term->attr.x.bgcolor || attr.x.bgcolor == IC_COLOR_NONE); + assert(attr.x.bold == term->attr.x.bold || attr.x.bold == IC_NONE); + assert(attr.x.reverse == term->attr.x.reverse || attr.x.reverse == IC_NONE); + assert(attr.x.underline == term->attr.x.underline || attr.x.underline == IC_NONE); + assert(attr.x.italic == term->attr.x.italic || attr.x.italic == IC_NONE); +} + + +/* +ic_private void term_clear_lines_to_end(term_t* term) { + term_write(term, "\r" IC_CSI "J"); +} + +ic_private void term_show_cursor(term_t* term, bool on) { + term_write(term, on ? IC_CSI "?25h" : IC_CSI "?25l"); +} +*/ + +//------------------------------------------------------------- +// Formatted output +//------------------------------------------------------------- + +ic_private void term_writef(term_t* term, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + term_vwritef(term,fmt,ap); + va_end(ap); +} + +ic_private void term_vwritef(term_t* term, const char* fmt, va_list args ) { + sbuf_append_vprintf(term->buf, fmt, args); +} + +ic_private void term_write_formatted( term_t* term, const char* s, const attr_t* attrs ) { + term_write_formatted_n( term, s, attrs, ic_strlen(s)); +} + +ic_private void term_write_formatted_n( term_t* term, const char* s, const attr_t* attrs, ssize_t len ) { + if (attrs == NULL) { + // write directly + term_write(term,s); + } + else { + // ensure raw mode from now on + if (term->raw_enabled <= 0) { + term_start_raw(term); + } + // and output with text attributes + const attr_t default_attr = term_get_attr(term); + attr_t attr = attr_none(); + ssize_t i = 0; + ssize_t n = 0; + while( i+n < len && s[i+n] != 0 ) { + if (!attr_is_eq(attr,attrs[i+n])) { + if (n > 0) { + term_write_n( term, s+i, n ); + i += n; + n = 0; + } + attr = attrs[i]; + term_set_attr( term, attr_update_with(default_attr,attr) ); + } + n++; + } + if (n > 0) { + term_write_n( term, s+i, n ); + i += n; + n = 0; + } + assert(s[i] != 0 || i == len); + term_set_attr(term, default_attr); + } +} + +//------------------------------------------------------------- +// Write to the terminal +// The buffered functions are used to reduce cursor flicker +// during refresh +//------------------------------------------------------------- + +ic_private void term_beep(term_t* term) { + if (term->silent) return; + fprintf(stderr,"\x7"); + fflush(stderr); +} + +ic_private void term_write_repeat(term_t* term, const char* s, ssize_t count) { + for (; count > 0; count--) { + term_write(term, s); + } +} + +ic_private void term_write(term_t* term, const char* s) { + if (s == NULL || s[0] == 0) return; + ssize_t n = ic_strlen(s); + term_write_n(term,s,n); +} + +// Primitive terminal write; all writes go through here +ic_private void term_write_n(term_t* term, const char* s, ssize_t n) { + if (s == NULL || n <= 0) return; + // write to buffer to reduce flicker and to process escape sequences (this may flush too) + term_append_buf(term, s, n); +} + + +//------------------------------------------------------------- +// Buffering +//------------------------------------------------------------- + + +ic_private void term_flush(term_t* term) { + if (sbuf_len(term->buf) > 0) { + //term_show_cursor(term,false); + term_write_direct(term, sbuf_string(term->buf), sbuf_len(term->buf)); + //term_show_cursor(term,true); + sbuf_clear(term->buf); + } +} + +ic_private buffer_mode_t term_set_buffer_mode(term_t* term, buffer_mode_t mode) { + buffer_mode_t oldmode = term->bufmode; + if (oldmode != mode) { + if (mode == UNBUFFERED) { + term_flush(term); + } + term->bufmode = mode; + } + return oldmode; +} + +static void term_check_flush(term_t* term, bool contains_nl) { + if (term->bufmode == UNBUFFERED || + sbuf_len(term->buf) > 4000 || + (term->bufmode == LINEBUFFERED && contains_nl)) + { + term_flush(term); + } +} + +//------------------------------------------------------------- +// Init +//------------------------------------------------------------- + +static void term_init_raw(term_t* term); + +ic_private term_t* term_new(alloc_t* mem, tty_t* tty, bool nocolor, bool silent, int fd_out ) +{ + term_t* term = mem_zalloc_tp(mem, term_t); + if (term == NULL) return NULL; + + term->fd_out = (fd_out < 0 ? STDOUT_FILENO : fd_out); + term->nocolor = nocolor || (isatty(term->fd_out) == 0); + term->silent = silent; + term->mem = mem; + term->tty = tty; // can be NULL + term->width = 80; + term->height = 25; + term->is_utf8 = tty_is_utf8(tty); + term->palette = ANSI16; // almost universally supported + term->buf = sbuf_new(mem); + term->bufmode = LINEBUFFERED; + term->attr = attr_default(); + + // respect NO_COLOR + if (getenv("NO_COLOR") != NULL) { + term->nocolor = true; + } + if (!term->nocolor) { + // detect color palette + // COLORTERM takes precedence + const char* colorterm = getenv("COLORTERM"); + const char* eterm = getenv("TERM"); + if (ic_contains(colorterm,"24bit") || ic_contains(colorterm,"truecolor") || ic_contains(colorterm,"direct")) { + term->palette = ANSIRGB; + } + else if (ic_contains(colorterm,"8bit") || ic_contains(colorterm,"256color")) { term->palette = ANSI256; } + else if (ic_contains(colorterm,"4bit") || ic_contains(colorterm,"16color")) { term->palette = ANSI16; } + else if (ic_contains(colorterm,"3bit") || ic_contains(colorterm,"8color")) { term->palette = ANSI8; } + else if (ic_contains(colorterm,"1bit") || ic_contains(colorterm,"nocolor") || ic_contains(colorterm,"monochrome")) { + term->palette = MONOCHROME; + } + // otherwise check for some specific terminals + else if (getenv("WT_SESSION") != NULL) { term->palette = ANSIRGB; } // Windows terminal + else if (getenv("ITERM_SESSION_ID") != NULL) { term->palette = ANSIRGB; } // iTerm2 terminal + else if (getenv("VSCODE_PID") != NULL) { term->palette = ANSIRGB; } // vscode terminal + else { + // and otherwise fall back to checking TERM + if (ic_contains(eterm,"truecolor") || ic_contains(eterm,"direct") || ic_contains(colorterm,"24bit")) { + term->palette = ANSIRGB; + } + else if (ic_contains(eterm,"alacritty") || ic_contains(eterm,"kitty")) { + term->palette = ANSIRGB; + } + else if (ic_contains(eterm,"256color") || ic_contains(eterm,"gnome")) { + term->palette = ANSI256; + } + else if (ic_contains(eterm,"16color")){ term->palette = ANSI16; } + else if (ic_contains(eterm,"8color")) { term->palette = ANSI8; } + else if (ic_contains(eterm,"monochrome") || ic_contains(eterm,"nocolor") || ic_contains(eterm,"dumb")) { + term->palette = MONOCHROME; + } + } + debug_msg("term: color-bits: %d (COLORTERM=%s, TERM=%s)\n", term_get_color_bits(term), colorterm, eterm); + } + + // read COLUMS/LINES from the environment for a better initial guess. + const char* env_columns = getenv("COLUMNS"); + if (env_columns != NULL) { ic_atoz(env_columns, &term->width); } + const char* env_lines = getenv("LINES"); + if (env_lines != NULL) { ic_atoz(env_lines, &term->height); } + + // initialize raw terminal output and terminal dimensions + term_init_raw(term); + term_update_dim(term); + term_attr_reset(term); // ensure we are at default settings + + return term; +} + +ic_private bool term_is_interactive(const term_t* term) { + ic_unused(term); + // check dimensions (0 is used for debuggers) + // if (term->width <= 0) return false; + + // check editing support + const char* eterm = getenv("TERM"); + debug_msg("term: TERM=%s\n", eterm); + if (eterm != NULL && + (strstr("dumb|DUMB|cons25|CONS25|emacs|EMACS",eterm) != NULL)) { + return false; + } + + return true; +} + +ic_private bool term_enable_beep(term_t* term, bool enable) { + bool prev = term->silent; + term->silent = !enable; + return prev; +} + +ic_private bool term_enable_color(term_t* term, bool enable) { + bool prev = !term->nocolor; + term->nocolor = !enable; + return prev; +} + +ic_private void term_free(term_t* term) { + if (term == NULL) return; + term_flush(term); + term_end_raw(term, true); + sbuf_free(term->buf); term->buf = NULL; + mem_free(term->mem, term); +} + +//------------------------------------------------------------- +// For best portability and applications inserting CSI SGR (ESC[ .. m) +// codes themselves in strings, we interpret these at the +// lowest level so we can have a `term_get_attr` function which +// is needed for bracketed styles etc. +//------------------------------------------------------------- + +static void term_append_esc(term_t* term, const char* const s, ssize_t len) { + if (s[1]=='[' && s[len-1] == 'm') { + // it is a CSI SGR sequence: ESC[ ... m + if (term->nocolor) return; // ignore escape sequences if nocolor is set + term->attr = attr_update_with(term->attr, attr_from_esc_sgr(s,len)); + } + // and write out the escape sequence as-is + sbuf_append_n(term->buf, s, len); +} + + +static void term_append_utf8(term_t* term, const char* s, ssize_t len) { + ssize_t nread; + unicode_t uchr = unicode_from_qutf8((const uint8_t*)s, len, &nread); + uint8_t c; + if (unicode_is_raw(uchr, &c)) { + // write bytes as is; this also ensure that on non-utf8 terminals characters between 0x80-0xFF + // go through _as is_ due to the qutf8 encoding. + sbuf_append_char(term->buf,(char)c); + } + else if (!term->is_utf8) { + // on non-utf8 terminals still send utf-8 and hope for the best + // todo: we could try to convert to the locale first? + sbuf_append_n(term->buf, s, len); + // sbuf_appendf(term->buf, "\x1B[%" PRIu32 "u", uchr); // unicode escape code + } + else { + // write utf-8 as is + sbuf_append_n(term->buf, s, len); + } +} + +static void term_append_buf( term_t* term, const char* s, ssize_t len ) { + ssize_t pos = 0; + bool newline = false; + while (pos < len) { + // handle ascii sequences in bulk + ssize_t ascii = 0; + ssize_t next; + while ((next = str_next_ofs(s, len, pos+ascii, NULL)) > 0 && + (uint8_t)s[pos + ascii] > '\x1B' && (uint8_t)s[pos + ascii] <= 0x7F ) + { + ascii += next; + } + if (ascii > 0) { + sbuf_append_n(term->buf, s+pos, ascii); + pos += ascii; + } + if (next <= 0) break; + + const uint8_t c = (uint8_t)s[pos]; + // handle utf8 sequences (for non-utf8 terminals) + if (c >= 0x80) { + term_append_utf8(term, s+pos, next); + } + // handle escape sequence (note: str_next_ofs considers whole CSI escape sequences at a time) + else if (next > 1 && c == '\x1B') { + term_append_esc(term, s+pos, next); + } + else if (c < ' ' && c != 0 && (c < '\x07' || c > '\x0D')) { + // ignore control characters except \a, \b, \t, \n, \r, and form-feed and vertical tab. + } + else { + if (c == '\n') { newline = true; } + sbuf_append_n(term->buf, s+pos, next); + } + pos += next; + } + // possibly flush + term_check_flush(term, newline); +} + +//------------------------------------------------------------- +// Platform dependent: Write directly to the terminal +//------------------------------------------------------------- + +#if !defined(_WIN32) + +// write to the console without further processing +static bool term_write_direct(term_t* term, const char* s, ssize_t n) { + ssize_t count = 0; + while( count < n ) { + ssize_t nwritten = write(term->fd_out, s + count, to_size_t(n - count)); + if (nwritten > 0) { + count += nwritten; + } + else if (errno != EINTR && errno != EAGAIN) { + debug_msg("term: write failed: length %i, errno %i: \"%s\"\n", n, errno, s); + return false; + } + } + return true; +} + +#else + +//---------------------------------------------------------------------------------- +// On windows we use the new virtual terminal processing if it is available (Windows Terminal) +// but fall back to ansi escape emulation on older systems but also for example +// the PS terminal +// +// note: we use row/col as 1-based ANSI escape while windows X/Y coords are 0-based. +//----------------------------------------------------------------------------------- + +#if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0) +#endif +#if !defined(ENABLE_LVB_GRID_WORLDWIDE) +#define ENABLE_LVB_GRID_WORLDWIDE (0) +#endif + +// direct write to the console without further processing +static bool term_write_console(term_t* term, const char* s, ssize_t n ) { + DWORD written; + // WriteConsoleA(term->hcon, s, (DWORD)(to_size_t(n)), &written, NULL); + WriteFile(term->hcon, s, (DWORD)(to_size_t(n)), &written, NULL); // so it can be redirected + return (written == (DWORD)(to_size_t(n))); +} + +static bool term_get_cursor_pos( term_t* term, ssize_t* row, ssize_t* col) { + *row = 0; + *col = 0; + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return false; + *row = (ssize_t)info.dwCursorPosition.Y + 1; + *col = (ssize_t)info.dwCursorPosition.X + 1; + return true; +} + +static void term_move_cursor_to( term_t* term, ssize_t row, ssize_t col ) { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return; + if (col > info.dwSize.X) col = info.dwSize.X; + if (row > info.dwSize.Y) row = info.dwSize.Y; + if (col <= 0) col = 1; + if (row <= 0) row = 1; + COORD coord; + coord.X = (SHORT)col - 1; + coord.Y = (SHORT)row - 1; + SetConsoleCursorPosition( term->hcon, coord); +} + +static void term_cursor_save(term_t* term) { + memset(&term->hcon_save_cursor, 0, sizeof(term->hcon_save_cursor)); + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return; + term->hcon_save_cursor = info.dwCursorPosition; +} + +static void term_cursor_restore(term_t* term) { + if (term->hcon_save_cursor.X == 0) return; + SetConsoleCursorPosition(term->hcon, term->hcon_save_cursor); +} + +static void term_move_cursor( term_t* term, ssize_t drow, ssize_t dcol, ssize_t n ) { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return; + COORD cur = info.dwCursorPosition; + ssize_t col = (ssize_t)cur.X + 1 + n*dcol; + ssize_t row = (ssize_t)cur.Y + 1 + n*drow; + term_move_cursor_to( term, row, col ); +} + +static void term_cursor_visible( term_t* term, bool visible ) { + CONSOLE_CURSOR_INFO info; + if (!GetConsoleCursorInfo(term->hcon,&info)) return; + info.bVisible = visible; + SetConsoleCursorInfo(term->hcon,&info); +} + +static void term_erase_line( term_t* term, ssize_t mode ) { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return; + DWORD written; + COORD start; + ssize_t length; + if (mode == 2) { + // entire line + start.X = 0; + start.Y = info.dwCursorPosition.Y; + length = (ssize_t)info.srWindow.Right + 1; + } + else if (mode == 1) { + // to start of line + start.X = 0; + start.Y = info.dwCursorPosition.Y; + length = info.dwCursorPosition.X; + } + else { + // to end of line + length = (ssize_t)info.srWindow.Right - info.dwCursorPosition.X + 1; + start = info.dwCursorPosition; + } + FillConsoleOutputAttribute( term->hcon, term->hcon_default_attr, (DWORD)length, start, &written ); + FillConsoleOutputCharacterA( term->hcon, ' ', (DWORD)length, start, &written ); +} + +static void term_clear_screen(term_t* term, ssize_t mode) { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return; + COORD start; + start.X = 0; + start.Y = 0; + ssize_t length; + ssize_t width = (ssize_t)info.dwSize.X; + if (mode == 2) { + // entire screen + length = width * info.dwSize.Y; + } + else if (mode == 1) { + // to cursor + length = (width * ((ssize_t)info.dwCursorPosition.Y - 1)) + info.dwCursorPosition.X; + } + else { + // from cursor + start = info.dwCursorPosition; + length = (width * ((ssize_t)info.dwSize.Y - info.dwCursorPosition.Y)) + (width - info.dwCursorPosition.X + 1); + } + DWORD written; + FillConsoleOutputAttribute(term->hcon, term->hcon_default_attr, (DWORD)length, start, &written); + FillConsoleOutputCharacterA(term->hcon, ' ', (DWORD)length, start, &written); +} + +static WORD attr_color[8] = { + 0, // black + FOREGROUND_RED, // maroon + FOREGROUND_GREEN, // green + FOREGROUND_RED | FOREGROUND_GREEN, // orange + FOREGROUND_BLUE, // navy + FOREGROUND_RED | FOREGROUND_BLUE, // purple + FOREGROUND_GREEN | FOREGROUND_BLUE, // teal + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // light gray +}; + +static void term_set_win_attr( term_t* term, attr_t ta ) { + WORD def_attr = term->hcon_default_attr; + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return; + WORD cur_attr = info.wAttributes; + WORD attr = cur_attr; + if (ta.x.color != IC_COLOR_NONE) { + if (ta.x.color >= IC_ANSI_BLACK && ta.x.color <= IC_ANSI_SILVER) { + attr = (attr & 0xFFF0) | attr_color[ta.x.color - IC_ANSI_BLACK]; + } + else if (ta.x.color >= IC_ANSI_GRAY && ta.x.color <= IC_ANSI_WHITE) { + attr = (attr & 0xFFF0) | attr_color[ta.x.color - IC_ANSI_GRAY] | FOREGROUND_INTENSITY; + } + else if (ta.x.color == IC_ANSI_DEFAULT) { + attr = (attr & 0xFFF0) | (def_attr & 0x000F); + } + } + if (ta.x.bgcolor != IC_COLOR_NONE) { + if (ta.x.bgcolor >= IC_ANSI_BLACK && ta.x.bgcolor <= IC_ANSI_SILVER) { + attr = (attr & 0xFF0F) | (WORD)(attr_color[ta.x.bgcolor - IC_ANSI_BLACK] << 4); + } + else if (ta.x.bgcolor >= IC_ANSI_GRAY && ta.x.bgcolor <= IC_ANSI_WHITE) { + attr = (attr & 0xFF0F) | (WORD)(attr_color[ta.x.bgcolor - IC_ANSI_GRAY] << 4) | BACKGROUND_INTENSITY; + } + else if (ta.x.bgcolor == IC_ANSI_DEFAULT) { + attr = (attr & 0xFF0F) | (def_attr & 0x00F0); + } + } + if (ta.x.underline != IC_NONE) { + attr = (attr & ~COMMON_LVB_UNDERSCORE) | (ta.x.underline == IC_ON ? COMMON_LVB_UNDERSCORE : 0); + } + if (ta.x.reverse != IC_NONE) { + attr = (attr & ~COMMON_LVB_REVERSE_VIDEO) | (ta.x.reverse == IC_ON ? COMMON_LVB_REVERSE_VIDEO : 0); + } + if (attr != cur_attr) { + SetConsoleTextAttribute(term->hcon, attr); + } +} + +static ssize_t esc_param( const char* s, ssize_t def ) { + if (*s == '?') s++; + ssize_t n = def; + ic_atoz(s, &n); + return n; +} + +static void esc_param2( const char* s, ssize_t* p1, ssize_t* p2, ssize_t def ) { + if (*s == '?') s++; + *p1 = def; + *p2 = def; + ic_atoz2(s, p1, p2); +} + +// Emulate escape sequences on older windows. +static void term_write_esc( term_t* term, const char* s, ssize_t len ) { + ssize_t row; + ssize_t col; + + if (s[1] == '[') { + switch (s[len-1]) { + case 'A': + term_move_cursor(term, -1, 0, esc_param(s+2, 1)); + break; + case 'B': + term_move_cursor(term, 1, 0, esc_param(s+2, 1)); + break; + case 'C': + term_move_cursor(term, 0, 1, esc_param(s+2, 1)); + break; + case 'D': + term_move_cursor(term, 0, -1, esc_param(s+2, 1)); + break; + case 'H': + esc_param2(s+2, &row, &col, 1); + term_move_cursor_to(term, row, col); + break; + case 'K': + term_erase_line(term, esc_param(s+2, 0)); + break; + case 'm': + term_set_win_attr( term, attr_from_esc_sgr(s,len) ); + break; + + // support some less standard escape codes (currently not used by isocline) + case 'E': // line down + term_get_cursor_pos(term, &row, &col); + row += esc_param(s+2, 1); + term_move_cursor_to(term, row, 1); + break; + case 'F': // line up + term_get_cursor_pos(term, &row, &col); + row -= esc_param(s+2, 1); + term_move_cursor_to(term, row, 1); + break; + case 'G': // absolute column + term_get_cursor_pos(term, &row, &col); + col = esc_param(s+2, 1); + term_move_cursor_to(term, row, col); + break; + case 'J': + term_clear_screen(term, esc_param(s+2, 0)); + break; + case 'h': + if (strncmp(s+2, "?25h", 4) == 0) { + term_cursor_visible(term, true); + } + break; + case 'l': + if (strncmp(s+2, "?25l", 4) == 0) { + term_cursor_visible(term, false); + } + break; + case 's': + term_cursor_save(term); + break; + case 'u': + term_cursor_restore(term); + break; + // otherwise ignore + } + } + else if (s[1] == '7') { + term_cursor_save(term); + } + else if (s[1] == '8') { + term_cursor_restore(term); + } + else { + // otherwise ignore + } +} + +static bool term_write_direct(term_t* term, const char* s, ssize_t len ) { + term_cursor_visible(term,false); // reduce flicker + ssize_t pos = 0; + if ((term->hcon_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) { + // use the builtin virtual terminal processing. (enables truecolor for example) + term_write_console(term, s, len); + pos = len; + } + else { + // emulate escape sequences + while( pos < len ) { + // handle non-control in bulk (including utf-8 sequences) + // (We don't need to handle utf-8 separately as we set the codepage to always be in utf-8 mode) + ssize_t nonctrl = 0; + ssize_t next; + while( (next = str_next_ofs( s, len, pos+nonctrl, NULL )) > 0 && + (uint8_t)s[pos + nonctrl] >= ' ' && (uint8_t)s[pos + nonctrl] <= 0x7F) { + nonctrl += next; + } + if (nonctrl > 0) { + term_write_console(term, s+pos, nonctrl); + pos += nonctrl; + } + if (next <= 0) break; + + if ((uint8_t)s[pos] >= 0x80) { + // utf8 is already processed + term_write_console(term, s+pos, next); + } + else if (next > 1 && s[pos] == '\x1B') { + // handle control (note: str_next_ofs considers whole CSI escape sequences at a time) + term_write_esc(term, s+pos, next); + } + else if (next == 1 && (s[pos] == '\r' || s[pos] == '\n' || s[pos] == '\t' || s[pos] == '\b')) { + term_write_console( term, s+pos, next); + } + else { + // ignore + } + pos += next; + } + } + term_cursor_visible(term,true); + assert(pos == len); + return (pos == len); + +} +#endif + + + +//------------------------------------------------------------- +// Update terminal dimensions +//------------------------------------------------------------- + +#if !defined(_WIN32) + +// send escape query that may return a response on the tty +static bool term_esc_query_raw( term_t* term, const char* query, char* buf, ssize_t buflen ) +{ + if (buf==NULL || buflen <= 0 || query[0] == 0) return false; + bool osc = (query[1] == ']'); + if (!term_write_direct(term, query, ic_strlen(query))) return false; + debug_msg("term: read tty query response to: ESC %s\n", query + 1); + return tty_read_esc_response( term->tty, query[1], osc, buf, buflen ); +} + +static bool term_esc_query( term_t* term, const char* query, char* buf, ssize_t buflen ) +{ + if (!tty_start_raw(term->tty)) return false; + bool ok = term_esc_query_raw(term,query,buf,buflen); + tty_end_raw(term->tty); + return ok; +} + +// get the cursor position via an ESC[6n +static bool term_get_cursor_pos( term_t* term, ssize_t* row, ssize_t* col) +{ + // send escape query + char buf[128]; + if (!term_esc_query(term,"\x1B[6n",buf,128)) return false; + if (!ic_atoz2(buf,row,col)) return false; + return true; +} + +static void term_set_cursor_pos( term_t* term, ssize_t row, ssize_t col ) { + term_writef( term, IC_CSI "%zd;%zdH", row, col ); +} + +ic_private bool term_update_dim(term_t* term) { + ssize_t cols = 0; + ssize_t rows = 0; + struct winsize ws; + if (ioctl(term->fd_out, TIOCGWINSZ, &ws) >= 0) { + // ioctl succeeded + cols = ws.ws_col; // debuggers return 0 for the column + rows = ws.ws_row; + } + else { + // determine width by querying the cursor position + debug_msg("term: ioctl term-size failed: %d,%d\n", ws.ws_row, ws.ws_col); + ssize_t col0 = 0; + ssize_t row0 = 0; + if (term_get_cursor_pos(term,&row0,&col0)) { + term_set_cursor_pos(term,999,999); + ssize_t col1 = 0; + ssize_t row1 = 0; + if (term_get_cursor_pos(term,&row1,&col1)) { + cols = col1; + rows = row1; + } + term_set_cursor_pos(term,row0,col0); + } + else { + // cannot query position + // return 0 column + } + } + + // update width and return whether it changed. + bool changed = (term->width != cols || term->height != rows); + debug_msg("terminal dim: %zd,%zd: %s\n", rows, cols, changed ? "changed" : "unchanged"); + if (cols > 0) { + term->width = cols; + term->height = rows; + } + return changed; +} + +#else + +ic_private bool term_update_dim(term_t* term) { + if (term->hcon == 0) { + term->hcon = GetConsoleWindow(); + } + ssize_t rows = 0; + ssize_t cols = 0; + CONSOLE_SCREEN_BUFFER_INFO sbinfo; + if (GetConsoleScreenBufferInfo(term->hcon, &sbinfo)) { + cols = (ssize_t)sbinfo.srWindow.Right - (ssize_t)sbinfo.srWindow.Left + 1; + rows = (ssize_t)sbinfo.srWindow.Bottom - (ssize_t)sbinfo.srWindow.Top + 1; + } + bool changed = (term->width != cols || term->height != rows); + term->width = cols; + term->height = rows; + debug_msg("term: update dim: %zd, %zd\n", term->height, term->width ); + return changed; +} + +#endif + + + +//------------------------------------------------------------- +// Enable/disable terminal raw mode +//------------------------------------------------------------- + +#if !defined(_WIN32) + +// On non-windows, the terminal is set in raw mode by the tty. + +ic_private void term_start_raw(term_t* term) { + term->raw_enabled++; +} + +ic_private void term_end_raw(term_t* term, bool force) { + if (term->raw_enabled <= 0) return; + if (!force) { + term->raw_enabled--; + } + else { + term->raw_enabled = 0; + } +} + +static bool term_esc_query_color_raw(term_t* term, int color_idx, uint32_t* color ) { + char buf[128+1]; + snprintf(buf,128,"\x1B]4;%d;?\x1B\\", color_idx); + if (!term_esc_query_raw( term, buf, buf, 128 )) { + debug_msg("esc query response not received\n"); + return false; + } + if (buf[0] != '4') return false; + const char* rgb = strchr(buf,':'); + if (rgb==NULL) return false; + rgb++; // skip ':' + unsigned int r,g,b; + if (sscanf(rgb,"%x/%x/%x",&r,&g,&b) != 3) return false; + if (rgb[2]!='/') { // 48-bit rgb, hexadecimal round to 24-bit + r = (r+0x7F)/0x100; // note: can "overflow", e.g. 0xFFFF -> 0x100. (and we need `ic_cap8` to convert.) + g = (g+0x7F)/0x100; + b = (b+0x7F)/0x100; + } + *color = (ic_cap8(r)<<16) | (ic_cap8(g)<<8) | ic_cap8(b); + debug_msg("color query: %02x,%02x,%02x: %06x\n", r, g, b, *color); + return true; +} + +// update ansi 16 color palette for better color approximation +static void term_update_ansi16(term_t* term) { + debug_msg("update ansi colors\n"); + #if defined(GIO_CMAP) + // try ioctl first (on Linux) + uint8_t cmap[48]; + memset(cmap,0,48); + if (ioctl(term->fd_out,GIO_CMAP,&cmap) >= 0) { + // success + for(ssize_t i = 0; i < 48; i+=3) { + uint32_t color = ((uint32_t)(cmap[i]) << 16) | ((uint32_t)(cmap[i+1]) << 8) | cmap[i+2]; + debug_msg("term (ioctl) ansi color %d: 0x%06x\n", i, color); + ansi256[i] = color; + } + return; + } + else { + debug_msg("ioctl GIO_CMAP failed: entry 1: 0x%02x%02x%02x\n", cmap[3], cmap[4], cmap[5]); + } + #endif + // this seems to be unreliable on some systems (Ubuntu+Gnome terminal) so only enable when known ok. + #if __APPLE__ + // otherwise use OSC 4 escape sequence query + if (tty_start_raw(term->tty)) { + for(ssize_t i = 0; i < 16; i++) { + uint32_t color; + if (!term_esc_query_color_raw(term, i, &color)) break; + debug_msg("term ansi color %d: 0x%06x\n", i, color); + ansi256[i] = color; + } + tty_end_raw(term->tty); + } + #endif +} + +static void term_init_raw(term_t* term) { + if (term->palette < ANSIRGB) { + term_update_ansi16(term); + } +} + +#else + +ic_private void term_start_raw(term_t* term) { + if (term->raw_enabled++ > 0) return; + CONSOLE_SCREEN_BUFFER_INFO info; + if (GetConsoleScreenBufferInfo(term->hcon, &info)) { + term->hcon_orig_attr = info.wAttributes; + } + term->hcon_orig_cp = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + if (term->hcon_mode == 0) { + // first time initialization + DWORD mode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_LVB_GRID_WORLDWIDE; // for \r \n and \b + // use escape sequence handling if available and the terminal supports it (so we can use rgb colors in Windows terminal) + // Unfortunately, in plain powershell, we can successfully enable terminal processing + // but it still fails to render correctly; so we require the palette be large enough (like in Windows Terminal) + if (term->palette >= ANSI256 && SetConsoleMode(term->hcon, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { + term->hcon_mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; + debug_msg("term: console mode: virtual terminal processing enabled\n"); + } + // no virtual terminal processing, emulate instead + else if (SetConsoleMode(term->hcon, mode)) { + term->hcon_mode = mode; + term->palette = ANSI16; + } + GetConsoleMode(term->hcon, &mode); + debug_msg("term: console mode: orig: 0x%x, new: 0x%x, current 0x%x\n", term->hcon_orig_mode, term->hcon_mode, mode); + } + else { + SetConsoleMode(term->hcon, term->hcon_mode); + } +} + +ic_private void term_end_raw(term_t* term, bool force) { + if (term->raw_enabled <= 0) return; + if (!force && term->raw_enabled > 1) { + term->raw_enabled--; + } + else { + term->raw_enabled = 0; + SetConsoleMode(term->hcon, term->hcon_orig_mode); + SetConsoleOutputCP(term->hcon_orig_cp); + SetConsoleTextAttribute(term->hcon, term->hcon_orig_attr); + } +} + +static void term_init_raw(term_t* term) { + term->hcon = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleMode(term->hcon, &term->hcon_orig_mode); + CONSOLE_SCREEN_BUFFER_INFOEX info; + memset(&info, 0, sizeof(info)); + info.cbSize = sizeof(info); + if (GetConsoleScreenBufferInfoEx(term->hcon, &info)) { + // store default attributes + term->hcon_default_attr = info.wAttributes; + // update our color table with the actual colors used. + for (unsigned i = 0; i < 16; i++) { + COLORREF cr = info.ColorTable[i]; + uint32_t color = (ic_cap8(GetRValue(cr))<<16) | (ic_cap8(GetGValue(cr))<<8) | ic_cap8(GetBValue(cr)); // COLORREF = BGR + // index is also in reverse in the bits 0 and 2 + unsigned j = (i&0x08) | ((i&0x04)>>2) | (i&0x02) | (i&0x01)<<2; + debug_msg("term: ansi color %d is 0x%06x\n", j, color); + ansi256[j] = color; + } + } + else { + DWORD err = GetLastError(); + debug_msg("term: cannot get console screen buffer: %d %x", err, err); + } + term_start_raw(term); // initialize the hcon_mode + term_end_raw(term,false); +} + +#endif diff --git a/deps/isocline/src/term.h b/deps/isocline/src/term.h new file mode 100644 index 0000000..50bfd96 --- /dev/null +++ b/deps/isocline/src/term.h @@ -0,0 +1,85 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_TERM_H +#define IC_TERM_H + +#include "common.h" +#include "tty.h" +#include "stringbuf.h" +#include "attr.h" + +struct term_s; +typedef struct term_s term_t; + +typedef enum buffer_mode_e { + UNBUFFERED, + LINEBUFFERED, + BUFFERED, +} buffer_mode_t; + +// Primitives +ic_private term_t* term_new(alloc_t* mem, tty_t* tty, bool nocolor, bool silent, int fd_out); +ic_private void term_free(term_t* term); + +ic_private bool term_is_interactive(const term_t* term); +ic_private void term_start_raw(term_t* term); +ic_private void term_end_raw(term_t* term, bool force); + +ic_private bool term_enable_beep(term_t* term, bool enable); +ic_private bool term_enable_color(term_t* term, bool enable); + +ic_private void term_flush(term_t* term); +ic_private buffer_mode_t term_set_buffer_mode(term_t* term, buffer_mode_t mode); + +ic_private void term_write_n(term_t* term, const char* s, ssize_t n); +ic_private void term_write(term_t* term, const char* s); +ic_private void term_writeln(term_t* term, const char* s); +ic_private void term_write_char(term_t* term, char c); + +ic_private void term_write_repeat(term_t* term, const char* s, ssize_t count ); +ic_private void term_beep(term_t* term); + +ic_private bool term_update_dim(term_t* term); + +ic_private ssize_t term_get_width(term_t* term); +ic_private ssize_t term_get_height(term_t* term); +ic_private int term_get_color_bits(term_t* term); + +// Helpers +ic_private void term_writef(term_t* term, const char* fmt, ...); +ic_private void term_vwritef(term_t* term, const char* fmt, va_list args); + +ic_private void term_left(term_t* term, ssize_t n); +ic_private void term_right(term_t* term, ssize_t n); +ic_private void term_up(term_t* term, ssize_t n); +ic_private void term_down(term_t* term, ssize_t n); +ic_private void term_start_of_line(term_t* term ); +ic_private void term_clear_line(term_t* term); +ic_private void term_clear_to_end_of_line(term_t* term); +// ic_private void term_clear_lines_to_end(term_t* term); + + +ic_private void term_attr_reset(term_t* term); +ic_private void term_underline(term_t* term, bool on); +ic_private void term_reverse(term_t* term, bool on); +ic_private void term_bold(term_t* term, bool on); +ic_private void term_italic(term_t* term, bool on); + +ic_private void term_color(term_t* term, ic_color_t color); +ic_private void term_bgcolor(term_t* term, ic_color_t color); + +// Formatted output + +ic_private attr_t term_get_attr( const term_t* term ); +ic_private void term_set_attr( term_t* term, attr_t attr ); +ic_private void term_write_formatted( term_t* term, const char* s, const attr_t* attrs ); +ic_private void term_write_formatted_n( term_t* term, const char* s, const attr_t* attrs, ssize_t n ); + +ic_private ic_color_t color_from_ansi256(ssize_t i); + +#endif // IC_TERM_H diff --git a/deps/isocline/src/term_color.c b/deps/isocline/src/term_color.c new file mode 100644 index 0000000..98af3cf --- /dev/null +++ b/deps/isocline/src/term_color.c @@ -0,0 +1,371 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ + +// This file is included in "term.c" + +//------------------------------------------------------------- +// Standard ANSI palette for 256 colors +//------------------------------------------------------------- + +static uint32_t ansi256[256] = { + // not const as on some platforms (e.g. Windows, xterm) we update the first 16 entries with the actual used colors. + // 0, standard ANSI + 0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, + 0x008080, 0xc0c0c0, + // 8, bright ANSI + 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, + 0x00ffff, 0xffffff, + // 6x6x6 RGB colors + // 16 + 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, + 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, + 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, + 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, + 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, + 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, + // 52 + 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, + 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, + 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, + 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, + 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, + 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, + // 88 + 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, + 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, + 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, + 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, + 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, + 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, + // 124 + 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, + 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, + 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, + 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, + 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, + 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, + // 160 + 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, + 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, + 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, + 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, + 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, + 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, + // 196 + 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, + 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, + 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, + 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, + 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, + 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, + // 232, gray scale + 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, + 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, + 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, + 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee +}; + + +//------------------------------------------------------------- +// Create colors +//------------------------------------------------------------- + +// Create a color from a 24-bit color value. +ic_private ic_color_t ic_rgb(uint32_t hex) { + return (ic_color_t)(0x1000000 | (hex & 0xFFFFFF)); +} + +// Limit an int to values between 0 and 255. +static uint32_t ic_cap8(ssize_t i) { + return (i < 0 ? 0 : (i > 255 ? 255 : (uint32_t)i)); +} + +// Create a color from a 24-bit color value. +ic_private ic_color_t ic_rgbx(ssize_t r, ssize_t g, ssize_t b) { + return ic_rgb( (ic_cap8(r)<<16) | (ic_cap8(g)<<8) | ic_cap8(b) ); +} + + +//------------------------------------------------------------- +// Match an rgb color to a ansi8, ansi16, or ansi256 +//------------------------------------------------------------- + +static bool color_is_rgb( ic_color_t color ) { + return (color >= IC_RGB(0)); // bit 24 is set for rgb colors +} + +static void color_to_rgb(ic_color_t color, int* r, int* g, int* b) { + assert(color_is_rgb(color)); + *r = ((color >> 16) & 0xFF); + *g = ((color >> 8) & 0xFF); + *b = (color & 0xFF); +} + +ic_private ic_color_t color_from_ansi256(ssize_t i) { + if (i >= 0 && i < 8) { + return (IC_ANSI_BLACK + (uint32_t)i); + } + else if (i >= 8 && i < 16) { + return (IC_ANSI_DARKGRAY + (uint32_t)(i - 8)); + } + else if (i >= 16 && i <= 255) { + return ic_rgb( ansi256[i] ); + } + else if (i == 256) { + return IC_ANSI_DEFAULT; + } + else { + return IC_ANSI_DEFAULT; + } +} + +static bool is_grayish(int r, int g, int b) { + return (abs(r-g) <= 4) && (abs((r+g)/2 - b) <= 4); +} + +static bool is_grayish_color( uint32_t rgb ) { + int r, g, b; + color_to_rgb(IC_RGB(rgb),&r,&g,&b); + return is_grayish(r,g,b); +} + +static int_least32_t sqr(int_least32_t x) { + return x*x; +} + +// Approximation to delta-E CIE color distance using much +// simpler calculations. See . +// This is essentialy weighted euclidean distance but the weight distribution +// depends on how big the "red" component of the color is. +// We do not take the square root as we only need to find +// the minimal distance (and multiply by 256 to increase precision). +// Needs at least 28-bit signed integers to avoid overflow. +static int_least32_t rgb_distance_rmean( uint32_t color, int r2, int g2, int b2 ) { + int r1, g1, b1; + color_to_rgb(IC_RGB(color),&r1,&g1,&b1); + int_least32_t rmean = (r1 + r2) / 2; + int_least32_t dr2 = sqr(r1 - r2); + int_least32_t dg2 = sqr(g1 - g2); + int_least32_t db2 = sqr(b1 - b2); + int_least32_t dist = ((512+rmean)*dr2) + 1024*dg2 + ((767-rmean)*db2); + return dist; +} + +// Another approximation to delta-E CIE color distance using +// simpler calculations. Similar to `rmean` but adds an adjustment factor +// based on the "red/blue" difference. +static int_least32_t rgb_distance_rbmean( uint32_t color, int r2, int g2, int b2 ) { + int r1, g1, b1; + color_to_rgb(IC_RGB(color),&r1,&g1,&b1); + int_least32_t rmean = (r1 + r2) / 2; + int_least32_t dr2 = sqr(r1 - r2); + int_least32_t dg2 = sqr(g1 - g2); + int_least32_t db2 = sqr(b1 - b2); + int_least32_t dist = 2*dr2 + 4*dg2 + 3*db2 + ((rmean*(dr2 - db2))/256); + return dist; +} + + +// Maintain a small cache of recently used colors. Should be short enough to be effectively constant time. +// If we ever use a more expensive color distance method, we may increase the size a bit (64?) +// (Initial zero initialized cache is valid.) +#define RGB_CACHE_LEN (16) +typedef struct rgb_cache_s { + int last; + int indices[RGB_CACHE_LEN]; + ic_color_t colors[RGB_CACHE_LEN]; +} rgb_cache_t; + +// remember a color in the LRU cache +void rgb_remember( rgb_cache_t* cache, ic_color_t color, int idx ) { + if (cache == NULL) return; + cache->colors[cache->last] = color; + cache->indices[cache->last] = idx; + cache->last++; + if (cache->last >= RGB_CACHE_LEN) { cache->last = 0; } +} + +// quick lookup in cache; -1 on failure +int rgb_lookup( const rgb_cache_t* cache, ic_color_t color ) { + if (cache != NULL) { + for(int i = 0; i < RGB_CACHE_LEN; i++) { + if (cache->colors[i] == color) return cache->indices[i]; + } + } + return -1; +} + +// return the index of the closest matching color +static int rgb_match( uint32_t* palette, int start, int len, rgb_cache_t* cache, ic_color_t color ) { + assert(color_is_rgb(color)); + // in cache? + int min = rgb_lookup(cache,color); + if (min >= 0) { + return min; + } + // otherwise find closest color match in the palette + int r, g, b; + color_to_rgb(color,&r,&g,&b); + min = start; + int_least32_t mindist = (INT_LEAST32_MAX)/4; + for(int i = start; i < len; i++) { + //int_least32_t dist = rgb_distance_rbmean(palette[i],r,g,b); + int_least32_t dist = rgb_distance_rmean(palette[i],r,g,b); + if (is_grayish_color(palette[i]) != is_grayish(r, g, b)) { + // with few colors, make it less eager to substitute a gray for a non-gray (or the other way around) + if (len <= 16) { + dist *= 4; + } + else { + dist = (dist/4)*5; + } + } + if (dist < mindist) { + min = i; + mindist = dist; + } + } + rgb_remember(cache,color,min); + return min; +} + + +// Match RGB to an index in the ANSI 256 color table +static int rgb_to_ansi256(ic_color_t color) { + static rgb_cache_t ansi256_cache; + int c = rgb_match(ansi256, 16, 256, &ansi256_cache, color); // not the first 16 ANSI colors as those may be different + //debug_msg("term: rgb %x -> ansi 256: %d\n", color, c ); + return c; +} + +// Match RGB to an ANSI 16 color code (30-37, 90-97) +static int color_to_ansi16(ic_color_t color) { + if (!color_is_rgb(color)) { + return (int)color; + } + else { + static rgb_cache_t ansi16_cache; + int c = rgb_match(ansi256, 0, 16, &ansi16_cache, color); + //debug_msg("term: rgb %x -> ansi 16: %d\n", color, c ); + return (c < 8 ? 30 + c : 90 + c - 8); + } +} + +// Match RGB to an ANSI 16 color code (30-37, 90-97) +// but assuming the bright colors are simulated using 'bold'. +static int color_to_ansi8(ic_color_t color) { + if (!color_is_rgb(color)) { + return (int)color; + } + else { + // match to basic 8 colors first + static rgb_cache_t ansi8_cache; + int c = 30 + rgb_match(ansi256, 0, 8, &ansi8_cache, color); + // and then adjust for brightness + int r, g, b; + color_to_rgb(color,&r,&g,&b); + if (r>=196 || g>=196 || b>=196) c += 60; + //debug_msg("term: rgb %x -> ansi 8: %d\n", color, c ); + return c; + } +} + + +//------------------------------------------------------------- +// Emit color escape codes based on the terminal capability +//------------------------------------------------------------- + +static void fmt_color_ansi8( char* buf, ssize_t len, ic_color_t color, bool bg ) { + int c = color_to_ansi8(color) + (bg ? 10 : 0); + if (c >= 90) { + snprintf(buf, to_size_t(len), IC_CSI "1;%dm", c - 60); + } + else { + snprintf(buf, to_size_t(len), IC_CSI "22;%dm", c ); + } +} + +static void fmt_color_ansi16( char* buf, ssize_t len, ic_color_t color, bool bg ) { + snprintf( buf, to_size_t(len), IC_CSI "%dm", color_to_ansi16(color) + (bg ? 10 : 0) ); +} + +static void fmt_color_ansi256( char* buf, ssize_t len, ic_color_t color, bool bg ) { + if (!color_is_rgb(color)) { + fmt_color_ansi16(buf,len,color,bg); + } + else { + snprintf( buf, to_size_t(len), IC_CSI "%d;5;%dm", (bg ? 48 : 38), rgb_to_ansi256(color) ); + } +} + +static void fmt_color_rgb( char* buf, ssize_t len, ic_color_t color, bool bg ) { + if (!color_is_rgb(color)) { + fmt_color_ansi16(buf,len,color,bg); + } + else { + int r,g,b; + color_to_rgb(color, &r,&g,&b); + snprintf( buf, to_size_t(len), IC_CSI "%d;2;%d;%d;%dm", (bg ? 48 : 38), r, g, b ); + } +} + +static void fmt_color_ex(char* buf, ssize_t len, palette_t palette, ic_color_t color, bool bg) { + if (color == IC_COLOR_NONE || palette == MONOCHROME) return; + if (palette == ANSI8) { + fmt_color_ansi8(buf,len,color,bg); + } + else if (!color_is_rgb(color) || palette == ANSI16) { + fmt_color_ansi16(buf,len,color,bg); + } + else if (palette == ANSI256) { + fmt_color_ansi256(buf,len,color,bg); + } + else { + fmt_color_rgb(buf,len,color,bg); + } +} + +static void term_color_ex(term_t* term, ic_color_t color, bool bg) { + char buf[128+1]; + fmt_color_ex(buf,128,term->palette,color,bg); + term_write(term,buf); +} + +//------------------------------------------------------------- +// Main API functions +//------------------------------------------------------------- + +ic_private void term_color(term_t* term, ic_color_t color) { + term_color_ex(term,color,false); +} + +ic_private void term_bgcolor(term_t* term, ic_color_t color) { + term_color_ex(term,color,true); +} + +ic_private void term_append_color(term_t* term, stringbuf_t* sbuf, ic_color_t color) { + char buf[128+1]; + fmt_color_ex(buf,128,term->palette,color,false); + sbuf_append(sbuf,buf); +} + +ic_private void term_append_bgcolor(term_t* term, stringbuf_t* sbuf, ic_color_t color) { + char buf[128+1]; + fmt_color_ex(buf, 128, term->palette, color, true); + sbuf_append(sbuf, buf); +} + +ic_private int term_get_color_bits(term_t* term) { + switch (term->palette) { + case MONOCHROME: return 1; + case ANSI8: return 3; + case ANSI16: return 4; + case ANSI256: return 8; + case ANSIRGB: return 24; + default: return 4; + } +} diff --git a/deps/isocline/src/tty.c b/deps/isocline/src/tty.c new file mode 100644 index 0000000..09f7aed --- /dev/null +++ b/deps/isocline/src/tty.c @@ -0,0 +1,889 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include + +#include "tty.h" + +#if defined(_WIN32) +#include +#include +#define isatty(fd) _isatty(fd) +#define read(fd,s,n) _read(fd,s,n) +#define STDIN_FILENO 0 +#if (_WIN32_WINNT < 0x0600) +WINBASEAPI ULONGLONG WINAPI GetTickCount64(VOID); +#endif +#else +#include +#include +#include +#include +#include +#include +#if !defined(FIONREAD) +#include +#endif +#endif + +#define TTY_PUSH_MAX (32) + +struct tty_s { + int fd_in; // input handle + bool raw_enabled; // is raw mode enabled? + bool is_utf8; // is the input stream in utf-8 mode? + bool has_term_resize_event; // are resize events generated? + bool term_resize_event; // did a term resize happen? + alloc_t* mem; // memory allocator + code_t pushbuf[TTY_PUSH_MAX]; // push back buffer for full key codes + ssize_t push_count; + uint8_t cpushbuf[TTY_PUSH_MAX]; // low level push back buffer for bytes + ssize_t cpush_count; + long esc_initial_timeout; // initial ms wait to see if ESC starts an escape sequence + long esc_timeout; // follow up delay for characters in an escape sequence + #if defined(_WIN32) + HANDLE hcon; // console input handle + DWORD hcon_orig_mode; // original console mode + #else + struct termios orig_ios; // original terminal settings + struct termios raw_ios; // raw terminal settings + #endif +}; + + +//------------------------------------------------------------- +// Forward declarations of platform dependent primitives below +//------------------------------------------------------------- + +ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms); // does not modify `c` when no input (false is returned) + +//------------------------------------------------------------- +// Key code helpers +//------------------------------------------------------------- + +ic_private bool code_is_ascii_char(code_t c, char* chr ) { + if (c >= ' ' && c <= 0x7F) { + if (chr != NULL) *chr = (char)c; + return true; + } + else { + if (chr != NULL) *chr = 0; + return false; + } +} + +ic_private bool code_is_unicode(code_t c, unicode_t* uchr) { + if (c <= KEY_UNICODE_MAX) { + if (uchr != NULL) *uchr = c; + return true; + } + else { + if (uchr != NULL) *uchr = 0; + return false; + } +} + +ic_private bool code_is_virt_key(code_t c ) { + return (KEY_NO_MODS(c) <= 0x20 || KEY_NO_MODS(c) >= KEY_VIRT); +} + + +//------------------------------------------------------------- +// Read a key code +//------------------------------------------------------------- +static code_t modify_code( code_t code ); + +static code_t tty_read_utf8( tty_t* tty, uint8_t c0 ) { + uint8_t buf[5]; + memset(buf, 0, 5); + + // try to read as many bytes as potentially needed + buf[0] = c0; + ssize_t count = 1; + if (c0 > 0x7F) { + if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) { + count++; + if (c0 > 0xDF) { + if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) { + count++; + if (c0 > 0xEF) { + if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) { + count++; + } + } + } + } + } + } + + buf[count] = 0; + debug_msg("tty: read utf8: count: %zd: %02x,%02x,%02x,%02x", count, buf[0], buf[1], buf[2], buf[3]); + + // decode the utf8 to unicode + ssize_t read = 0; + code_t code = key_unicode(unicode_from_qutf8(buf, count, &read)); + + // push back unused bytes (in the case of invalid utf8) + while (count > read) { + count--; + if (count >= 0 && count <= 4) { // to help the static analyzer + tty_cpush_char(tty, buf[count]); + } + } + return code; +} + +// pop a code from the pushback buffer. +static bool tty_code_pop(tty_t* tty, code_t* code); + + +// read a single char/key +ic_private bool tty_read_timeout(tty_t* tty, long timeout_ms, code_t* code) +{ + // is there a push_count back code? + if (tty_code_pop(tty,code)) { + return code; + } + + // read a single char/byte from a character stream + uint8_t c; + if (!tty_readc_noblock(tty, &c, timeout_ms)) return false; + + if (c == KEY_ESC) { + // escape sequence? + *code = tty_read_esc(tty, tty->esc_initial_timeout, tty->esc_timeout); + } + else if (c <= 0x7F) { + // ascii + *code = key_unicode(c); + } + else if (tty->is_utf8) { + // utf8 sequence + *code = tty_read_utf8(tty,c); + } + else { + // c >= 0x80 but tty is not utf8; use raw plane so we can translate it back in the end + *code = key_unicode( unicode_from_raw(c) ); + } + + *code = modify_code(*code); + return true; +} + +// Transform virtual keys to be more portable across platforms +static code_t modify_code( code_t code ) { + code_t key = KEY_NO_MODS(code); + code_t mods = KEY_MODS(code); + debug_msg( "tty: readc %s%s%s 0x%03x ('%c')\n", + mods&KEY_MOD_SHIFT ? "shift+" : "", mods&KEY_MOD_CTRL ? "ctrl+" : "", mods&KEY_MOD_ALT ? "alt+" : "", + key, (key >= ' ' && key <= '~' ? key : ' ')); + + // treat KEY_RUBOUT (0x7F) as KEY_BACKSP + if (key == KEY_RUBOUT) { + code = KEY_BACKSP | mods; + } + // ctrl+'_' is translated to '\x1F' on Linux, translate it back + else if (key == key_char('\x1F') && (mods & KEY_MOD_ALT) == 0) { + key = '_'; + code = WITH_CTRL(key_char('_')); + } + // treat ctrl/shift + enter always as KEY_LINEFEED for portability + else if (key == KEY_ENTER && (mods == KEY_MOD_SHIFT || mods == KEY_MOD_ALT || mods == KEY_MOD_CTRL)) { + code = KEY_LINEFEED; + } + // treat ctrl+tab always as shift+tab for portability + else if (code == WITH_CTRL(KEY_TAB)) { + code = KEY_SHIFT_TAB; + } + // treat ctrl+end/alt+>/alt-down and ctrl+home/alt+') || code == WITH_CTRL(KEY_END)) { + code = KEY_PAGEDOWN; + } + else if (code == WITH_ALT(KEY_UP) || code == WITH_ALT('<') || code == WITH_CTRL(KEY_HOME)) { + code = KEY_PAGEUP; + } + + // treat C0 codes without KEY_MOD_CTRL + if (key < ' ' && (mods&KEY_MOD_CTRL) != 0) { + code &= ~KEY_MOD_CTRL; + } + + return code; +} + + +// read a single char/key +ic_private code_t tty_read(tty_t* tty) +{ + code_t code; + if (!tty_read_timeout(tty, -1, &code)) return KEY_NONE; + return code; +} + +//------------------------------------------------------------- +// Read back an ANSI query response +//------------------------------------------------------------- + +ic_private bool tty_read_esc_response(tty_t* tty, char esc_start, bool final_st, char* buf, ssize_t buflen ) +{ + buf[0] = 0; + ssize_t len = 0; + uint8_t c = 0; + if (!tty_readc_noblock(tty, &c, 2*tty->esc_initial_timeout) || c != '\x1B') { + debug_msg("initial esc response failed: 0x%02x\n", c); + return false; + } + if (!tty_readc_noblock(tty, &c, tty->esc_timeout) || (c != esc_start)) return false; + while( len < buflen ) { + if (!tty_readc_noblock(tty, &c, tty->esc_timeout)) return false; + if (final_st) { + // OSC is terminated by BELL, or ESC \ (ST) (and STX) + if (c=='\x07' || c=='\x02') { + break; + } + else if (c=='\x1B') { + uint8_t c1; + if (!tty_readc_noblock(tty, &c1, tty->esc_timeout)) return false; + if (c1=='\\') break; + tty_cpush_char(tty,c1); + } + } + else { + if (c == '\x02') { // STX + break; + } + else if (!((c >= '0' && c <= '9') || strchr("<=>?;:",c) != NULL)) { + buf[len++] = (char)c; // for non-OSC save the terminating character + break; + } + } + buf[len++] = (char)c; + } + buf[len] = 0; + debug_msg("tty: escape query response: %s\n", buf); + return true; +} + +//------------------------------------------------------------- +// High level code pushback +//------------------------------------------------------------- + +static bool tty_code_pop( tty_t* tty, code_t* code ) { + if (tty->push_count <= 0) return false; + tty->push_count--; + *code = tty->pushbuf[tty->push_count]; + return true; +} + +ic_private void tty_code_pushback( tty_t* tty, code_t c ) { + // note: must be signal safe + if (tty->push_count >= TTY_PUSH_MAX) return; + tty->pushbuf[tty->push_count] = c; + tty->push_count++; +} + + +//------------------------------------------------------------- +// low-level character pushback (for escape sequences and windows) +//------------------------------------------------------------- + +ic_private bool tty_cpop(tty_t* tty, uint8_t* c) { + if (tty->cpush_count <= 0) { // do not modify c on failure (see `tty_decode_unicode`) + return false; + } + else { + tty->cpush_count--; + *c = tty->cpushbuf[tty->cpush_count]; + return true; + } +} + +static void tty_cpush(tty_t* tty, const char* s) { + ssize_t len = ic_strlen(s); + if (tty->push_count + len > TTY_PUSH_MAX) { + debug_msg("tty: cpush buffer full! (pushing %s)\n", s); + assert(false); + return; + } + for (ssize_t i = 0; i < len; i++) { + tty->cpushbuf[tty->cpush_count + i] = (uint8_t)( s[len - i - 1] ); + } + tty->cpush_count += len; + return; +} + +// convenience function for small sequences +static void tty_cpushf(tty_t* tty, const char* fmt, ...) { + va_list args; + va_start(args,fmt); + char buf[TTY_PUSH_MAX+1]; + vsnprintf(buf,TTY_PUSH_MAX,fmt,args); + buf[TTY_PUSH_MAX] = 0; + tty_cpush(tty,buf); + va_end(args); + return; +} + +ic_private void tty_cpush_char(tty_t* tty, uint8_t c) { + uint8_t buf[2]; + buf[0] = c; + buf[1] = 0; + tty_cpush(tty, (const char*)buf); +} + + +//------------------------------------------------------------- +// Push escape codes (used on Windows to insert keys) +//------------------------------------------------------------- + +static unsigned csi_mods(code_t mods) { + unsigned m = 1; + if (mods&KEY_MOD_SHIFT) m += 1; + if (mods&KEY_MOD_ALT) m += 2; + if (mods&KEY_MOD_CTRL) m += 4; + return m; +} + +// Push ESC [ ; ~ +static void tty_cpush_csi_vt( tty_t* tty, code_t mods, uint32_t vtcode ) { + tty_cpushf(tty,"\x1B[%u;%u~", vtcode, csi_mods(mods) ); +} + +// push ESC [ 1 ; +static void tty_cpush_csi_xterm( tty_t* tty, code_t mods, char xcode ) { + tty_cpushf(tty,"\x1B[1;%u%c", csi_mods(mods), xcode ); +} + +// push ESC [ ; u +static void tty_cpush_csi_unicode( tty_t* tty, code_t mods, uint32_t unicode ) { + if ((unicode < 0x80 && mods == 0) || + (mods == KEY_MOD_CTRL && unicode < ' ' && unicode != KEY_TAB && unicode != KEY_ENTER + && unicode != KEY_LINEFEED && unicode != KEY_BACKSP) || + (mods == KEY_MOD_SHIFT && unicode >= ' ' && unicode <= KEY_RUBOUT)) { + tty_cpush_char(tty,(uint8_t)unicode); + } + else { + tty_cpushf(tty,"\x1B[%u;%uu", unicode, csi_mods(mods) ); + } +} + +//------------------------------------------------------------- +// Init +//------------------------------------------------------------- + +static bool tty_init_raw(tty_t* tty); +static void tty_done_raw(tty_t* tty); + +static bool tty_init_utf8(tty_t* tty) { + #ifdef _WIN32 + tty->is_utf8 = true; + #else + const char* loc = setlocale(LC_ALL,""); + tty->is_utf8 = (ic_icontains(loc,"UTF-8") || ic_icontains(loc,"utf8") || ic_stricmp(loc,"C") == 0); + debug_msg("tty: utf8: %s (loc=%s)\n", tty->is_utf8 ? "true" : "false", loc); + #endif + return true; +} + +ic_private tty_t* tty_new(alloc_t* mem, int fd_in) +{ + tty_t* tty = mem_zalloc_tp(mem, tty_t); + tty->mem = mem; + tty->fd_in = (fd_in < 0 ? STDIN_FILENO : fd_in); + #if defined(__APPLE__) + tty->esc_initial_timeout = 200; // apple use ESC+ for alt- + #else + tty->esc_initial_timeout = 100; + #endif + tty->esc_timeout = 10; + if (!(isatty(tty->fd_in) && tty_init_raw(tty) && tty_init_utf8(tty))) { + tty_free(tty); + return NULL; + } + return tty; +} + +ic_private void tty_free(tty_t* tty) { + if (tty==NULL) return; + tty_end_raw(tty); + tty_done_raw(tty); + mem_free(tty->mem,tty); +} + +ic_private bool tty_is_utf8(const tty_t* tty) { + if (tty == NULL) return true; + return (tty->is_utf8); +} + +ic_private bool tty_term_resize_event(tty_t* tty) { + if (tty == NULL) return true; + if (tty->has_term_resize_event) { + if (!tty->term_resize_event) return false; + tty->term_resize_event = false; // reset. + } + return true; // always return true on systems without a resize event (more expensive but still ok) +} + +ic_private void tty_set_esc_delay(tty_t* tty, long initial_delay_ms, long followup_delay_ms) { + tty->esc_initial_timeout = (initial_delay_ms < 0 ? 0 : (initial_delay_ms > 1000 ? 1000 : initial_delay_ms)); + tty->esc_timeout = (followup_delay_ms < 0 ? 0 : (followup_delay_ms > 1000 ? 1000 : followup_delay_ms)); +} + +//------------------------------------------------------------- +// Unix +//------------------------------------------------------------- +#if !defined(_WIN32) + +static bool tty_readc_blocking(tty_t* tty, uint8_t* c) { + if (tty_cpop(tty,c)) return true; + *c = 0; + ssize_t nread = read(tty->fd_in, (char*)c, 1); + if (nread < 0 && errno == EINTR) { + // can happen on SIGWINCH signal for terminal resize + } + return (nread == 1); +} + + +// non blocking read -- with a small timeout used for reading escape sequences. +ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms) +{ + // in our pushback buffer? + if (tty_cpop(tty, c)) return true; + + // blocking read? + if (timeout_ms < 0) { + return tty_readc_blocking(tty,c); + } + + // if supported, peek first if any char is available. + #if defined(FIONREAD) + { int navail = 0; + if (ioctl(0, FIONREAD, &navail) == 0) { + if (navail >= 1) { + return tty_readc_blocking(tty, c); + } + else if (timeout_ms == 0) { + return false; // return early if there is no input available (with a zero timeout) + } + } + } + #endif + + // otherwise block for at most timeout milliseconds + #if defined(FD_SET) + // we can use select to detect when input becomes available + fd_set readset; + struct timeval time; + FD_ZERO(&readset); + FD_SET(tty->fd_in, &readset); + time.tv_sec = (timeout_ms > 0 ? timeout_ms / 1000 : 0); + time.tv_usec = (timeout_ms > 0 ? 1000*(timeout_ms % 1000) : 0); + if (select(tty->fd_in + 1, &readset, NULL, NULL, &time) == 1) { + // input available + return tty_readc_blocking(tty, c); + } + #else + // no select, we cannot timeout; use usleeps :-( + // todo: this seems very rare nowadays; should be even support this? + do { + // peek ahead if possible + #if defined(FIONREAD) + int navail = 0; + if (ioctl(0, FIONREAD, &navail) == 0 && navail >= 1) { + return tty_readc_blocking(tty, c); + } + #elif defined(O_NONBLOCK) + // use a temporary non-blocking read mode + int fstatus = fcntl(tty->fd_in, F_GETFL, 0); + if (fstatus != -1) { + if (fcntl(tty->fd_in, F_SETFL, (fstatus | O_NONBLOCK)) != -1) { + char buf[2] = { 0, 0 }; + ssize_t nread = read(tty->fd_in, buf, 1); + fcntl(tty->fd_in, F_SETFL, fstatus); + if (nread >= 1) { + *c = (uint8_t)buf[0]; + return true; + } + } + } + #else + #error "define an nonblocking read for this platform" + #endif + // and sleep a bit + if (timeout_ms > 0) { + usleep(50*1000L); // sleep at most 0.05s at a time + timeout_ms -= 100; + if (timeout_ms < 0) { timeout_ms = 0; } + } + } + while (timeout_ms > 0); + #endif + return false; +} + +#if defined(TIOCSTI) +ic_private bool tty_async_stop(const tty_t* tty) { + // insert ^C in the input stream + char c = KEY_CTRL_C; + return (ioctl(tty->fd_in, TIOCSTI, &c) >= 0); +} +#else +ic_private bool tty_async_stop(const tty_t* tty) { + return false; +} +#endif + +// We install various signal handlers to restore the terminal settings +// in case of a terminating signal. This is also used to catch terminal window resizes. +// This is not strictly needed so this can be disabled on +// (older) platforms that do not support signal handling well. +#if defined(SIGWINCH) && defined(SA_RESTART) // ensure basic signal functionality is defined + +// store the tty in a global so we access it on unexpected termination +static tty_t* sig_tty; // = NULL + +// Catch all termination signals (and SIGWINCH) +typedef struct signal_handler_s { + int signum; + union { + int _avoid_warning; + struct sigaction previous; + } action; +} signal_handler_t; + +static signal_handler_t sighandlers[] = { + { SIGWINCH, {0} }, + { SIGTERM , {0} }, + { SIGINT , {0} }, + { SIGQUIT , {0} }, + { SIGHUP , {0} }, + { SIGSEGV , {0} }, + { SIGTRAP , {0} }, + { SIGBUS , {0} }, + { SIGTSTP , {0} }, + { SIGTTIN , {0} }, + { SIGTTOU , {0} }, + { 0 , {0} } +}; + +static bool sigaction_is_valid( struct sigaction* sa ) { + return (sa->sa_sigaction != NULL && sa->sa_handler != SIG_DFL && sa->sa_handler != SIG_IGN); +} + +// Generic signal handler +static void sig_handler(int signum, siginfo_t* siginfo, void* uap ) { + if (signum == SIGWINCH) { + if (sig_tty != NULL) { + sig_tty->term_resize_event = true; + } + } + else { + // the rest are termination signals; restore the terminal mode. (`tcsetattr` is signal-safe) + if (sig_tty != NULL && sig_tty->raw_enabled) { + tcsetattr(sig_tty->fd_in, TCSAFLUSH, &sig_tty->orig_ios); + sig_tty->raw_enabled = false; + } + } + // call previous handler + signal_handler_t* sh = sighandlers; + while( sh->signum != 0 && sh->signum != signum) { sh++; } + if (sh->signum == signum) { + if (sigaction_is_valid(&sh->action.previous)) { + (sh->action.previous.sa_sigaction)(signum, siginfo, uap); + } + } +} + +static void signals_install(tty_t* tty) { + sig_tty = tty; + // generic signal handler + struct sigaction handler; + memset(&handler,0,sizeof(handler)); + sigemptyset(&handler.sa_mask); + handler.sa_sigaction = &sig_handler; + handler.sa_flags = SA_RESTART; + // install for all signals + for( signal_handler_t* sh = sighandlers; sh->signum != 0; sh++ ) { + if (sigaction( sh->signum, NULL, &sh->action.previous) == 0) { // get previous + if (sh->action.previous.sa_handler != SIG_IGN) { // if not to be ignored + if (sigaction( sh->signum, &handler, &sh->action.previous ) < 0) { // install our handler + sh->action.previous.sa_sigaction = NULL; // do not restore on error + } + else if (sh->signum == SIGWINCH) { + sig_tty->has_term_resize_event = true; + }; + } + } + } +} + +static void signals_restore(void) { + // restore all signal handlers + for( signal_handler_t* sh = sighandlers; sh->signum != 0; sh++ ) { + if (sigaction_is_valid(&sh->action.previous)) { + sigaction( sh->signum, &sh->action.previous, NULL ); + }; + } + sig_tty = NULL; +} + +#else +static void signals_install(tty_t* tty) { + ic_unused(tty); + // nothing +} +static void signals_restore(void) { + // nothing +} + +#endif + +ic_private bool tty_start_raw(tty_t* tty) { + if (tty == NULL) return false; + if (tty->raw_enabled) return true; + if (tcsetattr(tty->fd_in,TCSAFLUSH,&tty->raw_ios) < 0) return false; + tty->raw_enabled = true; + return true; +} + +ic_private void tty_end_raw(tty_t* tty) { + if (tty == NULL) return; + if (!tty->raw_enabled) return; + tty->cpush_count = 0; + if (tcsetattr(tty->fd_in,TCSAFLUSH,&tty->orig_ios) < 0) return; + tty->raw_enabled = false; +} + +static bool tty_init_raw(tty_t* tty) +{ + // Set input to raw mode. See . + if (tcgetattr(tty->fd_in,&tty->orig_ios) == -1) return false; + tty->raw_ios = tty->orig_ios; + // input: no break signal, no \r to \n, no parity check, no 8-bit to 7-bit, no flow control + tty->raw_ios.c_iflag &= ~(unsigned long)(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + // control: allow 8-bit + tty->raw_ios.c_cflag |= CS8; + // local: no echo, no line-by-line (canonical), no extended input processing, no signals for ^z,^c + tty->raw_ios.c_lflag &= ~(unsigned long)(ECHO | ICANON | IEXTEN | ISIG); + // 1 byte at a time, no delay + tty->raw_ios.c_cc[VTIME] = 0; + tty->raw_ios.c_cc[VMIN] = 1; + + // store in global so our signal handlers can restore the terminal mode + signals_install(tty); + + return true; +} + +static void tty_done_raw(tty_t* tty) { + ic_unused(tty); + signals_restore(); +} + + +#else + +//------------------------------------------------------------- +// Windows +// For best portability we push CSI escape sequences directly +// to the character stream (instead of returning key codes). +//------------------------------------------------------------- + +static void tty_waitc_console(tty_t* tty, long timeout_ms); + +ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms) { // don't modify `c` if there is no input + // in our pushback buffer? + if (tty_cpop(tty, c)) return true; + // any events in the input queue? + tty_waitc_console(tty, timeout_ms); + return tty_cpop(tty, c); +} + +// Read from the console input events and push escape codes into the tty cbuffer. +static void tty_waitc_console(tty_t* tty, long timeout_ms) +{ + // wait for a key down event + INPUT_RECORD inp; + DWORD count; + uint32_t surrogate_hi = 0; + while (true) { + // check if there are events if in non-blocking timeout mode + if (timeout_ms >= 0) { + // first peek ahead + if (!GetNumberOfConsoleInputEvents(tty->hcon, &count)) return; + if (count == 0) { + if (timeout_ms == 0) { + // out of time + return; + } + else { + // wait for input events for at most timeout milli seconds + ULONGLONG start_ms = GetTickCount64(); + DWORD res = WaitForSingleObject(tty->hcon, (DWORD)timeout_ms); + switch (res) { + case WAIT_OBJECT_0: { + // input is available, decrease our timeout + ULONGLONG waited_ms = (GetTickCount64() - start_ms); + timeout_ms -= (long)waited_ms; + if (timeout_ms < 0) { + timeout_ms = 0; + } + break; + } + case WAIT_TIMEOUT: + case WAIT_ABANDONED: + case WAIT_FAILED: + default: + return; + } + } + } + } + + // (blocking) Read from the input + if (!ReadConsoleInputW(tty->hcon, &inp, 1, &count)) return; + if (count != 1) return; + + // resize event? + if (inp.EventType == WINDOW_BUFFER_SIZE_EVENT) { + tty->term_resize_event = true; + continue; + } + + // wait for key down events + if (inp.EventType != KEY_EVENT) continue; + + // the modifier state + DWORD modstate = inp.Event.KeyEvent.dwControlKeyState; + + // we need to handle shift up events separately + if (!inp.Event.KeyEvent.bKeyDown && inp.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT) { + modstate &= (DWORD)~SHIFT_PRESSED; + } + + // ignore AltGr + DWORD altgr = LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED; + if ((modstate & altgr) == altgr) { modstate &= ~altgr; } + + + // get modifiers + code_t mods = 0; + if ((modstate & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED )) != 0) mods |= KEY_MOD_CTRL; + if ((modstate & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED )) != 0) mods |= KEY_MOD_ALT; + if ((modstate & SHIFT_PRESSED) != 0) mods |= KEY_MOD_SHIFT; + + // virtual keys + uint32_t chr = (uint32_t)inp.Event.KeyEvent.uChar.UnicodeChar; + WORD virt = inp.Event.KeyEvent.wVirtualKeyCode; + debug_msg("tty: console %s: %s%s%s virt 0x%04x, chr 0x%04x ('%c')\n", inp.Event.KeyEvent.bKeyDown ? "down" : "up", mods&KEY_MOD_CTRL ? "ctrl-" : "", mods&KEY_MOD_ALT ? "alt-" : "", mods&KEY_MOD_SHIFT ? "shift-" : "", virt, chr, chr); + + // only process keydown events (except for Alt-up which is used for unicode pasting...) + if (!inp.Event.KeyEvent.bKeyDown && virt != VK_MENU) { + continue; + } + + if (chr == 0) { + switch (virt) { + case VK_UP: tty_cpush_csi_xterm(tty, mods, 'A'); return; + case VK_DOWN: tty_cpush_csi_xterm(tty, mods, 'B'); return; + case VK_RIGHT: tty_cpush_csi_xterm(tty, mods, 'C'); return; + case VK_LEFT: tty_cpush_csi_xterm(tty, mods, 'D'); return; + case VK_END: tty_cpush_csi_xterm(tty, mods, 'F'); return; + case VK_HOME: tty_cpush_csi_xterm(tty, mods, 'H'); return; + case VK_DELETE: tty_cpush_csi_vt(tty,mods,3); return; + case VK_PRIOR: tty_cpush_csi_vt(tty,mods,5); return; //page up + case VK_NEXT: tty_cpush_csi_vt(tty,mods,6); return; //page down + case VK_TAB: tty_cpush_csi_unicode(tty,mods,9); return; + case VK_RETURN: tty_cpush_csi_unicode(tty,mods,13); return; + default: { + uint32_t vtcode = 0; + if (virt >= VK_F1 && virt <= VK_F5) { + vtcode = 10 + (virt - VK_F1); + } + else if (virt >= VK_F6 && virt <= VK_F10) { + vtcode = 17 + (virt - VK_F6); + } + else if (virt >= VK_F11 && virt <= VK_F12) { + vtcode = 13 + (virt - VK_F11); + } + if (vtcode > 0) { + tty_cpush_csi_vt(tty,mods,vtcode); + return; + } + } + } + // ignore other control keys (shift etc). + } + // high surrogate pair + else if (chr >= 0xD800 && chr <= 0xDBFF) { + surrogate_hi = (chr - 0xD800); + } + // low surrogate pair + else if (chr >= 0xDC00 && chr <= 0xDFFF) { + chr = ((surrogate_hi << 10) + (chr - 0xDC00) + 0x10000); + tty_cpush_csi_unicode(tty,mods,chr); + surrogate_hi = 0; + return; + } + // regular character + else { + tty_cpush_csi_unicode(tty,mods,chr); + return; + } + } +} + +ic_private bool tty_async_stop(const tty_t* tty) { + // send ^c + INPUT_RECORD events[2]; + memset(events, 0, 2*sizeof(INPUT_RECORD)); + events[0].EventType = KEY_EVENT; + events[0].Event.KeyEvent.bKeyDown = TRUE; + events[0].Event.KeyEvent.uChar.AsciiChar = KEY_CTRL_C; + events[1] = events[0]; + events[1].Event.KeyEvent.bKeyDown = FALSE; + DWORD nwritten = 0; + WriteConsoleInput(tty->hcon, events, 2, &nwritten); + return (nwritten == 2); +} + +ic_private bool tty_start_raw(tty_t* tty) { + if (tty->raw_enabled) return true; + GetConsoleMode(tty->hcon,&tty->hcon_orig_mode); + DWORD mode = ENABLE_QUICK_EDIT_MODE // cut&paste allowed + | ENABLE_WINDOW_INPUT // to catch resize events + // | ENABLE_VIRTUAL_TERMINAL_INPUT + // | ENABLE_PROCESSED_INPUT + ; + SetConsoleMode(tty->hcon, mode ); + tty->raw_enabled = true; + return true; +} + +ic_private void tty_end_raw(tty_t* tty) { + if (!tty->raw_enabled) return; + SetConsoleMode(tty->hcon, tty->hcon_orig_mode ); + tty->raw_enabled = false; +} + +static bool tty_init_raw(tty_t* tty) { + tty->hcon = GetStdHandle( STD_INPUT_HANDLE ); + tty->has_term_resize_event = true; + return true; +} + +static void tty_done_raw(tty_t* tty) { + ic_unused(tty); +} + +#endif + + diff --git a/deps/isocline/src/tty.h b/deps/isocline/src/tty.h new file mode 100644 index 0000000..a0062bf --- /dev/null +++ b/deps/isocline/src/tty.h @@ -0,0 +1,160 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_TTY_H +#define IC_TTY_H + +#include "common.h" + +//------------------------------------------------------------- +// TTY/Keyboard input +//------------------------------------------------------------- + +// Key code +typedef uint32_t code_t; + +// TTY interface +struct tty_s; +typedef struct tty_s tty_t; + + +ic_private tty_t* tty_new(alloc_t* mem, int fd_in); +ic_private void tty_free(tty_t* tty); + +ic_private bool tty_is_utf8(const tty_t* tty); +ic_private bool tty_start_raw(tty_t* tty); +ic_private void tty_end_raw(tty_t* tty); +ic_private code_t tty_read(tty_t* tty); +ic_private bool tty_read_timeout(tty_t* tty, long timeout_ms, code_t* c ); + +ic_private void tty_code_pushback( tty_t* tty, code_t c ); +ic_private bool code_is_ascii_char(code_t c, char* chr ); +ic_private bool code_is_unicode(code_t c, unicode_t* uchr); +ic_private bool code_is_virt_key(code_t c ); + +ic_private bool tty_term_resize_event(tty_t* tty); // did the terminal resize? +ic_private bool tty_async_stop(const tty_t* tty); // unblock the read asynchronously +ic_private void tty_set_esc_delay(tty_t* tty, long initial_delay_ms, long followup_delay_ms); + +// shared between tty.c and tty_esc.c: low level character push +ic_private void tty_cpush_char(tty_t* tty, uint8_t c); +ic_private bool tty_cpop(tty_t* tty, uint8_t* c); +ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms); +ic_private code_t tty_read_esc(tty_t* tty, long esc_initial_timeout, long esc_timeout); // in tty_esc.c + +// used by term.c to read back ANSI escape responses +ic_private bool tty_read_esc_response(tty_t* tty, char esc_start, bool final_st, char* buf, ssize_t buflen ); + + +//------------------------------------------------------------- +// Key codes: a code_t is 32 bits. +// we use the bottom 24 (nah, 21) bits for unicode (up to x0010FFFF) +// The codes after x01000000 are for virtual keys +// and events use x02000000. +// The top 4 bits are used for modifiers. +//------------------------------------------------------------- + +static inline code_t key_char( char c ) { + // careful about signed character conversion (negative char ~> 0x80 - 0xFF) + return ((uint8_t)c); +} + +static inline code_t key_unicode( unicode_t u ) { + return u; +} + + +#define KEY_MOD_SHIFT (0x10000000U) +#define KEY_MOD_ALT (0x20000000U) +#define KEY_MOD_CTRL (0x40000000U) + +#define KEY_NO_MODS(k) (k & 0x0FFFFFFFU) +#define KEY_MODS(k) (k & 0xF0000000U) + +#define WITH_SHIFT(x) (x | KEY_MOD_SHIFT) +#define WITH_ALT(x) (x | KEY_MOD_ALT) +#define WITH_CTRL(x) (x | KEY_MOD_CTRL) + +#define KEY_NONE (0) +#define KEY_CTRL_A (1) +#define KEY_CTRL_B (2) +#define KEY_CTRL_C (3) +#define KEY_CTRL_D (4) +#define KEY_CTRL_E (5) +#define KEY_CTRL_F (6) +#define KEY_BELL (7) +#define KEY_BACKSP (8) +#define KEY_TAB (9) +#define KEY_LINEFEED (10) // ctrl/shift + enter is considered KEY_LINEFEED +#define KEY_CTRL_K (11) +#define KEY_CTRL_L (12) +#define KEY_ENTER (13) +#define KEY_CTRL_N (14) +#define KEY_CTRL_O (15) +#define KEY_CTRL_P (16) +#define KEY_CTRL_Q (17) +#define KEY_CTRL_R (18) +#define KEY_CTRL_S (19) +#define KEY_CTRL_T (20) +#define KEY_CTRL_U (21) +#define KEY_CTRL_V (22) +#define KEY_CTRL_W (23) +#define KEY_CTRL_X (24) +#define KEY_CTRL_Y (25) +#define KEY_CTRL_Z (26) +#define KEY_ESC (27) +#define KEY_SPACE (32) +#define KEY_RUBOUT (127) // always translated to KEY_BACKSP +#define KEY_UNICODE_MAX (0x0010FFFFU) + + +#define KEY_VIRT (0x01000000U) +#define KEY_UP (KEY_VIRT+0) +#define KEY_DOWN (KEY_VIRT+1) +#define KEY_LEFT (KEY_VIRT+2) +#define KEY_RIGHT (KEY_VIRT+3) +#define KEY_HOME (KEY_VIRT+4) +#define KEY_END (KEY_VIRT+5) +#define KEY_DEL (KEY_VIRT+6) +#define KEY_PAGEUP (KEY_VIRT+7) +#define KEY_PAGEDOWN (KEY_VIRT+8) +#define KEY_INS (KEY_VIRT+9) + +#define KEY_F1 (KEY_VIRT+11) +#define KEY_F2 (KEY_VIRT+12) +#define KEY_F3 (KEY_VIRT+13) +#define KEY_F4 (KEY_VIRT+14) +#define KEY_F5 (KEY_VIRT+15) +#define KEY_F6 (KEY_VIRT+16) +#define KEY_F7 (KEY_VIRT+17) +#define KEY_F8 (KEY_VIRT+18) +#define KEY_F9 (KEY_VIRT+19) +#define KEY_F10 (KEY_VIRT+20) +#define KEY_F11 (KEY_VIRT+21) +#define KEY_F12 (KEY_VIRT+22) +#define KEY_F(n) (KEY_F1 + (n) - 1) + +#define KEY_EVENT_BASE (0x02000000U) +#define KEY_EVENT_RESIZE (KEY_EVENT_BASE+1) +#define KEY_EVENT_AUTOTAB (KEY_EVENT_BASE+2) +#define KEY_EVENT_STOP (KEY_EVENT_BASE+3) + +// Convenience +#define KEY_CTRL_UP (WITH_CTRL(KEY_UP)) +#define KEY_CTRL_DOWN (WITH_CTRL(KEY_DOWN)) +#define KEY_CTRL_LEFT (WITH_CTRL(KEY_LEFT)) +#define KEY_CTRL_RIGHT (WITH_CTRL(KEY_RIGHT)) +#define KEY_CTRL_HOME (WITH_CTRL(KEY_HOME)) +#define KEY_CTRL_END (WITH_CTRL(KEY_END)) +#define KEY_CTRL_DEL (WITH_CTRL(KEY_DEL)) +#define KEY_CTRL_PAGEUP (WITH_CTRL(KEY_PAGEUP)) +#define KEY_CTRL_PAGEDOWN (WITH_CTRL(KEY_PAGEDOWN))) +#define KEY_CTRL_INS (WITH_CTRL(KEY_INS)) + +#define KEY_SHIFT_TAB (WITH_SHIFT(KEY_TAB)) + +#endif // IC_TTY_H diff --git a/deps/isocline/src/tty_esc.c b/deps/isocline/src/tty_esc.c new file mode 100644 index 0000000..0ac8761 --- /dev/null +++ b/deps/isocline/src/tty_esc.c @@ -0,0 +1,401 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include "tty.h" + +/*------------------------------------------------------------- +Decoding escape sequences to key codes. +This is a bit tricky there are many variants to encode keys as escape sequences, see for example: +- . +- +- +- +- + +Generally, for our purposes we accept a subset of escape sequences as: + + escseq ::= ESC + | ESC char + | ESC start special? (number (';' modifiers)?)? final + +where: + char ::= [\x00-\xFF] # any character + special ::= [:<=>?] + number ::= [0-9+] + modifiers ::= [1-9] + intermediate ::= [\x20-\x2F] # !"#$%&'()*+,-./ + final ::= [\x40-\x7F] # @A–Z[\]^_`a–z{|}~ + ESC ::= '\x1B' + CSI ::= ESC '[' + SS3 ::= ESC 'O' + +In ECMA48 `special? (number (';' modifiers)?)?` is the more liberal `[\x30-\x3F]*` +but that seems never used for key codes. If the number (vtcode or unicode) or the +modifiers are not given, we assume these are '1'. +We then accept the following key sequences: + + key ::= ESC # lone ESC + | ESC char # Alt+char + | ESC '[' special? vtcode ';' modifiers '~' # vt100 codes + | ESC '[' special? '1' ';' modifiers [A-Z] # xterm codes + | ESC 'O' special? '1' ';' modifiers [A-Za-z] # SS3 codes + | ESC '[' special? unicode ';' modifiers 'u' # direct unicode code + +Moreover, we translate the following special cases that do not fit into the above grammar. +First we translate away special starter sequences: +--------------------------------------------------------------------- + ESC '[' '[' .. ~> ESC '[' .. # Linux sometimes uses extra '[' for CSI + ESC '[' 'O' .. ~> ESC 'O' .. # Linux sometimes uses extra '[' for SS3 + ESC 'o' .. ~> ESC 'O' .. # Eterm: ctrl + SS3 + ESC '?' .. ~> ESC 'O' .. # vt52 treated as SS3 + +And then translate the following special cases into a standard form: +--------------------------------------------------------------------- + ESC '[' .. '@' ~> ESC '[' '3' '~' # Del on Mach + ESC '[' .. '9' ~> ESC '[' '2' '~' # Ins on Mach + ESC .. [^@$] ~> ESC .. '~' # ETerm,xrvt,urxt: ^ = ctrl, $ = shift, @ = alt + ESC '[' [a-d] ~> ESC '[' '1' ';' '2' [A-D] # Eterm shift+ + ESC 'O' [1-9] final ~> ESC 'O' '1' ';' [1-9] final # modifiers as parameter 1 (like on Haiku) + ESC '[' [1-9] [^~u] ~> ESC 'O' '1' ';' [1-9] final # modifiers as parameter 1 + +The modifier keys are encoded as "(modifiers-1) & mask" where the +shift mask is 0x01, alt 0x02 and ctrl 0x04. Therefore: +------------------------------------------------------------ + 1: - 5: ctrl 9: alt (for minicom) + 2: shift 6: shift+ctrl + 3: alt 7: alt+ctrl + 4: shift+alt 8: shift+alt+ctrl + +The different encodings fox vt100, xterm, and SS3 are: + +vt100: ESC [ vtcode ';' modifiers '~' +-------------------------------------- + 1: Home 10-15: F1-F5 + 2: Ins 16 : F5 + 3: Del 17-21: F6-F10 + 4: End 23-26: F11-F14 + 5: PageUp 28 : F15 + 6: PageDn 29 : F16 + 7: Home 31-34: F17-F20 + 8: End + +xterm: ESC [ 1 ';' modifiers [A-Z] +----------------------------------- + A: Up N: F2 + B: Down O: F3 + C: Right P: F4 + D: Left Q: F5 + E: '5' R: F6 + F: End S: F7 + G: T: F8 + H: Home U: PageDn + I: PageUp V: PageUp + J: W: F11 + K: X: F12 + L: Ins Y: End + M: F1 Z: shift+Tab + +SS3: ESC 'O' 1 ';' modifiers [A-Za-z] +--------------------------------------- + (normal) (numpad) + A: Up N: a: Up n: + B: Down O: b: Down o: + C: Right P: F1 c: Right p: Ins + D: Left Q: F2 d: Left q: End + E: '5' R: F3 e: r: Down + F: End S: F4 f: s: PageDn + G: T: F5 g: t: Left + H: Home U: F6 h: u: '5' + I: Tab V: F7 i: v: Right + J: W: F8 j: '*' w: Home + K: X: F9 k: '+' x: Up + L: Y: F10 l: ',' y: PageUp + M: \x0A '\n' Z: shift+Tab m: '-' z: + +-------------------------------------------------------------*/ + +//------------------------------------------------------------- +// Decode escape sequences +//------------------------------------------------------------- + +static code_t esc_decode_vt(uint32_t vt_code ) { + switch(vt_code) { + case 1: return KEY_HOME; + case 2: return KEY_INS; + case 3: return KEY_DEL; + case 4: return KEY_END; + case 5: return KEY_PAGEUP; + case 6: return KEY_PAGEDOWN; + case 7: return KEY_HOME; + case 8: return KEY_END; + default: + if (vt_code >= 10 && vt_code <= 15) return KEY_F(1 + (vt_code - 10)); + if (vt_code == 16) return KEY_F5; // minicom + if (vt_code >= 17 && vt_code <= 21) return KEY_F(6 + (vt_code - 17)); + if (vt_code >= 23 && vt_code <= 26) return KEY_F(11 + (vt_code - 23)); + if (vt_code >= 28 && vt_code <= 29) return KEY_F(15 + (vt_code - 28)); + if (vt_code >= 31 && vt_code <= 34) return KEY_F(17 + (vt_code - 31)); + } + return KEY_NONE; +} + +static code_t esc_decode_xterm( uint8_t xcode ) { + // ESC [ + switch(xcode) { + case 'A': return KEY_UP; + case 'B': return KEY_DOWN; + case 'C': return KEY_RIGHT; + case 'D': return KEY_LEFT; + case 'E': return '5'; // numpad 5 + case 'F': return KEY_END; + case 'H': return KEY_HOME; + case 'Z': return KEY_TAB | KEY_MOD_SHIFT; + // Freebsd: + case 'I': return KEY_PAGEUP; + case 'L': return KEY_INS; + case 'M': return KEY_F1; + case 'N': return KEY_F2; + case 'O': return KEY_F3; + case 'P': return KEY_F4; // note: differs from + case 'Q': return KEY_F5; + case 'R': return KEY_F6; + case 'S': return KEY_F7; + case 'T': return KEY_F8; + case 'U': return KEY_PAGEDOWN; // Mach + case 'V': return KEY_PAGEUP; // Mach + case 'W': return KEY_F11; + case 'X': return KEY_F12; + case 'Y': return KEY_END; // Mach + } + return KEY_NONE; +} + +static code_t esc_decode_ss3( uint8_t ss3_code ) { + // ESC O + switch(ss3_code) { + case 'A': return KEY_UP; + case 'B': return KEY_DOWN; + case 'C': return KEY_RIGHT; + case 'D': return KEY_LEFT; + case 'E': return '5'; // numpad 5 + case 'F': return KEY_END; + case 'H': return KEY_HOME; + case 'I': return KEY_TAB; + case 'Z': return KEY_TAB | KEY_MOD_SHIFT; + case 'M': return KEY_LINEFEED; + case 'P': return KEY_F1; + case 'Q': return KEY_F2; + case 'R': return KEY_F3; + case 'S': return KEY_F4; + // on Mach + case 'T': return KEY_F5; + case 'U': return KEY_F6; + case 'V': return KEY_F7; + case 'W': return KEY_F8; + case 'X': return KEY_F9; // '=' on vt220 + case 'Y': return KEY_F10; + // numpad + case 'a': return KEY_UP; + case 'b': return KEY_DOWN; + case 'c': return KEY_RIGHT; + case 'd': return KEY_LEFT; + case 'j': return '*'; + case 'k': return '+'; + case 'l': return ','; + case 'm': return '-'; + case 'n': return KEY_DEL; // '.' + case 'o': return '/'; + case 'p': return KEY_INS; + case 'q': return KEY_END; + case 'r': return KEY_DOWN; + case 's': return KEY_PAGEDOWN; + case 't': return KEY_LEFT; + case 'u': return '5'; + case 'v': return KEY_RIGHT; + case 'w': return KEY_HOME; + case 'x': return KEY_UP; + case 'y': return KEY_PAGEUP; + } + return KEY_NONE; +} + +static void tty_read_csi_num(tty_t* tty, uint8_t* ppeek, uint32_t* num, long esc_timeout) { + *num = 1; // default + ssize_t count = 0; + uint32_t i = 0; + while (*ppeek >= '0' && *ppeek <= '9' && count < 16) { + uint8_t digit = *ppeek - '0'; + if (!tty_readc_noblock(tty,ppeek,esc_timeout)) break; // peek is not modified in this case + count++; + i = 10*i + digit; + } + if (count > 0) *num = i; +} + +static code_t tty_read_csi(tty_t* tty, uint8_t c1, uint8_t peek, code_t mods0, long esc_timeout) { + // CSI starts with 0x9b (c1=='[') | ESC [ (c1=='[') | ESC [Oo?] (c1 == 'O') /* = SS3 */ + + // check for extra starter '[' (Linux sends ESC [ [ 15 ~ for F5 for example) + if (c1 == '[' && strchr("[Oo", (char)peek) != NULL) { + uint8_t cx = peek; + if (tty_readc_noblock(tty,&peek,esc_timeout)) { + c1 = cx; + } + } + + // "special" characters ('?' is used for private sequences) + uint8_t special = 0; + if (strchr(":<=>?",(char)peek) != NULL) { + special = peek; + if (!tty_readc_noblock(tty,&peek,esc_timeout)) { + tty_cpush_char(tty,special); // recover + return (key_unicode(c1) | KEY_MOD_ALT); // Alt+ + } + } + + // up to 2 parameters that default to 1 + uint32_t num1 = 1; + uint32_t num2 = 1; + tty_read_csi_num(tty,&peek,&num1,esc_timeout); + if (peek == ';') { + if (!tty_readc_noblock(tty,&peek,esc_timeout)) return KEY_NONE; + tty_read_csi_num(tty,&peek,&num2,esc_timeout); + } + + // the final character (we do not allow 'intermediate characters') + uint8_t final = peek; + code_t modifiers = mods0; + + debug_msg("tty: escape sequence: ESC %c %c %d;%d %c\n", c1, (special == 0 ? '_' : special), num1, num2, final); + + // Adjust special cases into standard ones. + if ((final == '@' || final == '9') && c1 == '[' && num1 == 1) { + // ESC [ @, ESC [ 9 : on Mach + if (final == '@') num1 = 3; // DEL + else if (final == '9') num1 = 2; // INS + final = '~'; + } + else if (final == '^' || final == '$' || final == '@') { + // Eterm/rxvt/urxt + if (final=='^') modifiers |= KEY_MOD_CTRL; + if (final=='$') modifiers |= KEY_MOD_SHIFT; + if (final=='@') modifiers |= KEY_MOD_SHIFT | KEY_MOD_CTRL; + final = '~'; + } + else if (c1 == '[' && final >= 'a' && final <= 'd') { // note: do not catch ESC [ .. u (for unicode) + // ESC [ [a-d] : on Eterm for shift+ cursor + modifiers |= KEY_MOD_SHIFT; + final = 'A' + (final - 'a'); + } + + if (((c1 == 'O') || (c1=='[' && final != '~' && final != 'u')) && + (num2 == 1 && num1 > 1 && num1 <= 8)) + { + // on haiku the modifier can be parameter 1, make it parameter 2 instead + num2 = num1; + num1 = 1; + } + + // parameter 2 determines the modifiers + if (num2 > 1 && num2 <= 9) { + if (num2 == 9) num2 = 3; // iTerm2 in xterm mode + num2--; + if (num2 & 0x1) modifiers |= KEY_MOD_SHIFT; + if (num2 & 0x2) modifiers |= KEY_MOD_ALT; + if (num2 & 0x4) modifiers |= KEY_MOD_CTRL; + } + + // and translate + code_t code = KEY_NONE; + if (final == '~') { + // vt codes + code = esc_decode_vt(num1); + } + else if (c1 == '[' && final == 'u') { + // unicode + code = key_unicode(num1); + } + else if (c1 == 'O' && ((final >= 'A' && final <= 'Z') || (final >= 'a' && final <= 'z'))) { + // ss3 + code = esc_decode_ss3(final); + } + else if (num1 == 1 && final >= 'A' && final <= 'Z') { + // xterm + code = esc_decode_xterm(final); + } + else if (c1 == '[' && final == 'R') { + // cursor position + code = KEY_NONE; + } + + if (code == KEY_NONE && final != 'R') { + debug_msg("tty: ignore escape sequence: ESC %c %zu;%zu %c\n", c1, num1, num2, final); + } + return (code != KEY_NONE ? (code | modifiers) : KEY_NONE); +} + +static code_t tty_read_osc( tty_t* tty, uint8_t* ppeek, long esc_timeout ) { + debug_msg("discard OSC response..\n"); + // keep reading until termination: OSC is terminated by BELL, or ESC \ (ST) (and STX) + while (true) { + uint8_t c = *ppeek; + if (c <= '\x07') { // BELL and anything below (STX, ^C, ^D) + if (c != '\x07') { tty_cpush_char( tty, c ); } + break; + } + else if (c=='\x1B') { + uint8_t c1; + if (!tty_readc_noblock(tty, &c1, esc_timeout)) break; + if (c1=='\\') break; + tty_cpush_char(tty,c1); + } + if (!tty_readc_noblock(tty, ppeek, esc_timeout)) break; + } + return KEY_NONE; +} + +ic_private code_t tty_read_esc(tty_t* tty, long esc_initial_timeout, long esc_timeout) { + code_t mods = 0; + uint8_t peek = 0; + + // lone ESC? + if (!tty_readc_noblock(tty, &peek, esc_initial_timeout)) return KEY_ESC; + + // treat ESC ESC as Alt modifier (macOS sends ESC ESC [ [A-D] for alt-) + if (peek == KEY_ESC) { + if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt; + mods |= KEY_MOD_ALT; + } + + // CSI ? + if (peek == '[') { + if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt; + return tty_read_csi(tty, '[', peek, mods, esc_timeout); // ESC [ ... + } + + // SS3? + if (peek == 'O' || peek == 'o' || peek == '?' /*vt52*/) { + uint8_t c1 = peek; + if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt; + if (c1 == 'o') { + // ETerm uses this for ctrl+ + mods |= KEY_MOD_CTRL; + } + // treat all as standard SS3 'O' + return tty_read_csi(tty,'O',peek,mods, esc_timeout); // ESC [Oo?] ... + } + + // OSC: we may get a delayed query response; ensure it is ignored + if (peek == ']') { + if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt; + return tty_read_osc(tty, &peek, esc_timeout); // ESC ] ... + } + +alt: + // Alt+ + return (key_unicode(peek) | KEY_MOD_ALT); // ESC +} diff --git a/deps/isocline/src/undo.c b/deps/isocline/src/undo.c new file mode 100644 index 0000000..eefc318 --- /dev/null +++ b/deps/isocline/src/undo.c @@ -0,0 +1,67 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include +#include + +#include "../include/isocline.h" +#include "common.h" +#include "env.h" +#include "stringbuf.h" +#include "completions.h" +#include "undo.h" + + + +//------------------------------------------------------------- +// edit state +//------------------------------------------------------------- +struct editstate_s { + struct editstate_s* next; + const char* input; // input + ssize_t pos; // cursor position +}; + +ic_private void editstate_init( editstate_t** es ) { + *es = NULL; +} + +ic_private void editstate_done( alloc_t* mem, editstate_t** es ) { + while (*es != NULL) { + editstate_t* next = (*es)->next; + mem_free(mem, (*es)->input); + mem_free(mem, *es ); + *es = next; + } + *es = NULL; +} + +ic_private void editstate_capture( alloc_t* mem, editstate_t** es, const char* input, ssize_t pos) { + if (input==NULL) input = ""; + // alloc + editstate_t* entry = mem_zalloc_tp(mem, editstate_t); + if (entry == NULL) return; + // initialize + entry->input = mem_strdup( mem, input); + entry->pos = pos; + if (entry->input == NULL) { mem_free(mem, entry); return; } + // and push + entry->next = *es; + *es = entry; +} + +// caller should free *input +ic_private bool editstate_restore( alloc_t* mem, editstate_t** es, const char** input, ssize_t* pos ) { + if (*es == NULL) return false; + // pop + editstate_t* entry = *es; + *es = entry->next; + *input = entry->input; + *pos = entry->pos; + mem_free(mem, entry); + return true; +} + diff --git a/deps/isocline/src/undo.h b/deps/isocline/src/undo.h new file mode 100644 index 0000000..576cf97 --- /dev/null +++ b/deps/isocline/src/undo.h @@ -0,0 +1,24 @@ +/* ---------------------------------------------------------------------------- + Copyright (c) 2021, Daan Leijen + This is free software; you can redistribute it and/or modify it + under the terms of the MIT License. A copy of the license can be + found in the "LICENSE" file at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef IC_UNDO_H +#define IC_UNDO_H + +#include "common.h" + +//------------------------------------------------------------- +// Edit state +//------------------------------------------------------------- +struct editstate_s; +typedef struct editstate_s editstate_t; + +ic_private void editstate_init( editstate_t** es ); +ic_private void editstate_done( alloc_t* mem, editstate_t** es ); +ic_private void editstate_capture( alloc_t* mem, editstate_t** es, const char* input, ssize_t pos); +ic_private bool editstate_restore( alloc_t* mem, editstate_t** es, const char** input, ssize_t* pos ); // caller needs to free input + +#endif // IC_UNDO_H diff --git a/deps/isocline/src/wcwidth.c b/deps/isocline/src/wcwidth.c new file mode 100644 index 0000000..85187d4 --- /dev/null +++ b/deps/isocline/src/wcwidth.c @@ -0,0 +1,292 @@ +// include in "stringbuf.c" +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include +#include + +struct interval { + int32_t first; + int32_t last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(int32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +static int mk_is_wide_char(int32_t ucs) { + static const struct interval wide[] = { + {0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a}, + {0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3}, + {0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653}, + {0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1}, + {0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5}, + {0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea}, + {0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa}, + {0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b}, + {0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e}, + {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, + {0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c}, + {0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf}, + {0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf}, + {0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3}, + {0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f}, + {0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1}, + {0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff}, + {0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e}, + {0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b}, + {0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265}, + {0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c}, + {0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3}, + {0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e}, + {0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d}, + {0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a}, + {0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f}, + {0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2}, + {0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e}, + {0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997}, + {0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd}, + {0x30000, 0x3fffd}, + }; + + if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) { + return 1; + } + + return 0; +} + +static int mk_wcwidth(int32_t ucs) { + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + {0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489}, + {0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2}, + {0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a}, + {0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670}, + {0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8}, + {0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a}, + {0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819}, + {0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d}, + {0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902}, + {0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948}, + {0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963}, + {0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4}, + {0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02}, + {0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48}, + {0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71}, + {0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc}, + {0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd}, + {0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01}, + {0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44}, + {0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63}, + {0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd}, + {0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48}, + {0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63}, + {0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf}, + {0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3}, + {0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44}, + {0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca}, + {0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31}, + {0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1}, + {0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd}, + {0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37}, + {0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84}, + {0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc}, + {0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037}, + {0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059}, + {0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082}, + {0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d}, + {0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714}, + {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, + {0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6}, + {0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e}, + {0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922}, + {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b}, + {0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56}, + {0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62}, + {0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f}, + {0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34}, + {0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42}, + {0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5}, + {0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6}, + {0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1}, + {0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2}, + {0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced}, + {0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9}, + {0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e}, + {0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0}, + {0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff}, + {0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672}, + {0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1}, + {0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b}, + {0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1}, + {0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982}, + {0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc}, + {0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32}, + {0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c}, + {0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4}, + {0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1}, + {0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5}, + {0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e}, + {0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff}, + {0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, + {0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06}, + {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, + {0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046}, + {0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba}, + {0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134}, + {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be}, + {0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234}, + {0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df}, + {0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c}, + {0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374}, + {0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446}, + {0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0}, + {0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd}, + {0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a}, + {0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab}, + {0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7}, + {0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b}, + {0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38}, + {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56}, + {0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99}, + {0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f}, + {0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3}, + {0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, + {0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, + {0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92}, + {0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169}, + {0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, + {0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c}, + {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f}, + {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, + {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, + {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001}, + {0xe0020, 0xe007f}, {0xe0100, 0xe01ef}, + }; + + /* test for 8-bit control characters */ + if ( ucs == 0 ) { + return 0; + } + if ( ( ucs < 32 ) || ( ( ucs >= 0x7f ) && ( ucs < 0xa0 ) ) ) { + return -1; + } + + /* binary search in table of non-spacing characters */ + if ( bisearch( ucs, combining, sizeof( combining ) / sizeof( struct interval ) - 1 ) ) { + return 0; + } + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + return ( mk_is_wide_char( ucs ) ? 2 : 1 ); +} + diff --git a/makefile b/makefile index c09ec1f..3a390f8 100644 --- a/makefile +++ b/makefile @@ -9,18 +9,18 @@ HOST ?= linux CC ?= gcc WARNINGS_COMMON=-Wall -Wextra \ -Wnull-dereference -Wshadow -Wformat=2 -Wswitch -Wswitch-enum \ - -Wpedantic -Wstrict-prototypes -Wunused \ + -Wpedantic -Wstrict-prototypes -Wunused -Wformat-nonliteral \ #-Wconversion -Wsign-conversion NOWARNINGS_COMMON=-Wno-address-of-packed-member ifneq (,$(findstring gcc,$(CC))) WARNINGS=$(WARNINGS_COMMON) -Wduplicated-cond -Wduplicated-branches -Wrestrict -Wlogical-op -Wjump-misses-init NOWARNINGS=$(NOWARNINGS_COMMON) - CFLAGS_BESTLINE=-Wno-logical-op + CFLAGS_ISOCLINE=-Wno-duplicated-branches else ifneq (,$(findstring clang,$(CC))) WARNINGS=$(WARNINGS_COMMON) NOWARNINGS=$(NOWARNINGS_COMMON) -Wno-missing-prototype-for-cc - CFLAGS_BESTLINE= + CFLAGS_ISOCLINE=-Wno-format-nonliteral else $(error your compiler is not supported) endif @@ -59,8 +59,8 @@ test: umka_shell umka_shell: umka_shell.o umka.o shell.o trace.o trace_lbr.o vdisk.o \ vdisk/raw.o vdisk/qcow2.o deps/em_inflate/em_inflate.o vnet.o \ $(HOST)/vnet/tap.o vnet/file.o lodepng.o $(HOST)/pci.o \ - $(HOST)/thread.o umkaio.o umkart.o deps/optparse/optparse32.o \ - deps/bestline/bestline32.o + $(HOST)/thread.o umkaio.o umkart.o deps/optparse/optparse.o \ + deps/isocline/src/isocline.o $(CC) $(LDFLAGS_32) $^ -o $@ -T umka.ld umka_fuse: umka_fuse.o umka.o trace.o trace_lbr.o vdisk.o vdisk/raw.o \ @@ -71,7 +71,7 @@ umka_fuse: umka_fuse.o umka.o trace.o trace_lbr.o vdisk.o vdisk/raw.o \ umka_os: umka_os.o umka.o shell.o lodepng.o vdisk.o vdisk/raw.o vdisk/qcow2.o \ deps/em_inflate/em_inflate.o vnet.o $(HOST)/vnet/tap.o vnet/file.o \ trace.o trace_lbr.o $(HOST)/pci.o $(HOST)/thread.o umkaio.o umkart.o \ - deps/bestline/bestline32.o deps/optparse/optparse32.o + deps/isocline/src/isocline.o deps/optparse/optparse.o $(CC) $(LDFLAGS_32) `sdl2-config --libs` $^ -o $@ -T umka.ld umka_gen_devices_dat: umka_gen_devices_dat.o umka.o $(HOST)/pci.o \ @@ -96,17 +96,13 @@ $(HOST)/pci.o: $(HOST)/pci.c lodepng.o: lodepng.c lodepng.h $(CC) $(CFLAGS_32) -c $< -deps/bestline/bestline32.o: deps/bestline/bestline.c deps/bestline/bestline.h - $(CC) $(CFLAGS_32) $(CFLAGS_BESTLINE) -Wno-switch-enum -c $< -o $@ - -deps/bestline/bestline.o: deps/bestline/bestline.c deps/bestline/bestline.h - $(CC) $(CFLAGS) -Wno-switch-enum -c $< -o $@ - -deps/optparse/optparse32.o: deps/optparse/optparse.c deps/optparse/optparse.h - $(CC) $(CFLAGS_32) -c $< -o $@ +deps/isocline/src/isocline.o: deps/isocline/src/isocline.c \ + deps/isocline/include/isocline.h + $(CC) $(CFLAGS_32) $(CFLAGS_ISOCLINE) -c $< -o $@ -Wno-shadow \ + -Wno-unused-function deps/optparse/optparse.o: deps/optparse/optparse.c deps/optparse/optparse.h - $(CC) $(CFLAGS) -c $< -o $@ + $(CC) $(CFLAGS_32) -c $< -o $@ umkart.o: umkart.c umkart.h umka.h $(CC) $(CFLAGS_32) -c $< diff --git a/shell.c b/shell.c index 9467e34..939d992 100644 --- a/shell.c +++ b/shell.c @@ -39,7 +39,7 @@ #include "umkart.h" #include "lodepng.h" #include "optparse/optparse.h" -#include "bestline/bestline.h" +#include "isocline/include/isocline.h" char *bestlineFile(const char *prompt, FILE *fin, FILE *fout); @@ -380,21 +380,16 @@ prompt(struct shell_ctx *ctx) { } static void -completion(const char *buf, bestlineCompletions *lc) { - if (buf[0] == 'h') { - bestlineAddCompletion(lc,"hello"); - bestlineAddCompletion(lc,"hello there"); - } +completer(ic_completion_env_t *cenv, const char *prefix) { + (void)cenv; + (void)prefix; } -static char * -hints(const char *buf, const char **ansi1, const char **ansi2) { - if (!strcmp(buf,"hello")) { - *ansi1 = "\033[35m"; /* magenta foreground */ - *ansi2 = "\033[39m"; /* reset foreground */ - return " World"; - } - return NULL; +static void +highlighter(ic_highlight_env_t *henv, const char *input, void *arg) { + (void)henv; + (void)input; + (void)arg; } static int @@ -4010,18 +4005,37 @@ cmd_help(struct shell_ctx *ctx, int argc, char **argv) { void * run_test(struct shell_ctx *ctx) { + int fdstdin = -1; + if (ctx->fin != stdin) { + fdstdin = dup(STDIN_FILENO); + close(STDIN_FILENO); + dup2(fileno(ctx->fin), STDIN_FILENO); + fclose(ctx->fin); + } +// ic_style_def("ic-prompt","ansi-default"); + ic_enable_color(0); + ic_set_prompt_marker(NULL, NULL); + ic_enable_multiline(0); + ic_enable_beep(0); + pthread_mutex_lock(&ctx->cmd_mutex); int is_tty = isatty(fileno(ctx->fin)); char **argv = (char**)calloc(sizeof(char*), (MAX_COMMAND_ARGS + 1)); - bestlineSetCompletionCallback(completion); - bestlineSetHintsCallback(hints); - bestlineHistoryLoad(ctx->hist_file); - sprintf(prompt_line, "%s> ", last_dir); + ic_set_default_completer(&completer, NULL); + ic_set_default_highlighter(highlighter, NULL); + ic_enable_auto_tab(1); + sprintf(prompt_line, "%s", last_dir); char *line; - while((line = bestlineFile(prompt_line, ctx->fin, ctx->fout))) { + while((line = ic_readline(prompt_line)) || !feof(stdin)) { if (!is_tty) { prompt(ctx); - fprintf(ctx->fout, "%s\n", line); + fprintf(ctx->fout, "%s\n", line ? line : ""); + } + if (!line) { + continue; + } + if (line[0] == '\0') { + break; } if (!strcmp(line, "X") || !strcmp(line, "q")) { free(line); @@ -4031,7 +4045,6 @@ run_test(struct shell_ctx *ctx) { free(line); continue; } else { - bestlineHistoryAdd(line); int argc = split_args(line, argv); func_table_t *ft; for (ft = cmd_cmds; ft->name; ft++) { @@ -4048,16 +4061,20 @@ run_test(struct shell_ctx *ctx) { } } free(argv); - bestlineHistorySave(ctx->hist_file); - fclose(ctx->fin); pthread_mutex_unlock(&ctx->cmd_mutex); + + if (fdstdin != -1) { + close(STDIN_FILENO); + dup2(fdstdin, STDIN_FILENO); + close(fdstdin); + } return NULL; } struct shell_ctx * shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka, - struct umka_io *io, FILE *fin, FILE *fout, const atomic_int *running) { + struct umka_io *io, FILE *fin, const atomic_int *running) { struct shell_ctx *ctx = malloc(sizeof(struct shell_ctx)); ctx->umka = umka; ctx->io = io; @@ -4065,7 +4082,7 @@ shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka, ctx->hist_file = hist_file; ctx->var = NULL; ctx->fin = fin; - ctx->fout = fout; + ctx->fout = stdout; ctx->running = running; pthread_cond_init(&ctx->cmd_done, NULL); pthread_mutex_init(&ctx->cmd_mutex, NULL); diff --git a/shell.h b/shell.h index 1a6c5f2..ec71aed 100644 --- a/shell.h +++ b/shell.h @@ -51,7 +51,7 @@ struct shell_ctx { struct shell_ctx * shell_init(int reproducible, const char *hist_file, struct umka_ctx *umka, - struct umka_io *io, FILE *fin, FILE *fout, const atomic_int *running); + struct umka_io *io, FILE *fin, const atomic_int *running); void shell_close(struct shell_ctx *shell); diff --git a/test/016_#f01_#draw_all.ref.log b/test/016_#f01_#draw_all.ref.log index e040b4e..a5c4e23 100644 --- a/test/016_#f01_#draw_all.ref.log +++ b/test/016_#f01_#draw_all.ref.log @@ -66,7 +66,7 @@ terminate_protection: 1 keyboard_mode: 0 captionEncoding: 0 exec_params: (null) -wnd_caption: +wnd_caption: hi_there wnd_clientbox (ltwh): 5 24 291 172 priority: 0 in_schedule: prev (2), next (2) diff --git a/test/066_#f01_#draw_#draw16bit_all.ref.log b/test/066_#f01_#draw_#draw16bit_all.ref.log index 31dfa3c..471516b 100644 --- a/test/066_#f01_#draw_#draw16bit_all.ref.log +++ b/test/066_#f01_#draw_#draw16bit_all.ref.log @@ -67,7 +67,7 @@ terminate_protection: 1 keyboard_mode: 0 captionEncoding: 0 exec_params: (null) -wnd_caption: +wnd_caption: hi_there wnd_clientbox (ltwh): 5 24 291 172 priority: 0 in_schedule: prev (2), next (2) diff --git a/test/067_#f01_#draw_#draw24bit_all.ref.log b/test/067_#f01_#draw_#draw24bit_all.ref.log index fff6f33..2139b44 100644 --- a/test/067_#f01_#draw_#draw24bit_all.ref.log +++ b/test/067_#f01_#draw_#draw24bit_all.ref.log @@ -67,7 +67,7 @@ terminate_protection: 1 keyboard_mode: 0 captionEncoding: 0 exec_params: (null) -wnd_caption: +wnd_caption: hi_there wnd_clientbox (ltwh): 5 24 291 172 priority: 0 in_schedule: prev (2), next (2) diff --git a/umka_os.c b/umka_os.c index b7b34a6..5d45530 100644 --- a/umka_os.c +++ b/umka_os.c @@ -28,7 +28,7 @@ #include "shell.h" #include "trace.h" #include "vnet.h" -#include "bestline/bestline.h" +#include "isocline/include/isocline.h" #include "optparse/optparse.h" #define HIST_FILE_BASENAME ".umka_os.history" @@ -49,21 +49,16 @@ struct umka_os_ctx *os; char history_filename[PATH_MAX]; static void -completion(const char *buf, bestlineCompletions *lc) { - if (buf[0] == 'h') { - bestlineAddCompletion(lc,"hello"); - bestlineAddCompletion(lc,"hello there"); - } +completer(ic_completion_env_t *cenv, const char *prefix) { + (void)cenv; + (void)prefix; } -static char * -hints(const char *buf, const char **ansi1, const char **ansi2) { - if (!strcmp(buf,"hello")) { - *ansi1 = "\033[35m"; /* magenta foreground */ - *ansi2 = "\033[39m"; /* reset foreground */ - return " World"; - } - return NULL; +static void +highlighter(ic_highlight_env_t *henv, const char *input, void *arg) { + (void)henv; + (void)input; + (void)arg; } static int @@ -74,13 +69,13 @@ hw_int_mouse(void *arg) { } struct umka_os_ctx * -umka_os_init(FILE *fin, FILE *fout, FILE *fboardlog) { +umka_os_init(FILE *fstartup, FILE *fboardlog) { struct umka_os_ctx *ctx = malloc(sizeof(struct umka_os_ctx)); ctx->fboardlog = fboardlog; ctx->umka = umka_init(); ctx->io = io_init(&ctx->umka->running); ctx->shell = shell_init(SHELL_LOG_NONREPRODUCIBLE, history_filename, - ctx->umka, ctx->io, fin, fout, &ctx->umka->running); + ctx->umka, ctx->io, fstartup, &ctx->umka->running); return ctx; } @@ -276,9 +271,9 @@ main(int argc, char *argv[]) { umka_sti(); build_history_filename(); - bestlineSetCompletionCallback(completion); - bestlineSetHintsCallback(hints); - bestlineHistoryLoad(history_filename); + ic_set_default_completer(&completer, NULL); + ic_set_default_highlighter(highlighter, NULL); + ic_enable_auto_tab(1); const char *startupfile = NULL; const char *infile = NULL; @@ -350,7 +345,7 @@ main(int argc, char *argv[]) { fboardlog = fout; } - os = umka_os_init(fstartup, fout, fboardlog); + os = umka_os_init(fstartup, fboardlog); struct sigaction sa; sa.sa_sigaction = irq0; diff --git a/umka_shell.c b/umka_shell.c index cc39ec8..23ced68 100644 --- a/umka_shell.c +++ b/umka_shell.c @@ -32,12 +32,12 @@ struct umka_shell_ctx { char history_filename[PATH_MAX]; struct umka_shell_ctx * -umka_shell_init(int reproducible, FILE *fin, FILE *fout) { +umka_shell_init(int reproducible, FILE *fin) { struct umka_shell_ctx *ctx = malloc(sizeof(struct umka_shell_ctx)); ctx->umka = umka_init(); ctx->io = io_init(NULL); ctx->shell = shell_init(reproducible, history_filename, ctx->umka, ctx->io, - fin, fout, &ctx->umka->running); + fin, &ctx->umka->running); return ctx; } @@ -118,14 +118,14 @@ main(int argc, char **argv) { } } if (outfile) { - fout = fopen(outfile, "w"); + fout = freopen(outfile, "w", stdout); if (!fout) { fprintf(stderr, "[!] can't open file for writing: %s\n", outfile); exit(1); } } - struct umka_shell_ctx *ctx = umka_shell_init(reproducible, fin, fout); + struct umka_shell_ctx *ctx = umka_shell_init(reproducible, fin); if (coverage) trace_begin(); diff --git a/windows/vnet/tap.c b/windows/vnet/tap.c index 4e43767..cb12385 100644 --- a/windows/vnet/tap.c +++ b/windows/vnet/tap.c @@ -10,7 +10,7 @@ #include struct vnet * -vnet_init_tap() { +vnet_init_tap(void) { fprintf(stderr, "[vnet] tap interface isn't implemented for windows\n"); return NULL; }