| | @@ -1,19 +1,718 @@ |
| 1 | +#line 1 "utf8.h" |
| 2 | +#ifndef UTF8_UTIL_H |
| 3 | +#define UTF8_UTIL_H |
| 4 | + |
| 5 | +#ifdef __cplusplus |
| 6 | +extern "C" { |
| 7 | +#endif |
| 8 | + |
| 9 | +/** |
| 10 | + * UTF-8 utility functions |
| 11 | + * |
| 12 | + * (c) 2010-2019 Steve Bennett <[email protected]> |
| 13 | + * |
| 14 | + * See utf8.c for licence details. |
| 15 | + */ |
| 16 | + |
| 17 | +#ifndef USE_UTF8 |
| 18 | +#include <ctype.h> |
| 19 | + |
| 20 | +#define MAX_UTF8_LEN 1 |
| 21 | + |
| 22 | +/* No utf-8 support. 1 byte = 1 char */ |
| 23 | +#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B)) |
| 24 | +#define utf8_strwidth(S, B) utf8_strlen((S), (B)) |
| 25 | +#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1) |
| 26 | +#define utf8_index(C, I) (I) |
| 27 | +#define utf8_charlen(C) 1 |
| 28 | + #define utf8_width(C) 1 |
| 29 | + |
| 30 | +#else |
| 31 | + |
| 32 | +#define MAX_UTF8_LEN 4 |
| 33 | + |
| 34 | +/** |
| 35 | + * Converts the given unicode codepoint (0 - 0x1fffff) to utf-8 |
| 36 | + * and stores the result at 'p'. |
| 37 | + * |
| 38 | + * Returns the number of utf-8 characters |
| 39 | + */ |
| 40 | +int utf8_fromunicode(char *p, unsigned uc); |
| 41 | + |
| 42 | +/** |
| 43 | + * Returns the length of the utf-8 sequence starting with 'c'. |
| 44 | + * |
| 45 | + * Returns 1-4, or -1 if this is not a valid start byte. |
| 46 | + * |
| 47 | + * Note that charlen=4 is not supported by the rest of the API. |
| 48 | + */ |
| 49 | +int utf8_charlen(int c); |
| 50 | + |
| 51 | +/** |
| 52 | + * Returns the number of characters in the utf-8 |
| 53 | + * string of the given byte length. |
| 54 | + * |
| 55 | + * Any bytes which are not part of an valid utf-8 |
| 56 | + * sequence are treated as individual characters. |
| 57 | + * |
| 58 | + * The string *must* be null terminated. |
| 59 | + * |
| 60 | + * Does not support unicode code points > \u1fffff |
| 61 | + */ |
| 62 | +int utf8_strlen(const char *str, int bytelen); |
| 63 | + |
| 64 | +/** |
| 65 | + * Calculates the display width of the first 'charlen' characters in 'str'. |
| 66 | + * See utf8_width() |
| 67 | + */ |
| 68 | +int utf8_strwidth(const char *str, int charlen); |
| 69 | + |
| 70 | +/** |
| 71 | + * Returns the byte index of the given character in the utf-8 string. |
| 72 | + * |
| 73 | + * The string *must* be null terminated. |
| 74 | + * |
| 75 | + * This will return the byte length of a utf-8 string |
| 76 | + * if given the char length. |
| 77 | + */ |
| 78 | +int utf8_index(const char *str, int charindex); |
| 79 | + |
| 80 | +/** |
| 81 | + * Returns the unicode codepoint corresponding to the |
| 82 | + * utf-8 sequence 'str'. |
| 83 | + * |
| 84 | + * Stores the result in *uc and returns the number of bytes |
| 85 | + * consumed. |
| 86 | + * |
| 87 | + * If 'str' is null terminated, then an invalid utf-8 sequence |
| 88 | + * at the end of the string will be returned as individual bytes. |
| 89 | + * |
| 90 | + * If it is not null terminated, the length *must* be checked first. |
| 91 | + * |
| 92 | + * Does not support unicode code points > \u1fffff |
| 93 | + */ |
| 94 | +int utf8_tounicode(const char *str, int *uc); |
| 95 | + |
| 96 | +/** |
| 97 | + * Returns the width (in characters) of the given unicode codepoint. |
| 98 | + * This is 1 for normal letters and 0 for combining characters and 2 for wide characters. |
| 99 | + */ |
| 100 | +int utf8_width(int ch); |
| 101 | + |
| 102 | +#endif |
| 103 | + |
| 104 | +#ifdef __cplusplus |
| 105 | +} |
| 106 | +#endif |
| 107 | + |
| 108 | +#endif |
| 109 | +#line 1 "utf8.c" |
| 110 | +/** |
| 111 | + * UTF-8 utility functions |
| 112 | + * |
| 113 | + * (c) 2010-2019 Steve Bennett <[email protected]> |
| 114 | + * |
| 115 | + * All rights reserved. |
| 116 | + * |
| 117 | + * Redistribution and use in source and binary forms, with or without |
| 118 | + * modification, are permitted provided that the following conditions are met: |
| 119 | + * |
| 120 | + * * Redistributions of source code must retain the above copyright notice, |
| 121 | + * this list of conditions and the following disclaimer. |
| 122 | + * |
| 123 | + * * Redistributions in binary form must reproduce the above copyright notice, |
| 124 | + * this list of conditions and the following disclaimer in the documentation |
| 125 | + * and/or other materials provided with the distribution. |
| 126 | + * |
| 127 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 128 | + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 129 | + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 130 | + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
| 131 | + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 132 | + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 133 | + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 134 | + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 135 | + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 136 | + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 137 | + */ |
| 138 | + |
| 139 | +#include <ctype.h> |
| 140 | +#include <stdlib.h> |
| 141 | +#include <string.h> |
| 142 | +#include <stdio.h> |
| 143 | +#ifndef UTF8_UTIL_H |
| 144 | +#include "utf8.h" |
| 145 | +#endif |
| 146 | + |
| 147 | +#ifdef USE_UTF8 |
| 148 | +int utf8_fromunicode(char *p, unsigned uc) |
| 149 | +{ |
| 150 | + if (uc <= 0x7f) { |
| 151 | + *p = uc; |
| 152 | + return 1; |
| 153 | + } |
| 154 | + else if (uc <= 0x7ff) { |
| 155 | + *p++ = 0xc0 | ((uc & 0x7c0) >> 6); |
| 156 | + *p = 0x80 | (uc & 0x3f); |
| 157 | + return 2; |
| 158 | + } |
| 159 | + else if (uc <= 0xffff) { |
| 160 | + *p++ = 0xe0 | ((uc & 0xf000) >> 12); |
| 161 | + *p++ = 0x80 | ((uc & 0xfc0) >> 6); |
| 162 | + *p = 0x80 | (uc & 0x3f); |
| 163 | + return 3; |
| 164 | + } |
| 165 | + /* Note: We silently truncate to 21 bits here: 0x1fffff */ |
| 166 | + else { |
| 167 | + *p++ = 0xf0 | ((uc & 0x1c0000) >> 18); |
| 168 | + *p++ = 0x80 | ((uc & 0x3f000) >> 12); |
| 169 | + *p++ = 0x80 | ((uc & 0xfc0) >> 6); |
| 170 | + *p = 0x80 | (uc & 0x3f); |
| 171 | + return 4; |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +int utf8_charlen(int c) |
| 176 | +{ |
| 177 | + if ((c & 0x80) == 0) { |
| 178 | + return 1; |
| 179 | + } |
| 180 | + if ((c & 0xe0) == 0xc0) { |
| 181 | + return 2; |
| 182 | + } |
| 183 | + if ((c & 0xf0) == 0xe0) { |
| 184 | + return 3; |
| 185 | + } |
| 186 | + if ((c & 0xf8) == 0xf0) { |
| 187 | + return 4; |
| 188 | + } |
| 189 | + /* Invalid sequence */ |
| 190 | + return -1; |
| 191 | +} |
| 192 | + |
| 193 | +int utf8_strlen(const char *str, int bytelen) |
| 194 | +{ |
| 195 | + int charlen = 0; |
| 196 | + if (bytelen < 0) { |
| 197 | + bytelen = strlen(str); |
| 198 | + } |
| 199 | + while (bytelen > 0) { |
| 200 | + int c; |
| 201 | + int l = utf8_tounicode(str, &c); |
| 202 | + charlen++; |
| 203 | + str += l; |
| 204 | + bytelen -= l; |
| 205 | + } |
| 206 | + return charlen; |
| 207 | +} |
| 208 | + |
| 209 | +int utf8_strwidth(const char *str, int charlen) |
| 210 | +{ |
| 211 | + int width = 0; |
| 212 | + while (charlen) { |
| 213 | + int c; |
| 214 | + int l = utf8_tounicode(str, &c); |
| 215 | + width += utf8_width(c); |
| 216 | + str += l; |
| 217 | + charlen--; |
| 218 | + } |
| 219 | + return width; |
| 220 | +} |
| 221 | + |
| 222 | +int utf8_index(const char *str, int index) |
| 223 | +{ |
| 224 | + const char *s = str; |
| 225 | + while (index--) { |
| 226 | + int c; |
| 227 | + s += utf8_tounicode(s, &c); |
| 228 | + } |
| 229 | + return s - str; |
| 230 | +} |
| 231 | + |
| 232 | +int utf8_tounicode(const char *str, int *uc) |
| 233 | +{ |
| 234 | + unsigned const char *s = (unsigned const char *)str; |
| 235 | + |
| 236 | + if (s[0] < 0xc0) { |
| 237 | + *uc = s[0]; |
| 238 | + return 1; |
| 239 | + } |
| 240 | + if (s[0] < 0xe0) { |
| 241 | + if ((s[1] & 0xc0) == 0x80) { |
| 242 | + *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80); |
| 243 | + if (*uc >= 0x80) { |
| 244 | + return 2; |
| 245 | + } |
| 246 | + /* Otherwise this is an invalid sequence */ |
| 247 | + } |
| 248 | + } |
| 249 | + else if (s[0] < 0xf0) { |
| 250 | + if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) { |
| 251 | + *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80); |
| 252 | + if (*uc >= 0x800) { |
| 253 | + return 3; |
| 254 | + } |
| 255 | + /* Otherwise this is an invalid sequence */ |
| 256 | + } |
| 257 | + } |
| 258 | + else if (s[0] < 0xf8) { |
| 259 | + if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80) && ((str[3] & 0xc0) == 0x80)) { |
| 260 | + *uc = ((s[0] & ~0xf0) << 18) | ((s[1] & ~0x80) << 12) | ((s[2] & ~0x80) << 6) | (s[3] & ~0x80); |
| 261 | + if (*uc >= 0x10000) { |
| 262 | + return 4; |
| 263 | + } |
| 264 | + /* Otherwise this is an invalid sequence */ |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + /* Invalid sequence, so just return the byte */ |
| 269 | + *uc = *s; |
| 270 | + return 1; |
| 271 | +} |
| 272 | + |
| 273 | +struct utf8range { |
| 274 | + int lower; /* lower inclusive */ |
| 275 | + int upper; /* upper exclusive */ |
| 276 | +}; |
| 277 | + |
| 278 | +/* From http://unicode.org/Public/UNIDATA/UnicodeData.txt */ |
| 279 | +static const struct utf8range unicode_range_combining[] = { |
| 280 | + { 0x0300, 0x0370 }, { 0x0483, 0x048a }, { 0x0591, 0x05d0 }, { 0x0610, 0x061b }, |
| 281 | + { 0x064b, 0x0660 }, { 0x0670, 0x0671 }, { 0x06d6, 0x06dd }, { 0x06df, 0x06e5 }, |
| 282 | + { 0x06e7, 0x06ee }, { 0x0711, 0x0712 }, { 0x0730, 0x074d }, { 0x07a6, 0x07b1 }, |
| 283 | + { 0x07eb, 0x07f4 }, { 0x0816, 0x0830 }, { 0x0859, 0x085e }, { 0x08d4, 0x0904 }, |
| 284 | + { 0x093a, 0x0958 }, { 0x0962, 0x0964 }, { 0x0981, 0x0985 }, { 0x09bc, 0x09ce }, |
| 285 | + { 0x09d7, 0x09dc }, { 0x09e2, 0x09e6 }, { 0x0a01, 0x0a05 }, { 0x0a3c, 0x0a59 }, |
| 286 | + { 0x0a70, 0x0a72 }, { 0x0a75, 0x0a85 }, { 0x0abc, 0x0ad0 }, { 0x0ae2, 0x0ae6 }, |
| 287 | + { 0x0afa, 0x0b05 }, { 0x0b3c, 0x0b5c }, { 0x0b62, 0x0b66 }, { 0x0b82, 0x0b83 }, |
| 288 | + { 0x0bbe, 0x0bd0 }, { 0x0bd7, 0x0be6 }, { 0x0c00, 0x0c05 }, { 0x0c3e, 0x0c58 }, |
| 289 | + { 0x0c62, 0x0c66 }, { 0x0c81, 0x0c85 }, { 0x0cbc, 0x0cde }, { 0x0ce2, 0x0ce6 }, |
| 290 | + { 0x0d00, 0x0d05 }, { 0x0d3b, 0x0d4e }, { 0x0d57, 0x0d58 }, { 0x0d62, 0x0d66 }, |
| 291 | + { 0x0d82, 0x0d85 }, { 0x0dca, 0x0de6 }, { 0x0df2, 0x0df4 }, { 0x0e31, 0x0e32 }, |
| 292 | + { 0x0e34, 0x0e3f }, { 0x0e47, 0x0e4f }, { 0x0eb1, 0x0eb2 }, { 0x0eb4, 0x0ebd }, |
| 293 | + { 0x0ec8, 0x0ed0 }, { 0x0f18, 0x0f1a }, { 0x0f35, 0x0f3a }, { 0x0f3e, 0x0f40 }, |
| 294 | + { 0x0f71, 0x0f88 }, { 0x0f8d, 0x0fbe }, { 0x0fc6, 0x0fc7 }, { 0x102b, 0x103f }, |
| 295 | + { 0x1056, 0x105a }, { 0x105e, 0x1065 }, { 0x1067, 0x106e }, { 0x1071, 0x1075 }, |
| 296 | + { 0x1082, 0x1090 }, { 0x109a, 0x109e }, { 0x135d, 0x1360 }, { 0x1712, 0x1720 }, |
| 297 | + { 0x1732, 0x1735 }, { 0x1752, 0x1760 }, { 0x1772, 0x1780 }, { 0x17b4, 0x17d4 }, |
| 298 | + { 0x17dd, 0x17e0 }, { 0x180b, 0x180e }, { 0x1885, 0x1887 }, { 0x18a9, 0x18aa }, |
| 299 | + { 0x1920, 0x1940 }, { 0x1a17, 0x1a1e }, { 0x1a55, 0x1a80 }, { 0x1ab0, 0x1b05 }, |
| 300 | + { 0x1b34, 0x1b45 }, { 0x1b6b, 0x1b74 }, { 0x1b80, 0x1b83 }, { 0x1ba1, 0x1bae }, |
| 301 | + { 0x1be6, 0x1bfc }, { 0x1c24, 0x1c3b }, { 0x1cd0, 0x1ce9 }, { 0x1ced, 0x1cee }, |
| 302 | + { 0x1cf2, 0x1cf5 }, { 0x1cf7, 0x1d00 }, { 0x1dc0, 0x1e00 }, { 0x20d0, 0x2100 }, |
| 303 | + { 0x2cef, 0x2cf2 }, { 0x2d7f, 0x2d80 }, { 0x2de0, 0x2e00 }, { 0x302a, 0x3030 }, |
| 304 | + { 0x3099, 0x309b }, { 0xa66f, 0xa67e }, { 0xa69e, 0xa6a0 }, { 0xa6f0, 0xa6f2 }, |
| 305 | + { 0xa802, 0xa803 }, { 0xa806, 0xa807 }, { 0xa80b, 0xa80c }, { 0xa823, 0xa828 }, |
| 306 | + { 0xa880, 0xa882 }, { 0xa8b4, 0xa8ce }, { 0xa8e0, 0xa8f2 }, { 0xa926, 0xa92e }, |
| 307 | + { 0xa947, 0xa95f }, { 0xa980, 0xa984 }, { 0xa9b3, 0xa9c1 }, { 0xa9e5, 0xa9e6 }, |
| 308 | + { 0xaa29, 0xaa40 }, { 0xaa43, 0xaa44 }, { 0xaa4c, 0xaa50 }, { 0xaa7b, 0xaa7e }, |
| 309 | + { 0xaab0, 0xaab5 }, { 0xaab7, 0xaab9 }, { 0xaabe, 0xaac2 }, { 0xaaeb, 0xaaf0 }, |
| 310 | + { 0xaaf5, 0xab01 }, { 0xabe3, 0xabf0 }, { 0xfb1e, 0xfb1f }, { 0xfe00, 0xfe10 }, |
| 311 | + { 0xfe20, 0xfe30 }, |
| 312 | +}; |
| 313 | + |
| 314 | +/* From http://unicode.org/Public/UNIDATA/EastAsianWidth.txt */ |
| 315 | +static const struct utf8range unicode_range_wide[] = { |
| 316 | + { 0x1100, 0x115f }, { 0x231a, 0x231b }, { 0x2329, 0x232a }, { 0x23e9, 0x23ec }, |
| 317 | + { 0x23f0, 0x23f0 }, { 0x23f3, 0x23f3 }, { 0x25fd, 0x25fe }, { 0x2614, 0x2615 }, |
| 318 | + { 0x2648, 0x2653 }, { 0x267f, 0x267f }, { 0x2693, 0x2693 }, { 0x26a1, 0x26a1 }, |
| 319 | + { 0x26aa, 0x26ab }, { 0x26bd, 0x26be }, { 0x26c4, 0x26c5 }, { 0x26ce, 0x26ce }, |
| 320 | + { 0x26d4, 0x26d4 }, { 0x26ea, 0x26ea }, { 0x26f2, 0x26f3 }, { 0x26f5, 0x26f5 }, |
| 321 | + { 0x26fa, 0x26fa }, { 0x26fd, 0x26fd }, { 0x2705, 0x2705 }, { 0x270a, 0x270b }, |
| 322 | + { 0x2728, 0x2728 }, { 0x274c, 0x274c }, { 0x274e, 0x274e }, { 0x2753, 0x2755 }, |
| 323 | + { 0x2757, 0x2757 }, { 0x2795, 0x2797 }, { 0x27b0, 0x27b0 }, { 0x27bf, 0x27bf }, |
| 324 | + { 0x2b1b, 0x2b1c }, { 0x2b50, 0x2b50 }, { 0x2b55, 0x2b55 }, { 0x2e80, 0x2e99 }, |
| 325 | + { 0x2e9b, 0x2ef3 }, { 0x2f00, 0x2fd5 }, { 0x2ff0, 0x2ffb }, { 0x3001, 0x303e }, |
| 326 | + { 0x3041, 0x3096 }, { 0x3099, 0x30ff }, { 0x3105, 0x312e }, { 0x3131, 0x318e }, |
| 327 | + { 0x3190, 0x31ba }, { 0x31c0, 0x31e3 }, { 0x31f0, 0x321e }, { 0x3220, 0x3247 }, |
| 328 | + { 0x3250, 0x32fe }, { 0x3300, 0x4dbf }, { 0x4e00, 0xa48c }, { 0xa490, 0xa4c6 }, |
| 329 | + { 0xa960, 0xa97c }, { 0xac00, 0xd7a3 }, { 0xf900, 0xfaff }, { 0xfe10, 0xfe19 }, |
| 330 | + { 0xfe30, 0xfe52 }, { 0xfe54, 0xfe66 }, { 0xfe68, 0xfe6b }, { 0x16fe0, 0x16fe1 }, |
| 331 | + { 0x17000, 0x187ec }, { 0x18800, 0x18af2 }, { 0x1b000, 0x1b11e }, { 0x1b170, 0x1b2fb }, |
| 332 | + { 0x1f004, 0x1f004 }, { 0x1f0cf, 0x1f0cf }, { 0x1f18e, 0x1f18e }, { 0x1f191, 0x1f19a }, |
| 333 | + { 0x1f200, 0x1f202 }, { 0x1f210, 0x1f23b }, { 0x1f240, 0x1f248 }, { 0x1f250, 0x1f251 }, |
| 334 | + { 0x1f260, 0x1f265 }, { 0x1f300, 0x1f320 }, { 0x1f32d, 0x1f335 }, { 0x1f337, 0x1f37c }, |
| 335 | + { 0x1f37e, 0x1f393 }, { 0x1f3a0, 0x1f3ca }, { 0x1f3cf, 0x1f3d3 }, { 0x1f3e0, 0x1f3f0 }, |
| 336 | + { 0x1f3f4, 0x1f3f4 }, { 0x1f3f8, 0x1f43e }, { 0x1f440, 0x1f440 }, { 0x1f442, 0x1f4fc }, |
| 337 | + { 0x1f4ff, 0x1f53d }, { 0x1f54b, 0x1f54e }, { 0x1f550, 0x1f567 }, { 0x1f57a, 0x1f57a }, |
| 338 | + { 0x1f595, 0x1f596 }, { 0x1f5a4, 0x1f5a4 }, { 0x1f5fb, 0x1f64f }, { 0x1f680, 0x1f6c5 }, |
| 339 | + { 0x1f6cc, 0x1f6cc }, { 0x1f6d0, 0x1f6d2 }, { 0x1f6eb, 0x1f6ec }, { 0x1f6f4, 0x1f6f8 }, |
| 340 | + { 0x1f910, 0x1f93e }, { 0x1f940, 0x1f94c }, { 0x1f950, 0x1f96b }, { 0x1f980, 0x1f997 }, |
| 341 | + { 0x1f9c0, 0x1f9c0 }, { 0x1f9d0, 0x1f9e6 }, { 0x20000, 0x2fffd }, { 0x30000, 0x3fffd }, |
| 342 | +}; |
| 343 | + |
| 344 | +#define ARRAYSIZE(A) sizeof(A) / sizeof(*(A)) |
| 345 | + |
| 346 | +static int cmp_range(const void *key, const void *cm) |
| 347 | +{ |
| 348 | + const struct utf8range *range = (const struct utf8range *)cm; |
| 349 | + int ch = *(int *)key; |
| 350 | + if (ch < range->lower) { |
| 351 | + return -1; |
| 352 | + } |
| 353 | + if (ch >= range->upper) { |
| 354 | + return 1; |
| 355 | + } |
| 356 | + return 0; |
| 357 | +} |
| 358 | + |
| 359 | +static int utf8_in_range(const struct utf8range *range, int num, int ch) |
| 360 | +{ |
| 361 | + const struct utf8range *r = |
| 362 | + bsearch(&ch, range, num, sizeof(*range), cmp_range); |
| 363 | + |
| 364 | + if (r) { |
| 365 | + return 1; |
| 366 | + } |
| 367 | + return 0; |
| 368 | +} |
| 369 | + |
| 370 | +int utf8_width(int ch) |
| 371 | +{ |
| 372 | + /* short circuit for common case */ |
| 373 | + if (isascii(ch)) { |
| 374 | + return 1; |
| 375 | + } |
| 376 | + if (utf8_in_range(unicode_range_combining, ARRAYSIZE(unicode_range_combining), ch)) { |
| 377 | + return 0; |
| 378 | + } |
| 379 | + if (utf8_in_range(unicode_range_wide, ARRAYSIZE(unicode_range_wide), ch)) { |
| 380 | + return 2; |
| 381 | + } |
| 382 | + return 1; |
| 383 | +} |
| 384 | +#endif |
| 385 | +#line 1 "stringbuf.h" |
| 386 | +#ifndef STRINGBUF_H |
| 387 | +#define STRINGBUF_H |
| 388 | +/** |
| 389 | + * resizable string buffer |
| 390 | + * |
| 391 | + * (c) 2017-2020 Steve Bennett <[email protected]> |
| 392 | + * |
| 393 | + * See utf8.c for licence details. |
| 394 | + */ |
| 395 | +#ifdef __cplusplus |
| 396 | +extern "C" { |
| 397 | +#endif |
| 398 | + |
| 399 | +/** @file |
| 400 | + * A stringbuf is a resizing, null terminated string buffer. |
| 401 | + * |
| 402 | + * The buffer is reallocated as necessary. |
| 403 | + * |
| 404 | + * In general it is *not* OK to call these functions with a NULL pointer |
| 405 | + * unless stated otherwise. |
| 406 | + * |
| 407 | + * If USE_UTF8 is defined, supports utf8. |
| 408 | + */ |
| 409 | + |
| 410 | +/** |
| 411 | + * The stringbuf structure should not be accessed directly. |
| 412 | + * Use the functions below. |
| 413 | + */ |
| 414 | +typedef struct { |
| 415 | + int remaining; /**< Allocated, but unused space */ |
| 416 | + int last; /**< Index of the null terminator (and thus the length of the string) */ |
| 417 | +#ifdef USE_UTF8 |
| 418 | + int chars; /**< Count of characters */ |
| 419 | +#endif |
| 420 | + char *data; /**< Allocated memory containing the string or NULL for empty */ |
| 421 | +} stringbuf; |
| 422 | + |
| 423 | +/** |
| 424 | + * Allocates and returns a new stringbuf with no elements. |
| 425 | + */ |
| 426 | +stringbuf *sb_alloc(void); |
| 427 | + |
| 428 | +/** |
| 429 | + * Frees a stringbuf. |
| 430 | + * It is OK to call this with NULL. |
| 431 | + */ |
| 432 | +void sb_free(stringbuf *sb); |
| 433 | + |
| 434 | +/** |
| 435 | + * Returns an allocated copy of the stringbuf |
| 436 | + */ |
| 437 | +stringbuf *sb_copy(stringbuf *sb); |
| 438 | + |
| 439 | +/** |
| 440 | + * Returns the byte length of the buffer. |
| 441 | + * |
| 442 | + * Returns 0 for both a NULL buffer and an empty buffer. |
| 443 | + */ |
| 444 | +static inline int sb_len(stringbuf *sb) { |
| 445 | + return sb->last; |
| 446 | +} |
| 447 | + |
| 448 | +/** |
| 449 | + * Returns the utf8 character length of the buffer. |
| 450 | + * |
| 451 | + * Returns 0 for both a NULL buffer and an empty buffer. |
| 452 | + */ |
| 453 | +static inline int sb_chars(stringbuf *sb) { |
| 454 | +#ifdef USE_UTF8 |
| 455 | + return sb->chars; |
| 456 | +#else |
| 457 | + return sb->last; |
| 458 | +#endif |
| 459 | +} |
| 460 | + |
| 461 | +/** |
| 462 | + * Appends a null terminated string to the stringbuf |
| 463 | + */ |
| 464 | +void sb_append(stringbuf *sb, const char *str); |
| 465 | + |
| 466 | +/** |
| 467 | + * Like sb_append() except does not require a null terminated string. |
| 468 | + * The length of 'str' is given as 'len' |
| 469 | + * |
| 470 | + * Note that in utf8 mode, characters will *not* be counted correctly |
| 471 | + * if a partial utf8 sequence is added with sb_append_len() |
| 472 | + */ |
| 473 | +void sb_append_len(stringbuf *sb, const char *str, int len); |
| 474 | + |
| 475 | +/** |
| 476 | + * Returns a pointer to the null terminated string in the buffer. |
| 477 | + * |
| 478 | + * Note this pointer only remains valid until the next modification to the |
| 479 | + * string buffer. |
| 480 | + * |
| 481 | + * The returned pointer can be used to update the buffer in-place |
| 482 | + * as long as care is taken to not overwrite the end of the buffer. |
| 483 | + */ |
| 484 | +static inline char *sb_str(const stringbuf *sb) |
| 485 | +{ |
| 486 | + return sb->data; |
| 487 | +} |
| 488 | + |
| 489 | +/** |
| 490 | + * Inserts the given string *before* (zero-based) byte 'index' in the stringbuf. |
| 491 | + * If index is past the end of the buffer, the string is appended, |
| 492 | + * just like sb_append() |
| 493 | + */ |
| 494 | +void sb_insert(stringbuf *sb, int index, const char *str); |
| 495 | + |
| 496 | +/** |
| 497 | + * Delete 'len' bytes in the string at the given index. |
| 498 | + * |
| 499 | + * Any bytes past the end of the buffer are ignored. |
| 500 | + * The buffer remains null terminated. |
| 501 | + * |
| 502 | + * If len is -1, deletes to the end of the buffer. |
| 503 | + */ |
| 504 | +void sb_delete(stringbuf *sb, int index, int len); |
| 505 | + |
| 506 | +/** |
| 507 | + * Clear to an empty buffer. |
| 508 | + */ |
| 509 | +void sb_clear(stringbuf *sb); |
| 510 | + |
| 511 | +/** |
| 512 | + * Return an allocated copy of buffer and frees 'sb'. |
| 513 | + * |
| 514 | + * If 'sb' is empty, returns an allocated copy of "". |
| 515 | + */ |
| 516 | +char *sb_to_string(stringbuf *sb); |
| 517 | + |
| 518 | +#ifdef __cplusplus |
| 519 | +} |
| 520 | +#endif |
| 521 | + |
| 522 | +#endif |
| 523 | +#line 1 "stringbuf.c" |
| 524 | +/** |
| 525 | + * resizable string buffer |
| 526 | + * |
| 527 | + * (c) 2017-2020 Steve Bennett <[email protected]> |
| 528 | + * |
| 529 | + * See utf8.c for licence details. |
| 530 | + */ |
| 531 | +#include <stdlib.h> |
| 532 | +#include <string.h> |
| 533 | +#include <stdio.h> |
| 534 | +#include <ctype.h> |
| 535 | +#include <assert.h> |
| 536 | + |
| 537 | +#ifndef STRINGBUF_H |
| 538 | +#include "stringbuf.h" |
| 539 | +#endif |
| 540 | +#ifdef USE_UTF8 |
| 541 | +#ifndef UTF8_UTIL_H |
| 542 | +#include "utf8.h" |
| 543 | +#endif |
| 544 | +#endif |
| 545 | + |
| 546 | +#define SB_INCREMENT 200 |
| 547 | + |
| 548 | +stringbuf *sb_alloc(void) |
| 549 | +{ |
| 550 | + stringbuf *sb = (stringbuf *)malloc(sizeof(*sb)); |
| 551 | + sb->remaining = 0; |
| 552 | + sb->last = 0; |
| 553 | +#ifdef USE_UTF8 |
| 554 | + sb->chars = 0; |
| 555 | +#endif |
| 556 | + sb->data = NULL; |
| 557 | + |
| 558 | + return(sb); |
| 559 | +} |
| 560 | + |
| 561 | +void sb_free(stringbuf *sb) |
| 562 | +{ |
| 563 | + if (sb) { |
| 564 | + free(sb->data); |
| 565 | + } |
| 566 | + free(sb); |
| 567 | +} |
| 568 | + |
| 569 | +static void sb_realloc(stringbuf *sb, int newlen) |
| 570 | +{ |
| 571 | + sb->data = (char *)realloc(sb->data, newlen); |
| 572 | + sb->remaining = newlen - sb->last; |
| 573 | +} |
| 574 | + |
| 575 | +void sb_append(stringbuf *sb, const char *str) |
| 576 | +{ |
| 577 | + sb_append_len(sb, str, strlen(str)); |
| 578 | +} |
| 579 | + |
| 580 | +void sb_append_len(stringbuf *sb, const char *str, int len) |
| 581 | +{ |
| 582 | + if (sb->remaining < len + 1) { |
| 583 | + sb_realloc(sb, sb->last + len + 1 + SB_INCREMENT); |
| 584 | + } |
| 585 | + memcpy(sb->data + sb->last, str, len); |
| 586 | + sb->data[sb->last + len] = 0; |
| 587 | + |
| 588 | + sb->last += len; |
| 589 | + sb->remaining -= len; |
| 590 | +#ifdef USE_UTF8 |
| 591 | + sb->chars += utf8_strlen(str, len); |
| 592 | +#endif |
| 593 | +} |
| 594 | + |
| 595 | +char *sb_to_string(stringbuf *sb) |
| 596 | +{ |
| 597 | + if (sb->data == NULL) { |
| 598 | + /* Return an allocated empty string, not null */ |
| 599 | + return strdup(""); |
| 600 | + } |
| 601 | + else { |
| 602 | + /* Just return the data and free the stringbuf structure */ |
| 603 | + char *pt = sb->data; |
| 604 | + free(sb); |
| 605 | + return pt; |
| 606 | + } |
| 607 | +} |
| 608 | + |
| 609 | +/* Insert and delete operations */ |
| 610 | + |
| 611 | +/* Moves up all the data at position 'pos' and beyond by 'len' bytes |
| 612 | + * to make room for new data |
| 613 | + * |
| 614 | + * Note: Does *not* update sb->chars |
| 615 | + */ |
| 616 | +static void sb_insert_space(stringbuf *sb, int pos, int len) |
| 617 | +{ |
| 618 | + assert(pos <= sb->last); |
| 619 | + |
| 620 | + /* Make sure there is enough space */ |
| 621 | + if (sb->remaining < len) { |
| 622 | + sb_realloc(sb, sb->last + len + SB_INCREMENT); |
| 623 | + } |
| 624 | + /* Now move it up */ |
| 625 | + memmove(sb->data + pos + len, sb->data + pos, sb->last - pos); |
| 626 | + sb->last += len; |
| 627 | + sb->remaining -= len; |
| 628 | + /* And null terminate */ |
| 629 | + sb->data[sb->last] = 0; |
| 630 | +} |
| 631 | + |
| 632 | +/** |
| 633 | + * Move down all the data from pos + len, effectively |
| 634 | + * deleting the data at position 'pos' of length 'len' |
| 635 | + */ |
| 636 | +static void sb_delete_space(stringbuf *sb, int pos, int len) |
| 637 | +{ |
| 638 | + assert(pos < sb->last); |
| 639 | + assert(pos + len <= sb->last); |
| 640 | + |
| 641 | +#ifdef USE_UTF8 |
| 642 | + sb->chars -= utf8_strlen(sb->data + pos, len); |
| 643 | +#endif |
| 644 | + |
| 645 | + /* Now move it up */ |
| 646 | + memmove(sb->data + pos, sb->data + pos + len, sb->last - pos - len); |
| 647 | + sb->last -= len; |
| 648 | + sb->remaining += len; |
| 649 | + /* And null terminate */ |
| 650 | + sb->data[sb->last] = 0; |
| 651 | +} |
| 652 | + |
| 653 | +void sb_insert(stringbuf *sb, int index, const char *str) |
| 654 | +{ |
| 655 | + if (index >= sb->last) { |
| 656 | + /* Inserting after the end of the list appends. */ |
| 657 | + sb_append(sb, str); |
| 658 | + } |
| 659 | + else { |
| 660 | + int len = strlen(str); |
| 661 | + |
| 662 | + sb_insert_space(sb, index, len); |
| 663 | + memcpy(sb->data + index, str, len); |
| 664 | +#ifdef USE_UTF8 |
| 665 | + sb->chars += utf8_strlen(str, len); |
| 666 | +#endif |
| 667 | + } |
| 668 | +} |
| 669 | + |
| 670 | +/** |
| 671 | + * Delete the bytes at index 'index' for length 'len' |
| 672 | + * Has no effect if the index is past the end of the list. |
| 673 | + */ |
| 674 | +void sb_delete(stringbuf *sb, int index, int len) |
| 675 | +{ |
| 676 | + if (index < sb->last) { |
| 677 | + char *pos = sb->data + index; |
| 678 | + if (len < 0) { |
| 679 | + len = sb->last; |
| 680 | + } |
| 681 | + |
| 682 | + sb_delete_space(sb, pos - sb->data, len); |
| 683 | + } |
| 684 | +} |
| 685 | + |
| 686 | +void sb_clear(stringbuf *sb) |
| 687 | +{ |
| 688 | + if (sb->data) { |
| 689 | + /* Null terminate */ |
| 690 | + sb->data[0] = 0; |
| 691 | + sb->last = 0; |
| 692 | +#ifdef USE_UTF8 |
| 693 | + sb->chars = 0; |
| 694 | +#endif |
| 695 | + } |
| 696 | +} |
| 697 | +#line 1 "linenoise.c" |
| 1 | 698 | /* linenoise.c -- guerrilla line editing library against the idea that a |
| 2 | 699 | * line editing lib needs to be 20,000 lines of C code. |
| 3 | 700 | * |
| 4 | 701 | * You can find the latest source code at: |
| 5 | 702 | * |
| 6 | | - * http://github.com/antirez/linenoise |
| 703 | + * http://github.com/msteveb/linenoise |
| 704 | + * (forked from http://github.com/antirez/linenoise) |
| 7 | 705 | * |
| 8 | 706 | * Does a number of crazy assumptions that happen to be true in 99.9999% of |
| 9 | 707 | * the 2010 UNIX computers around. |
| 10 | 708 | * |
| 11 | 709 | * ------------------------------------------------------------------------ |
| 12 | 710 | * |
| 13 | | - * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com> |
| 14 | | - * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
| 711 | + * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> |
| 712 | + * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
| 713 | + * Copyright (c) 2011, Steve Bennett <steveb at workware dot net dot au> |
| 15 | 714 | * |
| 16 | 715 | * All rights reserved. |
| 17 | 716 | * |
| 18 | 717 | * Redistribution and use in source and binary forms, with or without |
| 19 | 718 | * modification, are permitted provided that the following conditions are |
| | @@ -42,211 +741,381 @@ |
| 42 | 741 | * |
| 43 | 742 | * References: |
| 44 | 743 | * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html |
| 45 | 744 | * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html |
| 46 | 745 | * |
| 47 | | - * Todo list: |
| 48 | | - * - Filter bogus Ctrl+<char> combinations. |
| 49 | | - * - Win32 support |
| 50 | | - * |
| 51 | 746 | * Bloat: |
| 52 | | - * - History search like Ctrl+r in readline? |
| 747 | + * - Completion? |
| 53 | 748 | * |
| 749 | + * Unix/termios |
| 750 | + * ------------ |
| 54 | 751 | * List of escape sequences used by this program, we do everything just |
| 55 | | - * with three sequences. In order to be so cheap we may have some |
| 752 | + * a few sequences. In order to be so cheap we may have some |
| 56 | 753 | * flickering effect with some slow terminal, but the lesser sequences |
| 57 | 754 | * the more compatible. |
| 58 | 755 | * |
| 59 | 756 | * EL (Erase Line) |
| 60 | | - * Sequence: ESC [ n K |
| 61 | | - * Effect: if n is 0 or missing, clear from cursor to end of line |
| 62 | | - * Effect: if n is 1, clear from beginning of line to cursor |
| 63 | | - * Effect: if n is 2, clear entire line |
| 757 | + * Sequence: ESC [ 0 K |
| 758 | + * Effect: clear from cursor to end of line |
| 64 | 759 | * |
| 65 | 760 | * CUF (CUrsor Forward) |
| 66 | 761 | * Sequence: ESC [ n C |
| 67 | 762 | * Effect: moves cursor forward n chars |
| 68 | 763 | * |
| 69 | | - * CUB (CUrsor Backward) |
| 70 | | - * Sequence: ESC [ n D |
| 71 | | - * Effect: moves cursor backward n chars |
| 72 | | - * |
| 73 | | - * The following is used to get the terminal width if getting |
| 74 | | - * the width with the TIOCGWINSZ ioctl fails |
| 75 | | - * |
| 76 | | - * DSR (Device Status Report) |
| 77 | | - * Sequence: ESC [ 6 n |
| 78 | | - * Effect: reports the current cusor position as ESC [ n ; m R |
| 79 | | - * where n is the row and m is the column |
| 80 | | - * |
| 81 | | - * When multi line mode is enabled, we also use an additional escape |
| 82 | | - * sequence. However multi line editing is disabled by default. |
| 83 | | - * |
| 84 | | - * CUU (Cursor Up) |
| 85 | | - * Sequence: ESC [ n A |
| 86 | | - * Effect: moves cursor up of n chars. |
| 87 | | - * |
| 88 | | - * CUD (Cursor Down) |
| 89 | | - * Sequence: ESC [ n B |
| 90 | | - * Effect: moves cursor down of n chars. |
| 91 | | - * |
| 92 | | - * When linenoiseClearScreen() is called, two additional escape sequences |
| 93 | | - * are used in order to clear the screen and position the cursor at home |
| 94 | | - * position. |
| 95 | | - * |
| 96 | | - * CUP (Cursor position) |
| 764 | + * CR (Carriage Return) |
| 765 | + * Sequence: \r |
| 766 | + * Effect: moves cursor to column 1 |
| 767 | + * |
| 768 | + * The following are used to clear the screen: ESC [ H ESC [ 2 J |
| 769 | + * This is actually composed of two sequences: |
| 770 | + * |
| 771 | + * cursorhome |
| 97 | 772 | * Sequence: ESC [ H |
| 98 | 773 | * Effect: moves the cursor to upper left corner |
| 99 | 774 | * |
| 100 | | - * ED (Erase display) |
| 775 | + * ED2 (Clear entire screen) |
| 101 | 776 | * Sequence: ESC [ 2 J |
| 102 | 777 | * Effect: clear the whole screen |
| 103 | 778 | * |
| 779 | + * == For highlighting control characters, we also use the following two == |
| 780 | + * SO (enter StandOut) |
| 781 | + * Sequence: ESC [ 7 m |
| 782 | + * Effect: Uses some standout mode such as reverse video |
| 783 | + * |
| 784 | + * SE (Standout End) |
| 785 | + * Sequence: ESC [ 0 m |
| 786 | + * Effect: Exit standout mode |
| 787 | + * |
| 788 | + * == Only used if TIOCGWINSZ fails == |
| 789 | + * DSR/CPR (Report cursor position) |
| 790 | + * Sequence: ESC [ 6 n |
| 791 | + * Effect: reports current cursor position as ESC [ NNN ; MMM R |
| 792 | + * |
| 793 | + * == Only used in multiline mode == |
| 794 | + * CUU (Cursor Up) |
| 795 | + * Sequence: ESC [ n A |
| 796 | + * Effect: moves cursor up n chars. |
| 797 | + * |
| 798 | + * CUD (Cursor Down) |
| 799 | + * Sequence: ESC [ n B |
| 800 | + * Effect: moves cursor down n chars. |
| 801 | + * |
| 802 | + * win32/console |
| 803 | + * ------------- |
| 804 | + * If __MINGW32__ is defined, the win32 console API is used. |
| 805 | + * This could probably be made to work for the msvc compiler too. |
| 806 | + * This support based in part on work by Jon Griffiths. |
| 104 | 807 | */ |
| 105 | 808 | |
| 809 | +#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */ |
| 810 | +#include <windows.h> |
| 811 | +#include <fcntl.h> |
| 812 | +#define USE_WINCONSOLE |
| 813 | +#ifdef __MINGW32__ |
| 814 | +#define HAVE_UNISTD_H |
| 815 | +#endif |
| 816 | +#else |
| 106 | 817 | #include <termios.h> |
| 818 | +#include <sys/ioctl.h> |
| 819 | +#include <poll.h> |
| 820 | +#define USE_TERMIOS |
| 821 | +#define HAVE_UNISTD_H |
| 822 | +#endif |
| 823 | + |
| 824 | +#ifdef HAVE_UNISTD_H |
| 107 | 825 | #include <unistd.h> |
| 826 | +#endif |
| 108 | 827 | #include <stdlib.h> |
| 828 | +#include <stdarg.h> |
| 109 | 829 | #include <stdio.h> |
| 830 | +#include <assert.h> |
| 110 | 831 | #include <errno.h> |
| 111 | 832 | #include <string.h> |
| 833 | +#include <signal.h> |
| 112 | 834 | #include <stdlib.h> |
| 113 | | -#include <ctype.h> |
| 114 | | -#include <sys/stat.h> |
| 115 | 835 | #include <sys/types.h> |
| 116 | | -#include <sys/ioctl.h> |
| 117 | | -#include <unistd.h> |
| 836 | + |
| 837 | +#if defined(_WIN32) && !defined(__MINGW32__) |
| 838 | +/* Microsoft headers don't like old POSIX names */ |
| 839 | +#define strdup _strdup |
| 840 | +#define snprintf _snprintf |
| 841 | +#endif |
| 842 | + |
| 118 | 843 | #include "linenoise.h" |
| 844 | +#ifndef STRINGBUF_H |
| 845 | +#include "stringbuf.h" |
| 846 | +#endif |
| 847 | +#ifndef UTF8_UTIL_H |
| 848 | +#include "utf8.h" |
| 849 | +#endif |
| 119 | 850 | |
| 120 | 851 | #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 |
| 121 | | -#define LINENOISE_MAX_LINE 4096 |
| 122 | | -static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; |
| 123 | | -static linenoiseCompletionCallback *completionCallback = NULL; |
| 124 | | -static linenoiseHintsCallback *hintsCallback = NULL; |
| 125 | | -static linenoiseFreeHintsCallback *freeHintsCallback = NULL; |
| 126 | | - |
| 127 | | -static struct termios orig_termios; /* In order to restore at exit.*/ |
| 128 | | -static int maskmode = 0; /* Show "***" instead of input. For passwords. */ |
| 129 | | -static int rawmode = 0; /* For atexit() function to check if restore is needed*/ |
| 130 | | -static int mlmode = 0; /* Multi line mode. Default is single line. */ |
| 131 | | -static int atexit_registered = 0; /* Register atexit just 1 time. */ |
| 852 | + |
| 853 | +/* ctrl('A') -> 0x01 */ |
| 854 | +#define ctrl(C) ((C) - '@') |
| 855 | +/* meta('a') -> 0xe1 */ |
| 856 | +#define meta(C) ((C) | 0x80) |
| 857 | + |
| 858 | +/* Use -ve numbers here to co-exist with normal unicode chars */ |
| 859 | +enum { |
| 860 | + SPECIAL_NONE, |
| 861 | + /* don't use -1 here since that indicates error */ |
| 862 | + SPECIAL_UP = -20, |
| 863 | + SPECIAL_DOWN = -21, |
| 864 | + SPECIAL_LEFT = -22, |
| 865 | + SPECIAL_RIGHT = -23, |
| 866 | + SPECIAL_DELETE = -24, |
| 867 | + SPECIAL_HOME = -25, |
| 868 | + SPECIAL_END = -26, |
| 869 | + SPECIAL_INSERT = -27, |
| 870 | + SPECIAL_PAGE_UP = -28, |
| 871 | + SPECIAL_PAGE_DOWN = -29, |
| 872 | + |
| 873 | + /* Some handy names for other special keycodes */ |
| 874 | + CHAR_ESCAPE = 27, |
| 875 | + CHAR_DELETE = 127, |
| 876 | +}; |
| 877 | + |
| 132 | 878 | static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; |
| 133 | 879 | static int history_len = 0; |
| 880 | +static int history_index = 0; |
| 134 | 881 | static char **history = NULL; |
| 135 | 882 | |
| 136 | | -/* The linenoiseState structure represents the state during line editing. |
| 137 | | - * We pass this state to functions implementing specific editing |
| 138 | | - * functionalities. */ |
| 139 | | -struct linenoiseState { |
| 140 | | - int ifd; /* Terminal stdin file descriptor. */ |
| 141 | | - int ofd; /* Terminal stdout file descriptor. */ |
| 142 | | - char *buf; /* Edited line buffer. */ |
| 143 | | - size_t buflen; /* Edited line buffer size. */ |
| 144 | | - const char *prompt; /* Prompt to display. */ |
| 145 | | - size_t plen; /* Prompt length. */ |
| 146 | | - size_t pos; /* Current cursor position. */ |
| 147 | | - size_t oldpos; /* Previous refresh cursor position. */ |
| 148 | | - size_t len; /* Current edited line length. */ |
| 149 | | - size_t cols; /* Number of columns in terminal. */ |
| 150 | | - size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ |
| 151 | | - int history_index; /* The history index we are currently editing. */ |
| 152 | | -}; |
| 153 | | - |
| 154 | | -enum KEY_ACTION{ |
| 155 | | - KEY_NULL = 0, /* NULL */ |
| 156 | | - CTRL_A = 1, /* Ctrl+a */ |
| 157 | | - CTRL_B = 2, /* Ctrl-b */ |
| 158 | | - CTRL_C = 3, /* Ctrl-c */ |
| 159 | | - CTRL_D = 4, /* Ctrl-d */ |
| 160 | | - CTRL_E = 5, /* Ctrl-e */ |
| 161 | | - CTRL_F = 6, /* Ctrl-f */ |
| 162 | | - CTRL_H = 8, /* Ctrl-h */ |
| 163 | | - TAB = 9, /* Tab */ |
| 164 | | - CTRL_K = 11, /* Ctrl+k */ |
| 165 | | - CTRL_L = 12, /* Ctrl+l */ |
| 166 | | - ENTER = 13, /* Enter */ |
| 167 | | - CTRL_N = 14, /* Ctrl-n */ |
| 168 | | - CTRL_P = 16, /* Ctrl-p */ |
| 169 | | - CTRL_T = 20, /* Ctrl-t */ |
| 170 | | - CTRL_U = 21, /* Ctrl+u */ |
| 171 | | - CTRL_W = 23, /* Ctrl+w */ |
| 172 | | - ESC = 27, /* Escape */ |
| 173 | | - BACKSPACE = 127 /* Backspace */ |
| 174 | | -}; |
| 175 | | - |
| 176 | | -static void linenoiseAtExit(void); |
| 177 | | -int linenoiseHistoryAdd(const char *line); |
| 178 | | -static void refreshLine(struct linenoiseState *l); |
| 179 | | - |
| 180 | | -/* Debugging macro. */ |
| 181 | | -#if 0 |
| 182 | | -FILE *lndebug_fp = NULL; |
| 183 | | -#define lndebug(...) \ |
| 184 | | - do { \ |
| 185 | | - if (lndebug_fp == NULL) { \ |
| 186 | | - lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ |
| 187 | | - fprintf(lndebug_fp, \ |
| 188 | | - "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ |
| 189 | | - (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ |
| 190 | | - (int)l->maxrows,old_rows); \ |
| 191 | | - } \ |
| 192 | | - fprintf(lndebug_fp, ", " __VA_ARGS__); \ |
| 193 | | - fflush(lndebug_fp); \ |
| 194 | | - } while (0) |
| 195 | | -#else |
| 196 | | -#define lndebug(fmt, ...) |
| 197 | | -#endif |
| 198 | | - |
| 199 | | -/* ======================= Low level terminal handling ====================== */ |
| 200 | | - |
| 201 | | -/* Enable "mask mode". When it is enabled, instead of the input that |
| 202 | | - * the user is typing, the terminal will just display a corresponding |
| 203 | | - * number of asterisks, like "****". This is useful for passwords and other |
| 204 | | - * secrets that should not be displayed. */ |
| 205 | | -void linenoiseMaskModeEnable(void) { |
| 206 | | - maskmode = 1; |
| 207 | | -} |
| 208 | | - |
| 209 | | -/* Disable mask mode. */ |
| 210 | | -void linenoiseMaskModeDisable(void) { |
| 211 | | - maskmode = 0; |
| 212 | | -} |
| 213 | | - |
| 214 | | -/* Set if to use or not the multi line mode. */ |
| 215 | | -void linenoiseSetMultiLine(int ml) { |
| 216 | | - mlmode = ml; |
| 217 | | -} |
| 218 | | - |
| 219 | | -/* Return true if the terminal name is in the list of terminals we know are |
| 220 | | - * not able to understand basic escape sequences. */ |
| 221 | | -static int isUnsupportedTerm(void) { |
| 222 | | - char *term = getenv("TERM"); |
| 223 | | - int j; |
| 224 | | - |
| 225 | | - if (term == NULL) return 0; |
| 226 | | - for (j = 0; unsupported_term[j]; j++) |
| 227 | | - if (!strcasecmp(term,unsupported_term[j])) return 1; |
| 228 | | - return 0; |
| 229 | | -} |
| 230 | | - |
| 231 | | -/* Raw mode: 1960 magic shit. */ |
| 232 | | -static int enableRawMode(int fd) { |
| 233 | | - struct termios raw; |
| 234 | | - |
| 235 | | - if (!isatty(STDIN_FILENO)) goto fatal; |
| 883 | +/* Structure to contain the status of the current (being edited) line */ |
| 884 | +struct current { |
| 885 | + stringbuf *buf; /* Current buffer. Always null terminated */ |
| 886 | + int pos; /* Cursor position, measured in chars */ |
| 887 | + int cols; /* Size of the window, in chars */ |
| 888 | + int nrows; /* How many rows are being used in multiline mode (>= 1) */ |
| 889 | + int rpos; /* The current row containing the cursor - multiline mode only */ |
| 890 | + int colsright; /* refreshLine() cached cols for insert_char() optimisation */ |
| 891 | + int colsleft; /* refreshLine() cached cols for remove_char() optimisation */ |
| 892 | + const char *prompt; |
| 893 | + stringbuf *capture; /* capture buffer, or NULL for none. Always null terminated */ |
| 894 | + stringbuf *output; /* used only during refreshLine() - output accumulator */ |
| 895 | +#if defined(USE_TERMIOS) |
| 896 | + int fd; /* Terminal fd */ |
| 897 | +#elif defined(USE_WINCONSOLE) |
| 898 | + HANDLE outh; /* Console output handle */ |
| 899 | + HANDLE inh; /* Console input handle */ |
| 900 | + int rows; /* Screen rows */ |
| 901 | + int x; /* Current column during output */ |
| 902 | + int y; /* Current row */ |
| 903 | +#ifdef USE_UTF8 |
| 904 | + #define UBUF_MAX_CHARS 132 |
| 905 | + WORD ubuf[UBUF_MAX_CHARS + 1]; /* Accumulates utf16 output - one extra for final surrogate pairs */ |
| 906 | + int ubuflen; /* length used in ubuf */ |
| 907 | + int ubufcols; /* how many columns are represented by the chars in ubuf? */ |
| 908 | +#endif |
| 909 | +#endif |
| 910 | +}; |
| 911 | + |
| 912 | +static int fd_read(struct current *current); |
| 913 | +static int getWindowSize(struct current *current); |
| 914 | +static void cursorDown(struct current *current, int n); |
| 915 | +static void cursorUp(struct current *current, int n); |
| 916 | +static void eraseEol(struct current *current); |
| 917 | +static void refreshLine(struct current *current); |
| 918 | +static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos); |
| 919 | +static void setCursorPos(struct current *current, int x); |
| 920 | +static void setOutputHighlight(struct current *current, const int *props, int nprops); |
| 921 | +static void set_current(struct current *current, const char *str); |
| 922 | + |
| 923 | +static int fd_isatty(struct current *current) |
| 924 | +{ |
| 925 | +#ifdef USE_TERMIOS |
| 926 | + return isatty(current->fd); |
| 927 | +#else |
| 928 | + (void)current; |
| 929 | + return 0; |
| 930 | +#endif |
| 931 | +} |
| 932 | + |
| 933 | +void linenoiseHistoryFree(void) { |
| 934 | + if (history) { |
| 935 | + int j; |
| 936 | + |
| 937 | + for (j = 0; j < history_len; j++) |
| 938 | + free(history[j]); |
| 939 | + free(history); |
| 940 | + history = NULL; |
| 941 | + history_len = 0; |
| 942 | + } |
| 943 | +} |
| 944 | + |
| 945 | +typedef enum { |
| 946 | + EP_START, /* looking for ESC */ |
| 947 | + EP_ESC, /* looking for [ */ |
| 948 | + EP_DIGITS, /* parsing digits */ |
| 949 | + EP_PROPS, /* parsing digits or semicolons */ |
| 950 | + EP_END, /* ok */ |
| 951 | + EP_ERROR, /* error */ |
| 952 | +} ep_state_t; |
| 953 | + |
| 954 | +struct esc_parser { |
| 955 | + ep_state_t state; |
| 956 | + int props[5]; /* properties are stored here */ |
| 957 | + int maxprops; /* size of the props[] array */ |
| 958 | + int numprops; /* number of properties found */ |
| 959 | + int termchar; /* terminator char, or 0 for any alpha */ |
| 960 | + int current; /* current (partial) property value */ |
| 961 | +}; |
| 962 | + |
| 963 | +/** |
| 964 | + * Initialise the escape sequence parser at *parser. |
| 965 | + * |
| 966 | + * If termchar is 0 any alpha char terminates ok. Otherwise only the given |
| 967 | + * char terminates successfully. |
| 968 | + * Run the parser state machine with calls to parseEscapeSequence() for each char. |
| 969 | + */ |
| 970 | +static void initParseEscapeSeq(struct esc_parser *parser, int termchar) |
| 971 | +{ |
| 972 | + parser->state = EP_START; |
| 973 | + parser->maxprops = sizeof(parser->props) / sizeof(*parser->props); |
| 974 | + parser->numprops = 0; |
| 975 | + parser->current = 0; |
| 976 | + parser->termchar = termchar; |
| 977 | +} |
| 978 | + |
| 979 | +/** |
| 980 | + * Pass character 'ch' into the state machine to parse: |
| 981 | + * 'ESC' '[' <digits> (';' <digits>)* <termchar> |
| 982 | + * |
| 983 | + * The first character must be ESC. |
| 984 | + * Returns the current state. The state machine is done when it returns either EP_END |
| 985 | + * or EP_ERROR. |
| 986 | + * |
| 987 | + * On EP_END, the "property/attribute" values can be read from parser->props[] |
| 988 | + * of length parser->numprops. |
| 989 | + */ |
| 990 | +static int parseEscapeSequence(struct esc_parser *parser, int ch) |
| 991 | +{ |
| 992 | + switch (parser->state) { |
| 993 | + case EP_START: |
| 994 | + parser->state = (ch == '\x1b') ? EP_ESC : EP_ERROR; |
| 995 | + break; |
| 996 | + case EP_ESC: |
| 997 | + parser->state = (ch == '[') ? EP_DIGITS : EP_ERROR; |
| 998 | + break; |
| 999 | + case EP_PROPS: |
| 1000 | + if (ch == ';') { |
| 1001 | + parser->state = EP_DIGITS; |
| 1002 | +donedigits: |
| 1003 | + if (parser->numprops + 1 < parser->maxprops) { |
| 1004 | + parser->props[parser->numprops++] = parser->current; |
| 1005 | + parser->current = 0; |
| 1006 | + } |
| 1007 | + break; |
| 1008 | + } |
| 1009 | + /* fall through */ |
| 1010 | + case EP_DIGITS: |
| 1011 | + if (ch >= '0' && ch <= '9') { |
| 1012 | + parser->current = parser->current * 10 + (ch - '0'); |
| 1013 | + parser->state = EP_PROPS; |
| 1014 | + break; |
| 1015 | + } |
| 1016 | + /* must be terminator */ |
| 1017 | + if (parser->termchar != ch) { |
| 1018 | + if (parser->termchar != 0 || !((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))) { |
| 1019 | + parser->state = EP_ERROR; |
| 1020 | + break; |
| 1021 | + } |
| 1022 | + } |
| 1023 | + parser->state = EP_END; |
| 1024 | + goto donedigits; |
| 1025 | + case EP_END: |
| 1026 | + parser->state = EP_ERROR; |
| 1027 | + break; |
| 1028 | + case EP_ERROR: |
| 1029 | + break; |
| 1030 | + } |
| 1031 | + return parser->state; |
| 1032 | +} |
| 1033 | + |
| 1034 | +/*#define DEBUG_REFRESHLINE*/ |
| 1035 | + |
| 1036 | +#ifdef DEBUG_REFRESHLINE |
| 1037 | +#define DRL(ARGS...) fprintf(dfh, ARGS) |
| 1038 | +static FILE *dfh; |
| 1039 | + |
| 1040 | +static void DRL_CHAR(int ch) |
| 1041 | +{ |
| 1042 | + if (ch < ' ') { |
| 1043 | + DRL("^%c", ch + '@'); |
| 1044 | + } |
| 1045 | + else if (ch > 127) { |
| 1046 | + DRL("\\u%04x", ch); |
| 1047 | + } |
| 1048 | + else { |
| 1049 | + DRL("%c", ch); |
| 1050 | + } |
| 1051 | +} |
| 1052 | +static void DRL_STR(const char *str) |
| 1053 | +{ |
| 1054 | + while (*str) { |
| 1055 | + int ch; |
| 1056 | + int n = utf8_tounicode(str, &ch); |
| 1057 | + str += n; |
| 1058 | + DRL_CHAR(ch); |
| 1059 | + } |
| 1060 | +} |
| 1061 | +#else |
| 1062 | +#define DRL(...) |
| 1063 | +#define DRL_CHAR(ch) |
| 1064 | +#define DRL_STR(str) |
| 1065 | +#endif |
| 1066 | + |
| 1067 | +#if defined(USE_WINCONSOLE) |
| 1068 | +#include "linenoise-win32.c" |
| 1069 | +#endif |
| 1070 | + |
| 1071 | +#if defined(USE_TERMIOS) |
| 1072 | +static void linenoiseAtExit(void); |
| 1073 | +static struct termios orig_termios; /* in order to restore at exit */ |
| 1074 | +static int rawmode = 0; /* for atexit() function to check if restore is needed*/ |
| 1075 | +static int atexit_registered = 0; /* register atexit just 1 time */ |
| 1076 | + |
| 1077 | +static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; |
| 1078 | + |
| 1079 | +static int isUnsupportedTerm(void) { |
| 1080 | + char *term = getenv("TERM"); |
| 1081 | + |
| 1082 | + if (term) { |
| 1083 | + int j; |
| 1084 | + for (j = 0; unsupported_term[j]; j++) { |
| 1085 | + if (strcmp(term, unsupported_term[j]) == 0) { |
| 1086 | + return 1; |
| 1087 | + } |
| 1088 | + } |
| 1089 | + } |
| 1090 | + return 0; |
| 1091 | +} |
| 1092 | + |
| 1093 | +static int enableRawMode(struct current *current) { |
| 1094 | + struct termios raw; |
| 1095 | + |
| 1096 | + current->fd = STDIN_FILENO; |
| 1097 | + current->cols = 0; |
| 1098 | + |
| 1099 | + if (!isatty(current->fd) || isUnsupportedTerm() || |
| 1100 | + tcgetattr(current->fd, &orig_termios) == -1) { |
| 1101 | +fatal: |
| 1102 | + errno = ENOTTY; |
| 1103 | + return -1; |
| 1104 | + } |
| 1105 | + |
| 236 | 1106 | if (!atexit_registered) { |
| 237 | 1107 | atexit(linenoiseAtExit); |
| 238 | 1108 | atexit_registered = 1; |
| 239 | 1109 | } |
| 240 | | - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; |
| 241 | 1110 | |
| 242 | 1111 | raw = orig_termios; /* modify the original mode */ |
| 243 | 1112 | /* input modes: no break, no CR to NL, no parity check, no strip char, |
| 244 | 1113 | * no start/stop output control. */ |
| 245 | 1114 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); |
| 246 | | - /* output modes - disable post processing */ |
| 247 | | - raw.c_oflag &= ~(OPOST); |
| 1115 | + /* output modes - actually, no need to disable post processing */ |
| 1116 | + /*raw.c_oflag &= ~(OPOST);*/ |
| 248 | 1117 | /* control modes - set 8 bit chars */ |
| 249 | 1118 | raw.c_cflag |= (CS8); |
| 250 | 1119 | /* local modes - choing off, canonical off, no extended functions, |
| 251 | 1120 | * no signal chars (^Z,^C) */ |
| 252 | 1121 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); |
| | @@ -253,163 +1122,452 @@ |
| 253 | 1122 | /* control chars - set return condition: min number of bytes and timer. |
| 254 | 1123 | * We want read to return every single byte, without timeout. */ |
| 255 | 1124 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ |
| 256 | 1125 | |
| 257 | 1126 | /* put terminal in raw mode after flushing */ |
| 258 | | - if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; |
| 1127 | + if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) { |
| 1128 | + goto fatal; |
| 1129 | + } |
| 259 | 1130 | rawmode = 1; |
| 260 | 1131 | return 0; |
| 261 | | - |
| 262 | | -fatal: |
| 263 | | - errno = ENOTTY; |
| 264 | | - return -1; |
| 265 | 1132 | } |
| 266 | 1133 | |
| 267 | | -static void disableRawMode(int fd) { |
| 1134 | +static void disableRawMode(struct current *current) { |
| 268 | 1135 | /* Don't even check the return value as it's too late. */ |
| 269 | | - if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) |
| 1136 | + if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1) |
| 270 | 1137 | rawmode = 0; |
| 271 | 1138 | } |
| 272 | 1139 | |
| 273 | | -/* Use the ESC [6n escape sequence to query the horizontal cursor position |
| 274 | | - * and return it. On error -1 is returned, on success the position of the |
| 275 | | - * cursor. */ |
| 276 | | -static int getCursorPosition(int ifd, int ofd) { |
| 277 | | - char buf[32]; |
| 278 | | - int cols, rows; |
| 279 | | - unsigned int i = 0; |
| 280 | | - |
| 281 | | - /* Report cursor location */ |
| 282 | | - if (write(ofd, "\x1b[6n", 4) != 4) return -1; |
| 283 | | - |
| 284 | | - /* Read the response: ESC [ rows ; cols R */ |
| 285 | | - while (i < sizeof(buf)-1) { |
| 286 | | - if (read(ifd,buf+i,1) != 1) break; |
| 287 | | - if (buf[i] == 'R') break; |
| 288 | | - i++; |
| 289 | | - } |
| 290 | | - buf[i] = '\0'; |
| 291 | | - |
| 292 | | - /* Parse it. */ |
| 293 | | - if (buf[0] != ESC || buf[1] != '[') return -1; |
| 294 | | - if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; |
| 295 | | - return cols; |
| 296 | | -} |
| 297 | | - |
| 298 | | -/* Try to get the number of columns in the current terminal, or assume 80 |
| 299 | | - * if it fails. */ |
| 300 | | -static int getColumns(int ifd, int ofd) { |
| 1140 | +/* At exit we'll try to fix the terminal to the initial conditions. */ |
| 1141 | +static void linenoiseAtExit(void) { |
| 1142 | + if (rawmode) { |
| 1143 | + tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios); |
| 1144 | + } |
| 1145 | + linenoiseHistoryFree(); |
| 1146 | +} |
| 1147 | + |
| 1148 | +/* gcc/glibc insists that we care about the return code of write! |
| 1149 | + * Clarification: This means that a void-cast like "(void) (EXPR)" |
| 1150 | + * does not work. |
| 1151 | + */ |
| 1152 | +#define IGNORE_RC(EXPR) if (EXPR) {} |
| 1153 | + |
| 1154 | +/** |
| 1155 | + * Output bytes directly, or accumulate output (if current->output is set) |
| 1156 | + */ |
| 1157 | +static void outputChars(struct current *current, const char *buf, int len) |
| 1158 | +{ |
| 1159 | + if (len < 0) { |
| 1160 | + len = strlen(buf); |
| 1161 | + } |
| 1162 | + if (current->output) { |
| 1163 | + sb_append_len(current->output, buf, len); |
| 1164 | + } |
| 1165 | + else { |
| 1166 | + IGNORE_RC(write(current->fd, buf, len)); |
| 1167 | + } |
| 1168 | +} |
| 1169 | + |
| 1170 | +/* Like outputChars, but using printf-style formatting |
| 1171 | + */ |
| 1172 | +static void outputFormatted(struct current *current, const char *format, ...) |
| 1173 | +{ |
| 1174 | + va_list args; |
| 1175 | + char buf[64]; |
| 1176 | + int n; |
| 1177 | + |
| 1178 | + va_start(args, format); |
| 1179 | + n = vsnprintf(buf, sizeof(buf), format, args); |
| 1180 | + /* This will never happen because we are sure to use outputFormatted() only for short sequences */ |
| 1181 | + assert(n < (int)sizeof(buf)); |
| 1182 | + va_end(args); |
| 1183 | + outputChars(current, buf, n); |
| 1184 | +} |
| 1185 | + |
| 1186 | +static void cursorToLeft(struct current *current) |
| 1187 | +{ |
| 1188 | + outputChars(current, "\r", -1); |
| 1189 | +} |
| 1190 | + |
| 1191 | +static void setOutputHighlight(struct current *current, const int *props, int nprops) |
| 1192 | +{ |
| 1193 | + outputChars(current, "\x1b[", -1); |
| 1194 | + while (nprops--) { |
| 1195 | + outputFormatted(current, "%d%c", *props, (nprops == 0) ? 'm' : ';'); |
| 1196 | + props++; |
| 1197 | + } |
| 1198 | +} |
| 1199 | + |
| 1200 | +static void eraseEol(struct current *current) |
| 1201 | +{ |
| 1202 | + outputChars(current, "\x1b[0K", -1); |
| 1203 | +} |
| 1204 | + |
| 1205 | +static void setCursorPos(struct current *current, int x) |
| 1206 | +{ |
| 1207 | + if (x == 0) { |
| 1208 | + cursorToLeft(current); |
| 1209 | + } |
| 1210 | + else { |
| 1211 | + outputFormatted(current, "\r\x1b[%dC", x); |
| 1212 | + } |
| 1213 | +} |
| 1214 | + |
| 1215 | +static void cursorUp(struct current *current, int n) |
| 1216 | +{ |
| 1217 | + if (n) { |
| 1218 | + outputFormatted(current, "\x1b[%dA", n); |
| 1219 | + } |
| 1220 | +} |
| 1221 | + |
| 1222 | +static void cursorDown(struct current *current, int n) |
| 1223 | +{ |
| 1224 | + if (n) { |
| 1225 | + outputFormatted(current, "\x1b[%dB", n); |
| 1226 | + } |
| 1227 | +} |
| 1228 | + |
| 1229 | +void linenoiseClearScreen(void) |
| 1230 | +{ |
| 1231 | + IGNORE_RC(write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7)); |
| 1232 | +} |
| 1233 | + |
| 1234 | +/** |
| 1235 | + * Reads a char from 'fd', waiting at most 'timeout' milliseconds. |
| 1236 | + * |
| 1237 | + * A timeout of -1 means to wait forever. |
| 1238 | + * |
| 1239 | + * Returns -1 if no char is received within the time or an error occurs. |
| 1240 | + */ |
| 1241 | +static int fd_read_char(int fd, int timeout) |
| 1242 | +{ |
| 1243 | + struct pollfd p; |
| 1244 | + unsigned char c; |
| 1245 | + |
| 1246 | + p.fd = fd; |
| 1247 | + p.events = POLLIN; |
| 1248 | + |
| 1249 | + if (poll(&p, 1, timeout) == 0) { |
| 1250 | + /* timeout */ |
| 1251 | + return -1; |
| 1252 | + } |
| 1253 | + if (read(fd, &c, 1) != 1) { |
| 1254 | + return -1; |
| 1255 | + } |
| 1256 | + return c; |
| 1257 | +} |
| 1258 | + |
| 1259 | +/** |
| 1260 | + * Reads a complete utf-8 character |
| 1261 | + * and returns the unicode value, or -1 on error. |
| 1262 | + */ |
| 1263 | +static int fd_read(struct current *current) |
| 1264 | +{ |
| 1265 | +#ifdef USE_UTF8 |
| 1266 | + char buf[MAX_UTF8_LEN]; |
| 1267 | + int n; |
| 1268 | + int i; |
| 1269 | + int c; |
| 1270 | + |
| 1271 | + if (read(current->fd, &buf[0], 1) != 1) { |
| 1272 | + return -1; |
| 1273 | + } |
| 1274 | + n = utf8_charlen(buf[0]); |
| 1275 | + if (n < 1) { |
| 1276 | + return -1; |
| 1277 | + } |
| 1278 | + for (i = 1; i < n; i++) { |
| 1279 | + if (read(current->fd, &buf[i], 1) != 1) { |
| 1280 | + return -1; |
| 1281 | + } |
| 1282 | + } |
| 1283 | + /* decode and return the character */ |
| 1284 | + utf8_tounicode(buf, &c); |
| 1285 | + return c; |
| 1286 | +#else |
| 1287 | + return fd_read_char(current->fd, -1); |
| 1288 | +#endif |
| 1289 | +} |
| 1290 | + |
| 1291 | + |
| 1292 | +/** |
| 1293 | + * Stores the current cursor column in '*cols'. |
| 1294 | + * Returns 1 if OK, or 0 if failed to determine cursor pos. |
| 1295 | + */ |
| 1296 | +static int queryCursor(struct current *current, int* cols) |
| 1297 | +{ |
| 1298 | + struct esc_parser parser; |
| 1299 | + int ch; |
| 1300 | + |
| 1301 | + /* Should not be buffering this output, it needs to go immediately */ |
| 1302 | + assert(current->output == NULL); |
| 1303 | + |
| 1304 | + /* control sequence - report cursor location */ |
| 1305 | + outputChars(current, "\x1b[6n", -1); |
| 1306 | + |
| 1307 | + /* Parse the response: ESC [ rows ; cols R */ |
| 1308 | + initParseEscapeSeq(&parser, 'R'); |
| 1309 | + while ((ch = fd_read_char(current->fd, 100)) > 0) { |
| 1310 | + switch (parseEscapeSequence(&parser, ch)) { |
| 1311 | + default: |
| 1312 | + continue; |
| 1313 | + case EP_END: |
| 1314 | + if (parser.numprops == 2 && parser.props[1] < 1000) { |
| 1315 | + *cols = parser.props[1]; |
| 1316 | + return 1; |
| 1317 | + } |
| 1318 | + break; |
| 1319 | + case EP_ERROR: |
| 1320 | + break; |
| 1321 | + } |
| 1322 | + /* failed */ |
| 1323 | + break; |
| 1324 | + } |
| 1325 | + return 0; |
| 1326 | +} |
| 1327 | + |
| 1328 | +/** |
| 1329 | + * Updates current->cols with the current window size (width) |
| 1330 | + */ |
| 1331 | +static int getWindowSize(struct current *current) |
| 1332 | +{ |
| 301 | 1333 | struct winsize ws; |
| 302 | 1334 | |
| 303 | | - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { |
| 304 | | - /* ioctl() failed. Try to query the terminal itself. */ |
| 305 | | - int start, cols; |
| 306 | | - |
| 307 | | - /* Get the initial position so we can restore it later. */ |
| 308 | | - start = getCursorPosition(ifd,ofd); |
| 309 | | - if (start == -1) goto failed; |
| 310 | | - |
| 311 | | - /* Go to right margin and get position. */ |
| 312 | | - if (write(ofd,"\x1b[999C",6) != 6) goto failed; |
| 313 | | - cols = getCursorPosition(ifd,ofd); |
| 314 | | - if (cols == -1) goto failed; |
| 315 | | - |
| 316 | | - /* Restore position. */ |
| 317 | | - if (cols > start) { |
| 318 | | - char seq[32]; |
| 319 | | - snprintf(seq,32,"\x1b[%dD",cols-start); |
| 320 | | - if (write(ofd,seq,strlen(seq)) == -1) { |
| 321 | | - /* Can't recover... */ |
| 322 | | - } |
| 323 | | - } |
| 324 | | - return cols; |
| 325 | | - } else { |
| 326 | | - return ws.ws_col; |
| 327 | | - } |
| 328 | | - |
| 329 | | -failed: |
| 330 | | - return 80; |
| 331 | | -} |
| 332 | | - |
| 333 | | -/* Clear the screen. Used to handle ctrl+l */ |
| 334 | | -void linenoiseClearScreen(void) { |
| 335 | | - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { |
| 336 | | - /* nothing to do, just to avoid warning. */ |
| 337 | | - } |
| 338 | | -} |
| 339 | | - |
| 340 | | -/* Beep, used for completion when there is nothing to complete or when all |
| 341 | | - * the choices were already shown. */ |
| 342 | | -static void linenoiseBeep(void) { |
| 1335 | + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) { |
| 1336 | + current->cols = ws.ws_col; |
| 1337 | + return 0; |
| 1338 | + } |
| 1339 | + |
| 1340 | + /* Failed to query the window size. Perhaps we are on a serial terminal. |
| 1341 | + * Try to query the width by sending the cursor as far to the right |
| 1342 | + * and reading back the cursor position. |
| 1343 | + * Note that this is only done once per call to linenoise rather than |
| 1344 | + * every time the line is refreshed for efficiency reasons. |
| 1345 | + * |
| 1346 | + * In more detail, we: |
| 1347 | + * (a) request current cursor position, |
| 1348 | + * (b) move cursor far right, |
| 1349 | + * (c) request cursor position again, |
| 1350 | + * (d) at last move back to the old position. |
| 1351 | + * This gives us the width without messing with the externally |
| 1352 | + * visible cursor position. |
| 1353 | + */ |
| 1354 | + |
| 1355 | + if (current->cols == 0) { |
| 1356 | + int here; |
| 1357 | + |
| 1358 | + /* If anything fails => default 80 */ |
| 1359 | + current->cols = 80; |
| 1360 | + |
| 1361 | + /* (a) */ |
| 1362 | + if (queryCursor (current, &here)) { |
| 1363 | + /* (b) */ |
| 1364 | + setCursorPos(current, 999); |
| 1365 | + |
| 1366 | + /* (c). Note: If (a) succeeded, then (c) should as well. |
| 1367 | + * For paranoia we still check and have a fallback action |
| 1368 | + * for (d) in case of failure.. |
| 1369 | + */ |
| 1370 | + if (queryCursor (current, ¤t->cols)) { |
| 1371 | + /* (d) Reset the cursor back to the original location. */ |
| 1372 | + if (current->cols > here) { |
| 1373 | + setCursorPos(current, here); |
| 1374 | + } |
| 1375 | + } |
| 1376 | + } |
| 1377 | + } |
| 1378 | + |
| 1379 | + return 0; |
| 1380 | +} |
| 1381 | + |
| 1382 | +/** |
| 1383 | + * If CHAR_ESCAPE was received, reads subsequent |
| 1384 | + * chars to determine if this is a known special key. |
| 1385 | + * |
| 1386 | + * Returns SPECIAL_NONE if unrecognised, or -1 if EOF. |
| 1387 | + * |
| 1388 | + * If no additional char is received within a short time, |
| 1389 | + * CHAR_ESCAPE is returned. |
| 1390 | + */ |
| 1391 | +static int check_special(int fd) |
| 1392 | +{ |
| 1393 | + int c = fd_read_char(fd, 50); |
| 1394 | + int c2; |
| 1395 | + |
| 1396 | + if (c < 0) { |
| 1397 | + return CHAR_ESCAPE; |
| 1398 | + } |
| 1399 | + else if (c >= 'a' && c <= 'z') { |
| 1400 | + /* esc-a => meta-a */ |
| 1401 | + return meta(c); |
| 1402 | + } |
| 1403 | + |
| 1404 | + c2 = fd_read_char(fd, 50); |
| 1405 | + if (c2 < 0) { |
| 1406 | + return c2; |
| 1407 | + } |
| 1408 | + if (c == '[' || c == 'O') { |
| 1409 | + /* Potential arrow key */ |
| 1410 | + switch (c2) { |
| 1411 | + case 'A': |
| 1412 | + return SPECIAL_UP; |
| 1413 | + case 'B': |
| 1414 | + return SPECIAL_DOWN; |
| 1415 | + case 'C': |
| 1416 | + return SPECIAL_RIGHT; |
| 1417 | + case 'D': |
| 1418 | + return SPECIAL_LEFT; |
| 1419 | + case 'F': |
| 1420 | + return SPECIAL_END; |
| 1421 | + case 'H': |
| 1422 | + return SPECIAL_HOME; |
| 1423 | + } |
| 1424 | + } |
| 1425 | + if (c == '[' && c2 >= '1' && c2 <= '8') { |
| 1426 | + /* extended escape */ |
| 1427 | + c = fd_read_char(fd, 50); |
| 1428 | + if (c == '~') { |
| 1429 | + switch (c2) { |
| 1430 | + case '2': |
| 1431 | + return SPECIAL_INSERT; |
| 1432 | + case '3': |
| 1433 | + return SPECIAL_DELETE; |
| 1434 | + case '5': |
| 1435 | + return SPECIAL_PAGE_UP; |
| 1436 | + case '6': |
| 1437 | + return SPECIAL_PAGE_DOWN; |
| 1438 | + case '7': |
| 1439 | + return SPECIAL_HOME; |
| 1440 | + case '8': |
| 1441 | + return SPECIAL_END; |
| 1442 | + } |
| 1443 | + } |
| 1444 | + while (c != -1 && c != '~') { |
| 1445 | + /* .e.g \e[12~ or '\e[11;2~ discard the complete sequence */ |
| 1446 | + c = fd_read_char(fd, 50); |
| 1447 | + } |
| 1448 | + } |
| 1449 | + |
| 1450 | + return SPECIAL_NONE; |
| 1451 | +} |
| 1452 | +#endif |
| 1453 | + |
| 1454 | +static void clearOutputHighlight(struct current *current) |
| 1455 | +{ |
| 1456 | + int nohighlight = 0; |
| 1457 | + setOutputHighlight(current, &nohighlight, 1); |
| 1458 | +} |
| 1459 | + |
| 1460 | +static void outputControlChar(struct current *current, char ch) |
| 1461 | +{ |
| 1462 | + int reverse = 7; |
| 1463 | + setOutputHighlight(current, &reverse, 1); |
| 1464 | + outputChars(current, "^", 1); |
| 1465 | + outputChars(current, &ch, 1); |
| 1466 | + clearOutputHighlight(current); |
| 1467 | +} |
| 1468 | + |
| 1469 | +#ifndef utf8_getchars |
| 1470 | +static int utf8_getchars(char *buf, int c) |
| 1471 | +{ |
| 1472 | +#ifdef USE_UTF8 |
| 1473 | + return utf8_fromunicode(buf, c); |
| 1474 | +#else |
| 1475 | + *buf = c; |
| 1476 | + return 1; |
| 1477 | +#endif |
| 1478 | +} |
| 1479 | +#endif |
| 1480 | + |
| 1481 | +/** |
| 1482 | + * Returns the unicode character at the given offset, |
| 1483 | + * or -1 if none. |
| 1484 | + */ |
| 1485 | +static int get_char(struct current *current, int pos) |
| 1486 | +{ |
| 1487 | + if (pos >= 0 && pos < sb_chars(current->buf)) { |
| 1488 | + int c; |
| 1489 | + int i = utf8_index(sb_str(current->buf), pos); |
| 1490 | + (void)utf8_tounicode(sb_str(current->buf) + i, &c); |
| 1491 | + return c; |
| 1492 | + } |
| 1493 | + return -1; |
| 1494 | +} |
| 1495 | + |
| 1496 | +static int char_display_width(int ch) |
| 1497 | +{ |
| 1498 | + if (ch < ' ') { |
| 1499 | + /* control chars take two positions */ |
| 1500 | + return 2; |
| 1501 | + } |
| 1502 | + else { |
| 1503 | + return utf8_width(ch); |
| 1504 | + } |
| 1505 | +} |
| 1506 | + |
| 1507 | +#ifndef NO_COMPLETION |
| 1508 | +static linenoiseCompletionCallback *completionCallback = NULL; |
| 1509 | +static void *completionUserdata = NULL; |
| 1510 | +static int showhints = 1; |
| 1511 | +static linenoiseHintsCallback *hintsCallback = NULL; |
| 1512 | +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; |
| 1513 | +static void *hintsUserdata = NULL; |
| 1514 | + |
| 1515 | +static void beep(void) { |
| 1516 | +#ifdef USE_TERMIOS |
| 343 | 1517 | fprintf(stderr, "\x7"); |
| 344 | 1518 | fflush(stderr); |
| 1519 | +#endif |
| 345 | 1520 | } |
| 346 | 1521 | |
| 347 | | -/* ============================== Completion ================================ */ |
| 348 | | - |
| 349 | | -/* Free a list of completion option populated by linenoiseAddCompletion(). */ |
| 350 | 1522 | static void freeCompletions(linenoiseCompletions *lc) { |
| 351 | 1523 | size_t i; |
| 352 | 1524 | for (i = 0; i < lc->len; i++) |
| 353 | 1525 | free(lc->cvec[i]); |
| 354 | | - if (lc->cvec != NULL) |
| 355 | | - free(lc->cvec); |
| 1526 | + free(lc->cvec); |
| 356 | 1527 | } |
| 357 | 1528 | |
| 358 | | -/* This is an helper function for linenoiseEdit() and is called when the |
| 359 | | - * user types the <tab> key in order to complete the string currently in the |
| 360 | | - * input. |
| 361 | | - * |
| 362 | | - * The state of the editing is encapsulated into the pointed linenoiseState |
| 363 | | - * structure as described in the structure definition. */ |
| 364 | | -static int completeLine(struct linenoiseState *ls) { |
| 1529 | +static int completeLine(struct current *current) { |
| 365 | 1530 | linenoiseCompletions lc = { 0, NULL }; |
| 366 | | - int nread, nwritten; |
| 367 | | - char c = 0; |
| 1531 | + int c = 0; |
| 368 | 1532 | |
| 369 | | - completionCallback(ls->buf,&lc); |
| 1533 | + completionCallback(sb_str(current->buf),&lc,completionUserdata); |
| 370 | 1534 | if (lc.len == 0) { |
| 371 | | - linenoiseBeep(); |
| 1535 | + beep(); |
| 372 | 1536 | } else { |
| 373 | 1537 | size_t stop = 0, i = 0; |
| 374 | 1538 | |
| 375 | 1539 | while(!stop) { |
| 376 | 1540 | /* Show completion or original buffer */ |
| 377 | 1541 | if (i < lc.len) { |
| 378 | | - struct linenoiseState saved = *ls; |
| 379 | | - |
| 380 | | - ls->len = ls->pos = strlen(lc.cvec[i]); |
| 381 | | - ls->buf = lc.cvec[i]; |
| 382 | | - refreshLine(ls); |
| 383 | | - ls->len = saved.len; |
| 384 | | - ls->pos = saved.pos; |
| 385 | | - ls->buf = saved.buf; |
| 1542 | + int chars = utf8_strlen(lc.cvec[i], -1); |
| 1543 | + refreshLineAlt(current, current->prompt, lc.cvec[i], chars); |
| 386 | 1544 | } else { |
| 387 | | - refreshLine(ls); |
| 1545 | + refreshLine(current); |
| 388 | 1546 | } |
| 389 | 1547 | |
| 390 | | - nread = read(ls->ifd,&c,1); |
| 391 | | - if (nread <= 0) { |
| 392 | | - freeCompletions(&lc); |
| 393 | | - return -1; |
| 1548 | + c = fd_read(current); |
| 1549 | + if (c == -1) { |
| 1550 | + break; |
| 394 | 1551 | } |
| 395 | 1552 | |
| 396 | 1553 | switch(c) { |
| 397 | | - case 9: /* tab */ |
| 1554 | + case '\t': /* tab */ |
| 398 | 1555 | i = (i+1) % (lc.len+1); |
| 399 | | - if (i == lc.len) linenoiseBeep(); |
| 1556 | + if (i == lc.len) beep(); |
| 400 | 1557 | break; |
| 401 | | - case 27: /* escape */ |
| 1558 | + case CHAR_ESCAPE: /* escape */ |
| 402 | 1559 | /* Re-show original buffer */ |
| 403 | | - if (i < lc.len) refreshLine(ls); |
| 1560 | + if (i < lc.len) { |
| 1561 | + refreshLine(current); |
| 1562 | + } |
| 404 | 1563 | stop = 1; |
| 405 | 1564 | break; |
| 406 | 1565 | default: |
| 407 | 1566 | /* Update buffer and return */ |
| 408 | 1567 | if (i < lc.len) { |
| 409 | | - nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); |
| 410 | | - ls->len = ls->pos = nwritten; |
| 1568 | + set_current(current,lc.cvec[i]); |
| 411 | 1569 | } |
| 412 | 1570 | stop = 1; |
| 413 | 1571 | break; |
| 414 | 1572 | } |
| 415 | 1573 | } |
| | @@ -417,769 +1575,1126 @@ |
| 417 | 1575 | |
| 418 | 1576 | freeCompletions(&lc); |
| 419 | 1577 | return c; /* Return last read character */ |
| 420 | 1578 | } |
| 421 | 1579 | |
| 422 | | -/* Register a callback function to be called for tab-completion. */ |
| 423 | | -void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { |
| 1580 | +/* Register a callback function to be called for tab-completion. |
| 1581 | + Returns the prior callback so that the caller may (if needed) |
| 1582 | + restore it when done. */ |
| 1583 | +linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn, void *userdata) { |
| 1584 | + linenoiseCompletionCallback * old = completionCallback; |
| 424 | 1585 | completionCallback = fn; |
| 425 | | -} |
| 426 | | - |
| 427 | | -/* Register a hits function to be called to show hits to the user at the |
| 428 | | - * right of the prompt. */ |
| 429 | | -void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { |
| 430 | | - hintsCallback = fn; |
| 431 | | -} |
| 432 | | - |
| 433 | | -/* Register a function to free the hints returned by the hints callback |
| 434 | | - * registered with linenoiseSetHintsCallback(). */ |
| 435 | | -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { |
| 436 | | - freeHintsCallback = fn; |
| 437 | | -} |
| 438 | | - |
| 439 | | -/* This function is used by the callback function registered by the user |
| 440 | | - * in order to add completion options given the input string when the |
| 441 | | - * user typed <tab>. See the example.c source code for a very easy to |
| 442 | | - * understand example. */ |
| 1586 | + completionUserdata = userdata; |
| 1587 | + return old; |
| 1588 | +} |
| 1589 | + |
| 443 | 1590 | void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { |
| 444 | | - size_t len = strlen(str); |
| 445 | | - char *copy, **cvec; |
| 446 | | - |
| 447 | | - copy = malloc(len+1); |
| 448 | | - if (copy == NULL) return; |
| 449 | | - memcpy(copy,str,len+1); |
| 450 | | - cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); |
| 451 | | - if (cvec == NULL) { |
| 452 | | - free(copy); |
| 453 | | - return; |
| 454 | | - } |
| 455 | | - lc->cvec = cvec; |
| 456 | | - lc->cvec[lc->len++] = copy; |
| 457 | | -} |
| 458 | | - |
| 459 | | -/* =========================== Line editing ================================= */ |
| 460 | | - |
| 461 | | -/* We define a very simple "append buffer" structure, that is an heap |
| 462 | | - * allocated string where we can append to. This is useful in order to |
| 463 | | - * write all the escape sequences in a buffer and flush them to the standard |
| 464 | | - * output in a single call, to avoid flickering effects. */ |
| 465 | | -struct abuf { |
| 466 | | - char *b; |
| 467 | | - int len; |
| 468 | | -}; |
| 469 | | - |
| 470 | | -static void abInit(struct abuf *ab) { |
| 471 | | - ab->b = NULL; |
| 472 | | - ab->len = 0; |
| 473 | | -} |
| 474 | | - |
| 475 | | -static void abAppend(struct abuf *ab, const char *s, int len) { |
| 476 | | - char *new = realloc(ab->b,ab->len+len); |
| 477 | | - |
| 478 | | - if (new == NULL) return; |
| 479 | | - memcpy(new+ab->len,s,len); |
| 480 | | - ab->b = new; |
| 481 | | - ab->len += len; |
| 482 | | -} |
| 483 | | - |
| 484 | | -static void abFree(struct abuf *ab) { |
| 485 | | - free(ab->b); |
| 1591 | + lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); |
| 1592 | + lc->cvec[lc->len++] = strdup(str); |
| 1593 | +} |
| 1594 | + |
| 1595 | +void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata) |
| 1596 | +{ |
| 1597 | + hintsCallback = callback; |
| 1598 | + hintsUserdata = userdata; |
| 1599 | +} |
| 1600 | + |
| 1601 | +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback) |
| 1602 | +{ |
| 1603 | + freeHintsCallback = callback; |
| 1604 | +} |
| 1605 | + |
| 1606 | +#endif |
| 1607 | + |
| 1608 | + |
| 1609 | +static const char *reduceSingleBuf(const char *buf, int availcols, int *cursor_pos) |
| 1610 | +{ |
| 1611 | + /* We have availcols columns available. |
| 1612 | + * If necessary, strip chars off the front of buf until *cursor_pos |
| 1613 | + * fits within availcols |
| 1614 | + */ |
| 1615 | + int needcols = 0; |
| 1616 | + int pos = 0; |
| 1617 | + int new_cursor_pos = *cursor_pos; |
| 1618 | + const char *pt = buf; |
| 1619 | + |
| 1620 | + DRL("reduceSingleBuf: availcols=%d, cursor_pos=%d\n", availcols, *cursor_pos); |
| 1621 | + |
| 1622 | + while (*pt) { |
| 1623 | + int ch; |
| 1624 | + int n = utf8_tounicode(pt, &ch); |
| 1625 | + pt += n; |
| 1626 | + |
| 1627 | + needcols += char_display_width(ch); |
| 1628 | + |
| 1629 | + /* If we need too many cols, strip |
| 1630 | + * chars off the front of buf to make it fit. |
| 1631 | + * We keep 3 extra cols to the right of the cursor. |
| 1632 | + * 2 for possible wide chars, 1 for the last column that |
| 1633 | + * can't be used. |
| 1634 | + */ |
| 1635 | + while (needcols >= availcols - 3) { |
| 1636 | + n = utf8_tounicode(buf, &ch); |
| 1637 | + buf += n; |
| 1638 | + needcols -= char_display_width(ch); |
| 1639 | + DRL_CHAR(ch); |
| 1640 | + |
| 1641 | + /* and adjust the apparent cursor position */ |
| 1642 | + new_cursor_pos--; |
| 1643 | + |
| 1644 | + if (buf == pt) { |
| 1645 | + /* can't remove more than this */ |
| 1646 | + break; |
| 1647 | + } |
| 1648 | + } |
| 1649 | + |
| 1650 | + if (pos++ == *cursor_pos) { |
| 1651 | + break; |
| 1652 | + } |
| 1653 | + |
| 1654 | + } |
| 1655 | + DRL("<snip>"); |
| 1656 | + DRL_STR(buf); |
| 1657 | + DRL("\nafter reduce, needcols=%d, new_cursor_pos=%d\n", needcols, new_cursor_pos); |
| 1658 | + |
| 1659 | + /* Done, now new_cursor_pos contains the adjusted cursor position |
| 1660 | + * and buf points to he adjusted start |
| 1661 | + */ |
| 1662 | + *cursor_pos = new_cursor_pos; |
| 1663 | + return buf; |
| 1664 | +} |
| 1665 | + |
| 1666 | +static int mlmode = 0; |
| 1667 | + |
| 1668 | +void linenoiseSetMultiLine(int enableml) |
| 1669 | +{ |
| 1670 | + mlmode = enableml; |
| 486 | 1671 | } |
| 487 | 1672 | |
| 488 | 1673 | /* Helper of refreshSingleLine() and refreshMultiLine() to show hints |
| 489 | | - * to the right of the prompt. */ |
| 490 | | -void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { |
| 491 | | - char seq[64]; |
| 492 | | - if (hintsCallback && plen+l->len < l->cols) { |
| 493 | | - int color = -1, bold = 0; |
| 494 | | - char *hint = hintsCallback(l->buf,&color,&bold); |
| 1674 | + * to the right of the prompt. |
| 1675 | + * Returns 1 if a hint was shown, or 0 if not |
| 1676 | + * If 'display' is 0, does no output. Just returns the appropriate return code. |
| 1677 | + */ |
| 1678 | +static int refreshShowHints(struct current *current, const char *buf, int availcols, int display) |
| 1679 | +{ |
| 1680 | + int rc = 0; |
| 1681 | + if (showhints && hintsCallback && availcols > 0) { |
| 1682 | + int bold = 0; |
| 1683 | + int color = -1; |
| 1684 | + char *hint = hintsCallback(buf, &color, &bold, hintsUserdata); |
| 495 | 1685 | if (hint) { |
| 496 | | - int hintlen = strlen(hint); |
| 497 | | - int hintmaxlen = l->cols-(plen+l->len); |
| 498 | | - if (hintlen > hintmaxlen) hintlen = hintmaxlen; |
| 499 | | - if (bold == 1 && color == -1) color = 37; |
| 500 | | - if (color != -1 || bold != 0) |
| 501 | | - snprintf(seq,64,"\033[%d;%d;49m",bold,color); |
| 502 | | - else |
| 503 | | - seq[0] = '\0'; |
| 504 | | - abAppend(ab,seq,strlen(seq)); |
| 505 | | - abAppend(ab,hint,hintlen); |
| 506 | | - if (color != -1 || bold != 0) |
| 507 | | - abAppend(ab,"\033[0m",4); |
| 508 | | - /* Call the function to free the hint returned. */ |
| 509 | | - if (freeHintsCallback) freeHintsCallback(hint); |
| 510 | | - } |
| 511 | | - } |
| 512 | | -} |
| 513 | | - |
| 514 | | -/* Single line low level line refresh. |
| 515 | | - * |
| 516 | | - * Rewrite the currently edited line accordingly to the buffer content, |
| 517 | | - * cursor position, and number of columns of the terminal. */ |
| 518 | | -static void refreshSingleLine(struct linenoiseState *l) { |
| 519 | | - char seq[64]; |
| 520 | | - size_t plen = strlen(l->prompt); |
| 521 | | - int fd = l->ofd; |
| 522 | | - char *buf = l->buf; |
| 523 | | - size_t len = l->len; |
| 524 | | - size_t pos = l->pos; |
| 525 | | - struct abuf ab; |
| 526 | | - |
| 527 | | - while((plen+pos) >= l->cols) { |
| 528 | | - buf++; |
| 529 | | - len--; |
| 530 | | - pos--; |
| 531 | | - } |
| 532 | | - while (plen+len > l->cols) { |
| 533 | | - len--; |
| 534 | | - } |
| 535 | | - |
| 536 | | - abInit(&ab); |
| 537 | | - /* Cursor to left edge */ |
| 538 | | - snprintf(seq,64,"\r"); |
| 539 | | - abAppend(&ab,seq,strlen(seq)); |
| 540 | | - /* Write the prompt and the current buffer content */ |
| 541 | | - abAppend(&ab,l->prompt,strlen(l->prompt)); |
| 542 | | - if (maskmode == 1) { |
| 543 | | - while (len--) abAppend(&ab,"*",1); |
| 544 | | - } else { |
| 545 | | - abAppend(&ab,buf,len); |
| 546 | | - } |
| 547 | | - /* Show hits if any. */ |
| 548 | | - refreshShowHints(&ab,l,plen); |
| 549 | | - /* Erase to right */ |
| 550 | | - snprintf(seq,64,"\x1b[0K"); |
| 551 | | - abAppend(&ab,seq,strlen(seq)); |
| 552 | | - /* Move cursor to original position. */ |
| 553 | | - snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); |
| 554 | | - abAppend(&ab,seq,strlen(seq)); |
| 555 | | - if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ |
| 556 | | - abFree(&ab); |
| 557 | | -} |
| 558 | | - |
| 559 | | -/* Multi line low level line refresh. |
| 560 | | - * |
| 561 | | - * Rewrite the currently edited line accordingly to the buffer content, |
| 562 | | - * cursor position, and number of columns of the terminal. */ |
| 563 | | -static void refreshMultiLine(struct linenoiseState *l) { |
| 564 | | - char seq[64]; |
| 565 | | - int plen = strlen(l->prompt); |
| 566 | | - int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ |
| 567 | | - int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ |
| 568 | | - int rpos2; /* rpos after refresh. */ |
| 569 | | - int col; /* colum position, zero-based. */ |
| 570 | | - int old_rows = l->maxrows; |
| 571 | | - int fd = l->ofd, j; |
| 572 | | - struct abuf ab; |
| 573 | | - |
| 574 | | - /* Update maxrows if needed. */ |
| 575 | | - if (rows > (int)l->maxrows) l->maxrows = rows; |
| 576 | | - |
| 577 | | - /* First step: clear all the lines used before. To do so start by |
| 578 | | - * going to the last row. */ |
| 579 | | - abInit(&ab); |
| 580 | | - if (old_rows-rpos > 0) { |
| 581 | | - lndebug("go down %d", old_rows-rpos); |
| 582 | | - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); |
| 583 | | - abAppend(&ab,seq,strlen(seq)); |
| 584 | | - } |
| 585 | | - |
| 586 | | - /* Now for every row clear it, go up. */ |
| 587 | | - for (j = 0; j < old_rows-1; j++) { |
| 588 | | - lndebug("clear+up"); |
| 589 | | - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); |
| 590 | | - abAppend(&ab,seq,strlen(seq)); |
| 591 | | - } |
| 592 | | - |
| 593 | | - /* Clean the top line. */ |
| 594 | | - lndebug("clear"); |
| 595 | | - snprintf(seq,64,"\r\x1b[0K"); |
| 596 | | - abAppend(&ab,seq,strlen(seq)); |
| 597 | | - |
| 598 | | - /* Write the prompt and the current buffer content */ |
| 599 | | - abAppend(&ab,l->prompt,strlen(l->prompt)); |
| 600 | | - if (maskmode == 1) { |
| 601 | | - unsigned int i; |
| 602 | | - for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); |
| 603 | | - } else { |
| 604 | | - abAppend(&ab,l->buf,l->len); |
| 605 | | - } |
| 606 | | - |
| 607 | | - /* Show hits if any. */ |
| 608 | | - refreshShowHints(&ab,l,plen); |
| 609 | | - |
| 610 | | - /* If we are at the very end of the screen with our prompt, we need to |
| 611 | | - * emit a newline and move the prompt to the first column. */ |
| 612 | | - if (l->pos && |
| 613 | | - l->pos == l->len && |
| 614 | | - (l->pos+plen) % l->cols == 0) |
| 615 | | - { |
| 616 | | - lndebug("<newline>"); |
| 617 | | - abAppend(&ab,"\n",1); |
| 618 | | - snprintf(seq,64,"\r"); |
| 619 | | - abAppend(&ab,seq,strlen(seq)); |
| 620 | | - rows++; |
| 621 | | - if (rows > (int)l->maxrows) l->maxrows = rows; |
| 622 | | - } |
| 623 | | - |
| 624 | | - /* Move cursor to right position. */ |
| 625 | | - rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ |
| 626 | | - lndebug("rpos2 %d", rpos2); |
| 627 | | - |
| 628 | | - /* Go up till we reach the expected positon. */ |
| 629 | | - if (rows-rpos2 > 0) { |
| 630 | | - lndebug("go-up %d", rows-rpos2); |
| 631 | | - snprintf(seq,64,"\x1b[%dA", rows-rpos2); |
| 632 | | - abAppend(&ab,seq,strlen(seq)); |
| 633 | | - } |
| 634 | | - |
| 635 | | - /* Set column. */ |
| 636 | | - col = (plen+(int)l->pos) % (int)l->cols; |
| 637 | | - lndebug("set col %d", 1+col); |
| 638 | | - if (col) |
| 639 | | - snprintf(seq,64,"\r\x1b[%dC", col); |
| 640 | | - else |
| 641 | | - snprintf(seq,64,"\r"); |
| 642 | | - abAppend(&ab,seq,strlen(seq)); |
| 643 | | - |
| 644 | | - lndebug("\n"); |
| 645 | | - l->oldpos = l->pos; |
| 646 | | - |
| 647 | | - if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ |
| 648 | | - abFree(&ab); |
| 649 | | -} |
| 650 | | - |
| 651 | | -/* Calls the two low level functions refreshSingleLine() or |
| 652 | | - * refreshMultiLine() according to the selected mode. */ |
| 653 | | -static void refreshLine(struct linenoiseState *l) { |
| 654 | | - if (mlmode) |
| 655 | | - refreshMultiLine(l); |
| 656 | | - else |
| 657 | | - refreshSingleLine(l); |
| 658 | | -} |
| 659 | | - |
| 660 | | -/* Insert the character 'c' at cursor current position. |
| 661 | | - * |
| 662 | | - * On error writing to the terminal -1 is returned, otherwise 0. */ |
| 663 | | -int linenoiseEditInsert(struct linenoiseState *l, char c) { |
| 664 | | - if (l->len < l->buflen) { |
| 665 | | - if (l->len == l->pos) { |
| 666 | | - l->buf[l->pos] = c; |
| 667 | | - l->pos++; |
| 668 | | - l->len++; |
| 669 | | - l->buf[l->len] = '\0'; |
| 670 | | - if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { |
| 671 | | - /* Avoid a full update of the line in the |
| 672 | | - * trivial case. */ |
| 673 | | - char d = (maskmode==1) ? '*' : c; |
| 674 | | - if (write(l->ofd,&d,1) == -1) return -1; |
| 675 | | - } else { |
| 676 | | - refreshLine(l); |
| 677 | | - } |
| 678 | | - } else { |
| 679 | | - memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); |
| 680 | | - l->buf[l->pos] = c; |
| 681 | | - l->len++; |
| 682 | | - l->pos++; |
| 683 | | - l->buf[l->len] = '\0'; |
| 684 | | - refreshLine(l); |
| 685 | | - } |
| 1686 | + rc = 1; |
| 1687 | + if (display) { |
| 1688 | + const char *pt; |
| 1689 | + if (bold == 1 && color == -1) color = 37; |
| 1690 | + if (bold || color > 0) { |
| 1691 | + int props[3] = { bold, color, 49 }; /* bold, color, fgnormal */ |
| 1692 | + setOutputHighlight(current, props, 3); |
| 1693 | + } |
| 1694 | + DRL("<hint bold=%d,color=%d>", bold, color); |
| 1695 | + pt = hint; |
| 1696 | + while (*pt) { |
| 1697 | + int ch; |
| 1698 | + int n = utf8_tounicode(pt, &ch); |
| 1699 | + int width = char_display_width(ch); |
| 1700 | + |
| 1701 | + if (width >= availcols) { |
| 1702 | + DRL("<hinteol>"); |
| 1703 | + break; |
| 1704 | + } |
| 1705 | + DRL_CHAR(ch); |
| 1706 | + |
| 1707 | + availcols -= width; |
| 1708 | + outputChars(current, pt, n); |
| 1709 | + pt += n; |
| 1710 | + } |
| 1711 | + if (bold || color > 0) { |
| 1712 | + clearOutputHighlight(current); |
| 1713 | + } |
| 1714 | + /* Call the function to free the hint returned. */ |
| 1715 | + if (freeHintsCallback) freeHintsCallback(hint, hintsUserdata); |
| 1716 | + } |
| 1717 | + } |
| 1718 | + } |
| 1719 | + return rc; |
| 1720 | +} |
| 1721 | + |
| 1722 | +#ifdef USE_TERMIOS |
| 1723 | +static void refreshStart(struct current *current) |
| 1724 | +{ |
| 1725 | + /* We accumulate all output here */ |
| 1726 | + assert(current->output == NULL); |
| 1727 | + current->output = sb_alloc(); |
| 1728 | +} |
| 1729 | + |
| 1730 | +static void refreshEnd(struct current *current) |
| 1731 | +{ |
| 1732 | + /* Output everything at once */ |
| 1733 | + IGNORE_RC(write(current->fd, sb_str(current->output), sb_len(current->output))); |
| 1734 | + sb_free(current->output); |
| 1735 | + current->output = NULL; |
| 1736 | +} |
| 1737 | + |
| 1738 | +static void refreshStartChars(struct current *current) |
| 1739 | +{ |
| 1740 | + (void)current; |
| 1741 | +} |
| 1742 | + |
| 1743 | +static void refreshNewline(struct current *current) |
| 1744 | +{ |
| 1745 | + DRL("<nl>"); |
| 1746 | + outputChars(current, "\n", 1); |
| 1747 | +} |
| 1748 | + |
| 1749 | +static void refreshEndChars(struct current *current) |
| 1750 | +{ |
| 1751 | + (void)current; |
| 1752 | +} |
| 1753 | +#endif |
| 1754 | + |
| 1755 | +static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos) |
| 1756 | +{ |
| 1757 | + int i; |
| 1758 | + const char *pt; |
| 1759 | + int displaycol; |
| 1760 | + int displayrow; |
| 1761 | + int visible; |
| 1762 | + int currentpos; |
| 1763 | + int notecursor; |
| 1764 | + int cursorcol = 0; |
| 1765 | + int cursorrow = 0; |
| 1766 | + int hint; |
| 1767 | + struct esc_parser parser; |
| 1768 | + |
| 1769 | +#ifdef DEBUG_REFRESHLINE |
| 1770 | + dfh = fopen("linenoise.debuglog", "a"); |
| 1771 | +#endif |
| 1772 | + |
| 1773 | + /* Should intercept SIGWINCH. For now, just get the size every time */ |
| 1774 | + getWindowSize(current); |
| 1775 | + |
| 1776 | + refreshStart(current); |
| 1777 | + |
| 1778 | + DRL("wincols=%d, cursor_pos=%d, nrows=%d, rpos=%d\n", current->cols, cursor_pos, current->nrows, current->rpos); |
| 1779 | + |
| 1780 | + /* Here is the plan: |
| 1781 | + * (a) move the the bottom row, going down the appropriate number of lines |
| 1782 | + * (b) move to beginning of line and erase the current line |
| 1783 | + * (c) go up one line and do the same, until we have erased up to the first row |
| 1784 | + * (d) output the prompt, counting cols and rows, taking into account escape sequences |
| 1785 | + * (e) output the buffer, counting cols and rows |
| 1786 | + * (e') when we hit the current pos, save the cursor position |
| 1787 | + * (f) move the cursor to the saved cursor position |
| 1788 | + * (g) save the current cursor row and number of rows |
| 1789 | + */ |
| 1790 | + |
| 1791 | + /* (a) - The cursor is currently at row rpos */ |
| 1792 | + cursorDown(current, current->nrows - current->rpos - 1); |
| 1793 | + DRL("<cud=%d>", current->nrows - current->rpos - 1); |
| 1794 | + |
| 1795 | + /* (b), (c) - Erase lines upwards until we get to the first row */ |
| 1796 | + for (i = 0; i < current->nrows; i++) { |
| 1797 | + if (i) { |
| 1798 | + DRL("<cup>"); |
| 1799 | + cursorUp(current, 1); |
| 1800 | + } |
| 1801 | + DRL("<clearline>"); |
| 1802 | + cursorToLeft(current); |
| 1803 | + eraseEol(current); |
| 1804 | + } |
| 1805 | + DRL("\n"); |
| 1806 | + |
| 1807 | + /* (d) First output the prompt. control sequences don't take up display space */ |
| 1808 | + pt = prompt; |
| 1809 | + displaycol = 0; /* current display column */ |
| 1810 | + displayrow = 0; /* current display row */ |
| 1811 | + visible = 1; |
| 1812 | + |
| 1813 | + refreshStartChars(current); |
| 1814 | + |
| 1815 | + while (*pt) { |
| 1816 | + int width; |
| 1817 | + int ch; |
| 1818 | + int n = utf8_tounicode(pt, &ch); |
| 1819 | + |
| 1820 | + if (visible && ch == CHAR_ESCAPE) { |
| 1821 | + /* The start of an escape sequence, so not visible */ |
| 1822 | + visible = 0; |
| 1823 | + initParseEscapeSeq(&parser, 'm'); |
| 1824 | + DRL("<esc-seq-start>"); |
| 1825 | + } |
| 1826 | + |
| 1827 | + if (ch == '\n' || ch == '\r') { |
| 1828 | + /* treat both CR and NL the same and force wrap */ |
| 1829 | + refreshNewline(current); |
| 1830 | + displaycol = 0; |
| 1831 | + displayrow++; |
| 1832 | + } |
| 1833 | + else { |
| 1834 | + width = visible * utf8_width(ch); |
| 1835 | + |
| 1836 | + displaycol += width; |
| 1837 | + if (displaycol >= current->cols) { |
| 1838 | + /* need to wrap to the next line because of newline or if it doesn't fit |
| 1839 | + * XXX this is a problem in single line mode |
| 1840 | + */ |
| 1841 | + refreshNewline(current); |
| 1842 | + displaycol = width; |
| 1843 | + displayrow++; |
| 1844 | + } |
| 1845 | + |
| 1846 | + DRL_CHAR(ch); |
| 1847 | +#ifdef USE_WINCONSOLE |
| 1848 | + if (visible) { |
| 1849 | + outputChars(current, pt, n); |
| 1850 | + } |
| 1851 | +#else |
| 1852 | + outputChars(current, pt, n); |
| 1853 | +#endif |
| 1854 | + } |
| 1855 | + pt += n; |
| 1856 | + |
| 1857 | + if (!visible) { |
| 1858 | + switch (parseEscapeSequence(&parser, ch)) { |
| 1859 | + case EP_END: |
| 1860 | + visible = 1; |
| 1861 | + setOutputHighlight(current, parser.props, parser.numprops); |
| 1862 | + DRL("<esc-seq-end,numprops=%d>", parser.numprops); |
| 1863 | + break; |
| 1864 | + case EP_ERROR: |
| 1865 | + DRL("<esc-seq-err>"); |
| 1866 | + visible = 1; |
| 1867 | + break; |
| 1868 | + } |
| 1869 | + } |
| 1870 | + } |
| 1871 | + |
| 1872 | + /* Now we are at the first line with all lines erased */ |
| 1873 | + DRL("\nafter prompt: displaycol=%d, displayrow=%d\n", displaycol, displayrow); |
| 1874 | + |
| 1875 | + |
| 1876 | + /* (e) output the buffer, counting cols and rows */ |
| 1877 | + if (mlmode == 0) { |
| 1878 | + /* In this mode we may need to trim chars from the start of the buffer until the |
| 1879 | + * cursor fits in the window. |
| 1880 | + */ |
| 1881 | + pt = reduceSingleBuf(buf, current->cols - displaycol, &cursor_pos); |
| 1882 | + } |
| 1883 | + else { |
| 1884 | + pt = buf; |
| 1885 | + } |
| 1886 | + |
| 1887 | + currentpos = 0; |
| 1888 | + notecursor = -1; |
| 1889 | + |
| 1890 | + while (*pt) { |
| 1891 | + int ch; |
| 1892 | + int n = utf8_tounicode(pt, &ch); |
| 1893 | + int width = char_display_width(ch); |
| 1894 | + |
| 1895 | + if (currentpos == cursor_pos) { |
| 1896 | + /* (e') wherever we output this character is where we want the cursor */ |
| 1897 | + notecursor = 1; |
| 1898 | + } |
| 1899 | + |
| 1900 | + if (displaycol + width >= current->cols) { |
| 1901 | + if (mlmode == 0) { |
| 1902 | + /* In single line mode stop once we print as much as we can on one line */ |
| 1903 | + DRL("<slmode>"); |
| 1904 | + break; |
| 1905 | + } |
| 1906 | + /* need to wrap to the next line since it doesn't fit */ |
| 1907 | + refreshNewline(current); |
| 1908 | + displaycol = 0; |
| 1909 | + displayrow++; |
| 1910 | + } |
| 1911 | + |
| 1912 | + if (notecursor == 1) { |
| 1913 | + /* (e') Save this position as the current cursor position */ |
| 1914 | + cursorcol = displaycol; |
| 1915 | + cursorrow = displayrow; |
| 1916 | + notecursor = 0; |
| 1917 | + DRL("<cursor>"); |
| 1918 | + } |
| 1919 | + |
| 1920 | + displaycol += width; |
| 1921 | + |
| 1922 | + if (ch < ' ') { |
| 1923 | + outputControlChar(current, ch + '@'); |
| 1924 | + } |
| 1925 | + else { |
| 1926 | + outputChars(current, pt, n); |
| 1927 | + } |
| 1928 | + DRL_CHAR(ch); |
| 1929 | + if (width != 1) { |
| 1930 | + DRL("<w=%d>", width); |
| 1931 | + } |
| 1932 | + |
| 1933 | + pt += n; |
| 1934 | + currentpos++; |
| 1935 | + } |
| 1936 | + |
| 1937 | + /* If we didn't see the cursor, it is at the current location */ |
| 1938 | + if (notecursor) { |
| 1939 | + DRL("<cursor>"); |
| 1940 | + cursorcol = displaycol; |
| 1941 | + cursorrow = displayrow; |
| 1942 | + } |
| 1943 | + |
| 1944 | + DRL("\nafter buf: displaycol=%d, displayrow=%d, cursorcol=%d, cursorrow=%d\n", displaycol, displayrow, cursorcol, cursorrow); |
| 1945 | + |
| 1946 | + /* (f) show hints */ |
| 1947 | + hint = refreshShowHints(current, buf, current->cols - displaycol, 1); |
| 1948 | + |
| 1949 | + /* Remember how many many cols are available for insert optimisation */ |
| 1950 | + if (prompt == current->prompt && hint == 0) { |
| 1951 | + current->colsright = current->cols - displaycol; |
| 1952 | + current->colsleft = displaycol; |
| 1953 | + } |
| 1954 | + else { |
| 1955 | + /* Can't optimise */ |
| 1956 | + current->colsright = 0; |
| 1957 | + current->colsleft = 0; |
| 1958 | + } |
| 1959 | + DRL("\nafter hints: colsleft=%d, colsright=%d\n\n", current->colsleft, current->colsright); |
| 1960 | + |
| 1961 | + refreshEndChars(current); |
| 1962 | + |
| 1963 | + /* (g) move the cursor to the correct place */ |
| 1964 | + cursorUp(current, displayrow - cursorrow); |
| 1965 | + setCursorPos(current, cursorcol); |
| 1966 | + |
| 1967 | + /* (h) Update the number of rows if larger, but never reduce this */ |
| 1968 | + if (displayrow >= current->nrows) { |
| 1969 | + current->nrows = displayrow + 1; |
| 1970 | + } |
| 1971 | + /* And remember the row that the cursor is on */ |
| 1972 | + current->rpos = cursorrow; |
| 1973 | + |
| 1974 | + refreshEnd(current); |
| 1975 | + |
| 1976 | +#ifdef DEBUG_REFRESHLINE |
| 1977 | + fclose(dfh); |
| 1978 | +#endif |
| 1979 | +} |
| 1980 | + |
| 1981 | +static void refreshLine(struct current *current) |
| 1982 | +{ |
| 1983 | + refreshLineAlt(current, current->prompt, sb_str(current->buf), current->pos); |
| 1984 | +} |
| 1985 | + |
| 1986 | +static void set_current(struct current *current, const char *str) |
| 1987 | +{ |
| 1988 | + sb_clear(current->buf); |
| 1989 | + sb_append(current->buf, str); |
| 1990 | + current->pos = sb_chars(current->buf); |
| 1991 | +} |
| 1992 | + |
| 1993 | +/** |
| 1994 | + * Removes the char at 'pos'. |
| 1995 | + * |
| 1996 | + * Returns 1 if the line needs to be refreshed, 2 if not |
| 1997 | + * and 0 if nothing was removed |
| 1998 | + */ |
| 1999 | +static int remove_char(struct current *current, int pos) |
| 2000 | +{ |
| 2001 | + if (pos >= 0 && pos < sb_chars(current->buf)) { |
| 2002 | + int offset = utf8_index(sb_str(current->buf), pos); |
| 2003 | + int nbytes = utf8_index(sb_str(current->buf) + offset, 1); |
| 2004 | + int rc = 1; |
| 2005 | + |
| 2006 | + /* Now we try to optimise in the simple but very common case that: |
| 2007 | + * - outputChars() can be used directly (not win32) |
| 2008 | + * - we are removing the char at EOL |
| 2009 | + * - the buffer is not empty |
| 2010 | + * - there are columns available to the left |
| 2011 | + * - the char being deleted is not a wide or utf-8 character |
| 2012 | + * - no hints are being shown |
| 2013 | + */ |
| 2014 | + if (current->output && current->pos == pos + 1 && current->pos == sb_chars(current->buf) && pos > 0) { |
| 2015 | +#ifdef USE_UTF8 |
| 2016 | + /* Could implement utf8_prev_len() but simplest just to not optimise this case */ |
| 2017 | + char last = sb_str(current->buf)[offset]; |
| 2018 | +#else |
| 2019 | + char last = 0; |
| 2020 | +#endif |
| 2021 | + if (current->colsleft > 0 && (last & 0x80) == 0) { |
| 2022 | + /* Have cols on the left and not a UTF-8 char or continuation */ |
| 2023 | + /* Yes, can optimise */ |
| 2024 | + current->colsleft--; |
| 2025 | + current->colsright++; |
| 2026 | + rc = 2; |
| 2027 | + } |
| 2028 | + } |
| 2029 | + |
| 2030 | + sb_delete(current->buf, offset, nbytes); |
| 2031 | + |
| 2032 | + if (current->pos > pos) { |
| 2033 | + current->pos--; |
| 2034 | + } |
| 2035 | + if (rc == 2) { |
| 2036 | + if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) { |
| 2037 | + /* A hint needs to be shown, so can't optimise after all */ |
| 2038 | + rc = 1; |
| 2039 | + } |
| 2040 | + else { |
| 2041 | + /* optimised output */ |
| 2042 | + outputChars(current, "\b \b", 3); |
| 2043 | + } |
| 2044 | + } |
| 2045 | + return rc; |
| 2046 | + return 1; |
| 2047 | + } |
| 2048 | + return 0; |
| 2049 | +} |
| 2050 | + |
| 2051 | +/** |
| 2052 | + * Insert 'ch' at position 'pos' |
| 2053 | + * |
| 2054 | + * Returns 1 if the line needs to be refreshed, 2 if not |
| 2055 | + * and 0 if nothing was inserted (no room) |
| 2056 | + */ |
| 2057 | +static int insert_char(struct current *current, int pos, int ch) |
| 2058 | +{ |
| 2059 | + if (pos >= 0 && pos <= sb_chars(current->buf)) { |
| 2060 | + char buf[MAX_UTF8_LEN + 1]; |
| 2061 | + int offset = utf8_index(sb_str(current->buf), pos); |
| 2062 | + int n = utf8_getchars(buf, ch); |
| 2063 | + int rc = 1; |
| 2064 | + |
| 2065 | + /* null terminate since sb_insert() requires it */ |
| 2066 | + buf[n] = 0; |
| 2067 | + |
| 2068 | + /* Now we try to optimise in the simple but very common case that: |
| 2069 | + * - outputChars() can be used directly (not win32) |
| 2070 | + * - we are inserting at EOL |
| 2071 | + * - there are enough columns available |
| 2072 | + * - no hints are being shown |
| 2073 | + */ |
| 2074 | + if (current->output && pos == current->pos && pos == sb_chars(current->buf)) { |
| 2075 | + int width = char_display_width(ch); |
| 2076 | + if (current->colsright > width) { |
| 2077 | + /* Yes, can optimise */ |
| 2078 | + current->colsright -= width; |
| 2079 | + current->colsleft -= width; |
| 2080 | + rc = 2; |
| 2081 | + } |
| 2082 | + } |
| 2083 | + sb_insert(current->buf, offset, buf); |
| 2084 | + if (current->pos >= pos) { |
| 2085 | + current->pos++; |
| 2086 | + } |
| 2087 | + if (rc == 2) { |
| 2088 | + if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) { |
| 2089 | + /* A hint needs to be shown, so can't optimise after all */ |
| 2090 | + rc = 1; |
| 2091 | + } |
| 2092 | + else { |
| 2093 | + /* optimised output */ |
| 2094 | + outputChars(current, buf, n); |
| 2095 | + } |
| 2096 | + } |
| 2097 | + return rc; |
| 686 | 2098 | } |
| 687 | 2099 | return 0; |
| 688 | 2100 | } |
| 689 | 2101 | |
| 690 | | -/* Move cursor on the left. */ |
| 691 | | -void linenoiseEditMoveLeft(struct linenoiseState *l) { |
| 692 | | - if (l->pos > 0) { |
| 693 | | - l->pos--; |
| 694 | | - refreshLine(l); |
| 695 | | - } |
| 696 | | -} |
| 697 | | - |
| 698 | | -/* Move cursor on the right. */ |
| 699 | | -void linenoiseEditMoveRight(struct linenoiseState *l) { |
| 700 | | - if (l->pos != l->len) { |
| 701 | | - l->pos++; |
| 702 | | - refreshLine(l); |
| 703 | | - } |
| 704 | | -} |
| 705 | | - |
| 706 | | -/* Move cursor to the start of the line. */ |
| 707 | | -void linenoiseEditMoveHome(struct linenoiseState *l) { |
| 708 | | - if (l->pos != 0) { |
| 709 | | - l->pos = 0; |
| 710 | | - refreshLine(l); |
| 711 | | - } |
| 712 | | -} |
| 713 | | - |
| 714 | | -/* Move cursor to the end of the line. */ |
| 715 | | -void linenoiseEditMoveEnd(struct linenoiseState *l) { |
| 716 | | - if (l->pos != l->len) { |
| 717 | | - l->pos = l->len; |
| 718 | | - refreshLine(l); |
| 719 | | - } |
| 720 | | -} |
| 721 | | - |
| 722 | | -/* Substitute the currently edited line with the next or previous history |
| 723 | | - * entry as specified by 'dir'. */ |
| 724 | | -#define LINENOISE_HISTORY_NEXT 0 |
| 725 | | -#define LINENOISE_HISTORY_PREV 1 |
| 726 | | -void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { |
| 727 | | - if (history_len > 1) { |
| 728 | | - /* Update the current history entry before to |
| 729 | | - * overwrite it with the next one. */ |
| 730 | | - free(history[history_len - 1 - l->history_index]); |
| 731 | | - history[history_len - 1 - l->history_index] = strdup(l->buf); |
| 732 | | - /* Show the new entry */ |
| 733 | | - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; |
| 734 | | - if (l->history_index < 0) { |
| 735 | | - l->history_index = 0; |
| 736 | | - return; |
| 737 | | - } else if (l->history_index >= history_len) { |
| 738 | | - l->history_index = history_len-1; |
| 739 | | - return; |
| 740 | | - } |
| 741 | | - strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); |
| 742 | | - l->buf[l->buflen-1] = '\0'; |
| 743 | | - l->len = l->pos = strlen(l->buf); |
| 744 | | - refreshLine(l); |
| 745 | | - } |
| 746 | | -} |
| 747 | | - |
| 748 | | -/* Delete the character at the right of the cursor without altering the cursor |
| 749 | | - * position. Basically this is what happens with the "Delete" keyboard key. */ |
| 750 | | -void linenoiseEditDelete(struct linenoiseState *l) { |
| 751 | | - if (l->len > 0 && l->pos < l->len) { |
| 752 | | - memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); |
| 753 | | - l->len--; |
| 754 | | - l->buf[l->len] = '\0'; |
| 755 | | - refreshLine(l); |
| 756 | | - } |
| 757 | | -} |
| 758 | | - |
| 759 | | -/* Backspace implementation. */ |
| 760 | | -void linenoiseEditBackspace(struct linenoiseState *l) { |
| 761 | | - if (l->pos > 0 && l->len > 0) { |
| 762 | | - memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); |
| 763 | | - l->pos--; |
| 764 | | - l->len--; |
| 765 | | - l->buf[l->len] = '\0'; |
| 766 | | - refreshLine(l); |
| 767 | | - } |
| 768 | | -} |
| 769 | | - |
| 770 | | -/* Delete the previosu word, maintaining the cursor at the start of the |
| 771 | | - * current word. */ |
| 772 | | -void linenoiseEditDeletePrevWord(struct linenoiseState *l) { |
| 773 | | - size_t old_pos = l->pos; |
| 774 | | - size_t diff; |
| 775 | | - |
| 776 | | - while (l->pos > 0 && l->buf[l->pos-1] == ' ') |
| 777 | | - l->pos--; |
| 778 | | - while (l->pos > 0 && l->buf[l->pos-1] != ' ') |
| 779 | | - l->pos--; |
| 780 | | - diff = old_pos - l->pos; |
| 781 | | - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); |
| 782 | | - l->len -= diff; |
| 783 | | - refreshLine(l); |
| 784 | | -} |
| 785 | | - |
| 786 | | -/* This function is the core of the line editing capability of linenoise. |
| 787 | | - * It expects 'fd' to be already in "raw mode" so that every key pressed |
| 788 | | - * will be returned ASAP to read(). |
| 789 | | - * |
| 790 | | - * The resulting string is put into 'buf' when the user type enter, or |
| 791 | | - * when ctrl+d is typed. |
| 792 | | - * |
| 793 | | - * The function returns the length of the current buffer. */ |
| 794 | | -static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) |
| 795 | | -{ |
| 796 | | - struct linenoiseState l; |
| 797 | | - |
| 798 | | - /* Populate the linenoise state that we pass to functions implementing |
| 799 | | - * specific editing functionalities. */ |
| 800 | | - l.ifd = stdin_fd; |
| 801 | | - l.ofd = stdout_fd; |
| 802 | | - l.buf = buf; |
| 803 | | - l.buflen = buflen; |
| 804 | | - l.prompt = prompt; |
| 805 | | - l.plen = strlen(prompt); |
| 806 | | - l.oldpos = l.pos = 0; |
| 807 | | - l.len = 0; |
| 808 | | - l.cols = getColumns(stdin_fd, stdout_fd); |
| 809 | | - l.maxrows = 0; |
| 810 | | - l.history_index = 0; |
| 811 | | - |
| 812 | | - /* Buffer starts empty. */ |
| 813 | | - l.buf[0] = '\0'; |
| 814 | | - l.buflen--; /* Make sure there is always space for the nulterm */ |
| 815 | | - |
| 816 | | - /* The latest history entry is always our current buffer, that |
| 817 | | - * initially is just an empty string. */ |
| 818 | | - linenoiseHistoryAdd(""); |
| 819 | | - |
| 820 | | - if (write(l.ofd,prompt,l.plen) == -1) return -1; |
| 821 | | - while(1) { |
| 822 | | - char c; |
| 823 | | - int nread; |
| 824 | | - char seq[3]; |
| 825 | | - |
| 826 | | - nread = read(l.ifd,&c,1); |
| 827 | | - if (nread <= 0) return l.len; |
| 828 | | - |
| 829 | | - /* Only autocomplete when the callback is set. It returns < 0 when |
| 830 | | - * there was an error reading from fd. Otherwise it will return the |
| 831 | | - * character that should be handled next. */ |
| 832 | | - if (c == 9 && completionCallback != NULL) { |
| 833 | | - c = completeLine(&l); |
| 834 | | - /* Return on errors */ |
| 835 | | - if (c < 0) return l.len; |
| 836 | | - /* Read next character when 0 */ |
| 837 | | - if (c == 0) continue; |
| 838 | | - } |
| 839 | | - |
| 840 | | - switch(c) { |
| 841 | | - case ENTER: /* enter */ |
| 842 | | - history_len--; |
| 843 | | - free(history[history_len]); |
| 844 | | - if (mlmode) linenoiseEditMoveEnd(&l); |
| 845 | | - if (hintsCallback) { |
| 846 | | - /* Force a refresh without hints to leave the previous |
| 847 | | - * line as the user typed it after a newline. */ |
| 848 | | - linenoiseHintsCallback *hc = hintsCallback; |
| 849 | | - hintsCallback = NULL; |
| 850 | | - refreshLine(&l); |
| 851 | | - hintsCallback = hc; |
| 852 | | - } |
| 853 | | - return (int)l.len; |
| 854 | | - case CTRL_C: /* ctrl-c */ |
| 855 | | - errno = EAGAIN; |
| 856 | | - return -1; |
| 857 | | - case BACKSPACE: /* backspace */ |
| 858 | | - case 8: /* ctrl-h */ |
| 859 | | - linenoiseEditBackspace(&l); |
| 860 | | - break; |
| 861 | | - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the |
| 862 | | - line is empty, act as end-of-file. */ |
| 863 | | - if (l.len > 0) { |
| 864 | | - linenoiseEditDelete(&l); |
| 865 | | - } else { |
| 866 | | - history_len--; |
| 867 | | - free(history[history_len]); |
| 868 | | - return -1; |
| 869 | | - } |
| 870 | | - break; |
| 871 | | - case CTRL_T: /* ctrl-t, swaps current character with previous. */ |
| 872 | | - if (l.pos > 0 && l.pos < l.len) { |
| 873 | | - int aux = buf[l.pos-1]; |
| 874 | | - buf[l.pos-1] = buf[l.pos]; |
| 875 | | - buf[l.pos] = aux; |
| 876 | | - if (l.pos != l.len-1) l.pos++; |
| 877 | | - refreshLine(&l); |
| 878 | | - } |
| 879 | | - break; |
| 880 | | - case CTRL_B: /* ctrl-b */ |
| 881 | | - linenoiseEditMoveLeft(&l); |
| 882 | | - break; |
| 883 | | - case CTRL_F: /* ctrl-f */ |
| 884 | | - linenoiseEditMoveRight(&l); |
| 885 | | - break; |
| 886 | | - case CTRL_P: /* ctrl-p */ |
| 887 | | - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); |
| 888 | | - break; |
| 889 | | - case CTRL_N: /* ctrl-n */ |
| 890 | | - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); |
| 891 | | - break; |
| 892 | | - case ESC: /* escape sequence */ |
| 893 | | - /* Read the next two bytes representing the escape sequence. |
| 894 | | - * Use two calls to handle slow terminals returning the two |
| 895 | | - * chars at different times. */ |
| 896 | | - if (read(l.ifd,seq,1) == -1) break; |
| 897 | | - if (read(l.ifd,seq+1,1) == -1) break; |
| 898 | | - |
| 899 | | - /* ESC [ sequences. */ |
| 900 | | - if (seq[0] == '[') { |
| 901 | | - if (seq[1] >= '0' && seq[1] <= '9') { |
| 902 | | - /* Extended escape, read additional byte. */ |
| 903 | | - if (read(l.ifd,seq+2,1) == -1) break; |
| 904 | | - if (seq[2] == '~') { |
| 905 | | - switch(seq[1]) { |
| 906 | | - case '3': /* Delete key. */ |
| 907 | | - linenoiseEditDelete(&l); |
| 908 | | - break; |
| 909 | | - } |
| 910 | | - } |
| 911 | | - } else { |
| 912 | | - switch(seq[1]) { |
| 913 | | - case 'A': /* Up */ |
| 914 | | - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); |
| 915 | | - break; |
| 916 | | - case 'B': /* Down */ |
| 917 | | - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); |
| 918 | | - break; |
| 919 | | - case 'C': /* Right */ |
| 920 | | - linenoiseEditMoveRight(&l); |
| 921 | | - break; |
| 922 | | - case 'D': /* Left */ |
| 923 | | - linenoiseEditMoveLeft(&l); |
| 924 | | - break; |
| 925 | | - case 'H': /* Home */ |
| 926 | | - linenoiseEditMoveHome(&l); |
| 927 | | - break; |
| 928 | | - case 'F': /* End*/ |
| 929 | | - linenoiseEditMoveEnd(&l); |
| 930 | | - break; |
| 931 | | - } |
| 932 | | - } |
| 933 | | - } |
| 934 | | - |
| 935 | | - /* ESC O sequences. */ |
| 936 | | - else if (seq[0] == 'O') { |
| 937 | | - switch(seq[1]) { |
| 938 | | - case 'H': /* Home */ |
| 939 | | - linenoiseEditMoveHome(&l); |
| 940 | | - break; |
| 941 | | - case 'F': /* End*/ |
| 942 | | - linenoiseEditMoveEnd(&l); |
| 943 | | - break; |
| 944 | | - } |
| 945 | | - } |
| 946 | | - break; |
| 947 | | - default: |
| 948 | | - if (linenoiseEditInsert(&l,c)) return -1; |
| 949 | | - break; |
| 950 | | - case CTRL_U: /* Ctrl+u, delete the whole line. */ |
| 951 | | - buf[0] = '\0'; |
| 952 | | - l.pos = l.len = 0; |
| 953 | | - refreshLine(&l); |
| 954 | | - break; |
| 955 | | - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ |
| 956 | | - buf[l.pos] = '\0'; |
| 957 | | - l.len = l.pos; |
| 958 | | - refreshLine(&l); |
| 959 | | - break; |
| 960 | | - case CTRL_A: /* Ctrl+a, go to the start of the line */ |
| 961 | | - linenoiseEditMoveHome(&l); |
| 962 | | - break; |
| 963 | | - case CTRL_E: /* ctrl+e, go to the end of the line */ |
| 964 | | - linenoiseEditMoveEnd(&l); |
| 965 | | - break; |
| 966 | | - case CTRL_L: /* ctrl+l, clear screen */ |
| 967 | | - linenoiseClearScreen(); |
| 968 | | - refreshLine(&l); |
| 969 | | - break; |
| 970 | | - case CTRL_W: /* ctrl+w, delete previous word */ |
| 971 | | - linenoiseEditDeletePrevWord(&l); |
| 972 | | - break; |
| 973 | | - } |
| 974 | | - } |
| 975 | | - return l.len; |
| 976 | | -} |
| 977 | | - |
| 978 | | -/* This special mode is used by linenoise in order to print scan codes |
| 979 | | - * on screen for debugging / development purposes. It is implemented |
| 980 | | - * by the linenoise_example program using the --keycodes option. */ |
| 981 | | -void linenoisePrintKeyCodes(void) { |
| 982 | | - char quit[4]; |
| 983 | | - |
| 984 | | - printf("Linenoise key codes debugging mode.\n" |
| 985 | | - "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); |
| 986 | | - if (enableRawMode(STDIN_FILENO) == -1) return; |
| 987 | | - memset(quit,' ',4); |
| 988 | | - while(1) { |
| 989 | | - char c; |
| 990 | | - int nread; |
| 991 | | - |
| 992 | | - nread = read(STDIN_FILENO,&c,1); |
| 993 | | - if (nread <= 0) continue; |
| 994 | | - memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ |
| 995 | | - quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ |
| 996 | | - if (memcmp(quit,"quit",sizeof(quit)) == 0) break; |
| 997 | | - |
| 998 | | - printf("'%c' %02x (%d) (type quit to exit)\n", |
| 999 | | - isprint(c) ? c : '?', (int)c, (int)c); |
| 1000 | | - printf("\r"); /* Go left edge manually, we are in raw mode. */ |
| 1001 | | - fflush(stdout); |
| 1002 | | - } |
| 1003 | | - disableRawMode(STDIN_FILENO); |
| 1004 | | -} |
| 1005 | | - |
| 1006 | | -/* This function calls the line editing function linenoiseEdit() using |
| 1007 | | - * the STDIN file descriptor set in raw mode. */ |
| 1008 | | -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { |
| 1009 | | - int count; |
| 1010 | | - |
| 1011 | | - if (buflen == 0) { |
| 1012 | | - errno = EINVAL; |
| 1013 | | - return -1; |
| 1014 | | - } |
| 1015 | | - |
| 1016 | | - if (enableRawMode(STDIN_FILENO) == -1) return -1; |
| 1017 | | - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); |
| 1018 | | - disableRawMode(STDIN_FILENO); |
| 1019 | | - printf("\n"); |
| 1020 | | - return count; |
| 1021 | | -} |
| 1022 | | - |
| 1023 | | -/* This function is called when linenoise() is called with the standard |
| 1024 | | - * input file descriptor not attached to a TTY. So for example when the |
| 1025 | | - * program using linenoise is called in pipe or with a file redirected |
| 1026 | | - * to its standard input. In this case, we want to be able to return the |
| 1027 | | - * line regardless of its length (by default we are limited to 4k). */ |
| 1028 | | -static char *linenoiseNoTTY(void) { |
| 1029 | | - char *line = NULL; |
| 1030 | | - size_t len = 0, maxlen = 0; |
| 1031 | | - |
| 1032 | | - while(1) { |
| 1033 | | - int c; |
| 1034 | | - if (len == maxlen) { |
| 1035 | | - char *oldval = line; |
| 1036 | | - if (maxlen == 0) maxlen = 16; |
| 1037 | | - maxlen *= 2; |
| 1038 | | - line = realloc(line,maxlen); |
| 1039 | | - if (line == NULL) { |
| 1040 | | - if (oldval) free(oldval); |
| 1041 | | - return NULL; |
| 1042 | | - } |
| 1043 | | - } |
| 1044 | | - c = fgetc(stdin); |
| 1045 | | - if (c == EOF || c == '\n') { |
| 1046 | | - if (c == EOF && len == 0) { |
| 1047 | | - free(line); |
| 1048 | | - return NULL; |
| 1049 | | - } else { |
| 1050 | | - line[len] = '\0'; |
| 1051 | | - return line; |
| 1052 | | - } |
| 1053 | | - } else { |
| 1054 | | - line[len] = c; |
| 1055 | | - len++; |
| 1056 | | - } |
| 1057 | | - } |
| 1058 | | -} |
| 1059 | | - |
| 1060 | | -/* The high level function that is the main API of the linenoise library. |
| 1061 | | - * This function checks if the terminal has basic capabilities, just checking |
| 1062 | | - * for a blacklist of stupid terminals, and later either calls the line |
| 1063 | | - * editing function or uses dummy fgets() so that you will be able to type |
| 1064 | | - * something even in the most desperate of the conditions. */ |
| 1065 | | -char *linenoise(const char *prompt) { |
| 1066 | | - char buf[LINENOISE_MAX_LINE]; |
| 1067 | | - int count; |
| 1068 | | - |
| 1069 | | - if (!isatty(STDIN_FILENO)) { |
| 1070 | | - /* Not a tty: read from file / pipe. In this mode we don't want any |
| 1071 | | - * limit to the line size, so we call a function to handle that. */ |
| 1072 | | - return linenoiseNoTTY(); |
| 1073 | | - } else if (isUnsupportedTerm()) { |
| 1074 | | - size_t len; |
| 1075 | | - |
| 1076 | | - printf("%s",prompt); |
| 1077 | | - fflush(stdout); |
| 1078 | | - if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; |
| 1079 | | - len = strlen(buf); |
| 1080 | | - while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { |
| 1081 | | - len--; |
| 1082 | | - buf[len] = '\0'; |
| 1083 | | - } |
| 1084 | | - return strdup(buf); |
| 1085 | | - } else { |
| 1086 | | - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); |
| 1087 | | - if (count == -1) return NULL; |
| 1088 | | - return strdup(buf); |
| 1089 | | - } |
| 1090 | | -} |
| 1091 | | - |
| 1092 | | -/* This is just a wrapper the user may want to call in order to make sure |
| 1093 | | - * the linenoise returned buffer is freed with the same allocator it was |
| 1094 | | - * created with. Useful when the main program is using an alternative |
| 1095 | | - * allocator. */ |
| 1096 | | -void linenoiseFree(void *ptr) { |
| 1097 | | - free(ptr); |
| 1098 | | -} |
| 1099 | | - |
| 1100 | | -/* ================================ History ================================= */ |
| 1101 | | - |
| 1102 | | -/* Free the history, but does not reset it. Only used when we have to |
| 1103 | | - * exit() to avoid memory leaks are reported by valgrind & co. */ |
| 1104 | | -static void freeHistory(void) { |
| 1105 | | - if (history) { |
| 1106 | | - int j; |
| 1107 | | - |
| 1108 | | - for (j = 0; j < history_len; j++) |
| 1109 | | - free(history[j]); |
| 1110 | | - free(history); |
| 1111 | | - } |
| 1112 | | -} |
| 1113 | | - |
| 1114 | | -/* At exit we'll try to fix the terminal to the initial conditions. */ |
| 1115 | | -static void linenoiseAtExit(void) { |
| 1116 | | - disableRawMode(STDIN_FILENO); |
| 1117 | | - freeHistory(); |
| 1118 | | -} |
| 1119 | | - |
| 1120 | | -/* This is the API call to add a new entry in the linenoise history. |
| 1121 | | - * It uses a fixed array of char pointers that are shifted (memmoved) |
| 1122 | | - * when the history max length is reached in order to remove the older |
| 1123 | | - * entry and make room for the new one, so it is not exactly suitable for huge |
| 1124 | | - * histories, but will work well for a few hundred of entries. |
| 1125 | | - * |
| 1126 | | - * Using a circular buffer is smarter, but a bit more complex to handle. */ |
| 1127 | | -int linenoiseHistoryAdd(const char *line) { |
| 1128 | | - char *linecopy; |
| 1129 | | - |
| 1130 | | - if (history_max_len == 0) return 0; |
| 1131 | | - |
| 1132 | | - /* Initialization on first call. */ |
| 1133 | | - if (history == NULL) { |
| 1134 | | - history = malloc(sizeof(char*)*history_max_len); |
| 1135 | | - if (history == NULL) return 0; |
| 1136 | | - memset(history,0,(sizeof(char*)*history_max_len)); |
| 1137 | | - } |
| 1138 | | - |
| 1139 | | - /* Don't add duplicated lines. */ |
| 1140 | | - if (history_len && !strcmp(history[history_len-1], line)) return 0; |
| 1141 | | - |
| 1142 | | - /* Add an heap allocated copy of the line in the history. |
| 1143 | | - * If we reached the max length, remove the older line. */ |
| 1144 | | - linecopy = strdup(line); |
| 1145 | | - if (!linecopy) return 0; |
| 2102 | +/** |
| 2103 | + * Captures up to 'n' characters starting at 'pos' for the cut buffer. |
| 2104 | + * |
| 2105 | + * This replaces any existing characters in the cut buffer. |
| 2106 | + */ |
| 2107 | +static void capture_chars(struct current *current, int pos, int nchars) |
| 2108 | +{ |
| 2109 | + if (pos >= 0 && (pos + nchars - 1) < sb_chars(current->buf)) { |
| 2110 | + int offset = utf8_index(sb_str(current->buf), pos); |
| 2111 | + int nbytes = utf8_index(sb_str(current->buf) + offset, nchars); |
| 2112 | + |
| 2113 | + if (nbytes > 0) { |
| 2114 | + if (current->capture) { |
| 2115 | + sb_clear(current->capture); |
| 2116 | + } |
| 2117 | + else { |
| 2118 | + current->capture = sb_alloc(); |
| 2119 | + } |
| 2120 | + sb_append_len(current->capture, sb_str(current->buf) + offset, nbytes); |
| 2121 | + } |
| 2122 | + } |
| 2123 | +} |
| 2124 | + |
| 2125 | +/** |
| 2126 | + * Removes up to 'n' characters at cursor position 'pos'. |
| 2127 | + * |
| 2128 | + * Returns 0 if no chars were removed or non-zero otherwise. |
| 2129 | + */ |
| 2130 | +static int remove_chars(struct current *current, int pos, int n) |
| 2131 | +{ |
| 2132 | + int removed = 0; |
| 2133 | + |
| 2134 | + /* First save any chars which will be removed */ |
| 2135 | + capture_chars(current, pos, n); |
| 2136 | + |
| 2137 | + while (n-- && remove_char(current, pos)) { |
| 2138 | + removed++; |
| 2139 | + } |
| 2140 | + return removed; |
| 2141 | +} |
| 2142 | +/** |
| 2143 | + * Inserts the characters (string) 'chars' at the cursor position 'pos'. |
| 2144 | + * |
| 2145 | + * Returns 0 if no chars were inserted or non-zero otherwise. |
| 2146 | + */ |
| 2147 | +static int insert_chars(struct current *current, int pos, const char *chars) |
| 2148 | +{ |
| 2149 | + int inserted = 0; |
| 2150 | + |
| 2151 | + while (*chars) { |
| 2152 | + int ch; |
| 2153 | + int n = utf8_tounicode(chars, &ch); |
| 2154 | + if (insert_char(current, pos, ch) == 0) { |
| 2155 | + break; |
| 2156 | + } |
| 2157 | + inserted++; |
| 2158 | + pos++; |
| 2159 | + chars += n; |
| 2160 | + } |
| 2161 | + return inserted; |
| 2162 | +} |
| 2163 | + |
| 2164 | +static int skip_space_nonspace(struct current *current, int dir, int check_is_space) |
| 2165 | +{ |
| 2166 | + int moved = 0; |
| 2167 | + int checkoffset = (dir < 0) ? -1 : 0; |
| 2168 | + int limit = (dir < 0) ? 0 : sb_chars(current->buf); |
| 2169 | + while (current->pos != limit && (get_char(current, current->pos + checkoffset) == ' ') == check_is_space) { |
| 2170 | + current->pos += dir; |
| 2171 | + moved++; |
| 2172 | + } |
| 2173 | + return moved; |
| 2174 | +} |
| 2175 | + |
| 2176 | +static int skip_space(struct current *current, int dir) |
| 2177 | +{ |
| 2178 | + return skip_space_nonspace(current, dir, 1); |
| 2179 | +} |
| 2180 | + |
| 2181 | +static int skip_nonspace(struct current *current, int dir) |
| 2182 | +{ |
| 2183 | + return skip_space_nonspace(current, dir, 0); |
| 2184 | +} |
| 2185 | + |
| 2186 | +static void set_history_index(struct current *current, int new_index) |
| 2187 | +{ |
| 2188 | + if (history_len > 1) { |
| 2189 | + /* Update the current history entry before to |
| 2190 | + * overwrite it with the next one. */ |
| 2191 | + free(history[history_len - 1 - history_index]); |
| 2192 | + history[history_len - 1 - history_index] = strdup(sb_str(current->buf)); |
| 2193 | + /* Show the new entry */ |
| 2194 | + history_index = new_index; |
| 2195 | + if (history_index < 0) { |
| 2196 | + history_index = 0; |
| 2197 | + } else if (history_index >= history_len) { |
| 2198 | + history_index = history_len - 1; |
| 2199 | + } else { |
| 2200 | + set_current(current, history[history_len - 1 - history_index]); |
| 2201 | + refreshLine(current); |
| 2202 | + } |
| 2203 | + } |
| 2204 | +} |
| 2205 | + |
| 2206 | +/** |
| 2207 | + * Returns the keycode to process, or 0 if none. |
| 2208 | + */ |
| 2209 | +static int reverseIncrementalSearch(struct current *current) |
| 2210 | +{ |
| 2211 | + /* Display the reverse-i-search prompt and process chars */ |
| 2212 | + char rbuf[50]; |
| 2213 | + char rprompt[80]; |
| 2214 | + int rchars = 0; |
| 2215 | + int rlen = 0; |
| 2216 | + int searchpos = history_len - 1; |
| 2217 | + int c; |
| 2218 | + |
| 2219 | + rbuf[0] = 0; |
| 2220 | + while (1) { |
| 2221 | + int n = 0; |
| 2222 | + const char *p = NULL; |
| 2223 | + int skipsame = 0; |
| 2224 | + int searchdir = -1; |
| 2225 | + |
| 2226 | + snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf); |
| 2227 | + refreshLineAlt(current, rprompt, sb_str(current->buf), current->pos); |
| 2228 | + c = fd_read(current); |
| 2229 | + if (c == ctrl('H') || c == CHAR_DELETE) { |
| 2230 | + if (rchars) { |
| 2231 | + int p_ind = utf8_index(rbuf, --rchars); |
| 2232 | + rbuf[p_ind] = 0; |
| 2233 | + rlen = strlen(rbuf); |
| 2234 | + } |
| 2235 | + continue; |
| 2236 | + } |
| 2237 | +#ifdef USE_TERMIOS |
| 2238 | + if (c == CHAR_ESCAPE) { |
| 2239 | + c = check_special(current->fd); |
| 2240 | + } |
| 2241 | +#endif |
| 2242 | + if (c == ctrl('R')) { |
| 2243 | + /* Search for the previous (earlier) match */ |
| 2244 | + if (searchpos > 0) { |
| 2245 | + searchpos--; |
| 2246 | + } |
| 2247 | + skipsame = 1; |
| 2248 | + } |
| 2249 | + else if (c == ctrl('S')) { |
| 2250 | + /* Search for the next (later) match */ |
| 2251 | + if (searchpos < history_len) { |
| 2252 | + searchpos++; |
| 2253 | + } |
| 2254 | + searchdir = 1; |
| 2255 | + skipsame = 1; |
| 2256 | + } |
| 2257 | + else if (c == ctrl('P') || c == SPECIAL_UP) { |
| 2258 | + /* Exit Ctrl-R mode and go to the previous history line from the current search pos */ |
| 2259 | + set_history_index(current, history_len - searchpos); |
| 2260 | + c = 0; |
| 2261 | + break; |
| 2262 | + } |
| 2263 | + else if (c == ctrl('N') || c == SPECIAL_DOWN) { |
| 2264 | + /* Exit Ctrl-R mode and go to the next history line from the current search pos */ |
| 2265 | + set_history_index(current, history_len - searchpos - 2); |
| 2266 | + c = 0; |
| 2267 | + break; |
| 2268 | + } |
| 2269 | + else if (c >= ' ' && c <= '~') { |
| 2270 | + /* >= here to allow for null terminator */ |
| 2271 | + if (rlen >= (int)sizeof(rbuf) - MAX_UTF8_LEN) { |
| 2272 | + continue; |
| 2273 | + } |
| 2274 | + |
| 2275 | + n = utf8_getchars(rbuf + rlen, c); |
| 2276 | + rlen += n; |
| 2277 | + rchars++; |
| 2278 | + rbuf[rlen] = 0; |
| 2279 | + |
| 2280 | + /* Adding a new char resets the search location */ |
| 2281 | + searchpos = history_len - 1; |
| 2282 | + } |
| 2283 | + else { |
| 2284 | + /* Exit from incremental search mode */ |
| 2285 | + break; |
| 2286 | + } |
| 2287 | + |
| 2288 | + /* Now search through the history for a match */ |
| 2289 | + for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) { |
| 2290 | + p = strstr(history[searchpos], rbuf); |
| 2291 | + if (p) { |
| 2292 | + /* Found a match */ |
| 2293 | + if (skipsame && strcmp(history[searchpos], sb_str(current->buf)) == 0) { |
| 2294 | + /* But it is identical, so skip it */ |
| 2295 | + continue; |
| 2296 | + } |
| 2297 | + /* Copy the matching line and set the cursor position */ |
| 2298 | + history_index = history_len - 1 - searchpos; |
| 2299 | + set_current(current,history[searchpos]); |
| 2300 | + current->pos = utf8_strlen(history[searchpos], p - history[searchpos]); |
| 2301 | + break; |
| 2302 | + } |
| 2303 | + } |
| 2304 | + if (!p && n) { |
| 2305 | + /* No match, so don't add it */ |
| 2306 | + rchars--; |
| 2307 | + rlen -= n; |
| 2308 | + rbuf[rlen] = 0; |
| 2309 | + } |
| 2310 | + } |
| 2311 | + if (c == ctrl('G') || c == ctrl('C')) { |
| 2312 | + /* ctrl-g terminates the search with no effect */ |
| 2313 | + set_current(current, ""); |
| 2314 | + history_index = 0; |
| 2315 | + c = 0; |
| 2316 | + } |
| 2317 | + else if (c == ctrl('J')) { |
| 2318 | + /* ctrl-j terminates the search leaving the buffer in place */ |
| 2319 | + history_index = 0; |
| 2320 | + c = 0; |
| 2321 | + } |
| 2322 | + /* Go process the char normally */ |
| 2323 | + refreshLine(current); |
| 2324 | + return c; |
| 2325 | +} |
| 2326 | + |
| 2327 | +static int linenoiseEdit(struct current *current) { |
| 2328 | + history_index = 0; |
| 2329 | + |
| 2330 | + refreshLine(current); |
| 2331 | + |
| 2332 | + while(1) { |
| 2333 | + int c = fd_read(current); |
| 2334 | + |
| 2335 | +#ifndef NO_COMPLETION |
| 2336 | + /* Only autocomplete when the callback is set. It returns < 0 when |
| 2337 | + * there was an error reading from fd. Otherwise it will return the |
| 2338 | + * character that should be handled next. */ |
| 2339 | + if (c == '\t' && current->pos == sb_chars(current->buf) && completionCallback != NULL) { |
| 2340 | + c = completeLine(current); |
| 2341 | + } |
| 2342 | +#endif |
| 2343 | + if (c == ctrl('R')) { |
| 2344 | + /* reverse incremental search will provide an alternative keycode or 0 for none */ |
| 2345 | + c = reverseIncrementalSearch(current); |
| 2346 | + /* go on to process the returned char normally */ |
| 2347 | + } |
| 2348 | + |
| 2349 | +#ifdef USE_TERMIOS |
| 2350 | + if (c == CHAR_ESCAPE) { /* escape sequence */ |
| 2351 | + c = check_special(current->fd); |
| 2352 | + } |
| 2353 | +#endif |
| 2354 | + if (c == -1) { |
| 2355 | + /* Return on errors */ |
| 2356 | + return sb_len(current->buf); |
| 2357 | + } |
| 2358 | + |
| 2359 | + switch(c) { |
| 2360 | + case SPECIAL_NONE: |
| 2361 | + break; |
| 2362 | + case '\r': /* enter/CR */ |
| 2363 | + case '\n': /* LF */ |
| 2364 | + history_len--; |
| 2365 | + free(history[history_len]); |
| 2366 | + current->pos = sb_chars(current->buf); |
| 2367 | + if (mlmode || hintsCallback) { |
| 2368 | + showhints = 0; |
| 2369 | + refreshLine(current); |
| 2370 | + showhints = 1; |
| 2371 | + } |
| 2372 | + return sb_len(current->buf); |
| 2373 | + case ctrl('C'): /* ctrl-c */ |
| 2374 | + errno = EAGAIN; |
| 2375 | + return -1; |
| 2376 | + case ctrl('Z'): /* ctrl-z */ |
| 2377 | +#ifdef SIGTSTP |
| 2378 | + /* send ourselves SIGSUSP */ |
| 2379 | + disableRawMode(current); |
| 2380 | + raise(SIGTSTP); |
| 2381 | + /* and resume */ |
| 2382 | + enableRawMode(current); |
| 2383 | + refreshLine(current); |
| 2384 | +#endif |
| 2385 | + continue; |
| 2386 | + case CHAR_DELETE: /* backspace */ |
| 2387 | + case ctrl('H'): |
| 2388 | + if (remove_char(current, current->pos - 1) == 1) { |
| 2389 | + refreshLine(current); |
| 2390 | + } |
| 2391 | + break; |
| 2392 | + case ctrl('D'): /* ctrl-d */ |
| 2393 | + if (sb_len(current->buf) == 0) { |
| 2394 | + /* Empty line, so EOF */ |
| 2395 | + history_len--; |
| 2396 | + free(history[history_len]); |
| 2397 | + return -1; |
| 2398 | + } |
| 2399 | + /* Otherwise fall through to delete char to right of cursor */ |
| 2400 | + /* fall-thru */ |
| 2401 | + case SPECIAL_DELETE: |
| 2402 | + if (remove_char(current, current->pos) == 1) { |
| 2403 | + refreshLine(current); |
| 2404 | + } |
| 2405 | + break; |
| 2406 | + case SPECIAL_INSERT: |
| 2407 | + /* Ignore. Expansion Hook. |
| 2408 | + * Future possibility: Toggle Insert/Overwrite Modes |
| 2409 | + */ |
| 2410 | + break; |
| 2411 | + case meta('b'): /* meta-b, move word left */ |
| 2412 | + if (skip_nonspace(current, -1)) { |
| 2413 | + refreshLine(current); |
| 2414 | + } |
| 2415 | + else if (skip_space(current, -1)) { |
| 2416 | + skip_nonspace(current, -1); |
| 2417 | + refreshLine(current); |
| 2418 | + } |
| 2419 | + break; |
| 2420 | + case meta('f'): /* meta-f, move word right */ |
| 2421 | + if (skip_space(current, 1)) { |
| 2422 | + refreshLine(current); |
| 2423 | + } |
| 2424 | + else if (skip_nonspace(current, 1)) { |
| 2425 | + skip_space(current, 1); |
| 2426 | + refreshLine(current); |
| 2427 | + } |
| 2428 | + break; |
| 2429 | + case ctrl('W'): /* ctrl-w, delete word at left. save deleted chars */ |
| 2430 | + /* eat any spaces on the left */ |
| 2431 | + { |
| 2432 | + int pos = current->pos; |
| 2433 | + while (pos > 0 && get_char(current, pos - 1) == ' ') { |
| 2434 | + pos--; |
| 2435 | + } |
| 2436 | + |
| 2437 | + /* now eat any non-spaces on the left */ |
| 2438 | + while (pos > 0 && get_char(current, pos - 1) != ' ') { |
| 2439 | + pos--; |
| 2440 | + } |
| 2441 | + |
| 2442 | + if (remove_chars(current, pos, current->pos - pos)) { |
| 2443 | + refreshLine(current); |
| 2444 | + } |
| 2445 | + } |
| 2446 | + break; |
| 2447 | + case ctrl('T'): /* ctrl-t */ |
| 2448 | + if (current->pos > 0 && current->pos <= sb_chars(current->buf)) { |
| 2449 | + /* If cursor is at end, transpose the previous two chars */ |
| 2450 | + int fixer = (current->pos == sb_chars(current->buf)); |
| 2451 | + c = get_char(current, current->pos - fixer); |
| 2452 | + remove_char(current, current->pos - fixer); |
| 2453 | + insert_char(current, current->pos - 1, c); |
| 2454 | + refreshLine(current); |
| 2455 | + } |
| 2456 | + break; |
| 2457 | + case ctrl('V'): /* ctrl-v */ |
| 2458 | + /* Insert the ^V first */ |
| 2459 | + if (insert_char(current, current->pos, c)) { |
| 2460 | + refreshLine(current); |
| 2461 | + /* Now wait for the next char. Can insert anything except \0 */ |
| 2462 | + c = fd_read(current); |
| 2463 | + |
| 2464 | + /* Remove the ^V first */ |
| 2465 | + remove_char(current, current->pos - 1); |
| 2466 | + if (c > 0) { |
| 2467 | + /* Insert the actual char, can't be error or null */ |
| 2468 | + insert_char(current, current->pos, c); |
| 2469 | + } |
| 2470 | + refreshLine(current); |
| 2471 | + } |
| 2472 | + break; |
| 2473 | + case ctrl('B'): |
| 2474 | + case SPECIAL_LEFT: |
| 2475 | + if (current->pos > 0) { |
| 2476 | + current->pos--; |
| 2477 | + refreshLine(current); |
| 2478 | + } |
| 2479 | + break; |
| 2480 | + case ctrl('F'): |
| 2481 | + case SPECIAL_RIGHT: |
| 2482 | + if (current->pos < sb_chars(current->buf)) { |
| 2483 | + current->pos++; |
| 2484 | + refreshLine(current); |
| 2485 | + } |
| 2486 | + break; |
| 2487 | + case SPECIAL_PAGE_UP: /* move to start of history */ |
| 2488 | + set_history_index(current, history_len - 1); |
| 2489 | + break; |
| 2490 | + case SPECIAL_PAGE_DOWN: /* move to 0 == end of history, i.e. current */ |
| 2491 | + set_history_index(current, 0); |
| 2492 | + break; |
| 2493 | + case ctrl('P'): |
| 2494 | + case SPECIAL_UP: |
| 2495 | + set_history_index(current, history_index + 1); |
| 2496 | + break; |
| 2497 | + case ctrl('N'): |
| 2498 | + case SPECIAL_DOWN: |
| 2499 | + set_history_index(current, history_index - 1); |
| 2500 | + break; |
| 2501 | + case ctrl('A'): /* Ctrl+a, go to the start of the line */ |
| 2502 | + case SPECIAL_HOME: |
| 2503 | + current->pos = 0; |
| 2504 | + refreshLine(current); |
| 2505 | + break; |
| 2506 | + case ctrl('E'): /* ctrl+e, go to the end of the line */ |
| 2507 | + case SPECIAL_END: |
| 2508 | + current->pos = sb_chars(current->buf); |
| 2509 | + refreshLine(current); |
| 2510 | + break; |
| 2511 | + case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */ |
| 2512 | + if (remove_chars(current, 0, current->pos)) { |
| 2513 | + refreshLine(current); |
| 2514 | + } |
| 2515 | + break; |
| 2516 | + case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */ |
| 2517 | + if (remove_chars(current, current->pos, sb_chars(current->buf) - current->pos)) { |
| 2518 | + refreshLine(current); |
| 2519 | + } |
| 2520 | + break; |
| 2521 | + case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */ |
| 2522 | + if (current->capture && insert_chars(current, current->pos, sb_str(current->capture))) { |
| 2523 | + refreshLine(current); |
| 2524 | + } |
| 2525 | + break; |
| 2526 | + case ctrl('L'): /* Ctrl+L, clear screen */ |
| 2527 | + linenoiseClearScreen(); |
| 2528 | + /* Force recalc of window size for serial terminals */ |
| 2529 | + current->cols = 0; |
| 2530 | + current->rpos = 0; |
| 2531 | + refreshLine(current); |
| 2532 | + break; |
| 2533 | + default: |
| 2534 | + if (c >= meta('a') && c <= meta('z')) { |
| 2535 | + /* Don't insert meta chars that are not bound */ |
| 2536 | + break; |
| 2537 | + } |
| 2538 | + /* Only tab is allowed without ^V */ |
| 2539 | + if (c == '\t' || c >= ' ') { |
| 2540 | + if (insert_char(current, current->pos, c) == 1) { |
| 2541 | + refreshLine(current); |
| 2542 | + } |
| 2543 | + } |
| 2544 | + break; |
| 2545 | + } |
| 2546 | + } |
| 2547 | + return sb_len(current->buf); |
| 2548 | +} |
| 2549 | + |
| 2550 | +int linenoiseColumns(void) |
| 2551 | +{ |
| 2552 | + struct current current; |
| 2553 | + current.output = NULL; |
| 2554 | + enableRawMode (¤t); |
| 2555 | + getWindowSize (¤t); |
| 2556 | + disableRawMode (¤t); |
| 2557 | + return current.cols; |
| 2558 | +} |
| 2559 | + |
| 2560 | +/** |
| 2561 | + * Reads a line from the file handle (without the trailing NL or CRNL) |
| 2562 | + * and returns it in a stringbuf. |
| 2563 | + * Returns NULL if no characters are read before EOF or error. |
| 2564 | + * |
| 2565 | + * Note that the character count will *not* be correct for lines containing |
| 2566 | + * utf8 sequences. Do not rely on the character count. |
| 2567 | + */ |
| 2568 | +static stringbuf *sb_getline(FILE *fh) |
| 2569 | +{ |
| 2570 | + stringbuf *sb = sb_alloc(); |
| 2571 | + int c; |
| 2572 | + int n = 0; |
| 2573 | + |
| 2574 | + while ((c = getc(fh)) != EOF) { |
| 2575 | + char ch; |
| 2576 | + n++; |
| 2577 | + if (c == '\r') { |
| 2578 | + /* CRLF -> LF */ |
| 2579 | + continue; |
| 2580 | + } |
| 2581 | + if (c == '\n' || c == '\r') { |
| 2582 | + break; |
| 2583 | + } |
| 2584 | + ch = c; |
| 2585 | + /* ignore the effect of character count for partial utf8 sequences */ |
| 2586 | + sb_append_len(sb, &ch, 1); |
| 2587 | + } |
| 2588 | + if (n == 0 || sb->data == NULL) { |
| 2589 | + sb_free(sb); |
| 2590 | + return NULL; |
| 2591 | + } |
| 2592 | + return sb; |
| 2593 | +} |
| 2594 | + |
| 2595 | +char *linenoiseWithInitial(const char *prompt, const char *initial) |
| 2596 | +{ |
| 2597 | + int count; |
| 2598 | + struct current current; |
| 2599 | + stringbuf *sb; |
| 2600 | + |
| 2601 | + memset(¤t, 0, sizeof(current)); |
| 2602 | + |
| 2603 | + if (enableRawMode(¤t) == -1) { |
| 2604 | + printf("%s", prompt); |
| 2605 | + fflush(stdout); |
| 2606 | + sb = sb_getline(stdin); |
| 2607 | + if (sb && !fd_isatty(¤t)) { |
| 2608 | + printf("%s\n", sb_str(sb)); |
| 2609 | + fflush(stdout); |
| 2610 | + } |
| 2611 | + } |
| 2612 | + else { |
| 2613 | + current.buf = sb_alloc(); |
| 2614 | + current.pos = 0; |
| 2615 | + current.nrows = 1; |
| 2616 | + current.prompt = prompt; |
| 2617 | + |
| 2618 | + /* The latest history entry is always our current buffer */ |
| 2619 | + linenoiseHistoryAdd(initial); |
| 2620 | + set_current(¤t, initial); |
| 2621 | + |
| 2622 | + count = linenoiseEdit(¤t); |
| 2623 | + |
| 2624 | + disableRawMode(¤t); |
| 2625 | + printf("\n"); |
| 2626 | + |
| 2627 | + sb_free(current.capture); |
| 2628 | + if (count == -1) { |
| 2629 | + sb_free(current.buf); |
| 2630 | + return NULL; |
| 2631 | + } |
| 2632 | + sb = current.buf; |
| 2633 | + } |
| 2634 | + return sb ? sb_to_string(sb) : NULL; |
| 2635 | +} |
| 2636 | + |
| 2637 | +char *linenoise(const char *prompt) |
| 2638 | +{ |
| 2639 | + return linenoiseWithInitial(prompt, ""); |
| 2640 | +} |
| 2641 | + |
| 2642 | +/* Using a circular buffer is smarter, but a bit more complex to handle. */ |
| 2643 | +static int linenoiseHistoryAddAllocated(char *line) { |
| 2644 | + |
| 2645 | + if (history_max_len == 0) { |
| 2646 | +notinserted: |
| 2647 | + free(line); |
| 2648 | + return 0; |
| 2649 | + } |
| 2650 | + if (history == NULL) { |
| 2651 | + history = (char **)calloc(sizeof(char*), history_max_len); |
| 2652 | + } |
| 2653 | + |
| 2654 | + /* do not insert duplicate lines into history */ |
| 2655 | + if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) { |
| 2656 | + goto notinserted; |
| 2657 | + } |
| 2658 | + |
| 1146 | 2659 | if (history_len == history_max_len) { |
| 1147 | 2660 | free(history[0]); |
| 1148 | 2661 | memmove(history,history+1,sizeof(char*)*(history_max_len-1)); |
| 1149 | 2662 | history_len--; |
| 1150 | 2663 | } |
| 1151 | | - history[history_len] = linecopy; |
| 2664 | + history[history_len] = line; |
| 1152 | 2665 | history_len++; |
| 1153 | 2666 | return 1; |
| 1154 | 2667 | } |
| 1155 | 2668 | |
| 1156 | | -/* Set the maximum length for the history. This function can be called even |
| 1157 | | - * if there is already some history, the function will make sure to retain |
| 1158 | | - * just the latest 'len' elements if the new history length value is smaller |
| 1159 | | - * than the amount of items already inside the history. */ |
| 2669 | +int linenoiseHistoryAdd(const char *line) { |
| 2670 | + return linenoiseHistoryAddAllocated(strdup(line)); |
| 2671 | +} |
| 2672 | + |
| 2673 | +int linenoiseHistoryGetMaxLen(void) { |
| 2674 | + return history_max_len; |
| 2675 | +} |
| 2676 | + |
| 1160 | 2677 | int linenoiseHistorySetMaxLen(int len) { |
| 1161 | | - char **new; |
| 2678 | + char **newHistory; |
| 1162 | 2679 | |
| 1163 | 2680 | if (len < 1) return 0; |
| 1164 | 2681 | if (history) { |
| 1165 | 2682 | int tocopy = history_len; |
| 1166 | 2683 | |
| 1167 | | - new = malloc(sizeof(char*)*len); |
| 1168 | | - if (new == NULL) return 0; |
| 2684 | + newHistory = (char **)calloc(sizeof(char*), len); |
| 1169 | 2685 | |
| 1170 | 2686 | /* If we can't copy everything, free the elements we'll not use. */ |
| 1171 | 2687 | if (len < tocopy) { |
| 1172 | 2688 | int j; |
| 1173 | 2689 | |
| 1174 | 2690 | for (j = 0; j < tocopy-len; j++) free(history[j]); |
| 1175 | 2691 | tocopy = len; |
| 1176 | 2692 | } |
| 1177 | | - memset(new,0,sizeof(char*)*len); |
| 1178 | | - memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); |
| 2693 | + memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy); |
| 1179 | 2694 | free(history); |
| 1180 | | - history = new; |
| 2695 | + history = newHistory; |
| 1181 | 2696 | } |
| 1182 | 2697 | history_max_len = len; |
| 1183 | 2698 | if (history_len > history_max_len) |
| 1184 | 2699 | history_len = history_max_len; |
| 1185 | 2700 | return 1; |
| | @@ -1186,41 +2701,86 @@ |
| 1186 | 2701 | } |
| 1187 | 2702 | |
| 1188 | 2703 | /* Save the history in the specified file. On success 0 is returned |
| 1189 | 2704 | * otherwise -1 is returned. */ |
| 1190 | 2705 | int linenoiseHistorySave(const char *filename) { |
| 1191 | | - mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); |
| 1192 | | - FILE *fp; |
| 1193 | | - int j; |
| 1194 | | - |
| 1195 | | - fp = fopen(filename,"w"); |
| 1196 | | - umask(old_umask); |
| 1197 | | - if (fp == NULL) return -1; |
| 1198 | | - chmod(filename,S_IRUSR|S_IWUSR); |
| 1199 | | - for (j = 0; j < history_len; j++) |
| 1200 | | - fprintf(fp,"%s\n",history[j]); |
| 1201 | | - fclose(fp); |
| 1202 | | - return 0; |
| 1203 | | -} |
| 1204 | | - |
| 1205 | | -/* Load the history from the specified file. If the file does not exist |
| 1206 | | - * zero is returned and no operation is performed. |
| 1207 | | - * |
| 1208 | | - * If the file exists and the operation succeeded 0 is returned, otherwise |
| 1209 | | - * on error -1 is returned. */ |
| 1210 | | -int linenoiseHistoryLoad(const char *filename) { |
| 1211 | | - FILE *fp = fopen(filename,"r"); |
| 1212 | | - char buf[LINENOISE_MAX_LINE]; |
| 1213 | | - |
| 1214 | | - if (fp == NULL) return -1; |
| 1215 | | - |
| 1216 | | - while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { |
| 1217 | | - char *p; |
| 1218 | | - |
| 1219 | | - p = strchr(buf,'\r'); |
| 1220 | | - if (!p) p = strchr(buf,'\n'); |
| 1221 | | - if (p) *p = '\0'; |
| 1222 | | - linenoiseHistoryAdd(buf); |
| 1223 | | - } |
| 1224 | | - fclose(fp); |
| 1225 | | - return 0; |
| 2706 | + FILE *fp = fopen(filename,"w"); |
| 2707 | + int j; |
| 2708 | + |
| 2709 | + if (fp == NULL) return -1; |
| 2710 | + for (j = 0; j < history_len; j++) { |
| 2711 | + const char *str = history[j]; |
| 2712 | + /* Need to encode backslash, nl and cr */ |
| 2713 | + while (*str) { |
| 2714 | + if (*str == '\\') { |
| 2715 | + fputs("\\\\", fp); |
| 2716 | + } |
| 2717 | + else if (*str == '\n') { |
| 2718 | + fputs("\\n", fp); |
| 2719 | + } |
| 2720 | + else if (*str == '\r') { |
| 2721 | + fputs("\\r", fp); |
| 2722 | + } |
| 2723 | + else { |
| 2724 | + fputc(*str, fp); |
| 2725 | + } |
| 2726 | + str++; |
| 2727 | + } |
| 2728 | + fputc('\n', fp); |
| 2729 | + } |
| 2730 | + |
| 2731 | + fclose(fp); |
| 2732 | + return 0; |
| 2733 | +} |
| 2734 | + |
| 2735 | +/* Load the history from the specified file. |
| 2736 | + * |
| 2737 | + * If the file does not exist or can't be opened, no operation is performed |
| 2738 | + * and -1 is returned. |
| 2739 | + * Otherwise 0 is returned. |
| 2740 | + */ |
| 2741 | +int linenoiseHistoryLoad(const char *filename) { |
| 2742 | + FILE *fp = fopen(filename,"r"); |
| 2743 | + stringbuf *sb; |
| 2744 | + |
| 2745 | + if (fp == NULL) return -1; |
| 2746 | + |
| 2747 | + while ((sb = sb_getline(fp)) != NULL) { |
| 2748 | + /* Take the stringbuf and decode backslash escaped values */ |
| 2749 | + char *buf = sb_to_string(sb); |
| 2750 | + char *dest = buf; |
| 2751 | + const char *src; |
| 2752 | + |
| 2753 | + for (src = buf; *src; src++) { |
| 2754 | + char ch = *src; |
| 2755 | + |
| 2756 | + if (ch == '\\') { |
| 2757 | + src++; |
| 2758 | + if (*src == 'n') { |
| 2759 | + ch = '\n'; |
| 2760 | + } |
| 2761 | + else if (*src == 'r') { |
| 2762 | + ch = '\r'; |
| 2763 | + } else { |
| 2764 | + ch = *src; |
| 2765 | + } |
| 2766 | + } |
| 2767 | + *dest++ = ch; |
| 2768 | + } |
| 2769 | + *dest = 0; |
| 2770 | + |
| 2771 | + linenoiseHistoryAddAllocated(buf); |
| 2772 | + } |
| 2773 | + fclose(fp); |
| 2774 | + return 0; |
| 2775 | +} |
| 2776 | + |
| 2777 | +/* Provide access to the history buffer. |
| 2778 | + * |
| 2779 | + * If 'len' is not NULL, the length is stored in *len. |
| 2780 | + */ |
| 2781 | +char **linenoiseHistory(int *len) { |
| 2782 | + if (len) { |
| 2783 | + *len = history_len; |
| 2784 | + } |
| 2785 | + return history; |
| 1226 | 2786 | } |
| 1227 | 2787 | |