OCaml port of Linenoise
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit

Patrick Ferris 08ed9938

+2163
+1
.gitignore
··· 1 + _build
+1
.ocamlformat
··· 1 + version=0.28.1
+5
README.md
··· 1 + bruit 2 + ----- 3 + 4 + An OCaml port of the excellent 5 + [linenoise](https://github.com/antirez/linenoise) library.
+32
bruit.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "A short synopsis" 4 + description: "A longer description" 5 + maintainer: ["Maintainer Name <maintainer@example.com>"] 6 + authors: ["Author Name <author@example.com>"] 7 + license: "LICENSE" 8 + tags: ["add topics" "to describe" "your" "project"] 9 + homepage: "https://github.com/username/reponame" 10 + doc: "https://url/to/documentation" 11 + bug-reports: "https://github.com/username/reponame/issues" 12 + depends: [ 13 + "dune" {>= "3.20"} 14 + "ocaml" 15 + "odoc" {with-doc} 16 + ] 17 + build: [ 18 + ["dune" "subst"] {dev} 19 + [ 20 + "dune" 21 + "build" 22 + "-p" 23 + name 24 + "-j" 25 + jobs 26 + "@install" 27 + "@runtest" {with-test} 28 + "@doc" {with-doc} 29 + ] 30 + ] 31 + dev-repo: "git+https://github.com/username/reponame.git" 32 + x-maintenance-intent: ["(latest)"]
+26
dune-project
··· 1 + (lang dune 3.20) 2 + 3 + (name bruit) 4 + 5 + (generate_opam_files true) 6 + 7 + (source 8 + (github username/reponame)) 9 + 10 + (authors "Author Name <author@example.com>") 11 + 12 + (maintainers "Maintainer Name <maintainer@example.com>") 13 + 14 + (license LICENSE) 15 + 16 + (documentation https://url/to/documentation) 17 + 18 + (package 19 + (name bruit) 20 + (synopsis "A short synopsis") 21 + (description "A longer description") 22 + (depends ocaml) 23 + (tags 24 + ("add topics" "to describe" your project))) 25 + 26 + ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
+3
example/dune
··· 1 + (executable 2 + (name main) 3 + (libraries bruit fmt.tty))
+15
example/main.ml
··· 1 + let () = 2 + Fmt_tty.setup_std_outputs (); 3 + let rec loop sys_break = 4 + let prompt = 5 + if sys_break then Fmt.str "[%a] >> " Fmt.(styled (`Fg `Red) int) 130 6 + else ">> " 7 + in 8 + match Bruit.bruit prompt with 9 + | String (Some s) -> 10 + Fmt.pr "%s\n%!" s; 11 + loop false 12 + | String None -> () 13 + | Ctrl_c -> loop true 14 + in 15 + loop false
+312
src/bruit.ml
··· 1 + (* See the end of the file for the original license of Linenoise. *) 2 + 3 + let max_line = 2048 4 + 5 + type key = 6 + | Enter 7 + | Ctrl_a 8 + | Ctrl_b 9 + | Ctrl_c 10 + | Ctrl_d 11 + | Ctrl_e 12 + | Backspace 13 + | Escape_sequence 14 + | Tab 15 + | Unknown of Uchar.t 16 + 17 + let key_of_char c = 18 + match Char.code c with 19 + | 1 -> Ctrl_a 20 + | 2 -> Ctrl_b 21 + | 3 -> Ctrl_c 22 + | 4 -> Ctrl_d 23 + | 5 -> Ctrl_e 24 + | 9 -> Tab 25 + | 13 -> Enter 26 + | 27 -> Escape_sequence 27 + | 127 -> Backspace 28 + | _ -> Unknown (Uchar.of_char c) 29 + 30 + module State = struct 31 + type t = { 32 + in_completion : int; 33 + ifd : Unix.file_descr; 34 + ofd : Unix.file_descr; 35 + buf : bytes; 36 + buf_len : int; 37 + prompt : bytes; 38 + plen : int; 39 + old_pos : int; 40 + pos : int; 41 + len : int; 42 + (* Update later *) 43 + cols : int; 44 + old_rows : int; 45 + old_row_pos : int; 46 + history_index : int; 47 + read_buf : Bytes.t; 48 + } 49 + 50 + let make ?(in_completion = 0) ?(old_pos = 0) ?(pos = 0) ?(len = 0) 51 + ?(ifd = Unix.stdin) ?(ofd = Unix.stdout) ~prompt buf = 52 + { 53 + in_completion; 54 + ifd; 55 + ofd; 56 + buf; 57 + buf_len = Bytes.length buf; 58 + prompt; 59 + plen = Bytes.length prompt; 60 + old_pos; 61 + pos; 62 + len; 63 + cols = 0; 64 + old_row_pos = 1; 65 + old_rows = 0; 66 + history_index = 0; 67 + read_buf = Bytes.create 1 (* For reading a character *); 68 + } 69 + 70 + let override ?in_completion ?ifd ?ofd ?buf ?buf_len ?prompt ?plen ?old_pos 71 + ?pos ?len ?cols ?old_rows ?old_row_pos ?history_index (t : t) = 72 + { 73 + in_completion = Option.value ~default:t.in_completion in_completion; 74 + ifd = Option.value ~default:t.ifd ifd; 75 + ofd = Option.value ~default:t.ofd ofd; 76 + buf = Option.value ~default:t.buf buf; 77 + buf_len = Option.value ~default:t.buf_len buf_len; 78 + prompt = Option.value ~default:t.prompt prompt; 79 + plen = Option.value ~default:t.plen plen; 80 + old_pos = Option.value ~default:t.old_pos old_pos; 81 + pos = Option.value ~default:t.pos pos; 82 + len = Option.value ~default:t.len len; 83 + cols = Option.value ~default:t.cols cols; 84 + old_rows = Option.value ~default:t.old_rows old_rows; 85 + old_row_pos = Option.value ~default:t.old_row_pos old_row_pos; 86 + history_index = Option.value ~default:t.history_index history_index; 87 + read_buf = t.read_buf; 88 + } 89 + end 90 + 91 + let get_columns () = 92 + match Terminal.Size.get_columns () with Some n -> n | None -> 80 93 + 94 + let with_raw_mode (state : State.t) fn = 95 + let saved_tio = Unix.tcgetattr state.ifd in 96 + let tio : Unix.terminal_io = 97 + { 98 + saved_tio with 99 + c_brkint = false; 100 + c_icrnl = false; 101 + c_inpck = false; 102 + c_istrip = false; 103 + c_ixon = false; 104 + c_opost = false; 105 + c_csize = 8; 106 + c_echo = false; 107 + c_icanon = false; 108 + c_isig = false; 109 + c_vtime = 0; 110 + c_vmin = 1; 111 + } 112 + in 113 + Unix.tcsetattr state.ifd TCSAFLUSH tio; 114 + Fun.protect 115 + ~finally:(fun () -> Unix.tcsetattr state.ifd TCSADRAIN saved_tio) 116 + fn 117 + 118 + let write_bytes fd s = 119 + let len = Bytes.length s in 120 + let wrote = Unix.write fd s 0 len in 121 + assert (Int.equal len wrote) 122 + 123 + let write_uchar fd u = 124 + let b_len = Uchar.utf_8_byte_length u in 125 + let bs = Bytes.create b_len in 126 + let wrote = Bytes.set_utf_8_uchar bs 0 u in 127 + assert (Int.equal b_len wrote); 128 + write_bytes fd bs 129 + 130 + type edit = Editing of State.t | Finished of bytes option | Ctrl_c 131 + 132 + let read_char state = 133 + try 134 + let read = Unix.read state.State.ifd state.read_buf 0 1 in 135 + if read = 0 then `None else `Some (Bytes.unsafe_get state.read_buf 0) 136 + with Unix.Unix_error ((Unix.EWOULDBLOCK | Unix.EAGAIN), _, _) -> `Editing 137 + 138 + let edit_start ~stdin:_ ~stdout:_ state fn = 139 + with_raw_mode state @@ fun () -> 140 + let cols = get_columns () in 141 + Bytes.set state.buf 0 '\000'; 142 + let state = State.override ~cols ~buf_len:(state.buf_len - 1) state in 143 + write_bytes state.ofd state.prompt; 144 + fn state 145 + 146 + let utf8_display_width b len = 147 + let s = Bytes.to_string b in 148 + Terminal.guess_printed_width (String.sub s 0 len) 149 + 150 + let utf8_next_char_len s off = 151 + Bytes.get_utf_8_uchar s off 152 + |> Uchar.utf_decode_uchar |> Uchar.utf_8_byte_length 153 + 154 + let utf8_prev_char_len s off = 155 + let rec loop pos = 156 + if pos < 0 || pos < off - 4 then 157 + invalid_arg "UTF8 previous character length"; 158 + let decode = Bytes.get_utf_8_uchar s off in 159 + if Uchar.utf_decode_is_valid decode then 160 + Uchar.utf_decode_uchar decode |> Uchar.utf_8_byte_length 161 + else loop (pos - 1) 162 + in 163 + loop (off - 1) 164 + 165 + type refresh_flag = Rewrite 166 + 167 + let refresh_single_line ?(flags = []) (state : State.t) = 168 + let pwidth = utf8_display_width state.prompt state.plen in 169 + let poscol = ref @@ utf8_display_width state.buf state.pos in 170 + let lencol = ref @@ utf8_display_width state.buf state.len in 171 + 172 + let rec loop (state : State.t) = 173 + if pwidth + !poscol >= state.cols then begin 174 + let clen = utf8_next_char_len state.buf 0 in 175 + let c_width = 176 + Uchar.utf_8_byte_length 177 + (Bytes.get_utf_8_uchar state.buf clen |> Uchar.utf_decode_uchar) 178 + in 179 + poscol := !poscol - c_width; 180 + lencol := !lencol - c_width; 181 + let state = 182 + State.override ~len:(state.len - clen) ~pos:(state.pos - clen) state 183 + in 184 + loop state 185 + end 186 + else state 187 + in 188 + let state = loop state in 189 + let ab = Buffer.create 0 in 190 + (* Clear line *) 191 + Buffer.add_char ab '\r'; 192 + 193 + (* Add prompt *) 194 + if List.mem Rewrite flags then begin 195 + Buffer.add_bytes ab state.prompt; 196 + Buffer.add_bytes ab (Bytes.sub state.buf 0 state.len) 197 + end; 198 + 199 + (* Erase to the right *) 200 + Buffer.add_string ab "\x1b[0K"; 201 + 202 + (* Cursor to the original position *) 203 + if List.mem Rewrite flags then begin 204 + Buffer.add_string ab (Format.sprintf "\r\x1b[%dC" (!poscol + pwidth)) 205 + end; 206 + write_bytes state.ofd (Buffer.to_bytes ab); 207 + state 208 + 209 + let refresh_line state = refresh_single_line ~flags:[ Rewrite ] state 210 + 211 + let edit_insert (state : State.t) c = 212 + let clen = Uchar.utf_8_byte_length c in 213 + (* At the end of the line *) 214 + if Int.equal state.len state.pos then begin 215 + let _ : int = Bytes.set_utf_8_uchar state.buf state.pos c in 216 + let state = 217 + State.override ~pos:(state.pos + clen) ~len:(state.len + clen) state 218 + in 219 + if 220 + utf8_display_width state.prompt state.plen 221 + + utf8_display_width state.buf state.len 222 + < state.cols 223 + then begin 224 + write_uchar state.ofd c; 225 + state 226 + end 227 + else refresh_line state 228 + end 229 + else begin 230 + assert false 231 + end 232 + 233 + let edit_backspace (state : State.t) = 234 + let state = 235 + if state.pos > 0 && state.len > 0 then begin 236 + let clen = utf8_prev_char_len state.buf state.pos in 237 + let dst = state.pos - clen in 238 + let src = state.pos in 239 + let len = state.len - state.pos in 240 + Bytes.blit state.buf src state.buf dst len; 241 + State.override ~pos:(state.pos - clen) ~len:(state.len - clen) state 242 + end 243 + else state 244 + in 245 + refresh_line state 246 + 247 + let edit_feed state = 248 + match read_char state with 249 + | `Editing -> Editing state 250 + | `None -> Finished None 251 + | `Some c -> ( 252 + (* TODO: line completion *) 253 + match key_of_char c with 254 + | Enter -> Finished (Some state.buf) 255 + | Unknown i -> 256 + let state = edit_insert state i in 257 + Editing state 258 + | Ctrl_d -> if Int.equal state.len 0 then Finished None else assert false 259 + | Ctrl_c -> Ctrl_c 260 + | Backspace -> Editing (edit_backspace state) 261 + | _ -> assert false) 262 + 263 + type result = String of string option | Ctrl_c 264 + 265 + let blocking_edit ~stdin ~stdout buf ~prompt = 266 + let state = State.make ~prompt buf in 267 + let res = 268 + edit_start ~stdin ~stdout state @@ fun state -> 269 + let rec loop = function 270 + | Editing state -> loop (edit_feed state) 271 + | Finished s -> String (Option.map Bytes.to_string s) 272 + | Ctrl_c -> Ctrl_c 273 + in 274 + loop (edit_feed state) 275 + in 276 + Format.printf "\n%!"; 277 + res 278 + 279 + let bruit prompt = 280 + let prompt = Bytes.of_string prompt in 281 + let buf = Bytes.create max_line in 282 + if not (Unix.isatty Unix.stdin) then failwith "Stdin is not a tty" 283 + else blocking_edit ~stdin:Unix.stdin ~stdout:Unix.stdout buf ~prompt 284 + 285 + (* 286 + * Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com> 287 + * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> 288 + * 289 + * All rights reserved. 290 + * 291 + * Redistribution and use in source and binary forms, with or without 292 + * modification, are permitted provided that the following conditions are 293 + * met: 294 + * 295 + * * Redistributions of source code must retain the above copyright 296 + * notice, this list of conditions and the following disclaimer. 297 + * 298 + * * Redistributions in binary form must reproduce the above copyright 299 + * notice, this list of conditions and the following disclaimer in the 300 + * documentation and/or other materials provided with the distribution. 301 + * 302 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 303 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 304 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 305 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 306 + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 307 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 308 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 309 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 310 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 311 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 312 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *)
+4
src/dune
··· 1 + (library 2 + (public_name bruit) 3 + (libraries terminal unix fmt) 4 + (name bruit))
+1762
src/linenoise.c
··· 1 + /* linenoise.c -- guerrilla line editing library against the idea that a 2 + * line editing lib needs to be 20,000 lines of C code. 3 + * 4 + * You can find the latest source code at: 5 + * 6 + * http://github.com/antirez/linenoise 7 + * 8 + * Does a number of crazy assumptions that happen to be true in 99.9999% of 9 + * the 2010 UNIX computers around. 10 + * 11 + * ------------------------------------------------------------------------ 12 + * 13 + * Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com> 14 + * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> 15 + * 16 + * All rights reserved. 17 + * 18 + * Redistribution and use in source and binary forms, with or without 19 + * modification, are permitted provided that the following conditions are 20 + * met: 21 + * 22 + * * Redistributions of source code must retain the above copyright 23 + * notice, this list of conditions and the following disclaimer. 24 + * 25 + * * Redistributions in binary form must reproduce the above copyright 26 + * notice, this list of conditions and the following disclaimer in the 27 + * documentation and/or other materials provided with the distribution. 28 + * 29 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33 + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 + * 41 + * ------------------------------------------------------------------------ 42 + * 43 + * References: 44 + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 45 + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 46 + * 47 + * Todo list: 48 + * - Filter bogus Ctrl+<char> combinations. 49 + * - Win32 support 50 + * 51 + * Bloat: 52 + * - History search like Ctrl+r in readline? 53 + * 54 + * 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 56 + * flickering effect with some slow terminal, but the lesser sequences 57 + * the more compatible. 58 + * 59 + * 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 64 + * 65 + * CUF (CUrsor Forward) 66 + * Sequence: ESC [ n C 67 + * Effect: moves cursor forward n chars 68 + * 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) 97 + * Sequence: ESC [ H 98 + * Effect: moves the cursor to upper left corner 99 + * 100 + * ED (Erase display) 101 + * Sequence: ESC [ 2 J 102 + * Effect: clear the whole screen 103 + * 104 + */ 105 + 106 + #include <termios.h> 107 + #include <unistd.h> 108 + #include <stdlib.h> 109 + #include <stdio.h> 110 + #include <errno.h> 111 + #include <string.h> 112 + #include <stdlib.h> 113 + #include <ctype.h> 114 + #include <sys/stat.h> 115 + #include <sys/types.h> 116 + #include <sys/ioctl.h> 117 + #include <unistd.h> 118 + #include <stdint.h> 119 + #include "linenoise.h" 120 + 121 + #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 122 + #define LINENOISE_MAX_LINE 4096 123 + static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; 124 + static linenoiseCompletionCallback *completionCallback = NULL; 125 + static linenoiseHintsCallback *hintsCallback = NULL; 126 + static linenoiseFreeHintsCallback *freeHintsCallback = NULL; 127 + static char *linenoiseNoTTY(void); 128 + static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); 129 + static void refreshLineWithFlags(struct linenoiseState *l, int flags); 130 + 131 + static struct termios orig_termios; /* In order to restore at exit.*/ 132 + static int maskmode = 0; /* Show "***" instead of input. For passwords. */ 133 + static int rawmode = 0; /* For atexit() function to check if restore is needed*/ 134 + static int mlmode = 0; /* Multi line mode. Default is single line. */ 135 + static int atexit_registered = 0; /* Register atexit just 1 time. */ 136 + static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; 137 + static int history_len = 0; 138 + static char **history = NULL; 139 + 140 + /* =========================== UTF-8 support ================================ */ 141 + 142 + /* Return the number of bytes that compose the UTF-8 character starting at 143 + * 'c'. This function assumes a valid UTF-8 encoding and handles the four 144 + * standard byte patterns: 145 + * 0xxxxxxx -> 1 byte (ASCII) 146 + * 110xxxxx -> 2 bytes 147 + * 1110xxxx -> 3 bytes 148 + * 11110xxx -> 4 bytes */ 149 + static int utf8ByteLen(char c) { 150 + unsigned char uc = (unsigned char)c; 151 + if ((uc & 0x80) == 0) return 1; /* 0xxxxxxx: ASCII */ 152 + if ((uc & 0xE0) == 0xC0) return 2; /* 110xxxxx: 2-byte seq */ 153 + if ((uc & 0xF0) == 0xE0) return 3; /* 1110xxxx: 3-byte seq */ 154 + if ((uc & 0xF8) == 0xF0) return 4; /* 11110xxx: 4-byte seq */ 155 + return 1; /* Fallback for invalid encoding, treat as single byte. */ 156 + } 157 + 158 + /* Decode a UTF-8 sequence starting at 's' into a Unicode codepoint. 159 + * Returns the codepoint value. Assumes valid UTF-8 encoding. */ 160 + static uint32_t utf8DecodeChar(const char *s, size_t *len) { 161 + unsigned char *p = (unsigned char *)s; 162 + uint32_t cp; 163 + 164 + if ((*p & 0x80) == 0) { 165 + *len = 1; 166 + return *p; 167 + } else if ((*p & 0xE0) == 0xC0) { 168 + *len = 2; 169 + cp = (*p & 0x1F) << 6; 170 + cp |= (p[1] & 0x3F); 171 + return cp; 172 + } else if ((*p & 0xF0) == 0xE0) { 173 + *len = 3; 174 + cp = (*p & 0x0F) << 12; 175 + cp |= (p[1] & 0x3F) << 6; 176 + cp |= (p[2] & 0x3F); 177 + return cp; 178 + } else if ((*p & 0xF8) == 0xF0) { 179 + *len = 4; 180 + cp = (*p & 0x07) << 18; 181 + cp |= (p[1] & 0x3F) << 12; 182 + cp |= (p[2] & 0x3F) << 6; 183 + cp |= (p[3] & 0x3F); 184 + return cp; 185 + } 186 + *len = 1; 187 + return *p; /* Fallback for invalid sequences. */ 188 + } 189 + 190 + /* Check if codepoint is a variation selector (emoji style modifiers). */ 191 + static int isVariationSelector(uint32_t cp) { 192 + return cp == 0xFE0E || cp == 0xFE0F; /* Text/emoji style */ 193 + } 194 + 195 + /* Check if codepoint is a skin tone modifier. */ 196 + static int isSkinToneModifier(uint32_t cp) { 197 + return cp >= 0x1F3FB && cp <= 0x1F3FF; 198 + } 199 + 200 + /* Check if codepoint is Zero Width Joiner. */ 201 + static int isZWJ(uint32_t cp) { 202 + return cp == 0x200D; 203 + } 204 + 205 + /* Check if codepoint is a Regional Indicator (for flag emoji). */ 206 + static int isRegionalIndicator(uint32_t cp) { 207 + return cp >= 0x1F1E6 && cp <= 0x1F1FF; 208 + } 209 + 210 + /* Check if codepoint is a combining mark or other zero-width character. */ 211 + static int isCombiningMark(uint32_t cp) { 212 + return (cp >= 0x0300 && cp <= 0x036F) || /* Combining Diacriticals */ 213 + (cp >= 0x1AB0 && cp <= 0x1AFF) || /* Combining Diacriticals Extended */ 214 + (cp >= 0x1DC0 && cp <= 0x1DFF) || /* Combining Diacriticals Supplement */ 215 + (cp >= 0x20D0 && cp <= 0x20FF) || /* Combining Diacriticals for Symbols */ 216 + (cp >= 0xFE20 && cp <= 0xFE2F); /* Combining Half Marks */ 217 + } 218 + 219 + /* Check if codepoint extends the previous character (doesn't start a new grapheme). */ 220 + static int isGraphemeExtend(uint32_t cp) { 221 + return isVariationSelector(cp) || isSkinToneModifier(cp) || 222 + isZWJ(cp) || isCombiningMark(cp); 223 + } 224 + 225 + /* Decode the UTF-8 codepoint ending at position 'pos' (exclusive) and 226 + * return its value. Also sets *cplen to the byte length of the codepoint. */ 227 + static uint32_t utf8DecodePrev(const char *buf, size_t pos, size_t *cplen) { 228 + if (pos == 0) { 229 + *cplen = 0; 230 + return 0; 231 + } 232 + /* Scan backwards to find the start byte. */ 233 + size_t i = pos; 234 + do { 235 + i--; 236 + } while (i > 0 && (pos - i) < 4 && ((unsigned char)buf[i] & 0xC0) == 0x80); 237 + *cplen = pos - i; 238 + size_t dummy; 239 + return utf8DecodeChar(buf + i, &dummy); 240 + } 241 + 242 + /* Given a buffer and a position, return the byte length of the grapheme 243 + * cluster before that position. A grapheme cluster includes: 244 + * - The base character 245 + * - Any following variation selectors, skin tone modifiers 246 + * - ZWJ sequences (emoji joined by Zero Width Joiner) 247 + * - Regional indicator pairs (flag emoji) */ 248 + static size_t utf8PrevCharLen(const char *buf, size_t pos) { 249 + if (pos == 0) return 0; 250 + 251 + size_t total = 0; 252 + size_t curpos = pos; 253 + 254 + /* First, get the last codepoint. */ 255 + size_t cplen; 256 + uint32_t cp = utf8DecodePrev(buf, curpos, &cplen); 257 + if (cplen == 0) return 0; 258 + total += cplen; 259 + curpos -= cplen; 260 + 261 + /* If we're at an extending character, we need to find what it extends. 262 + * Keep going back through the grapheme cluster. */ 263 + while (curpos > 0) { 264 + size_t prevlen; 265 + uint32_t prevcp = utf8DecodePrev(buf, curpos, &prevlen); 266 + if (prevlen == 0) break; 267 + 268 + if (isZWJ(prevcp)) { 269 + /* ZWJ joins two emoji. Include the ZWJ and continue to get 270 + * the preceding character. */ 271 + total += prevlen; 272 + curpos -= prevlen; 273 + /* Now get the character before ZWJ. */ 274 + prevcp = utf8DecodePrev(buf, curpos, &prevlen); 275 + if (prevlen == 0) break; 276 + total += prevlen; 277 + curpos -= prevlen; 278 + cp = prevcp; 279 + continue; /* Check if there's more extending before this. */ 280 + } else if (isGraphemeExtend(cp)) { 281 + /* Current cp is an extending character; include previous. */ 282 + total += prevlen; 283 + curpos -= prevlen; 284 + cp = prevcp; 285 + continue; 286 + } else if (isRegionalIndicator(cp) && isRegionalIndicator(prevcp)) { 287 + /* Two regional indicators form a flag. But we need to be careful: 288 + * flags are always pairs, so only join if we're at an even boundary. 289 + * For simplicity, just join one pair. */ 290 + total += prevlen; 291 + curpos -= prevlen; 292 + break; 293 + } else { 294 + /* No more extending; we've found the start of the cluster. */ 295 + break; 296 + } 297 + } 298 + 299 + return total; 300 + } 301 + 302 + /* Given a buffer, position and total length, return the byte length of the 303 + * grapheme cluster at the current position. */ 304 + static size_t utf8NextCharLen(const char *buf, size_t pos, size_t len) { 305 + if (pos >= len) return 0; 306 + 307 + size_t total = 0; 308 + size_t curpos = pos; 309 + 310 + /* Get the first codepoint. */ 311 + size_t cplen; 312 + uint32_t cp = utf8DecodeChar(buf + curpos, &cplen); 313 + total += cplen; 314 + curpos += cplen; 315 + 316 + int isRI = isRegionalIndicator(cp); 317 + 318 + /* Consume any extending characters that follow. */ 319 + while (curpos < len) { 320 + size_t nextlen; 321 + uint32_t nextcp = utf8DecodeChar(buf + curpos, &nextlen); 322 + 323 + if (isZWJ(nextcp) && curpos + nextlen < len) { 324 + /* ZWJ: include it and the following character. */ 325 + total += nextlen; 326 + curpos += nextlen; 327 + /* Get the character after ZWJ. */ 328 + nextcp = utf8DecodeChar(buf + curpos, &nextlen); 329 + total += nextlen; 330 + curpos += nextlen; 331 + continue; /* Check for more extending after the joined char. */ 332 + } else if (isGraphemeExtend(nextcp)) { 333 + /* Variation selector, skin tone, combining mark, etc. */ 334 + total += nextlen; 335 + curpos += nextlen; 336 + continue; 337 + } else if (isRI && isRegionalIndicator(nextcp)) { 338 + /* Second regional indicator for a flag pair. */ 339 + total += nextlen; 340 + curpos += nextlen; 341 + isRI = 0; /* Only pair once. */ 342 + continue; 343 + } else { 344 + break; 345 + } 346 + } 347 + 348 + return total; 349 + } 350 + 351 + /* Return the display width of a Unicode codepoint. This is a heuristic 352 + * that works for most common cases: 353 + * - Control chars and zero-width: 0 columns 354 + * - Grapheme-extending chars (VS, skin tone, ZWJ): 0 columns 355 + * - ASCII printable: 1 column 356 + * - Wide chars (CJK, emoji, fullwidth): 2 columns 357 + * - Everything else: 1 column 358 + * 359 + * This is not a full wcwidth() implementation, but a minimal heuristic 360 + * that handles emoji and CJK characters reasonably well. */ 361 + static int utf8CharWidth(uint32_t cp) { 362 + /* Control characters and combining marks: zero width. */ 363 + if (cp < 32 || (cp >= 0x7F && cp < 0xA0)) return 0; 364 + if (isCombiningMark(cp)) return 0; 365 + 366 + /* Grapheme-extending characters: zero width. 367 + * These modify the preceding character rather than taking space. */ 368 + if (isVariationSelector(cp)) return 0; 369 + if (isSkinToneModifier(cp)) return 0; 370 + if (isZWJ(cp)) return 0; 371 + 372 + /* Wide character ranges - these display as 2 columns: 373 + * - CJK Unified Ideographs and Extensions 374 + * - Fullwidth forms 375 + * - Various emoji ranges */ 376 + if (cp >= 0x1100 && 377 + (cp <= 0x115F || /* Hangul Jamo */ 378 + cp == 0x2329 || cp == 0x232A || /* Angle brackets */ 379 + (cp >= 0x231A && cp <= 0x231B) || /* Watch, Hourglass */ 380 + (cp >= 0x23E9 && cp <= 0x23F3) || /* Various symbols */ 381 + (cp >= 0x23F8 && cp <= 0x23FA) || /* Various symbols */ 382 + (cp >= 0x25AA && cp <= 0x25AB) || /* Small squares */ 383 + (cp >= 0x25B6 && cp <= 0x25C0) || /* Play/reverse buttons */ 384 + (cp >= 0x25FB && cp <= 0x25FE) || /* Squares */ 385 + (cp >= 0x2600 && cp <= 0x26FF) || /* Misc Symbols (sun, cloud, etc) */ 386 + (cp >= 0x2700 && cp <= 0x27BF) || /* Dingbats (❤, ✂, etc) */ 387 + (cp >= 0x2934 && cp <= 0x2935) || /* Arrows */ 388 + (cp >= 0x2B05 && cp <= 0x2B07) || /* Arrows */ 389 + (cp >= 0x2B1B && cp <= 0x2B1C) || /* Squares */ 390 + cp == 0x2B50 || cp == 0x2B55 || /* Star, circle */ 391 + (cp >= 0x2E80 && cp <= 0xA4CF && 392 + cp != 0x303F) || /* CJK ... Yi */ 393 + (cp >= 0xAC00 && cp <= 0xD7A3) || /* Hangul Syllables */ 394 + (cp >= 0xF900 && cp <= 0xFAFF) || /* CJK Compatibility Ideographs */ 395 + (cp >= 0xFE10 && cp <= 0xFE1F) || /* Vertical forms */ 396 + (cp >= 0xFE30 && cp <= 0xFE6F) || /* CJK Compatibility Forms */ 397 + (cp >= 0xFF00 && cp <= 0xFF60) || /* Fullwidth Forms */ 398 + (cp >= 0xFFE0 && cp <= 0xFFE6) || /* Fullwidth Signs */ 399 + (cp >= 0x1F1E6 && cp <= 0x1F1FF) || /* Regional Indicators (flags) */ 400 + (cp >= 0x1F300 && cp <= 0x1F64F) || /* Misc Symbols and Emoticons */ 401 + (cp >= 0x1F680 && cp <= 0x1F6FF) || /* Transport and Map Symbols */ 402 + (cp >= 0x1F900 && cp <= 0x1F9FF) || /* Supplemental Symbols */ 403 + (cp >= 0x1FA00 && cp <= 0x1FAFF) || /* Chess, Extended-A */ 404 + (cp >= 0x20000 && cp <= 0x2FFFF))) /* CJK Extension B and beyond */ 405 + return 2; 406 + 407 + return 1; /* Default: single width */ 408 + } 409 + 410 + /* Calculate the display width of a UTF-8 string of 'len' bytes. 411 + * This is used for cursor positioning in the terminal. 412 + * Handles grapheme clusters: characters joined by ZWJ contribute 0 width 413 + * after the first character in the sequence. */ 414 + static size_t utf8StrWidth(const char *s, size_t len) { 415 + size_t width = 0; 416 + size_t i = 0; 417 + int after_zwj = 0; /* Track if previous char was ZWJ */ 418 + 419 + while (i < len) { 420 + size_t clen; 421 + uint32_t cp = utf8DecodeChar(s + i, &clen); 422 + 423 + if (after_zwj) { 424 + /* Character after ZWJ: don't add width, it's joined. 425 + * But do check for extending chars after it. */ 426 + after_zwj = 0; 427 + } else { 428 + width += utf8CharWidth(cp); 429 + } 430 + 431 + /* Check if this is a ZWJ - next char will be joined. */ 432 + if (isZWJ(cp)) { 433 + after_zwj = 1; 434 + } 435 + 436 + i += clen; 437 + } 438 + return width; 439 + } 440 + 441 + /* Return the display width of a single UTF-8 character at position 's'. */ 442 + static int utf8SingleCharWidth(const char *s, size_t len) { 443 + if (len == 0) return 0; 444 + size_t clen; 445 + uint32_t cp = utf8DecodeChar(s, &clen); 446 + return utf8CharWidth(cp); 447 + } 448 + 449 + enum KEY_ACTION{ 450 + KEY_NULL = 0, /* NULL */ 451 + CTRL_A = 1, /* Ctrl+a */ 452 + CTRL_B = 2, /* Ctrl-b */ 453 + CTRL_C = 3, /* Ctrl-c */ 454 + CTRL_D = 4, /* Ctrl-d */ 455 + CTRL_E = 5, /* Ctrl-e */ 456 + CTRL_F = 6, /* Ctrl-f */ 457 + CTRL_H = 8, /* Ctrl-h */ 458 + TAB = 9, /* Tab */ 459 + CTRL_K = 11, /* Ctrl+k */ 460 + CTRL_L = 12, /* Ctrl+l */ 461 + ENTER = 13, /* Enter */ 462 + CTRL_N = 14, /* Ctrl-n */ 463 + CTRL_P = 16, /* Ctrl-p */ 464 + CTRL_T = 20, /* Ctrl-t */ 465 + CTRL_U = 21, /* Ctrl+u */ 466 + CTRL_W = 23, /* Ctrl+w */ 467 + ESC = 27, /* Escape */ 468 + BACKSPACE = 127 /* Backspace */ 469 + }; 470 + 471 + static void linenoiseAtExit(void); 472 + int linenoiseHistoryAdd(const char *line); 473 + #define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen 474 + #define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen. 475 + #define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both. 476 + static void refreshLine(struct linenoiseState *l); 477 + 478 + /* Debugging macro. */ 479 + #if 0 480 + FILE *lndebug_fp = NULL; 481 + #define lndebug(...) \ 482 + do { \ 483 + if (lndebug_fp == NULL) { \ 484 + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ 485 + fprintf(lndebug_fp, \ 486 + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ 487 + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ 488 + (int)l->oldrows,old_rows); \ 489 + } \ 490 + fprintf(lndebug_fp, ", " __VA_ARGS__); \ 491 + fflush(lndebug_fp); \ 492 + } while (0) 493 + #else 494 + #define lndebug(fmt, ...) 495 + #endif 496 + 497 + /* ======================= Low level terminal handling ====================== */ 498 + 499 + /* Enable "mask mode". When it is enabled, instead of the input that 500 + * the user is typing, the terminal will just display a corresponding 501 + * number of asterisks, like "****". This is useful for passwords and other 502 + * secrets that should not be displayed. */ 503 + void linenoiseMaskModeEnable(void) { 504 + maskmode = 1; 505 + } 506 + 507 + /* Disable mask mode. */ 508 + void linenoiseMaskModeDisable(void) { 509 + maskmode = 0; 510 + } 511 + 512 + /* Set if to use or not the multi line mode. */ 513 + void linenoiseSetMultiLine(int ml) { 514 + mlmode = ml; 515 + } 516 + 517 + /* Return true if the terminal name is in the list of terminals we know are 518 + * not able to understand basic escape sequences. */ 519 + static int isUnsupportedTerm(void) { 520 + char *term = getenv("TERM"); 521 + int j; 522 + 523 + if (term == NULL) return 0; 524 + for (j = 0; unsupported_term[j]; j++) 525 + if (!strcasecmp(term,unsupported_term[j])) return 1; 526 + return 0; 527 + } 528 + 529 + /* Raw mode: 1960 magic shit. */ 530 + static int enableRawMode(int fd) { 531 + struct termios raw; 532 + 533 + /* Test mode: when LINENOISE_ASSUME_TTY is set, skip terminal setup. 534 + * This allows testing via pipes without a real terminal. */ 535 + if (getenv("LINENOISE_ASSUME_TTY")) { 536 + rawmode = 1; 537 + return 0; 538 + } 539 + 540 + if (!isatty(STDIN_FILENO)) goto fatal; 541 + if (!atexit_registered) { 542 + atexit(linenoiseAtExit); 543 + atexit_registered = 1; 544 + } 545 + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; 546 + 547 + raw = orig_termios; /* modify the original mode */ 548 + /* input modes: no break, no CR to NL, no parity check, no strip char, 549 + * no start/stop output control. */ 550 + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 551 + /* output modes - disable post processing */ 552 + raw.c_oflag &= ~(OPOST); 553 + /* control modes - set 8 bit chars */ 554 + raw.c_cflag |= (CS8); 555 + /* local modes - choing off, canonical off, no extended functions, 556 + * no signal chars (^Z,^C) */ 557 + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 558 + /* control chars - set return condition: min number of bytes and timer. 559 + * We want read to return every single byte, without timeout. */ 560 + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 561 + 562 + /* put terminal in raw mode after flushing */ 563 + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; 564 + rawmode = 1; 565 + return 0; 566 + 567 + fatal: 568 + errno = ENOTTY; 569 + return -1; 570 + } 571 + 572 + static void disableRawMode(int fd) { 573 + /* Test mode: nothing to restore. */ 574 + if (getenv("LINENOISE_ASSUME_TTY")) { 575 + rawmode = 0; 576 + return; 577 + } 578 + /* Don't even check the return value as it's too late. */ 579 + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) 580 + rawmode = 0; 581 + } 582 + 583 + /* Use the ESC [6n escape sequence to query the horizontal cursor position 584 + * and return it. On error -1 is returned, on success the position of the 585 + * cursor. */ 586 + static int getCursorPosition(int ifd, int ofd) { 587 + char buf[32]; 588 + int cols, rows; 589 + unsigned int i = 0; 590 + 591 + /* Report cursor location */ 592 + if (write(ofd, "\x1b[6n", 4) != 4) return -1; 593 + 594 + /* Read the response: ESC [ rows ; cols R */ 595 + while (i < sizeof(buf)-1) { 596 + if (read(ifd,buf+i,1) != 1) break; 597 + if (buf[i] == 'R') break; 598 + i++; 599 + } 600 + buf[i] = '\0'; 601 + 602 + /* Parse it. */ 603 + if (buf[0] != ESC || buf[1] != '[') return -1; 604 + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; 605 + return cols; 606 + } 607 + 608 + /* Try to get the number of columns in the current terminal, or assume 80 609 + * if it fails. */ 610 + static int getColumns(int ifd, int ofd) { 611 + struct winsize ws; 612 + 613 + /* Test mode: use LINENOISE_COLS env var for fixed width. */ 614 + char *cols_env = getenv("LINENOISE_COLS"); 615 + if (cols_env) return atoi(cols_env); 616 + 617 + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 618 + /* ioctl() failed. Try to query the terminal itself. */ 619 + int start, cols; 620 + 621 + /* Get the initial position so we can restore it later. */ 622 + start = getCursorPosition(ifd,ofd); 623 + if (start == -1) goto failed; 624 + 625 + /* Go to right margin and get position. */ 626 + if (write(ofd,"\x1b[999C",6) != 6) goto failed; 627 + cols = getCursorPosition(ifd,ofd); 628 + if (cols == -1) goto failed; 629 + 630 + /* Restore position. */ 631 + if (cols > start) { 632 + char seq[32]; 633 + snprintf(seq,32,"\x1b[%dD",cols-start); 634 + if (write(ofd,seq,strlen(seq)) == -1) { 635 + /* Can't recover... */ 636 + } 637 + } 638 + return cols; 639 + } else { 640 + return ws.ws_col; 641 + } 642 + 643 + failed: 644 + return 80; 645 + } 646 + 647 + /* Clear the screen. Used to handle ctrl+l */ 648 + void linenoiseClearScreen(void) { 649 + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { 650 + /* nothing to do, just to avoid warning. */ 651 + } 652 + } 653 + 654 + /* Beep, used for completion when there is nothing to complete or when all 655 + * the choices were already shown. */ 656 + static void linenoiseBeep(void) { 657 + fprintf(stderr, "\x7"); 658 + fflush(stderr); 659 + } 660 + 661 + /* ============================== Completion ================================ */ 662 + 663 + /* Free a list of completion option populated by linenoiseAddCompletion(). */ 664 + static void freeCompletions(linenoiseCompletions *lc) { 665 + size_t i; 666 + for (i = 0; i < lc->len; i++) 667 + free(lc->cvec[i]); 668 + if (lc->cvec != NULL) 669 + free(lc->cvec); 670 + } 671 + 672 + /* Called by completeLine() and linenoiseShow() to render the current 673 + * edited line with the proposed completion. If the current completion table 674 + * is already available, it is passed as second argument, otherwise the 675 + * function will use the callback to obtain it. 676 + * 677 + * Flags are the same as refreshLine*(), that is REFRESH_* macros. */ 678 + static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) { 679 + /* Obtain the table of completions if the caller didn't provide one. */ 680 + linenoiseCompletions ctable = { 0, NULL }; 681 + if (lc == NULL) { 682 + completionCallback(ls->buf,&ctable); 683 + lc = &ctable; 684 + } 685 + 686 + /* Show the edited line with completion if possible, or just refresh. */ 687 + if (ls->completion_idx < lc->len) { 688 + struct linenoiseState saved = *ls; 689 + ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]); 690 + ls->buf = lc->cvec[ls->completion_idx]; 691 + refreshLineWithFlags(ls,flags); 692 + ls->len = saved.len; 693 + ls->pos = saved.pos; 694 + ls->buf = saved.buf; 695 + } else { 696 + refreshLineWithFlags(ls,flags); 697 + } 698 + 699 + /* Free the completions table if needed. */ 700 + if (lc != &ctable) freeCompletions(&ctable); 701 + } 702 + 703 + /* This is an helper function for linenoiseEdit*() and is called when the 704 + * user types the <tab> key in order to complete the string currently in the 705 + * input. 706 + * 707 + * The state of the editing is encapsulated into the pointed linenoiseState 708 + * structure as described in the structure definition. 709 + * 710 + * If the function returns non-zero, the caller should handle the 711 + * returned value as a byte read from the standard input, and process 712 + * it as usually: this basically means that the function may return a byte 713 + * read from the termianl but not processed. Otherwise, if zero is returned, 714 + * the input was consumed by the completeLine() function to navigate the 715 + * possible completions, and the caller should read for the next characters 716 + * from stdin. */ 717 + static int completeLine(struct linenoiseState *ls, int keypressed) { 718 + linenoiseCompletions lc = { 0, NULL }; 719 + int nwritten; 720 + char c = keypressed; 721 + 722 + completionCallback(ls->buf,&lc); 723 + if (lc.len == 0) { 724 + linenoiseBeep(); 725 + ls->in_completion = 0; 726 + } else { 727 + switch(c) { 728 + case 9: /* tab */ 729 + if (ls->in_completion == 0) { 730 + ls->in_completion = 1; 731 + ls->completion_idx = 0; 732 + } else { 733 + ls->completion_idx = (ls->completion_idx+1) % (lc.len+1); 734 + if (ls->completion_idx == lc.len) linenoiseBeep(); 735 + } 736 + c = 0; 737 + break; 738 + case 27: /* escape */ 739 + /* Re-show original buffer */ 740 + if (ls->completion_idx < lc.len) refreshLine(ls); 741 + ls->in_completion = 0; 742 + c = 0; 743 + break; 744 + default: 745 + /* Update buffer and return */ 746 + if (ls->completion_idx < lc.len) { 747 + nwritten = snprintf(ls->buf,ls->buflen,"%s", 748 + lc.cvec[ls->completion_idx]); 749 + ls->len = ls->pos = nwritten; 750 + } 751 + ls->in_completion = 0; 752 + break; 753 + } 754 + 755 + /* Show completion or original buffer */ 756 + if (ls->in_completion && ls->completion_idx < lc.len) { 757 + refreshLineWithCompletion(ls,&lc,REFRESH_ALL); 758 + } else { 759 + refreshLine(ls); 760 + } 761 + } 762 + 763 + freeCompletions(&lc); 764 + return c; /* Return last read character */ 765 + } 766 + 767 + /* Register a callback function to be called for tab-completion. */ 768 + void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { 769 + completionCallback = fn; 770 + } 771 + 772 + /* Register a hits function to be called to show hits to the user at the 773 + * right of the prompt. */ 774 + void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { 775 + hintsCallback = fn; 776 + } 777 + 778 + /* Register a function to free the hints returned by the hints callback 779 + * registered with linenoiseSetHintsCallback(). */ 780 + void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { 781 + freeHintsCallback = fn; 782 + } 783 + 784 + /* This function is used by the callback function registered by the user 785 + * in order to add completion options given the input string when the 786 + * user typed <tab>. See the example.c source code for a very easy to 787 + * understand example. */ 788 + void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { 789 + size_t len = strlen(str); 790 + char *copy, **cvec; 791 + 792 + copy = malloc(len+1); 793 + if (copy == NULL) return; 794 + memcpy(copy,str,len+1); 795 + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); 796 + if (cvec == NULL) { 797 + free(copy); 798 + return; 799 + } 800 + lc->cvec = cvec; 801 + lc->cvec[lc->len++] = copy; 802 + } 803 + 804 + /* =========================== Line editing ================================= */ 805 + 806 + /* We define a very simple "append buffer" structure, that is an heap 807 + * allocated string where we can append to. This is useful in order to 808 + * write all the escape sequences in a buffer and flush them to the standard 809 + * output in a single call, to avoid flickering effects. */ 810 + struct abuf { 811 + char *b; 812 + int len; 813 + }; 814 + 815 + static void abInit(struct abuf *ab) { 816 + ab->b = NULL; 817 + ab->len = 0; 818 + } 819 + 820 + static void abAppend(struct abuf *ab, const char *s, int len) { 821 + char *new = realloc(ab->b,ab->len+len); 822 + 823 + if (new == NULL) return; 824 + memcpy(new+ab->len,s,len); 825 + ab->b = new; 826 + ab->len += len; 827 + } 828 + 829 + static void abFree(struct abuf *ab) { 830 + free(ab->b); 831 + } 832 + 833 + /* Helper of refreshSingleLine() and refreshMultiLine() to show hints 834 + * to the right of the prompt. Now uses display widths for proper UTF-8. */ 835 + void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pwidth) { 836 + char seq[64]; 837 + size_t bufwidth = utf8StrWidth(l->buf, l->len); 838 + if (hintsCallback && pwidth + bufwidth < l->cols) { 839 + int color = -1, bold = 0; 840 + char *hint = hintsCallback(l->buf,&color,&bold); 841 + if (hint) { 842 + size_t hintlen = strlen(hint); 843 + size_t hintwidth = utf8StrWidth(hint, hintlen); 844 + size_t hintmaxwidth = l->cols - (pwidth + bufwidth); 845 + /* Truncate hint to fit, respecting UTF-8 boundaries. */ 846 + if (hintwidth > hintmaxwidth) { 847 + size_t i = 0, w = 0; 848 + while (i < hintlen) { 849 + size_t clen = utf8NextCharLen(hint, i, hintlen); 850 + int cwidth = utf8SingleCharWidth(hint + i, clen); 851 + if (w + cwidth > hintmaxwidth) break; 852 + w += cwidth; 853 + i += clen; 854 + } 855 + hintlen = i; 856 + } 857 + if (bold == 1 && color == -1) color = 37; 858 + if (color != -1 || bold != 0) 859 + snprintf(seq,64,"\033[%d;%d;49m",bold,color); 860 + else 861 + seq[0] = '\0'; 862 + abAppend(ab,seq,strlen(seq)); 863 + abAppend(ab,hint,hintlen); 864 + if (color != -1 || bold != 0) 865 + abAppend(ab,"\033[0m",4); 866 + /* Call the function to free the hint returned. */ 867 + if (freeHintsCallback) freeHintsCallback(hint); 868 + } 869 + } 870 + } 871 + 872 + /* Single line low level line refresh. 873 + * 874 + * Rewrite the currently edited line accordingly to the buffer content, 875 + * cursor position, and number of columns of the terminal. 876 + * 877 + * Flags is REFRESH_* macros. The function can just remove the old 878 + * prompt, just write it, or both. 879 + * 880 + * This function is UTF-8 aware and uses display widths (not byte counts) 881 + * for cursor positioning and horizontal scrolling. */ 882 + static void refreshSingleLine(struct linenoiseState *l, int flags) { 883 + char seq[64]; 884 + size_t pwidth = utf8StrWidth(l->prompt, l->plen); /* Prompt display width */ 885 + int fd = l->ofd; 886 + char *buf = l->buf; 887 + size_t len = l->len; /* Byte length of buffer to display */ 888 + size_t pos = l->pos; /* Byte position of cursor */ 889 + size_t poscol; /* Display column of cursor */ 890 + size_t lencol; /* Display width of buffer */ 891 + struct abuf ab; 892 + 893 + /* Calculate the display width up to cursor and total display width. */ 894 + poscol = utf8StrWidth(buf, pos); 895 + lencol = utf8StrWidth(buf, len); 896 + 897 + /* Scroll the buffer horizontally if cursor is past the right edge. 898 + * We need to trim full UTF-8 characters from the left until the 899 + * cursor position fits within the terminal width. */ 900 + while (pwidth + poscol >= l->cols) { 901 + size_t clen = utf8NextCharLen(buf, 0, len); 902 + int cwidth = utf8SingleCharWidth(buf, clen); 903 + buf += clen; 904 + len -= clen; 905 + pos -= clen; 906 + poscol -= cwidth; 907 + lencol -= cwidth; 908 + } 909 + 910 + /* Trim from the right if the line still doesn't fit. */ 911 + while (pwidth + lencol > l->cols) { 912 + size_t clen = utf8PrevCharLen(buf, len); 913 + int cwidth = utf8SingleCharWidth(buf + len - clen, clen); 914 + len -= clen; 915 + lencol -= cwidth; 916 + } 917 + 918 + abInit(&ab); 919 + /* Cursor to left edge */ 920 + snprintf(seq,sizeof(seq),"\r"); 921 + abAppend(&ab,seq,strlen(seq)); 922 + 923 + if (flags & REFRESH_WRITE) { 924 + /* Write the prompt and the current buffer content */ 925 + abAppend(&ab,l->prompt,l->plen); 926 + if (maskmode == 1) { 927 + /* In mask mode, we output one '*' per UTF-8 character, not byte */ 928 + size_t i = 0; 929 + while (i < len) { 930 + abAppend(&ab,"*",1); 931 + i += utf8NextCharLen(buf, i, len); 932 + } 933 + } else { 934 + abAppend(&ab,buf,len); 935 + } 936 + /* Show hints if any. */ 937 + refreshShowHints(&ab,l,pwidth); 938 + } 939 + 940 + /* Erase to right */ 941 + snprintf(seq,sizeof(seq),"\x1b[0K"); 942 + abAppend(&ab,seq,strlen(seq)); 943 + 944 + if (flags & REFRESH_WRITE) { 945 + /* Move cursor to original position (using display column, not byte). */ 946 + snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(poscol+pwidth)); 947 + abAppend(&ab,seq,strlen(seq)); 948 + } 949 + 950 + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 951 + abFree(&ab); 952 + } 953 + 954 + /* Multi line low level line refresh. 955 + * 956 + * Rewrite the currently edited line accordingly to the buffer content, 957 + * cursor position, and number of columns of the terminal. 958 + * 959 + * Flags is REFRESH_* macros. The function can just remove the old 960 + * prompt, just write it, or both. 961 + * 962 + * This function is UTF-8 aware and uses display widths for positioning. */ 963 + static void refreshMultiLine(struct linenoiseState *l, int flags) { 964 + char seq[64]; 965 + size_t pwidth = utf8StrWidth(l->prompt, l->plen); /* Prompt display width */ 966 + size_t bufwidth = utf8StrWidth(l->buf, l->len); /* Buffer display width */ 967 + size_t poswidth = utf8StrWidth(l->buf, l->pos); /* Cursor display width */ 968 + int rows = (pwidth+bufwidth+l->cols-1)/l->cols; /* rows used by current buf. */ 969 + int rpos = l->oldrpos; /* cursor relative row from previous refresh. */ 970 + int rpos2; /* rpos after refresh. */ 971 + int col; /* column position, zero-based. */ 972 + int old_rows = l->oldrows; 973 + int fd = l->ofd, j; 974 + struct abuf ab; 975 + 976 + l->oldrows = rows; 977 + 978 + /* First step: clear all the lines used before. To do so start by 979 + * going to the last row. */ 980 + abInit(&ab); 981 + 982 + if (flags & REFRESH_CLEAN) { 983 + if (old_rows-rpos > 0) { 984 + lndebug("go down %d", old_rows-rpos); 985 + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); 986 + abAppend(&ab,seq,strlen(seq)); 987 + } 988 + 989 + /* Now for every row clear it, go up. */ 990 + for (j = 0; j < old_rows-1; j++) { 991 + lndebug("clear+up"); 992 + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); 993 + abAppend(&ab,seq,strlen(seq)); 994 + } 995 + } 996 + 997 + if (flags & REFRESH_ALL) { 998 + /* Clean the top line. */ 999 + lndebug("clear"); 1000 + snprintf(seq,64,"\r\x1b[0K"); 1001 + abAppend(&ab,seq,strlen(seq)); 1002 + } 1003 + 1004 + if (flags & REFRESH_WRITE) { 1005 + /* Write the prompt and the current buffer content */ 1006 + abAppend(&ab,l->prompt,l->plen); 1007 + if (maskmode == 1) { 1008 + /* In mask mode, output one '*' per UTF-8 character, not byte */ 1009 + size_t i = 0; 1010 + while (i < l->len) { 1011 + abAppend(&ab,"*",1); 1012 + i += utf8NextCharLen(l->buf, i, l->len); 1013 + } 1014 + } else { 1015 + abAppend(&ab,l->buf,l->len); 1016 + } 1017 + 1018 + /* Show hints if any. */ 1019 + refreshShowHints(&ab,l,pwidth); 1020 + 1021 + /* If we are at the very end of the screen with our prompt, we need to 1022 + * emit a newline and move the prompt to the first column. */ 1023 + if (l->pos && 1024 + l->pos == l->len && 1025 + (poswidth+pwidth) % l->cols == 0) 1026 + { 1027 + lndebug("<newline>"); 1028 + abAppend(&ab,"\n",1); 1029 + snprintf(seq,64,"\r"); 1030 + abAppend(&ab,seq,strlen(seq)); 1031 + rows++; 1032 + if (rows > (int)l->oldrows) l->oldrows = rows; 1033 + } 1034 + 1035 + /* Move cursor to right position. */ 1036 + rpos2 = (pwidth+poswidth+l->cols)/l->cols; /* Current cursor relative row */ 1037 + lndebug("rpos2 %d", rpos2); 1038 + 1039 + /* Go up till we reach the expected position. */ 1040 + if (rows-rpos2 > 0) { 1041 + lndebug("go-up %d", rows-rpos2); 1042 + snprintf(seq,64,"\x1b[%dA", rows-rpos2); 1043 + abAppend(&ab,seq,strlen(seq)); 1044 + } 1045 + 1046 + /* Set column. */ 1047 + col = (pwidth+poswidth) % l->cols; 1048 + lndebug("set col %d", 1+col); 1049 + if (col) 1050 + snprintf(seq,64,"\r\x1b[%dC", col); 1051 + else 1052 + snprintf(seq,64,"\r"); 1053 + abAppend(&ab,seq,strlen(seq)); 1054 + } 1055 + 1056 + lndebug("\n"); 1057 + l->oldpos = l->pos; 1058 + if (flags & REFRESH_WRITE) l->oldrpos = rpos2; 1059 + 1060 + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 1061 + abFree(&ab); 1062 + } 1063 + 1064 + /* Calls the two low level functions refreshSingleLine() or 1065 + * refreshMultiLine() according to the selected mode. */ 1066 + static void refreshLineWithFlags(struct linenoiseState *l, int flags) { 1067 + if (mlmode) 1068 + refreshMultiLine(l,flags); 1069 + else 1070 + refreshSingleLine(l,flags); 1071 + } 1072 + 1073 + /* Utility function to avoid specifying REFRESH_ALL all the times. */ 1074 + static void refreshLine(struct linenoiseState *l) { 1075 + refreshLineWithFlags(l,REFRESH_ALL); 1076 + } 1077 + 1078 + /* Hide the current line, when using the multiplexing API. */ 1079 + void linenoiseHide(struct linenoiseState *l) { 1080 + if (mlmode) 1081 + refreshMultiLine(l,REFRESH_CLEAN); 1082 + else 1083 + refreshSingleLine(l,REFRESH_CLEAN); 1084 + } 1085 + 1086 + /* Show the current line, when using the multiplexing API. */ 1087 + void linenoiseShow(struct linenoiseState *l) { 1088 + if (l->in_completion) { 1089 + refreshLineWithCompletion(l,NULL,REFRESH_WRITE); 1090 + } else { 1091 + refreshLineWithFlags(l,REFRESH_WRITE); 1092 + } 1093 + } 1094 + 1095 + /* Insert the character(s) 'c' of length 'clen' at cursor current position. 1096 + * This handles both single-byte ASCII and multi-byte UTF-8 sequences. 1097 + * 1098 + * On error writing to the terminal -1 is returned, otherwise 0. */ 1099 + int linenoiseEditInsert(struct linenoiseState *l, const char *c, size_t clen) { 1100 + if (l->len + clen <= l->buflen) { 1101 + if (l->len == l->pos) { 1102 + /* Append at end of line. */ 1103 + memcpy(l->buf+l->pos, c, clen); 1104 + l->pos += clen; 1105 + l->len += clen; 1106 + l->buf[l->len] = '\0'; 1107 + if ((!mlmode && 1108 + utf8StrWidth(l->prompt,l->plen)+utf8StrWidth(l->buf,l->len) < l->cols && 1109 + !hintsCallback)) { 1110 + /* Avoid a full update of the line in the trivial case: 1111 + * single-width char, no hints, fits in one line. */ 1112 + if (maskmode == 1) { 1113 + if (write(l->ofd,"*",1) == -1) return -1; 1114 + } else { 1115 + if (write(l->ofd,c,clen) == -1) return -1; 1116 + } 1117 + } else { 1118 + refreshLine(l); 1119 + } 1120 + } else { 1121 + /* Insert in the middle of the line. */ 1122 + memmove(l->buf+l->pos+clen, l->buf+l->pos, l->len-l->pos); 1123 + memcpy(l->buf+l->pos, c, clen); 1124 + l->len += clen; 1125 + l->pos += clen; 1126 + l->buf[l->len] = '\0'; 1127 + refreshLine(l); 1128 + } 1129 + } 1130 + return 0; 1131 + } 1132 + 1133 + /* Move cursor on the left. Moves by one UTF-8 character, not byte. */ 1134 + void linenoiseEditMoveLeft(struct linenoiseState *l) { 1135 + if (l->pos > 0) { 1136 + l->pos -= utf8PrevCharLen(l->buf, l->pos); 1137 + refreshLine(l); 1138 + } 1139 + } 1140 + 1141 + /* Move cursor on the right. Moves by one UTF-8 character, not byte. */ 1142 + void linenoiseEditMoveRight(struct linenoiseState *l) { 1143 + if (l->pos != l->len) { 1144 + l->pos += utf8NextCharLen(l->buf, l->pos, l->len); 1145 + refreshLine(l); 1146 + } 1147 + } 1148 + 1149 + /* Move cursor to the start of the line. */ 1150 + void linenoiseEditMoveHome(struct linenoiseState *l) { 1151 + if (l->pos != 0) { 1152 + l->pos = 0; 1153 + refreshLine(l); 1154 + } 1155 + } 1156 + 1157 + /* Move cursor to the end of the line. */ 1158 + void linenoiseEditMoveEnd(struct linenoiseState *l) { 1159 + if (l->pos != l->len) { 1160 + l->pos = l->len; 1161 + refreshLine(l); 1162 + } 1163 + } 1164 + 1165 + /* Substitute the currently edited line with the next or previous history 1166 + * entry as specified by 'dir'. */ 1167 + #define LINENOISE_HISTORY_NEXT 0 1168 + #define LINENOISE_HISTORY_PREV 1 1169 + void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { 1170 + if (history_len > 1) { 1171 + /* Update the current history entry before to 1172 + * overwrite it with the next one. */ 1173 + free(history[history_len - 1 - l->history_index]); 1174 + history[history_len - 1 - l->history_index] = strdup(l->buf); 1175 + /* Show the new entry */ 1176 + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; 1177 + if (l->history_index < 0) { 1178 + l->history_index = 0; 1179 + return; 1180 + } else if (l->history_index >= history_len) { 1181 + l->history_index = history_len-1; 1182 + return; 1183 + } 1184 + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); 1185 + l->buf[l->buflen-1] = '\0'; 1186 + l->len = l->pos = strlen(l->buf); 1187 + refreshLine(l); 1188 + } 1189 + } 1190 + 1191 + /* Delete the character at the right of the cursor without altering the cursor 1192 + * position. Basically this is what happens with the "Delete" keyboard key. 1193 + * Now handles multi-byte UTF-8 characters. */ 1194 + void linenoiseEditDelete(struct linenoiseState *l) { 1195 + if (l->len > 0 && l->pos < l->len) { 1196 + size_t clen = utf8NextCharLen(l->buf, l->pos, l->len); 1197 + memmove(l->buf+l->pos, l->buf+l->pos+clen, l->len-l->pos-clen); 1198 + l->len -= clen; 1199 + l->buf[l->len] = '\0'; 1200 + refreshLine(l); 1201 + } 1202 + } 1203 + 1204 + /* Backspace implementation. Deletes the UTF-8 character before the cursor. */ 1205 + void linenoiseEditBackspace(struct linenoiseState *l) { 1206 + if (l->pos > 0 && l->len > 0) { 1207 + size_t clen = utf8PrevCharLen(l->buf, l->pos); 1208 + memmove(l->buf+l->pos-clen, l->buf+l->pos, l->len-l->pos); 1209 + l->pos -= clen; 1210 + l->len -= clen; 1211 + l->buf[l->len] = '\0'; 1212 + refreshLine(l); 1213 + } 1214 + } 1215 + 1216 + /* Delete the previous word, maintaining the cursor at the start of the 1217 + * current word. Handles UTF-8 by moving character-by-character. */ 1218 + void linenoiseEditDeletePrevWord(struct linenoiseState *l) { 1219 + size_t old_pos = l->pos; 1220 + size_t diff; 1221 + 1222 + /* Skip spaces before the word (move backwards by UTF-8 chars). */ 1223 + while (l->pos > 0 && l->buf[l->pos-1] == ' ') 1224 + l->pos -= utf8PrevCharLen(l->buf, l->pos); 1225 + /* Skip non-space characters (move backwards by UTF-8 chars). */ 1226 + while (l->pos > 0 && l->buf[l->pos-1] != ' ') 1227 + l->pos -= utf8PrevCharLen(l->buf, l->pos); 1228 + diff = old_pos - l->pos; 1229 + memmove(l->buf+l->pos, l->buf+old_pos, l->len-old_pos+1); 1230 + l->len -= diff; 1231 + refreshLine(l); 1232 + } 1233 + 1234 + /* This function is part of the multiplexed API of Linenoise, that is used 1235 + * in order to implement the blocking variant of the API but can also be 1236 + * called by the user directly in an event driven program. It will: 1237 + * 1238 + * 1. Initialize the linenoise state passed by the user. 1239 + * 2. Put the terminal in RAW mode. 1240 + * 3. Show the prompt. 1241 + * 4. Return control to the user, that will have to call linenoiseEditFeed() 1242 + * each time there is some data arriving in the standard input. 1243 + * 1244 + * The user can also call linenoiseEditHide() and linenoiseEditShow() if it 1245 + * is required to show some input arriving asyncronously, without mixing 1246 + * it with the currently edited line. 1247 + * 1248 + * When linenoiseEditFeed() returns non-NULL, the user finished with the 1249 + * line editing session (pressed enter CTRL-D/C): in this case the caller 1250 + * needs to call linenoiseEditStop() to put back the terminal in normal 1251 + * mode. This will not destroy the buffer, as long as the linenoiseState 1252 + * is still valid in the context of the caller. 1253 + * 1254 + * The function returns 0 on success, or -1 if writing to standard output 1255 + * fails. If stdin_fd or stdout_fd are set to -1, the default is to use 1256 + * STDIN_FILENO and STDOUT_FILENO. 1257 + */ 1258 + int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { 1259 + /* Populate the linenoise state that we pass to functions implementing 1260 + * specific editing functionalities. */ 1261 + l->in_completion = 0; 1262 + l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO; 1263 + l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO; 1264 + l->buf = buf; 1265 + l->buflen = buflen; 1266 + l->prompt = prompt; 1267 + l->plen = strlen(prompt); 1268 + l->oldpos = l->pos = 0; 1269 + l->len = 0; 1270 + 1271 + /* Enter raw mode. */ 1272 + if (enableRawMode(l->ifd) == -1) return -1; 1273 + 1274 + l->cols = getColumns(stdin_fd, stdout_fd); 1275 + l->oldrows = 0; 1276 + l->oldrpos = 1; /* Cursor starts on row 1. */ 1277 + l->history_index = 0; 1278 + 1279 + /* Buffer starts empty. */ 1280 + l->buf[0] = '\0'; 1281 + l->buflen--; /* Make sure there is always space for the nulterm */ 1282 + 1283 + /* If stdin is not a tty, stop here with the initialization. We 1284 + * will actually just read a line from standard input in blocking 1285 + * mode later, in linenoiseEditFeed(). */ 1286 + if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return 0; 1287 + 1288 + /* The latest history entry is always our current buffer, that 1289 + * initially is just an empty string. */ 1290 + linenoiseHistoryAdd(""); 1291 + 1292 + if (write(l->ofd,prompt,l->plen) == -1) return -1; 1293 + return 0; 1294 + } 1295 + 1296 + char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information."; 1297 + 1298 + /* This function is part of the multiplexed API of linenoise, see the top 1299 + * comment on linenoiseEditStart() for more information. Call this function 1300 + * each time there is some data to read from the standard input file 1301 + * descriptor. In the case of blocking operations, this function can just be 1302 + * called in a loop, and block. 1303 + * 1304 + * The function returns linenoiseEditMore to signal that line editing is still 1305 + * in progress, that is, the user didn't yet pressed enter / CTRL-D. Otherwise 1306 + * the function returns the pointer to the heap-allocated buffer with the 1307 + * edited line, that the user should free with linenoiseFree(). 1308 + * 1309 + * On special conditions, NULL is returned and errno is populated: 1310 + * 1311 + * EAGAIN if the user pressed Ctrl-C 1312 + * ENOENT if the user pressed Ctrl-D 1313 + * 1314 + * Some other errno: I/O error. 1315 + */ 1316 + char *linenoiseEditFeed(struct linenoiseState *l) { 1317 + /* Not a TTY, pass control to line reading without character 1318 + * count limits. */ 1319 + if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return linenoiseNoTTY(); 1320 + 1321 + char c; 1322 + int nread; 1323 + char seq[3]; 1324 + 1325 + nread = read(l->ifd,&c,1); 1326 + if (nread < 0) { 1327 + return (errno == EAGAIN || errno == EWOULDBLOCK) ? linenoiseEditMore : NULL; 1328 + } else if (nread == 0) { 1329 + return NULL; 1330 + } 1331 + 1332 + /* Only autocomplete when the callback is set. It returns < 0 when 1333 + * there was an error reading from fd. Otherwise it will return the 1334 + * character that should be handled next. */ 1335 + if ((l->in_completion || c == 9) && completionCallback != NULL) { 1336 + c = completeLine(l,c); 1337 + /* Return on errors */ 1338 + if (c < 0) return NULL; 1339 + /* Read next character when 0 */ 1340 + if (c == 0) return linenoiseEditMore; 1341 + } 1342 + 1343 + switch(c) { 1344 + case ENTER: /* enter */ 1345 + history_len--; 1346 + free(history[history_len]); 1347 + if (mlmode) linenoiseEditMoveEnd(l); 1348 + if (hintsCallback) { 1349 + /* Force a refresh without hints to leave the previous 1350 + * line as the user typed it after a newline. */ 1351 + linenoiseHintsCallback *hc = hintsCallback; 1352 + hintsCallback = NULL; 1353 + refreshLine(l); 1354 + hintsCallback = hc; 1355 + } 1356 + return strdup(l->buf); 1357 + case CTRL_C: /* ctrl-c */ 1358 + errno = EAGAIN; 1359 + return NULL; 1360 + case BACKSPACE: /* backspace */ 1361 + case 8: /* ctrl-h */ 1362 + linenoiseEditBackspace(l); 1363 + break; 1364 + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the 1365 + line is empty, act as end-of-file. */ 1366 + if (l->len > 0) { 1367 + linenoiseEditDelete(l); 1368 + } else { 1369 + history_len--; 1370 + free(history[history_len]); 1371 + errno = ENOENT; 1372 + return NULL; 1373 + } 1374 + break; 1375 + case CTRL_T: /* ctrl-t, swaps current character with previous. */ 1376 + /* Handle UTF-8: swap the two UTF-8 characters around cursor. */ 1377 + if (l->pos > 0 && l->pos < l->len) { 1378 + char tmp[32]; 1379 + size_t prevlen = utf8PrevCharLen(l->buf, l->pos); 1380 + size_t currlen = utf8NextCharLen(l->buf, l->pos, l->len); 1381 + size_t prevstart = l->pos - prevlen; 1382 + /* Copy current char to tmp, move previous char right, paste tmp. */ 1383 + memcpy(tmp, l->buf + l->pos, currlen); 1384 + memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen); 1385 + memcpy(l->buf + prevstart, tmp, currlen); 1386 + if (l->pos + currlen <= l->len) l->pos += currlen; 1387 + refreshLine(l); 1388 + } 1389 + break; 1390 + case CTRL_B: /* ctrl-b */ 1391 + linenoiseEditMoveLeft(l); 1392 + break; 1393 + case CTRL_F: /* ctrl-f */ 1394 + linenoiseEditMoveRight(l); 1395 + break; 1396 + case CTRL_P: /* ctrl-p */ 1397 + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); 1398 + break; 1399 + case CTRL_N: /* ctrl-n */ 1400 + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); 1401 + break; 1402 + case ESC: /* escape sequence */ 1403 + /* Read the next two bytes representing the escape sequence. 1404 + * Use two calls to handle slow terminals returning the two 1405 + * chars at different times. */ 1406 + if (read(l->ifd,seq,1) == -1) break; 1407 + if (read(l->ifd,seq+1,1) == -1) break; 1408 + 1409 + /* ESC [ sequences. */ 1410 + if (seq[0] == '[') { 1411 + if (seq[1] >= '0' && seq[1] <= '9') { 1412 + /* Extended escape, read additional byte. */ 1413 + if (read(l->ifd,seq+2,1) == -1) break; 1414 + if (seq[2] == '~') { 1415 + switch(seq[1]) { 1416 + case '3': /* Delete key. */ 1417 + linenoiseEditDelete(l); 1418 + break; 1419 + } 1420 + } 1421 + } else { 1422 + switch(seq[1]) { 1423 + case 'A': /* Up */ 1424 + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); 1425 + break; 1426 + case 'B': /* Down */ 1427 + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); 1428 + break; 1429 + case 'C': /* Right */ 1430 + linenoiseEditMoveRight(l); 1431 + break; 1432 + case 'D': /* Left */ 1433 + linenoiseEditMoveLeft(l); 1434 + break; 1435 + case 'H': /* Home */ 1436 + linenoiseEditMoveHome(l); 1437 + break; 1438 + case 'F': /* End*/ 1439 + linenoiseEditMoveEnd(l); 1440 + break; 1441 + } 1442 + } 1443 + } 1444 + 1445 + /* ESC O sequences. */ 1446 + else if (seq[0] == 'O') { 1447 + switch(seq[1]) { 1448 + case 'H': /* Home */ 1449 + linenoiseEditMoveHome(l); 1450 + break; 1451 + case 'F': /* End*/ 1452 + linenoiseEditMoveEnd(l); 1453 + break; 1454 + } 1455 + } 1456 + break; 1457 + default: 1458 + /* Handle UTF-8 multi-byte sequences. When we receive the first byte 1459 + * of a multi-byte UTF-8 character, read the remaining bytes to 1460 + * complete the sequence before inserting. */ 1461 + { 1462 + char utf8[4]; 1463 + int utf8len = utf8ByteLen(c); 1464 + utf8[0] = c; 1465 + if (utf8len > 1) { 1466 + /* Read remaining bytes of the UTF-8 sequence. */ 1467 + int i; 1468 + for (i = 1; i < utf8len; i++) { 1469 + if (read(l->ifd, utf8+i, 1) != 1) break; 1470 + } 1471 + } 1472 + if (linenoiseEditInsert(l, utf8, utf8len)) return NULL; 1473 + } 1474 + break; 1475 + case CTRL_U: /* Ctrl+u, delete the whole line. */ 1476 + l->buf[0] = '\0'; 1477 + l->pos = l->len = 0; 1478 + refreshLine(l); 1479 + break; 1480 + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ 1481 + l->buf[l->pos] = '\0'; 1482 + l->len = l->pos; 1483 + refreshLine(l); 1484 + break; 1485 + case CTRL_A: /* Ctrl+a, go to the start of the line */ 1486 + linenoiseEditMoveHome(l); 1487 + break; 1488 + case CTRL_E: /* ctrl+e, go to the end of the line */ 1489 + linenoiseEditMoveEnd(l); 1490 + break; 1491 + case CTRL_L: /* ctrl+l, clear screen */ 1492 + linenoiseClearScreen(); 1493 + refreshLine(l); 1494 + break; 1495 + case CTRL_W: /* ctrl+w, delete previous word */ 1496 + linenoiseEditDeletePrevWord(l); 1497 + break; 1498 + } 1499 + return linenoiseEditMore; 1500 + } 1501 + 1502 + /* This is part of the multiplexed linenoise API. See linenoiseEditStart() 1503 + * for more information. This function is called when linenoiseEditFeed() 1504 + * returns something different than NULL. At this point the user input 1505 + * is in the buffer, and we can restore the terminal in normal mode. */ 1506 + void linenoiseEditStop(struct linenoiseState *l) { 1507 + if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return; 1508 + disableRawMode(l->ifd); 1509 + printf("\n"); 1510 + } 1511 + 1512 + /* This just implements a blocking loop for the multiplexed API. 1513 + * In many applications that are not event-drivern, we can just call 1514 + * the blocking linenoise API, wait for the user to complete the editing 1515 + * and return the buffer. */ 1516 + static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) 1517 + { 1518 + struct linenoiseState l; 1519 + 1520 + /* Editing without a buffer is invalid. */ 1521 + if (buflen == 0) { 1522 + errno = EINVAL; 1523 + return NULL; 1524 + } 1525 + 1526 + linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt); 1527 + char *res; 1528 + while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); 1529 + linenoiseEditStop(&l); 1530 + return res; 1531 + } 1532 + 1533 + /* This special mode is used by linenoise in order to print scan codes 1534 + * on screen for debugging / development purposes. It is implemented 1535 + * by the linenoise_example program using the --keycodes option. */ 1536 + void linenoisePrintKeyCodes(void) { 1537 + char quit[4]; 1538 + 1539 + printf("Linenoise key codes debugging mode.\n" 1540 + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); 1541 + if (enableRawMode(STDIN_FILENO) == -1) return; 1542 + memset(quit,' ',4); 1543 + while(1) { 1544 + char c; 1545 + int nread; 1546 + 1547 + nread = read(STDIN_FILENO,&c,1); 1548 + if (nread <= 0) continue; 1549 + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ 1550 + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ 1551 + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; 1552 + 1553 + printf("'%c' %02x (%d) (type quit to exit)\n", 1554 + isprint(c) ? c : '?', (int)c, (int)c); 1555 + printf("\r"); /* Go left edge manually, we are in raw mode. */ 1556 + fflush(stdout); 1557 + } 1558 + disableRawMode(STDIN_FILENO); 1559 + } 1560 + 1561 + /* This function is called when linenoise() is called with the standard 1562 + * input file descriptor not attached to a TTY. So for example when the 1563 + * program using linenoise is called in pipe or with a file redirected 1564 + * to its standard input. In this case, we want to be able to return the 1565 + * line regardless of its length (by default we are limited to 4k). */ 1566 + static char *linenoiseNoTTY(void) { 1567 + char *line = NULL; 1568 + size_t len = 0, maxlen = 0; 1569 + 1570 + while(1) { 1571 + if (len == maxlen) { 1572 + if (maxlen == 0) maxlen = 16; 1573 + maxlen *= 2; 1574 + char *oldval = line; 1575 + line = realloc(line,maxlen); 1576 + if (line == NULL) { 1577 + if (oldval) free(oldval); 1578 + return NULL; 1579 + } 1580 + } 1581 + int c = fgetc(stdin); 1582 + if (c == EOF || c == '\n') { 1583 + if (c == EOF && len == 0) { 1584 + free(line); 1585 + return NULL; 1586 + } else { 1587 + line[len] = '\0'; 1588 + return line; 1589 + } 1590 + } else { 1591 + line[len] = c; 1592 + len++; 1593 + } 1594 + } 1595 + } 1596 + 1597 + /* The high level function that is the main API of the linenoise library. 1598 + * This function checks if the terminal has basic capabilities, just checking 1599 + * for a blacklist of stupid terminals, and later either calls the line 1600 + * editing function or uses dummy fgets() so that you will be able to type 1601 + * something even in the most desperate of the conditions. */ 1602 + char *linenoise(const char *prompt) { 1603 + char buf[LINENOISE_MAX_LINE]; 1604 + 1605 + if (!isatty(STDIN_FILENO) && !getenv("LINENOISE_ASSUME_TTY")) { 1606 + /* Not a tty: read from file / pipe. In this mode we don't want any 1607 + * limit to the line size, so we call a function to handle that. */ 1608 + return linenoiseNoTTY(); 1609 + } else if (isUnsupportedTerm()) { 1610 + size_t len; 1611 + 1612 + printf("%s",prompt); 1613 + fflush(stdout); 1614 + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; 1615 + len = strlen(buf); 1616 + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { 1617 + len--; 1618 + buf[len] = '\0'; 1619 + } 1620 + return strdup(buf); 1621 + } else { 1622 + char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt); 1623 + return retval; 1624 + } 1625 + } 1626 + 1627 + /* This is just a wrapper the user may want to call in order to make sure 1628 + * the linenoise returned buffer is freed with the same allocator it was 1629 + * created with. Useful when the main program is using an alternative 1630 + * allocator. */ 1631 + void linenoiseFree(void *ptr) { 1632 + if (ptr == linenoiseEditMore) return; // Protect from API misuse. 1633 + free(ptr); 1634 + } 1635 + 1636 + /* ================================ History ================================= */ 1637 + 1638 + /* Free the history, but does not reset it. Only used when we have to 1639 + * exit() to avoid memory leaks are reported by valgrind & co. */ 1640 + static void freeHistory(void) { 1641 + if (history) { 1642 + int j; 1643 + 1644 + for (j = 0; j < history_len; j++) 1645 + free(history[j]); 1646 + free(history); 1647 + } 1648 + } 1649 + 1650 + /* At exit we'll try to fix the terminal to the initial conditions. */ 1651 + static void linenoiseAtExit(void) { 1652 + disableRawMode(STDIN_FILENO); 1653 + freeHistory(); 1654 + } 1655 + 1656 + /* This is the API call to add a new entry in the linenoise history. 1657 + * It uses a fixed array of char pointers that are shifted (memmoved) 1658 + * when the history max length is reached in order to remove the older 1659 + * entry and make room for the new one, so it is not exactly suitable for huge 1660 + * histories, but will work well for a few hundred of entries. 1661 + * 1662 + * Using a circular buffer is smarter, but a bit more complex to handle. */ 1663 + int linenoiseHistoryAdd(const char *line) { 1664 + char *linecopy; 1665 + 1666 + if (history_max_len == 0) return 0; 1667 + 1668 + /* Initialization on first call. */ 1669 + if (history == NULL) { 1670 + history = malloc(sizeof(char*)*history_max_len); 1671 + if (history == NULL) return 0; 1672 + memset(history,0,(sizeof(char*)*history_max_len)); 1673 + } 1674 + 1675 + /* Don't add duplicated lines. */ 1676 + if (history_len && !strcmp(history[history_len-1], line)) return 0; 1677 + 1678 + /* Add an heap allocated copy of the line in the history. 1679 + * If we reached the max length, remove the older line. */ 1680 + linecopy = strdup(line); 1681 + if (!linecopy) return 0; 1682 + if (history_len == history_max_len) { 1683 + free(history[0]); 1684 + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); 1685 + history_len--; 1686 + } 1687 + history[history_len] = linecopy; 1688 + history_len++; 1689 + return 1; 1690 + } 1691 + 1692 + /* Set the maximum length for the history. This function can be called even 1693 + * if there is already some history, the function will make sure to retain 1694 + * just the latest 'len' elements if the new history length value is smaller 1695 + * than the amount of items already inside the history. */ 1696 + int linenoiseHistorySetMaxLen(int len) { 1697 + char **new; 1698 + 1699 + if (len < 1) return 0; 1700 + if (history) { 1701 + int tocopy = history_len; 1702 + 1703 + new = malloc(sizeof(char*)*len); 1704 + if (new == NULL) return 0; 1705 + 1706 + /* If we can't copy everything, free the elements we'll not use. */ 1707 + if (len < tocopy) { 1708 + int j; 1709 + 1710 + for (j = 0; j < tocopy-len; j++) free(history[j]); 1711 + tocopy = len; 1712 + } 1713 + memset(new,0,sizeof(char*)*len); 1714 + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); 1715 + free(history); 1716 + history = new; 1717 + } 1718 + history_max_len = len; 1719 + if (history_len > history_max_len) 1720 + history_len = history_max_len; 1721 + return 1; 1722 + } 1723 + 1724 + /* Save the history in the specified file. On success 0 is returned 1725 + * otherwise -1 is returned. */ 1726 + int linenoiseHistorySave(const char *filename) { 1727 + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); 1728 + FILE *fp; 1729 + int j; 1730 + 1731 + fp = fopen(filename,"w"); 1732 + umask(old_umask); 1733 + if (fp == NULL) return -1; 1734 + fchmod(fileno(fp),S_IRUSR|S_IWUSR); 1735 + for (j = 0; j < history_len; j++) 1736 + fprintf(fp,"%s\n",history[j]); 1737 + fclose(fp); 1738 + return 0; 1739 + } 1740 + 1741 + /* Load the history from the specified file. If the file does not exist 1742 + * zero is returned and no operation is performed. 1743 + * 1744 + * If the file exists and the operation succeeded 0 is returned, otherwise 1745 + * on error -1 is returned. */ 1746 + int linenoiseHistoryLoad(const char *filename) { 1747 + FILE *fp = fopen(filename,"r"); 1748 + char buf[LINENOISE_MAX_LINE]; 1749 + 1750 + if (fp == NULL) return -1; 1751 + 1752 + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { 1753 + char *p; 1754 + 1755 + p = strchr(buf,'\r'); 1756 + if (!p) p = strchr(buf,'\n'); 1757 + if (p) *p = '\0'; 1758 + linenoiseHistoryAdd(buf); 1759 + } 1760 + fclose(fp); 1761 + return 0; 1762 + }
+2
test/dune
··· 1 + (test 2 + (name test_bruit))
test/test_bruit.ml

This is a binary file and will not be displayed.