From 7d0f732a575b3f62fd6704cbc1ed95be3f28a2b8 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sat, 11 Jun 2022 19:57:56 +0200 Subject: [PATCH] GNU Readline replace with bestline --- Makefile | 14 +- lib/bestline/LICENSE | 30 + lib/bestline/bestline.c | 3578 +++++++++++++++++++++++++++++++++++++++ lib/bestline/bestline.h | 39 + lib/bestline/example.c | 79 + src/main.cc | 38 +- 6 files changed, 3764 insertions(+), 14 deletions(-) create mode 100644 lib/bestline/LICENSE create mode 100644 lib/bestline/bestline.c create mode 100644 lib/bestline/bestline.h create mode 100644 lib/bestline/example.c diff --git a/Makefile b/Makefile index bfad6f9..0e0d82b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)" CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/ +CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/ -Ilib/bestline/ RELEASE_FLAGS=-O3 DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined CXX=g++ @@ -49,18 +49,22 @@ bin/%.o: src/%.cc src/*.hh @echo "CXX $@" @$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c -bin/musique: $(Release_Obj) bin/main.o src/*.hh lib/midi/libmidi-alsa.a +bin/musique: $(Release_Obj) bin/main.o bin/bestline.o src/*.hh lib/midi/libmidi-alsa.a @echo "CXX $@" - @$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/main.o $(LDFLAGS) $(LDLIBS) + @$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o bin/main.o $(LDFLAGS) $(LDLIBS) -bin/debug/musique: $(Debug_Obj) bin/debug/main.o src/*.hh +bin/debug/musique: $(Debug_Obj) bin/debug/main.o bin/bestline.o src/*.hh @echo "CXX $@" - @$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/debug/main.o $(LDFLAGS) $(LDLIBS) + @$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/bestline.o bin/debug/main.o $(LDFLAGS) $(LDLIBS) bin/debug/%.o: src/%.cc src/*.hh @echo "CXX $@" @$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c +bin/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h + @echo "CC $@" + @$(CC) $< -c -O3 -o $@ + unit-tests: bin/unit-tests ./$< diff --git a/lib/bestline/LICENSE b/lib/bestline/LICENSE new file mode 100644 index 0000000..3a4d06d --- /dev/null +++ b/lib/bestline/LICENSE @@ -0,0 +1,30 @@ +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/lib/bestline/bestline.c b/lib/bestline/bestline.c new file mode 100644 index 0000000..b1023b1 --- /dev/null +++ b/lib/bestline/bestline.c @@ -0,0 +1,3578 @@ +/*-*- 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__ +#define _POSIX_C_SOURCE 1 /* so GCC builds in ANSI mode */ +#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 *bestline(const char *prompt) { + if (prompt && *prompt && + (strchr(prompt, '\n') || strchr(prompt, '\t') || + strchr(prompt + 1, '\r'))) { + errno = EINVAL; + return 0; + } + if ((!isatty(fileno(stdin)) || + !isatty(fileno(stdout)))) { + if (prompt && *prompt && (IsCharDev(fileno(stdin)) && + IsCharDev(fileno(stdout)))) { + fputs(prompt,stdout); + fflush(stdout); + } + return GetLine(stdin, stdout); + } else if (bestlineIsUnsupportedTerm()) { + if (prompt && *prompt) { + fputs(prompt,stdout); + fflush(stdout); + } + return GetLine(stdin, stdout); + } else { + fflush(stdout); + return bestlineRaw(prompt,fileno(stdin),fileno(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/lib/bestline/bestline.h b/lib/bestline/bestline.h new file mode 100644 index 0000000..d8d01a1 --- /dev/null +++ b/lib/bestline/bestline.h @@ -0,0 +1,39 @@ +#pragma once + +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 *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/lib/bestline/example.c b/lib/bestline/example.c new file mode 100644 index 0000000..adce021 --- /dev/null +++ b/lib/bestline/example.c @@ -0,0 +1,79 @@ +#include "bestline.h" + +#ifndef __COSMOPOLITAN__ +#include +#include +#include +#endif + +#if 0 +// should be ~50kb statically linked +// will save history to ~/.foo_history +// cc -fno-jump-tables -Os -o foo foo.c bestline.c +int main() { + char *line; + while ((line = bestlineWithHistory("IN> ", "foo"))) { + fputs("OUT> ", stdout); + fputs(line, stdout); + fputs("\n", stdout); + free(line); + } +} +#endif + +void completion(const char *buf, bestlineCompletions *lc) { + if (buf[0] == 'h') { + bestlineAddCompletion(lc,"hello"); + bestlineAddCompletion(lc,"hello there"); + } +} + +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; +} + +int main(int argc, char **argv) { + char *line; + + /* Set the completion callback. This will be called every time the + * user uses the key. */ + bestlineSetCompletionCallback(completion); + bestlineSetHintsCallback(hints); + + /* Load history from file. The history file is just a plain text file + * where entries are separated by newlines. */ + bestlineHistoryLoad("history.txt"); /* Load the history at startup */ + + /* Now this is the main loop of the typical bestline-based application. + * The call to bestline() will block as long as the user types something + * and presses enter. + * + * The typed string is returned as a malloc() allocated string by + * bestline, so the user needs to free() it. */ + + while((line = bestline("hello> ")) != NULL) { + /* Do something with the string. */ + if (line[0] != '\0' && line[0] != '/') { + fputs("echo: '", stdout); + fputs(line, stdout); + fputs("'\n", stdout); + bestlineHistoryAdd(line); /* Add to the history. */ + bestlineHistorySave("history.txt"); /* Save the history on disk. */ + } else if (!strncmp(line, "/mask", 5)) { + bestlineMaskModeEnable(); + } else if (!strncmp(line, "/unmask", 7)) { + bestlineMaskModeDisable(); + } else if (line[0] == '/') { + fputs("Unreconized command: ", stdout); + fputs(line, stdout); + fputs("\n", stdout); + } + free(line); + } + return 0; +} diff --git a/src/main.cc b/src/main.cc index e8554f5..19fda0a 100644 --- a/src/main.cc +++ b/src/main.cc @@ -7,8 +7,10 @@ #include #include -#include -#include + +extern "C" { +#include +} namespace fs = std::filesystem; @@ -66,6 +68,8 @@ static void trim(std::string_view &s) /// Runs interpreter on given source code struct Runner { + static inline Runner *the; + midi::ALSA alsa; Interpreter interpreter; @@ -73,6 +77,9 @@ struct Runner Runner(std::string port) : alsa("musique") { + assert(the == nullptr, "Only one instance of runner is supported"); + the = this; + alsa.init_sequencer(); alsa.connect(port); interpreter.midi_connection = &alsa; @@ -88,6 +95,11 @@ struct Runner }); } + Runner(Runner const&) = delete; + Runner(Runner &&) = delete; + Runner& operator=(Runner const&) = delete; + Runner& operator=(Runner &&) = delete; + /// Run given source Result run(std::string_view source, std::string_view filename, bool output = false) { @@ -108,6 +120,19 @@ struct Runner /// some of the strings are only views into source std::vector eternal_sources; +void completion(char const* buf, bestlineCompletions *lc) +{ + std::string_view in{buf}; + + for (auto scope = Runner::the->interpreter.env.get(); scope != nullptr; scope = scope->parent.get()) { + for (auto const& [name, _] : scope->variables) { + if (name.starts_with(in)) { + bestlineAddCompletion(lc, name.c_str()); + } + } + } +} + /// Fancy main that supports Result forwarding on error (Try macro) static Result Main(std::span args) { @@ -189,8 +214,9 @@ static Result Main(std::span args) if (runnables.empty() || enable_repl) { enable_repl = true; + bestlineSetCompletionCallback(completion); for (;;) { - char *input_buffer = readline("> "); + char *input_buffer = bestlineWithHistory("> ", "musique"); if (input_buffer == nullptr) { break; } @@ -208,8 +234,6 @@ static Result Main(std::span args) continue; } - add_history(input_buffer); - if (command.starts_with(':')) { command.remove_prefix(1); if (command == "exit") { break; } @@ -234,10 +258,6 @@ static Result Main(std::span args) int main(int argc, char const** argv) { - rl_readline_name = *argv; - // Set editing mode to Vim-like - rl_editing_mode = 0; - auto const args = std::span(argv, argc).subspan(1); auto const result = Main(args); if (not result.has_value()) {