From 219d72226a3b95ac84ac278e67b71b5f944a0975 Mon Sep 17 00:00:00 2001 From: Ivan Baravy Date: Mon, 30 May 2022 02:08:15 +0400 Subject: [PATCH] Use bestline library, update test logs --- bestline.c | 3580 +++++++++++++++++ bestline.h | 39 + makefile | 8 +- shell.c | 105 +- shell.h | 1 + ...70s0_#xfs_#s05k_read_without_holes.ref.log | 10 +- ..._#f70s0_#xfs_#s05k_read_with_holes.ref.log | 28 +- test/007_#f70_#xfs_#lookup_all.ref.log | 22 +- test/010_#f70_#xfs_#lookup_#xattr_all.ref.log | 22 +- ...0_#f70s1_#xfs_#lookup_#unicode_all.ref.log | 14 +- test/013_#f70_#xfs_#v5_#lookup_all.ref.log | 22 +- ...0s0_#xfs_#v5_#s05k_read_with_holes.ref.log | 28 +- ..._#xfs_#v5_#s05k_read_without_holes.ref.log | 10 +- test/016_#f01_#draw_all.ref.log | 20 +- ..._#f70s0_#xfs_#s05k_read_above_4GiB.ref.log | 36 +- test/019_#xfs_deep_btree.ref.log | 6 +- test/044_#f01_#draw_winmap.ref.log | 18 +- ...6_#f70_#f70s1_#fat_#exfat_#s05k_ls.ref.log | 8 +- ...#f70_#f70s1_#fat_#exfat_#s05k_stat.ref.log | 4 +- test/049_#keyboard_#input_all.ref.log | 26 +- test/050_#mouse_#input_all.ref.log | 2 +- test/051_#draw_#cursor_all.ref.log | 16 +- umka_shell.c | 19 +- 23 files changed, 3826 insertions(+), 218 deletions(-) create mode 100644 bestline.c create mode 100644 bestline.h diff --git a/bestline.c b/bestline.c new file mode 100644 index 0000000..884dc48 --- /dev/null +++ b/bestline.c @@ -0,0 +1,3580 @@ +/*-*- 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, *ansi2; + if (!hintsCallback) return 0; + if (!(hint = hintsCallback(l->buf, &ansi1, &ansi2))) return 0; + abInit(&ab); + ansi1 = "\033[90m"; + ansi2 = "\033[39m"; + 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/bestline.h b/bestline.h new file mode 100644 index 0000000..d8d01a1 --- /dev/null +++ b/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/makefile b/makefile index 51b386b..156ec10 100644 --- a/makefile +++ b/makefile @@ -61,14 +61,14 @@ covpreproc: covpreproc.c $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ umka_shell: umka_shell.o umka.o shell.o trace.o trace_lbr.o vdisk.o vnet.o \ - lodepng.o pci.o thread.o util.o optparse.o + lodepng.o pci.o thread.o util.o optparse.o bestline.o $(CC) $(LDFLAGS_32) $^ -o $@ -T umka.ld umka_fuse: umka_fuse.o umka.o trace.o trace_lbr.o vdisk.o pci.o thread.o $(CC) $(LDFLAGS_32) $^ -o $@ `pkg-config fuse3 --libs` -T umka.ld umka_os: umka_os.o umka.o shell.o lodepng.o vdisk.o vnet.o trace.o trace_lbr.o \ - pci.o thread.o umka_ping.o util.o + pci.o thread.o umka_ping.o util.o bestline.o $(CC) $(LDFLAGS_32) $^ -o $@ -T umka.ld umka_gen_devices_dat: umka_gen_devices_dat.o umka.o pci.o thread.o util.o @@ -89,8 +89,8 @@ pci.o: $(HOST)/pci.c lodepng.o: lodepng.c lodepng.h $(CC) $(CFLAGS_32) -c $< -#bestline.o: bestline.c bestline.h -# $(CC) $(CFLAGS_32) -U_POSIX_C_SOURCE -Wno-logical-op -Wno-switch-enum -c $< +bestline.o: bestline.c bestline.h + $(CC) $(CFLAGS_32) -U_POSIX_C_SOURCE -Wno-logical-op -Wno-switch-enum -c $< optparse.o: optparse.c optparse.h $(CC) $(CFLAGS_32) -c $< diff --git a/shell.c b/shell.c index d190349..28c0501 100644 --- a/shell.c +++ b/shell.c @@ -33,12 +33,9 @@ // TODO: Cleanup #ifndef _WIN32 #include -#include #endif #include "shell.h" -#include "optparse.h" -//#include "bestline.h" #include "vdisk.h" #include "vnet.h" #include "umka.h" @@ -46,9 +43,9 @@ #include "pci.h" #include "util.h" #include "lodepng.h" +#include "optparse.h" +#include "bestline.h" -//#define PATH_MAX 4096 -#define FGETS_BUF_LEN 4096 #define MAX_COMMAND_ARGS 42 #define PRINT_BYTES_PER_LINE 32 #define MAX_DIRENTS_TO_READ 100 @@ -57,13 +54,11 @@ #define DEFAULT_READDIR_ENCODING UTF8 #define DEFAULT_PATH_ENCODING UTF8 -char prompt_line[2*PATH_MAX]; +char prompt_line[PATH_MAX]; char cur_dir[PATH_MAX] = "/"; const char *last_dir = cur_dir; bool cur_dir_changed = true; -char cmd_buf[FGETS_BUF_LEN]; - typedef struct { char *name; void (*func) (struct shell_ctx *, int, char **); @@ -219,7 +214,7 @@ prompt(struct shell_ctx *ctx) { fprintf(ctx->fout, "%s> ", last_dir); fflush(ctx->fout); } -/* + static void completion(const char *buf, bestlineCompletions *lc) { if (buf[0] == 'h') { @@ -237,39 +232,6 @@ hints(const char *buf, const char **ansi1, const char **ansi2) { } return NULL; } -*/ -static int -next_line(struct shell_ctx *ctx, int is_tty) { - if (is_tty) { - prompt(ctx); - } - return fgets(cmd_buf, FGETS_BUF_LEN, ctx->fin) != NULL; -// TODO: Cleanup -#ifdef _WIN32 - return fgets(cmd_buf, FGETS_BUF_LEN, ctx->fin) != NULL; -#else - fd_set readfds; -// FD_ZERO(&readfds); - memset(&readfds, 0, sizeof(readfds)); - FD_SET(fileno(ctx->fin), &readfds); - struct timeval timeout = {.tv_sec = 0, .tv_usec = 0}; - int sr = select(fileno(ctx->fin)+1, &readfds, NULL, NULL, &timeout); - cmd_buf[0] = '\0'; - if (sr > 0) { - fgets(cmd_buf, FGETS_BUF_LEN, ctx->fin); - } - return 1; -#endif -/* - sprintf(prompt_line, "%s> ", last_dir); - char *line = bestlineRaw(prompt_line, fileno(ctx->fin), fileno(ctx->fout)); - if (line) { - strcpy(cmd_buf, line); - bestlineFree(line); - } - return line != NULL; -*/ -} static int split_args(char *s, char **argv) { @@ -778,7 +740,13 @@ shell_set(struct shell_ctx *ctx, int argc, char **argv) { return; } const char *var = argv[1]; - size_t value = strtoul(argv[2], NULL, 0); + const char *val_str = argv[2]; + char *endptr; + ssize_t value = strtol(val_str, &endptr, 0); + if (*endptr != '\0') { + fprintf(ctx->fout, "integer required: %s\n", val_str); + return; + } if (!strcmp(var, "redraw_background")) { kos_redraw_background = value; } else if (!strcmp(var, "syslang")) { @@ -3398,35 +3366,42 @@ void * run_test(struct shell_ctx *ctx) { int is_tty = isatty(fileno(ctx->fin)); char **argv = (char**)calloc(sizeof(char*), (MAX_COMMAND_ARGS + 1)); -// bestlineSetCompletionCallback(completion); -// bestlineSetHintsCallback(hints); -// bestlineHistoryLoad("umka_shell.history"); - while(next_line(ctx, is_tty)) { - if (cmd_buf[0] == '#' || cmd_buf[0] == '\n' || cmd_buf[0] == '\0' || - cmd_buf[0] == '\r') { - fprintf(ctx->fout, "%s", cmd_buf); - continue; - } - if (cmd_buf[0] == 'X') break; + bestlineSetCompletionCallback(completion); + bestlineSetHintsCallback(hints); + bestlineHistoryLoad(ctx->hist_file); + sprintf(prompt_line, "%s> ", last_dir); + char *line; + while((line = bestline(prompt_line))) { if (!is_tty) { prompt(ctx); - fprintf(ctx->fout, "%s", cmd_buf); - fflush(ctx->fout); + fprintf(ctx->fout, "%s\n", line); } - int argc = split_args(cmd_buf, argv); - func_table_t *ft; - for (ft = shell_cmds; ft->name; ft++) { - if (!strcmp(argv[0], ft->name)) { - break; - } - } - if (ft->name) { - ft->func(ctx, argc, argv); + if (!strcmp(line, "X") || !strcmp(line, "q")) { + free(line); + break; + } else if (line[0] == '\0' || line[0] == '#' || line[0] == '\n' + || *line == '\r') { + free(line); + continue; } else { - fprintf(ctx->fout, "unknown command: %s\n", argv[0]); + bestlineHistoryAdd(line); + int argc = split_args(line, argv); + func_table_t *ft; + for (ft = shell_cmds; ft->name; ft++) { + if (!strcmp(argv[0], ft->name)) { + break; + } + } + if (ft->name) { + ft->func(ctx, argc, argv); + } else { + fprintf(ctx->fout, "unknown command: %s\n", argv[0]); + } + free(line); } } free(argv); + bestlineHistorySave(ctx->hist_file); return NULL; } diff --git a/shell.h b/shell.h index d6d685b..830d8fb 100644 --- a/shell.h +++ b/shell.h @@ -7,6 +7,7 @@ struct shell_ctx { FILE *fin; FILE *fout; int reproducible; + const char *hist_file; }; void *run_test(struct shell_ctx *ctx); diff --git a/test/003_#f70_#f70s0_#xfs_#s05k_read_without_holes.ref.log b/test/003_#f70_#f70s0_#xfs_#s05k_read_without_holes.ref.log index c58fded..d08e22b 100644 --- a/test/003_#f70_#f70s0_#xfs_#s05k_read_without_holes.ref.log +++ b/test/003_#f70_#f70s0_#xfs_#s05k_read_without_holes.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/xfs_v4_files_s05k_b4k_n8k.img hd0 -c 0 /hd0/1: xfs -# zero length +/> # zero length /> read70 /hd0/1/no_hole 0 0 -b status = 0 success, count = 0 @@ -74,7 +74,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/no_hole 0xffffffffffffffff 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/no_hole 0 1 -b status = 0 success, count = 1 00 @@ -147,7 +147,7 @@ status = 6 end_of_file, count = 0 /> read70 /hd0/1/no_hole 0xffffffffffffffff 1 -b status = 6 end_of_file, count = 0 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/no_hole 0 11 -b status = 0 success, count = 11 000102030405060708090a @@ -205,8 +205,8 @@ status = 6 end_of_file, count = 0 /> read70 /hd0/1/no_hole 0xffffffffffffffff 11 -b status = 6 end_of_file, count = 0 - -# btree +/> +/> # btree /> read70 /hd0/1/btree_l1_no_hole 0x80000 11 -b status = 0 success, count = 11 0000080004000800080008 diff --git a/test/006_#f70_#f70s0_#xfs_#s05k_read_with_holes.ref.log b/test/006_#f70_#f70s0_#xfs_#s05k_read_with_holes.ref.log index c24e1a2..2f28625 100644 --- a/test/006_#f70_#f70s0_#xfs_#s05k_read_with_holes.ref.log +++ b/test/006_#f70_#f70s0_#xfs_#s05k_read_with_holes.ref.log @@ -1,8 +1,8 @@ /> umka_init /> disk_add ../img/xfs_v4_files_s05k_b4k_n8k.img hd0 -c 0 /hd0/1: xfs -# hole begin -# zero length +/> # hole begin +/> # zero length /> read70 /hd0/1/hole_begin 0 0 -b status = 0 success, count = 0 @@ -21,7 +21,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/hole_begin 0x4001 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/hole_begin 0 1 -b status = 0 success, count = 1 00 @@ -40,7 +40,7 @@ status = 0 success, count = 1 /> read70 /hd0/1/hole_begin 0x4001 1 -b status = 0 success, count = 1 40 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/hole_begin 0 11 -b status = 0 success, count = 11 0000000000000000000000 @@ -71,9 +71,9 @@ status = 0 success, count = 11 /> read70 /hd0/1/hole_begin 0x4001 11 -b status = 0 success, count = 11 4002400440064008400a40 - -# hole middle -# zero length +/> +/> # hole middle +/> # zero length /> read70 /hd0/1/hole_middle 0x7ffe 0 -b status = 0 success, count = 0 @@ -98,7 +98,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/hole_middle 0xc001 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/hole_middle 0x7ffe 1 -b status = 0 success, count = 1 fe @@ -123,7 +123,7 @@ status = 0 success, count = 1 /> read70 /hd0/1/hole_middle 0xc001 1 -b status = 0 success, count = 1 c0 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/hole_middle 0x7ff4 11 -b status = 0 success, count = 11 f47ff67ff87ffa7ffc7ffe @@ -172,9 +172,9 @@ status = 0 success, count = 11 /> read70 /hd0/1/hole_middle 0xc001 11 -b status = 0 success, count = 11 c002c004c006c008c00ac0 - -# hole end -# zero length +/> +/> # hole end +/> # zero length /> read70 /hd0/1/hole_end 0xbffe 0 -b status = 0 success, count = 0 @@ -199,7 +199,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/hole_end 0x10001 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/hole_end 0xbffe 1 -b status = 0 success, count = 1 fe @@ -224,7 +224,7 @@ status = 6 end_of_file, count = 0 /> read70 /hd0/1/hole_end 0x10001 1 -b status = 6 end_of_file, count = 0 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/hole_end 0xbff4 11 -b status = 0 success, count = 11 f4bff6bff8bffabffcbffe diff --git a/test/007_#f70_#xfs_#lookup_all.ref.log b/test/007_#f70_#xfs_#lookup_all.ref.log index 250ece8..8eb3fe5 100644 --- a/test/007_#f70_#xfs_#lookup_all.ref.log +++ b/test/007_#f70_#xfs_#lookup_all.ref.log @@ -16,7 +16,7 @@ status = 5 file_not_found status = 5 file_not_found /> stat70 /hd0/1/sf_empty/deadbeef status = 5 file_not_found - +/> /> stat70 /hd0/1/sf/. status = 0 success attr: ----f @@ -38,9 +38,9 @@ attr: ----f /> stat70 /hd0/1/sf/../sf/ status = 0 success attr: ----f -#stat70 /hd0/1/sf///..//sf -#stat70 /hd0/1/sf///..//sf/ - +/> #stat70 /hd0/1/sf///..//sf +/> #stat70 /hd0/1/sf///..//sf/ +/> /> stat70 /hd0/1/sf/d0000000000_ status = 0 success attr: ----f @@ -52,7 +52,7 @@ status = 0 success attr: ----f /> stat70 /hd0/1/sf/d0000000003_xxx status = 5 file_not_found - +/> /> cd /hd0/1/sf sf> stat70 . status = 0 success @@ -68,7 +68,7 @@ status = 0 success attr: ----f sf> stat70 d0000000003_xxx status = 5 file_not_found - +sf> sf> cd /hd0/1/block block> stat70 . status = 0 success @@ -93,7 +93,7 @@ status = 0 success attr: ----f block> stat70 d0000000005_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 5 file_not_found - +block> block> cd /hd0/1/leaf leaf> stat70 . status = 0 success @@ -112,7 +112,7 @@ status = 0 success attr: ----f leaf> stat70 d0000000040_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 5 file_not_found - +leaf> leaf> cd /hd0/1/node node> stat70 d0000000000_ status = 0 success @@ -144,7 +144,7 @@ attr: ----f node> stat70 d0000001099_xxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +node> node> cd /hd0/1/btree_leaf btree_leaf> stat70 d0000000000_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success @@ -164,7 +164,7 @@ attr: ----f btree_leaf> stat70 d0000000999_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +btree_leaf> btree_leaf> cd /hd0/1/btree_leaf_free btree_leaf_free> stat70 d0000000000_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success @@ -184,5 +184,5 @@ attr: ----f btree_leaf_free> stat70 d0000001199_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +btree_leaf_free> btree_leaf_free> disk_del hd0 diff --git a/test/010_#f70_#xfs_#lookup_#xattr_all.ref.log b/test/010_#f70_#xfs_#lookup_#xattr_all.ref.log index a00c2ec..e38cb70 100644 --- a/test/010_#f70_#xfs_#lookup_#xattr_all.ref.log +++ b/test/010_#f70_#xfs_#lookup_#xattr_all.ref.log @@ -12,7 +12,7 @@ status = 0 success attr: ----f /> stat70 /hd0/1/sf_empty/deadbeef status = 5 file_not_found - +/> /> stat70 /hd0/1/sf/. status = 0 success attr: ----f @@ -34,9 +34,9 @@ attr: ----f /> stat70 /hd0/1/sf/../sf/ status = 0 success attr: ----f -#stat70 /hd0/1/sf///..//sf -#stat70 /hd0/1/sf///..//sf/ - +/> #stat70 /hd0/1/sf///..//sf +/> #stat70 /hd0/1/sf///..//sf/ +/> /> stat70 /hd0/1/sf/d0000000000_ status = 0 success attr: ----f @@ -48,7 +48,7 @@ status = 0 success attr: ----f /> stat70 /hd0/1/sf/d0000000003_xxx status = 5 file_not_found - +/> /> cd /hd0/1/sf sf> stat70 . status = 0 success @@ -64,7 +64,7 @@ status = 0 success attr: ----f sf> stat70 d0000000003_xxx status = 5 file_not_found - +sf> sf> cd /hd0/1/block block> stat70 . status = 0 success @@ -89,7 +89,7 @@ status = 0 success attr: ----f block> stat70 d0000000005_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 5 file_not_found - +block> block> cd /hd0/1/leaf leaf> stat70 . status = 0 success @@ -108,7 +108,7 @@ status = 0 success attr: ----f leaf> stat70 d0000000040_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 5 file_not_found - +leaf> leaf> cd /hd0/1/node node> stat70 d0000000000_ status = 0 success @@ -140,7 +140,7 @@ attr: ----f node> stat70 d0000001019_xxxxxxx status = 0 success attr: ----f - +node> node> cd /hd0/1/btree_leaf btree_leaf> stat70 d0000000000_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success @@ -160,7 +160,7 @@ attr: ----f btree_leaf> stat70 d0000000999_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +btree_leaf> btree_leaf> cd /hd0/1/btree_leaf_free btree_leaf_free> stat70 d0000000000_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success @@ -180,5 +180,5 @@ attr: ----f btree_leaf_free> stat70 d0000001199_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +btree_leaf_free> btree_leaf_free> disk_del hd0 diff --git a/test/011_#f70_#f70s1_#xfs_#lookup_#unicode_all.ref.log b/test/011_#f70_#f70s1_#xfs_#lookup_#unicode_all.ref.log index 1b5580f..b473f79 100644 --- a/test/011_#f70_#f70s1_#xfs_#lookup_#unicode_all.ref.log +++ b/test/011_#f70_#f70s1_#xfs_#lookup_#unicode_all.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/xfs_v4_unicode.img hd0 -c 0 /hd0/1: xfs - +/> /> stat80 /hd0/1/dir0 status = 0 success attr: ----f @@ -32,7 +32,7 @@ attr: ----f /> stat80 /hd0/1/дир3 status = 0 success attr: ----f - +/> /> stat80 /hd0/1/dir0/file00 status = 0 success attr: ----- @@ -61,7 +61,7 @@ size: 26 status = 0 success attr: ----- size: 26 - +/> /> read80 /hd0/1/dir0/file00 0 100 -b status = 6 end_of_file, count = 12 68656c6c6f5f776f726c640a @@ -74,7 +74,7 @@ d0bfd180d0b8d0b2d0b5d182e29da6d0bcd0b8d1800a /> read80 /hd0/1/дир3/файл33 0 100 -b status = 6 end_of_file, count = 26 d0bfd180d0b8d0b2d0b5d182e29da6f09f9297d0bcd0b8d1800a - +/> /> ls70 /hd0/1/ -e utf8 status = 6 end_of_file, count = 7 total = 7 @@ -131,7 +131,7 @@ status = 5 file_not_found status = 5 file_not_found /> ls70 /hd0/1/дир3/ -e default status = 5 file_not_found - +/> /> ls80 /hd0/1/ -e utf8 status = 6 end_of_file, count = 7 total = 7 @@ -220,7 +220,7 @@ total = 3 ----f . ----f .. ----- D09;3 - +/> /> ls80 /hd0/1/❦👩❦/ -e utf8 status = 6 end_of_file, count = 5 total = 5 @@ -253,5 +253,5 @@ total = 5 ----f i=f'f' ----f f'i=f' ----f f'f'i= - +/> /> disk_del hd0 diff --git a/test/013_#f70_#xfs_#v5_#lookup_all.ref.log b/test/013_#f70_#xfs_#v5_#lookup_all.ref.log index 2ff3acd..b212be1 100644 --- a/test/013_#f70_#xfs_#v5_#lookup_all.ref.log +++ b/test/013_#f70_#xfs_#v5_#lookup_all.ref.log @@ -12,7 +12,7 @@ status = 0 success attr: ----f /> stat70 /hd0/1/sf_empty/deadbeef status = 5 file_not_found - +/> /> stat70 /hd0/1/sf/. status = 0 success attr: ----f @@ -34,9 +34,9 @@ attr: ----f /> stat70 /hd0/1/sf/../sf/ status = 0 success attr: ----f -#stat70 /hd0/1/sf///..//sf -#stat70 /hd0/1/sf///..//sf/ - +/> #stat70 /hd0/1/sf///..//sf +/> #stat70 /hd0/1/sf///..//sf/ +/> /> stat70 /hd0/1/sf/d0000000000_ status = 0 success attr: ----f @@ -48,7 +48,7 @@ status = 0 success attr: ----f /> stat70 /hd0/1/sf/d0000000003_xxx status = 5 file_not_found - +/> /> cd /hd0/1/sf sf> stat70 . status = 0 success @@ -64,7 +64,7 @@ status = 0 success attr: ----f sf> stat70 d0000000003_xxx status = 5 file_not_found - +sf> sf> cd /hd0/1/block block> stat70 . status = 0 success @@ -89,7 +89,7 @@ status = 0 success attr: ----f block> stat70 d0000000005_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 5 file_not_found - +block> block> cd /hd0/1/leaf leaf> stat70 . status = 0 success @@ -108,7 +108,7 @@ status = 0 success attr: ----f leaf> stat70 d0000000040_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 5 file_not_found - +leaf> leaf> cd /hd0/1/node node> stat70 d0000000000_ status = 0 success @@ -140,7 +140,7 @@ attr: ----f node> stat70 d0000001099_xxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +node> node> cd /hd0/1/btree_leaf btree_leaf> stat70 d0000000000_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success @@ -160,7 +160,7 @@ attr: ----f btree_leaf> stat70 d0000000999_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +btree_leaf> btree_leaf> cd /hd0/1/btree_leaf_free btree_leaf_free> stat70 d0000000000_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success @@ -180,5 +180,5 @@ attr: ----f btree_leaf_free> stat70 d0000001199_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx status = 0 success attr: ----f - +btree_leaf_free> btree_leaf_free> disk_del hd0 diff --git a/test/014_#f70_#f70s0_#xfs_#v5_#s05k_read_with_holes.ref.log b/test/014_#f70_#f70s0_#xfs_#v5_#s05k_read_with_holes.ref.log index 19369c2..a764605 100644 --- a/test/014_#f70_#f70s0_#xfs_#v5_#s05k_read_with_holes.ref.log +++ b/test/014_#f70_#f70s0_#xfs_#v5_#s05k_read_with_holes.ref.log @@ -1,8 +1,8 @@ /> umka_init /> disk_add ../img/xfs_v5_files_s05k_b4k_n8k.img hd0 -c 0 /hd0/1: xfs -# hole begin -# zero length +/> # hole begin +/> # zero length /> read70 /hd0/1/hole_begin 0 0 -b status = 0 success, count = 0 @@ -21,7 +21,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/hole_begin 0x4001 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/hole_begin 0 1 -b status = 0 success, count = 1 00 @@ -40,7 +40,7 @@ status = 0 success, count = 1 /> read70 /hd0/1/hole_begin 0x4001 1 -b status = 0 success, count = 1 40 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/hole_begin 0 11 -b status = 0 success, count = 11 0000000000000000000000 @@ -71,9 +71,9 @@ status = 0 success, count = 11 /> read70 /hd0/1/hole_begin 0x4001 11 -b status = 0 success, count = 11 4002400440064008400a40 - -# hole middle -# zero length +/> +/> # hole middle +/> # zero length /> read70 /hd0/1/hole_middle 0x7ffe 0 -b status = 0 success, count = 0 @@ -98,7 +98,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/hole_middle 0xc001 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/hole_middle 0x7ffe 1 -b status = 0 success, count = 1 fe @@ -123,7 +123,7 @@ status = 0 success, count = 1 /> read70 /hd0/1/hole_middle 0xc001 1 -b status = 0 success, count = 1 c0 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/hole_middle 0x7ff4 11 -b status = 0 success, count = 11 f47ff67ff87ffa7ffc7ffe @@ -172,9 +172,9 @@ status = 0 success, count = 11 /> read70 /hd0/1/hole_middle 0xc001 11 -b status = 0 success, count = 11 c002c004c006c008c00ac0 - -# hole end -# zero length +/> +/> # hole end +/> # zero length /> read70 /hd0/1/hole_end 0xbffe 0 -b status = 0 success, count = 0 @@ -199,7 +199,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/hole_end 0x10001 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/hole_end 0xbffe 1 -b status = 0 success, count = 1 fe @@ -224,7 +224,7 @@ status = 6 end_of_file, count = 0 /> read70 /hd0/1/hole_end 0x10001 1 -b status = 6 end_of_file, count = 0 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/hole_end 0xbff4 11 -b status = 0 success, count = 11 f4bff6bff8bffabffcbffe diff --git a/test/015_#f70_#f70s0_#xfs_#v5_#s05k_read_without_holes.ref.log b/test/015_#f70_#f70s0_#xfs_#v5_#s05k_read_without_holes.ref.log index dce5543..e778d27 100644 --- a/test/015_#f70_#f70s0_#xfs_#v5_#s05k_read_without_holes.ref.log +++ b/test/015_#f70_#f70s0_#xfs_#v5_#s05k_read_without_holes.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/xfs_v5_files_s05k_b4k_n8k.img hd0 -c 0 /hd0/1: xfs -# zero length +/> # zero length /> read70 /hd0/1/no_hole 0 0 -b status = 0 success, count = 0 @@ -74,7 +74,7 @@ status = 0 success, count = 0 /> read70 /hd0/1/no_hole 0xffffffffffffffff 0 -b status = 0 success, count = 0 -# one-byte length +/> # one-byte length /> read70 /hd0/1/no_hole 0 1 -b status = 0 success, count = 1 00 @@ -147,7 +147,7 @@ status = 6 end_of_file, count = 0 /> read70 /hd0/1/no_hole 0xffffffffffffffff 1 -b status = 6 end_of_file, count = 0 -# fixed-size block, different begin/end positions +/> # fixed-size block, different begin/end positions /> read70 /hd0/1/no_hole 0 11 -b status = 0 success, count = 11 000102030405060708090a @@ -205,8 +205,8 @@ status = 6 end_of_file, count = 0 /> read70 /hd0/1/no_hole 0xffffffffffffffff 11 -b status = 6 end_of_file, count = 0 - -# btree +/> +/> # btree /> read70 /hd0/1/btree_l1_no_hole 0x80000 11 -b status = 0 success, count = 11 0000080004000800080008 diff --git a/test/016_#f01_#draw_all.ref.log b/test/016_#f01_#draw_all.ref.log index d21e55a..68a0944 100644 --- a/test/016_#f01_#draw_all.ref.log +++ b/test/016_#f01_#draw_all.ref.log @@ -1,6 +1,6 @@ /> umka_init /> set_mouse_pos_screen 40 30 -#disk_add ../img/kolibri.img rd -c 0 +/> #disk_add ../img/kolibri.img rd -c 0 /> ramdisk_init ../img/kolibri.img /rd/1: fat /> set_skin /sys/DEFAULT.SKN @@ -22,15 +22,15 @@ status: 0 /> display_number 0 10 4 0 0 1234 5 45 0xffff00 1 1 0 0 0x0000ff /> blit_bitmap chess_image.rgba 20 35 8 8 0 0 8 8 0 0 0 1 32 /> window_redraw 2 - +/> /> set_window_caption hi_there 0 - +/> /> get_font_smoothing font smoothing: 2 - subpixel /> set_font_smoothing 0 /> get_font_smoothing font smoothing: 0 - off - +/> /> get_window_colors 0x00586786 frame 0x00fefefe grab @@ -43,7 +43,7 @@ font smoothing: 0 - off 0x00000000 work_text 0x007e7e7e work_graph /> set_window_colors 0 0 0 0 0 0 0 0 0 0 - +/> /> dump_win_stack 2 0: 0 1: 1 @@ -69,7 +69,7 @@ wnd_caption: wnd_clientbox (ltwh): 5 24 291 172 priority: 0 in_schedule: prev (2), next (2) - +/> /> process_info -1 cpu_usage: 0 window_stack_position: 2 @@ -100,16 +100,16 @@ wnd_state: 0x00 5 top 65 right 3 bottom - +/> /> get_font_size 0px /> set_font_size 16 /> get_font_size 16px - +/> /> get_screen_size 400x300 - +/> /> scrot 016_#f01_#draw_all.out.png - +/> /> disk_del rd diff --git a/test/017_#f70_#f70s0_#xfs_#s05k_read_above_4GiB.ref.log b/test/017_#f70_#f70s0_#xfs_#s05k_read_above_4GiB.ref.log index 5ce831c..66ec342 100644 --- a/test/017_#f70_#f70s0_#xfs_#s05k_read_above_4GiB.ref.log +++ b/test/017_#f70_#f70s0_#xfs_#s05k_read_above_4GiB.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/xfs_v4_files_s05k_b4k_n8k.img hd0 -c 0 /hd0/1: xfs - +/> /> read70 /hd0/1/4GiB_plus 0x3ff4 11 -b status = 0 success, count = 11 0000000000000000000000 @@ -23,7 +23,7 @@ status = 0 success, count = 11 /> read70 /hd0/1/4GiB_plus 0x4001 11 -b status = 0 success, count = 11 4002400440064008400a40 - +/> /> read70 /hd0/1/4GiB_plus 0x7ff4 11 -b status = 0 success, count = 11 f47ff67ff87ffa7ffc7ffe @@ -45,7 +45,7 @@ status = 0 success, count = 11 /> read70 /hd0/1/4GiB_plus 0x8001 11 -b status = 0 success, count = 11 0000000000000000000000 - +/> /> read70 /hd0/1/4GiB_plus 0xfffffff4 11 -b status = 0 success, count = 11 f4fffffff8fffffffcffff @@ -67,7 +67,7 @@ status = 0 success, count = 11 /> read70 /hd0/1/4GiB_plus 0x100000001 11 -b status = 0 success, count = 11 0000000100000008000000 - +/> /> read70 /hd0/1/4GiB_plus 0x11ffffff4 11 -b status = 0 success, count = 11 0000000000000000000000 @@ -89,7 +89,7 @@ status = 0 success, count = 11 /> read70 /hd0/1/4GiB_plus 0x120000001 11 -b status = 0 success, count = 11 0000200100000008000020 - +/> /> read70 /hd0/1/4GiB_plus 0x120003ff4 11 -b status = 0 success, count = 11 01000000f83f0020010000 @@ -111,7 +111,7 @@ status = 0 success, count = 11 /> read70 /hd0/1/4GiB_plus 0x120004001 11 -b status = 0 success, count = 11 0000000000000000000000 - +/> /> read70 /hd0/1/4GiB_plus 0x11fffefff 0x0fff -h status = 0 success, count = 4095 1f65b79da1bf0dc2a529219184e005cc1453894c4ea996a1307784c7690eb7d2 @@ -124,7 +124,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x11fffefff 0x2001 -h status = 0 success, count = 8193 d22f0c1a9c494cc1a8a1e1a2ccf4057502f2839603632d47c69cc272b8d7ef96 - +/> /> read70 /hd0/1/4GiB_plus 0x11ffff000 0x0fff -h status = 0 success, count = 4095 1f65b79da1bf0dc2a529219184e005cc1453894c4ea996a1307784c7690eb7d2 @@ -137,7 +137,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x11ffff000 0x2001 -h status = 0 success, count = 8193 0f8d363587a204a536ae4e17046f1b946d3716805d181d147968eb2d1efc5c63 - +/> /> read70 /hd0/1/4GiB_plus 0x11ffff001 0x0fff -h status = 0 success, count = 4095 1f65b79da1bf0dc2a529219184e005cc1453894c4ea996a1307784c7690eb7d2 @@ -150,7 +150,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x11ffff001 0x2001 -h status = 0 success, count = 8193 56d243996479770cd97a33c679fb57ff3c354d7785f5d846f53e171bad850389 - +/> /> read70 /hd0/1/4GiB_plus 0x120002fff 0x0fff -h status = 0 success, count = 4095 a126d59a9acb9cd3c977779638d1df609be8bd079b1b57373c87974b73a562ec @@ -163,7 +163,7 @@ f78a428aec38eb1d81a138060671b2a450c7b7d4cdda6226db4cd9fcc073bbc3 /> read70 /hd0/1/4GiB_plus 0x120002fff 0x2001 -h status = 0 success, count = 8193 5d3ab86c1605d59550fe26506a8e16ca3e6b46b2a8cf9030a5dc5a00bd728823 - +/> /> read70 /hd0/1/4GiB_plus 0x120003000 0x0fff -h status = 0 success, count = 4095 5077407c7ecc83efb242c96ed6ec3ad86008f253a083b61cc9855a5e6c94c3f5 @@ -176,7 +176,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x120003000 0x2001 -h status = 0 success, count = 8193 e6a7c5df31fce8a8ee6ae52b715b2fca721e6fca63d1605e02be585de8526cf1 - +/> /> read70 /hd0/1/4GiB_plus 0x120003001 0x0fff -h status = 0 success, count = 4095 316310be962fd169a9961c0fc91a98deb30ab7c20fe35d32f4c0f14fa30660d5 @@ -189,7 +189,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x120003001 0x2001 -h status = 0 success, count = 8193 1adf6f95f4151bb640c60cf530ebfe91b5e268bf78044bd51de755a95bc07d08 - +/> /> read70 /hd0/1/4GiB_plus 0x120003fff 0x0fff -h status = 0 success, count = 4095 1f65b79da1bf0dc2a529219184e005cc1453894c4ea996a1307784c7690eb7d2 @@ -202,7 +202,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x120003fff 0x2001 -h status = 0 success, count = 8193 b70dd656adf5c5b3434b695386dd07658e667f6f9d0cd34af5a3e8527924964f - +/> /> read70 /hd0/1/4GiB_plus 0x120004000 0x0fff -h status = 0 success, count = 4095 1f65b79da1bf0dc2a529219184e005cc1453894c4ea996a1307784c7690eb7d2 @@ -215,7 +215,7 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x120004000 0x2001 -h status = 0 success, count = 8193 b70dd656adf5c5b3434b695386dd07658e667f6f9d0cd34af5a3e8527924964f - +/> /> read70 /hd0/1/4GiB_plus 0x120004001 0x0fff -h status = 0 success, count = 4095 1f65b79da1bf0dc2a529219184e005cc1453894c4ea996a1307784c7690eb7d2 @@ -228,8 +228,8 @@ status = 0 success, count = 4097 /> read70 /hd0/1/4GiB_plus 0x120004001 0x2001 -h status = 0 success, count = 8193 b70dd656adf5c5b3434b695386dd07658e667f6f9d0cd34af5a3e8527924964f - -# data, hole, data +/> +/> # data, hole, data /> read70 /hd0/1/4GiB_plus 0x120003fff 0x6000 -h status = 6 end_of_file, count = 20481 f4ab1c2ab5e007814bc19cf58547cf98df1c1c70a1d02f5c417fe2a15b47239f @@ -239,8 +239,8 @@ status = 6 end_of_file, count = 20480 /> read70 /hd0/1/4GiB_plus 0x120004001 0x6000 -h status = 6 end_of_file, count = 20479 ba9f86ce5ae88d4875fa38ef47f7257ba00e172ed2e2ee1473599de58c80823c - -# hole, data, hole +/> +/> # hole, data, hole /> read70 /hd0/1/4GiB_plus 0x11fffefff 0x6000 -h status = 0 success, count = 24576 8ffe00fc8d9cb9b2d33865a95917df2cee3f6711c8d6358fd082ed5494851411 diff --git a/test/019_#xfs_deep_btree.ref.log b/test/019_#xfs_deep_btree.ref.log index ea50b31..c569b34 100644 --- a/test/019_#xfs_deep_btree.ref.log +++ b/test/019_#xfs_deep_btree.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/xfs_v4_btrees_l2.img hd0 -c 0 /hd0/1: xfs - +/> /> ls80 /hd0/1/dir_btree_l2 -f 0 -c 1 status = 0 success, count = 1 ----f . @@ -393,7 +393,7 @@ status = 6 end_of_file, count = 83 ----f d0000193178_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ----f d0000193179_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ----f d0000193180_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - +/> /> read70 /hd0/1/file_btree_l2 0 16388096 -h status = 0 success, count = 16388096 8cbbb4bf572aaf6016e608260f372facfffb6e40fc31d8d7d83b08b526273d75 @@ -409,5 +409,5 @@ bb66ddff9fa267bf7a42153d0de9dc029b775e23d6378ade96e6c92ceaf6841a /> read70 /hd0/1/file_btree_l2 0x1000 0x1001 -h status = 0 success, count = 4097 aa026c31286b2ba83a047f8d07baa712b34da983235afb79b4d2cb60cf668f2b - +/> /> disk_del hd0 diff --git a/test/044_#f01_#draw_winmap.ref.log b/test/044_#f01_#draw_winmap.ref.log index 2378131..676459d 100644 --- a/test/044_#f01_#draw_winmap.ref.log +++ b/test/044_#f01_#draw_winmap.ref.log @@ -4,21 +4,21 @@ /rd/1: fat /> set_skin /sys/DEFAULT.SKN status: 0 - +/> /> window_redraw 1 /> draw_window 2 10 4 10 0x000088 1 1 1 0 1 4 hello /> window_redraw 2 - +/> /> set_window_caption hi_there 0 - +/> /> new_sys_thread tid: 3 /> switch_to_thread 3 - +/> /> window_redraw 1 /> draw_window 4 5 8 5 0x000088 1 1 1 0 1 4 hello /> window_redraw 2 - +/> /> dump_win_map 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 @@ -64,7 +64,7 @@ tid: 3 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 - +/> /> set redraw_background 0 /> move_window 6 8 5 5 /> dump_win_map @@ -112,7 +112,7 @@ tid: 3 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 - +/> /> set redraw_background 0 /> move_window 6 10 5 5 /> dump_win_map @@ -160,7 +160,7 @@ tid: 3 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111 - +/> /> scrot 044_#f01_#draw_winmap.out.png - +/> /> disk_del rd diff --git a/test/046_#f70_#f70s1_#fat_#exfat_#s05k_ls.ref.log b/test/046_#f70_#f70s1_#fat_#exfat_#s05k_ls.ref.log index 51209f8..8979df4 100644 --- a/test/046_#f70_#f70s1_#fat_#exfat_#s05k_ls.ref.log +++ b/test/046_#f70_#f70s1_#fat_#exfat_#s05k_ls.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/exfat_s05k_c16k_b16k.img hd0 -c 0 /hd0/1: exfat - +/> /> ls70 /hd0/1/dir_0 -f 0 -c 0 /> ls70 /hd0/1/dir_0 -f 0 -c 1 status = 0 success, count = 1 @@ -19,7 +19,7 @@ status = 6 end_of_file, count = 0 status = 6 end_of_file, count = 0 /> ls70 /hd0/1/dir_0 -f 1 -c 0xffffffff status = 6 end_of_file, count = 0 - +/> /> ls70 /hd0/1/dir_1 -f 0 -c 0 /> ls70 /hd0/1/dir_1 -f 0 -c 1 status = 0 success, count = 1 @@ -42,7 +42,7 @@ status = 6 end_of_file, count = 1 /> ls70 /hd0/1/dir_1 -f 1 -c 0xffffffff status = 6 end_of_file, count = 1 ----- file000 - +/> /> ls70 /hd0/1/dir_1000 status = 0 success, count = 100 total = 1001 @@ -11273,5 +11273,5 @@ total = 10001 ----f d0000009999_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx /> ls70 /hd0/1/dir_100000 status = 5 file_not_found - +/> /> disk_del hd0 diff --git a/test/047_#f70_#f70s1_#fat_#exfat_#s05k_stat.ref.log b/test/047_#f70_#f70s1_#fat_#exfat_#s05k_stat.ref.log index 43be168..5483fec 100644 --- a/test/047_#f70_#f70s1_#fat_#exfat_#s05k_stat.ref.log +++ b/test/047_#f70_#f70s1_#fat_#exfat_#s05k_stat.ref.log @@ -1,7 +1,7 @@ /> umka_init /> disk_add ../img/exfat_s05k_c8k_b8k.img hd0 -c 0 /hd0/1: exfat - +/> /> stat70 /hd0/1/dir_000 status = 0 success attr: ----f @@ -226,5 +226,5 @@ attr: ----f status = 0 success attr: ----- size: 1 - +/> /> disk_del hd0 diff --git a/test/049_#keyboard_#input_all.ref.log b/test/049_#keyboard_#input_all.ref.log index c9e594e..97c0fb1 100644 --- a/test/049_#keyboard_#input_all.ref.log +++ b/test/049_#keyboard_#input_all.ref.log @@ -1,5 +1,5 @@ /> umka_init - +/> /> get_keyboard_mode keyboard_mode: 0 - ASCII /> get_keyboard_lang @@ -7,7 +7,7 @@ keyboard_mode: 0 - ASCII /> get_system_lang 1 - en /> dump_key_buff - +/> /> get_keyboard_lang 1 - en /> set_keyboard_lang 0 @@ -49,7 +49,7 @@ invalid lang: 0 /> set_keyboard_lang 12 /> get_keyboard_lang invalid lang: 12 - +/> /> get_system_lang 1 - en /> set_system_lang 0 @@ -91,7 +91,7 @@ invalid lang: 0 /> set_system_lang 12 /> get_system_lang invalid lang: 12 - +/> /> set_keyboard_mode 0 /> get_keyboard_mode keyboard_mode: 0 - ASCII @@ -103,7 +103,7 @@ keyboard_mode: 0 - ASCII 0x00033200 /> dump_key_buff 0 0x34 0x05 - +/> /> set_keyboard_mode 1 /> get_keyboard_mode keyboard_mode: 1 - scancodes @@ -117,7 +117,7 @@ keyboard_mode: 1 - scancodes /> dump_key_buff 0 0x07 0x00 1 0x09 0x00 - +/> /> get_key 0x00000700 /> get key_count @@ -131,7 +131,7 @@ keyboard_mode: 1 - scancodes /> get key_count 0 /> dump_key_buff - +/> /> get_keyboard_layout -t 1 0x36 0x1b 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x2d 0x3d 0x08 0x09 0x71 0x77 0x65 0x72 0x74 0x79 0x75 0x69 0x6f 0x70 0x5b 0x5d 0x0d 0x7e 0x61 0x73 @@ -186,7 +186,7 @@ keyboard_mode: 1 - scancodes 0xb1 0xb7 0xb9 0xb6 0x41 0x42 0x43 0x44 0xff 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 - +/> /> set_keyboard_layout -t 0 -f 00ff.layout status: -1 /> get_keyboard_layout -t normal @@ -216,7 +216,7 @@ status: -1 0xb1 0xb7 0xb9 0xb6 0x41 0x42 0x43 0x44 0xff 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 - +/> /> set_keyboard_layout -t 1 -f 00ff.layout status: 0 /> get_keyboard_layout -t normal @@ -246,7 +246,7 @@ status: 0 0xb1 0xb7 0xb9 0xb6 0x41 0x42 0x43 0x44 0xff 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 - +/> /> set_keyboard_layout -t 2 -f 00ff.layout status: 0 /> get_keyboard_layout -t normal @@ -276,7 +276,7 @@ status: 0 0xb1 0xb7 0xb9 0xb6 0x41 0x42 0x43 0x44 0xff 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 - +/> /> set_keyboard_layout -t 3 -f 00ff.layout status: 0 /> get_keyboard_layout -t normal @@ -306,7 +306,7 @@ status: 0 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f - +/> /> set_keyboard_layout -t 4 -f 00ff.layout status: -1 /> get_keyboard_layout -t normal @@ -336,7 +336,7 @@ status: -1 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f - +/> /> set_keyboard_mode ascii /> send_scancode 0x11 /> get_key diff --git a/test/050_#mouse_#input_all.ref.log b/test/050_#mouse_#input_all.ref.log index 9a4e63c..f98495c 100644 --- a/test/050_#mouse_#input_all.ref.log +++ b/test/050_#mouse_#input_all.ref.log @@ -1,5 +1,5 @@ /> umka_init - +/> /> get_mouse_pos_screen x y: 200 150 /> get_mouse_pos_window diff --git a/test/051_#draw_#cursor_all.ref.log b/test/051_#draw_#cursor_all.ref.log index 51c172c..b82da45 100644 --- a/test/051_#draw_#cursor_all.ref.log +++ b/test/051_#draw_#cursor_all.ref.log @@ -1,22 +1,22 @@ /> umka_init -#set_mouse_pos_screen 40 30 +/> #set_mouse_pos_screen 40 30 /> ramdisk_init ../img/kolibri.img /rd/1: fat /> set_skin /sys/DEFAULT.SKN status: 0 - +/> /> load_cursor_from_file /sys/fill.cur handle = non-zero /> load_cursor_from_mem ../spray.cur handle = non-zero - +/> /> window_redraw 1 /> draw_window 10 300 5 200 0x000088 1 1 1 0 1 4 hello /> window_redraw 2 - - - +/> +/> +/> /> scrot 051_#draw_#cursor_all.out.png - - +/> +/> /> disk_del rd diff --git a/umka_shell.c b/umka_shell.c index 6c49254..babeec4 100644 --- a/umka_shell.c +++ b/umka_shell.c @@ -26,9 +26,20 @@ #include "umka.h" #include "trace.h" +#define HIST_FILE_BASENAME ".umka_shell.history" #define UMKA_DEFAULT_DISPLAY_WIDTH 400 #define UMKA_DEFAULT_DISPLAY_HEIGHT 300 +char history_filename[PATH_MAX]; + +void build_history_filename() { + const char *dir_name; + if (!(dir_name = getenv("HOME"))) { + dir_name = "."; + } + sprintf(history_filename, "%s/%s", dir_name, HIST_FILE_BASENAME); +} + int main(int argc, char **argv) { (void)argc; @@ -40,7 +51,9 @@ main(int argc, char **argv) { " -r reproducible logs (without pointers and datetime)\n" " -c collect coverage"; const char *infile = NULL, *outfile = NULL; - struct shell_ctx ctx = {.fin = stdin, .fout = stdout, .reproducible = 0}; + struct shell_ctx ctx = {.fin = stdin, .fout = stdout, .reproducible = 0, + .hist_file = history_filename}; + build_history_filename(); kos_boot.bpp = 32; kos_boot.x_res = UMKA_DEFAULT_DISPLAY_WIDTH; @@ -74,11 +87,11 @@ main(int argc, char **argv) { exit(1); } } - if (infile && !(ctx.fin = fopen(infile, "r"))) { + if (infile && !(ctx.fin = freopen(infile, "r", stdin))) { fprintf(stderr, "[!] can't open file for reading: %s", infile); exit(1); } - if (outfile && !(ctx.fout = fopen(outfile, "w"))) { + if (outfile && !(ctx.fout = freopen(outfile, "w", stdout))) { fprintf(stderr, "[!] can't open file for writing: %s", outfile); exit(1); }