├── .github
├── CODEOWNERS
└── workflows
│ └── main.yml
├── .gitignore
├── CHANGES.md
├── Makefile
├── README.md
├── dune-project
├── example.gif
├── examples
├── dune
└── show_off.ml
├── linenoise.opam
└── src
├── dune
├── lNoise.ml
├── lNoise.mli
├── linenoise_src.c
├── linenoise_src.h
└── linenoise_stubs.c
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @c-cube
2 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | jobs:
8 | run:
9 | name: build
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - ubuntu-latest
15 | - macos-latest
16 | - macos-13
17 | setup-version:
18 | - v2
19 | - v3
20 | ocaml-compiler:
21 | - 4.06.x
22 | - 4.14.x
23 | - 5.1.x
24 | exclude:
25 | - os: ubuntu-latest
26 | setup-version: v2
27 | - os: macos-13
28 | setup-version: v3
29 | - os: macos-latest
30 | setup-version: v2
31 | - os: macos-latest
32 | ocaml-compiler: 4.06.x
33 | runs-on: ${{ matrix.os }}
34 | steps:
35 | - uses: actions/checkout@v4
36 | - uses: ocaml/setup-ocaml@v2
37 | if: matrix.setup-version == 'v2'
38 | with:
39 | ocaml-compiler: ${{ matrix.ocaml-compiler }}
40 | allow-prerelease-opam: true
41 | - uses: ocaml/setup-ocaml@v3
42 | if: matrix.setup-version == 'v3'
43 | with:
44 | ocaml-compiler: ${{ matrix.ocaml-compiler }}
45 | allow-prerelease-opam: true
46 | - run: opam pin -n .
47 | - run: opam install -t . --deps-only
48 | - run: opam exec -- dune build --ignore-promoted-rules
49 | - run: opam exec -- dune runtest --ignore-promoted-rules
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 | *.docdir
3 |
4 | history.txt
5 | *.cmi
6 | *.cmt
7 | *.cmo
8 | *.cma
9 | *.cmx
10 | *.o
11 | T
12 | setup.data
13 | setup.log
14 | *.install
15 | .merlin
16 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # 1.5.1
2 |
3 | - fix a deadlock from 1.5
4 |
5 | # 1.5
6 |
7 | - release runtime lock when calling `lnoise`
8 | - fix potential memleaks and use of deprecate parts of
9 | the OCaml C API
10 | - remove dependency on `result`
11 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all: build test
3 |
4 | build:
5 | @dune build @install
6 |
7 | test:
8 | @dune runtest --no-buffer --force
9 |
10 | example:
11 | @dune exec examples/show_off.exe
12 |
13 | clean:
14 | @dune clean
15 |
16 | doc:
17 | @dune build @doc
18 |
19 | .PHONY: all build test example clean doc
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Linenoise in OCaml
2 | --------------------
3 |
4 | [](https://github.com/ocaml-community/ocaml-linenoise/actions/workflows/main.yml)
5 |
6 | # Benefits
7 | 1. BSD licensed.
8 | 2. No system dependencies, no need for `readline` on your machine.
9 | 3. Related to 2, these bindings are self-contained, the source for
10 | `linenoise` is in this repo and compiled all together with the
11 | `OCaml`.
12 | 4. Written in OCaml + C.
13 | 5. Pretty cool hints feature, see the gif.
14 | 6. Additional features compared to linenoise, such as history search
15 |
16 | # Installation
17 |
18 | It is easy with `opam`
19 |
20 | ```shell
21 | $ opam install linenoise
22 | ```
23 |
24 | See the pretty
25 | documentation [here](https://ocaml-community.github.io/ocaml-linenoise/)
26 |
27 | # Example code
28 | This example is also included in the repo under examples:
29 |
30 |
31 |
32 |
33 |
34 |
35 | ```ocaml
36 | let rec user_input prompt cb =
37 | match LNoise.linenoise prompt with
38 | | None -> ()
39 | | Some v ->
40 | cb v;
41 | user_input prompt cb
42 |
43 | let () =
44 | (* LNoise.set_multiline true; *)
45 | LNoise.set_hints_callback (fun line ->
46 | if line <> "git remote add " then None
47 | else Some (" ",
48 | LNoise.Yellow,
49 | true)
50 | );
51 | LNoise.history_load ~filename:"history.txt" |> ignore;
52 | LNoise.history_set ~max_length:100 |> ignore;
53 | LNoise.set_completion_callback begin fun line_so_far ln_completions ->
54 | if line_so_far <> "" && line_so_far.[0] = 'h' then
55 | ["Hey"; "Howard"; "Hughes";"Hocus"]
56 | |> List.iter (LNoise.add_completion ln_completions);
57 | end;
58 | ["These are OCaml bindings to linenoise";
59 | "get tab completion with , type h then hit ";
60 | "type quit to exit gracefully";
61 | "By Edgar Aroutiounian\n"]
62 | |> List.iter print_endline;
63 | (fun from_user ->
64 | if from_user = "quit" then exit 0;
65 | LNoise.history_add from_user |> ignore;
66 | LNoise.history_save ~filename:"history.txt" |> ignore;
67 | Printf.sprintf "Got: %s" from_user |> print_endline
68 | )
69 | |> user_input "test_program> "
70 | ```
71 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 1.1)
2 | (name linenoise)
3 |
--------------------------------------------------------------------------------
/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ocaml-community/ocaml-linenoise/fbf0f85f8e3c7abf14ecb561edc70972bd632a44/example.gif
--------------------------------------------------------------------------------
/examples/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name show_off)
3 | (libraries linenoise)
4 | )
5 |
--------------------------------------------------------------------------------
/examples/show_off.ml:
--------------------------------------------------------------------------------
1 | let rec user_input prompt cb =
2 | match LNoise.linenoise prompt with
3 | | None -> ()
4 | | Some v ->
5 | cb v;
6 | user_input prompt cb
7 |
8 | let () =
9 | (* LNoise.set_multiline true; *)
10 | LNoise.set_hints_callback (fun line ->
11 | if line <> "git remote add " then None
12 | else Some (" ",
13 | LNoise.Yellow,
14 | true)
15 | );
16 | LNoise.history_load ~filename:"history.txt" |> ignore;
17 | LNoise.history_set ~max_length:100 |> ignore;
18 | LNoise.set_completion_callback begin fun line_so_far ln_completions ->
19 | if line_so_far <> "" && line_so_far.[0] = 'h' then
20 | ["Hey"; "Howard"; "Hughes";"Hocus"]
21 | |> List.iter (LNoise.add_completion ln_completions);
22 | end;
23 | ["These are OCaml bindings to linenoise";
24 | "get tab completion with , type h then hit ";
25 | "type quit to exit gracefully";
26 | "By Edgar Aroutiounian\n"]
27 | |> List.iter print_endline;
28 | (fun from_user ->
29 | if from_user = "quit" then exit 0;
30 | LNoise.history_add from_user |> ignore;
31 | LNoise.history_save ~filename:"history.txt" |> ignore;
32 | Printf.sprintf "Got: %s" from_user |> print_endline
33 | )
34 | |> user_input "test_program> "
35 |
--------------------------------------------------------------------------------
/linenoise.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | name: "linenoise"
3 | version: "1.5.1"
4 | synopsis: "Lightweight readline alternative"
5 | maintainer: "Simon Cruanes"
6 | authors: [ "Edgar Aroutiounian " "Simon Cruanes" ]
7 | license: "BSD-3-clause"
8 | homepage: "https://github.com/ocaml-community/ocaml-linenoise"
9 | dev-repo: "git+https://github.com/ocaml-community/ocaml-linenoise.git"
10 | bug-reports: "https://github.com/ocaml-community/ocaml-linenoise/issues"
11 | build: [
12 | ["dune" "build" "@install" "-p" name "-j" jobs]
13 | ["dune" "runtest" "-p" name] {with-test}
14 | ["dune" "build" "@doc" "-p" name] {with-doc}
15 | ]
16 | depends: [
17 | "dune" { >= "1.1" }
18 | "ocaml" { >= "4.06.0" }
19 | "odoc" {with-doc}
20 | ]
21 |
--------------------------------------------------------------------------------
/src/dune:
--------------------------------------------------------------------------------
1 |
2 | (library
3 | (name linenoise)
4 | (public_name linenoise)
5 | (modules LNoise)
6 | (wrapped false)
7 | (flags :standard -warn-error -3)
8 | (c_names linenoise_src linenoise_stubs))
9 |
--------------------------------------------------------------------------------
/src/lNoise.ml:
--------------------------------------------------------------------------------
1 |
2 | type completions
3 |
4 | external add_completion : completions -> string -> unit = "ml_add_completion"
5 |
6 | external linenoise : string -> string option = "ml_linenoise"
7 |
8 | external history_add_ : string -> int = "ml_history_add"
9 | external history_set_ : max_length:int -> int = "ml_history_set_maxlen"
10 | external history_save_ : filename:string -> int = "ml_history_save"
11 | external history_load_ : filename:string -> int = "ml_history_load"
12 |
13 | external catch_break : bool -> unit = "ml_catch_break"
14 |
15 | external setup_bridges : unit -> unit = "ml_setup_bridges"
16 |
17 | type hint_color = Red | Green | Yellow | Blue | Magenta | Cyan | White
18 |
19 | let completion_cb = ref (fun _ _ -> ())
20 | let hints_cb = ref (fun _ -> None)
21 |
22 | let set_completion_callback (f:string->completions->unit) : unit =
23 | completion_cb := f;
24 | Callback.register "lnoise_completion_cb" f
25 |
26 | let set_hints_callback (f:string -> (string*hint_color*bool) option) : unit =
27 | hints_cb := f;
28 | Callback.register "lnoise_hints_cb" f
29 |
30 | (* initialization: register [Sys.Break] and enable catch-break *)
31 | let () =
32 | setup_bridges();
33 | set_completion_callback !completion_cb;
34 | set_hints_callback !hints_cb;
35 | Callback.register_exception "sys_break" Sys.Break;
36 | catch_break true
37 |
38 | let history_add h =
39 | if history_add_ h = 0 then Error "Couldn't add to history"
40 | else Ok ()
41 |
42 | let history_set ~max_length =
43 | if history_set_ ~max_length = 0
44 | then Error "Couldn't set the max length of history"
45 | else Ok ()
46 |
47 | let history_save ~filename =
48 | if history_save_ ~filename = 0 then Ok ()
49 | else Error "Couldn't save"
50 |
51 | let history_load ~filename =
52 | if history_load_ ~filename = 0 then Ok ()
53 | else Error "Couldn't load the file"
54 |
55 | external clear_screen : unit -> unit = "ml_clearscreen"
56 | external set_multiline : bool -> unit = "ml_set_multiline"
57 | external print_keycodes : unit -> unit = "ml_printkeycodes"
58 |
--------------------------------------------------------------------------------
/src/lNoise.mli:
--------------------------------------------------------------------------------
1 | (** OCaml bindings to linenoise, functions that can fail use result
2 | type *)
3 |
4 | (** Abstract type of completions, given to your completion callback *)
5 | type completions
6 |
7 | (** This function is used by the callback function registered by the
8 | user in order to add completion options given the input string
9 | when the user typed . *)
10 | val add_completion : completions -> string -> unit
11 |
12 | (** Register the callback function that is called for upon
13 | tab-completion, aka when is hit in the terminal *)
14 | val set_completion_callback : (string -> completions -> unit) -> unit
15 |
16 | (** The high level function that is the main API of the linenoise
17 | library. This function checks if the terminal has basic
18 | capabilities, just checking for a blacklist of stupid terminals,
19 | and later either calls the line editing function or uses dummy
20 | fgets() so that you will be able to type something even in the
21 | most desperate of the conditions. *)
22 | val linenoise : string -> string option
23 |
24 | (** Add a string to the history *)
25 | val history_add : string -> (unit, string) result
26 |
27 | (** Set the maximum length for the history. This function can be
28 | called even if there is already some history, the function will
29 | make sure to retain just the latest 'len' elements if the new
30 | history length value is smaller than the amount of items already
31 | inside the history. *)
32 | val history_set : max_length:int -> (unit, string) result
33 |
34 | (** Save the history in the specified file *)
35 | val history_save : filename:string -> (unit, string) result
36 |
37 | (** Load the history from the specified file. *)
38 | val history_load : filename:string -> (unit, string) result
39 |
40 | (** Clear the screen; used to handle CTRL+L *)
41 | val clear_screen : unit -> unit
42 |
43 | (** If [true], [ctrl-c] during a call to {!linenoise}
44 | will raise [Sys.Break] instead of returning an empty string.
45 | @since 1.1 *)
46 | val catch_break : bool -> unit
47 |
48 | (** Set if to use or not use the multi line mode. *)
49 | val set_multiline : bool -> unit
50 |
51 | (** This special mode is used by linenoise in order to print scan
52 | codes on screen for debugging / development purposes. *)
53 | val print_keycodes : unit -> unit
54 |
55 | (** What color you want the hints to be. *)
56 | type hint_color = Red | Green | Yellow | Blue | Magenta | Cyan | White
57 |
58 | (** Set a hints callback, callback gets a string, aka the line input,
59 | and you get a chance to give a hint to the user. Example, imagine
60 | if user types git remote add, then you can give a hint of , see animated gif in source repo for clear example. Returned
63 | tuple represents the hint message, color, and whether it ought to
64 | be bold. *)
65 | val set_hints_callback : (string -> (string * hint_color * bool) option) -> unit
66 |
--------------------------------------------------------------------------------
/src/linenoise_src.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-2016, Salvatore Sanfilippo
14 | * Copyright (c) 2010-2013, Pieter Noordhuis
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+ combinations.
49 | * - Win32 support
50 | *
51 | * List of escape sequences used by this program, we do everything just
52 | * with three sequences. In order to be so cheap we may have some
53 | * flickering effect with some slow terminal, but the lesser sequences
54 | * the more compatible.
55 | *
56 | * EL (Erase Line)
57 | * Sequence: ESC [ n K
58 | * Effect: if n is 0 or missing, clear from cursor to end of line
59 | * Effect: if n is 1, clear from beginning of line to cursor
60 | * Effect: if n is 2, clear entire line
61 | *
62 | * CUF (CUrsor Forward)
63 | * Sequence: ESC [ n C
64 | * Effect: moves cursor forward n chars
65 | *
66 | * CUB (CUrsor Backward)
67 | * Sequence: ESC [ n D
68 | * Effect: moves cursor backward n chars
69 | *
70 | * The following is used to get the terminal width if getting
71 | * the width with the TIOCGWINSZ ioctl fails
72 | *
73 | * DSR (Device Status Report)
74 | * Sequence: ESC [ 6 n
75 | * Effect: reports the current cusor position as ESC [ n ; m R
76 | * where n is the row and m is the column
77 | *
78 | * When multi line mode is enabled, we also use an additional escape
79 | * sequence. However multi line editing is disabled by default.
80 | *
81 | * CUU (Cursor Up)
82 | * Sequence: ESC [ n A
83 | * Effect: moves cursor up of n chars.
84 | *
85 | * CUD (Cursor Down)
86 | * Sequence: ESC [ n B
87 | * Effect: moves cursor down of n chars.
88 | *
89 | * When linenoiseClearScreen() is called, two additional escape sequences
90 | * are used in order to clear the screen and position the cursor at home
91 | * position.
92 | *
93 | * CUP (Cursor position)
94 | * Sequence: ESC [ H
95 | * Effect: moves the cursor to upper left corner
96 | *
97 | * ED (Erase display)
98 | * Sequence: ESC [ 2 J
99 | * Effect: clear the whole screen
100 | *
101 | */
102 |
103 | #include
104 | #include
105 | #include
106 | #include
107 | #include
108 | #include
109 | #include
110 | #include
111 | #include
112 | #include
113 | #include
114 | #include
115 | #include "linenoise_src.h"
116 |
117 | #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
118 | #define LINENOISE_MAX_LINE 4096
119 | static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
120 | static linenoiseCompletionCallback *completionCallback = NULL;
121 | static linenoiseHintsCallback *hintsCallback = NULL;
122 | static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
123 |
124 | static struct termios orig_termios; /* In order to restore at exit.*/
125 | static int rawmode = 0; /* For atexit() function to check if restore is needed*/
126 | static int mlmode = 0; /* Multi line mode. Default is single line. */
127 | static int atexit_registered = 0; /* Register atexit just 1 time. */
128 | static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
129 | static int history_len = 0;
130 | static char **history = NULL;
131 |
132 | int linenoiseWasInterrupted = 0;
133 |
134 | /* The linenoiseState structure represents the state during line editing.
135 | * We pass this state to functions implementing specific editing
136 | * functionalities. */
137 | struct linenoiseState {
138 | int ifd; /* Terminal stdin file descriptor. */
139 | int ofd; /* Terminal stdout file descriptor. */
140 | char *buf; /* Edited line buffer. */
141 | size_t buflen; /* Edited line buffer size. */
142 | const char *prompt; /* Prompt to display. */
143 | size_t plen; /* Prompt length. */
144 | size_t pos; /* Current cursor position. */
145 | size_t oldpos; /* Previous refresh cursor position. */
146 | size_t len; /* Current edited line length. */
147 | size_t cols; /* Number of columns in terminal. */
148 | size_t maxrows; /* Maximum num of rows used so far (multiline mode) */
149 | int history_index; /* The history index we are currently editing. */
150 | };
151 |
152 | enum KEY_ACTION{
153 | KEY_NULL = 0, /* NULL */
154 | CTRL_A = 1, /* Ctrl+a */
155 | CTRL_B = 2, /* Ctrl-b */
156 | CTRL_C = 3, /* Ctrl-c */
157 | CTRL_D = 4, /* Ctrl-d */
158 | CTRL_E = 5, /* Ctrl-e */
159 | CTRL_F = 6, /* Ctrl-f */
160 | CTRL_G = 7, /* Ctrl-g */
161 | CTRL_H = 8, /* Ctrl-h */
162 | TAB = 9, /* Tab */
163 | CTRL_K = 11, /* Ctrl+k */
164 | CTRL_L = 12, /* Ctrl+l */
165 | ENTER = 13, /* Enter */
166 | CTRL_N = 14, /* Ctrl-n */
167 | CTRL_P = 16, /* Ctrl-p */
168 | CTRL_R = 18, /* Ctrl-r */
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 | static void refreshLinePrompt(struct linenoiseState *l, const char *prompt);
180 |
181 | /* Debugging macro. */
182 | #if 0
183 | FILE *lndebug_fp = NULL;
184 | #define lndebug(...) \
185 | do { \
186 | if (lndebug_fp == NULL) { \
187 | lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
188 | fprintf(lndebug_fp, \
189 | "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
190 | (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
191 | (int)l->maxrows,old_rows); \
192 | } \
193 | fprintf(lndebug_fp, ", " __VA_ARGS__); \
194 | fflush(lndebug_fp); \
195 | } while (0)
196 | #else
197 | #define lndebug(fmt, ...)
198 | #endif
199 |
200 | /* ======================= Low level terminal handling ====================== */
201 |
202 | /* Set if to use or not the multi line mode. */
203 | void linenoiseSetMultiLine(int ml) {
204 | mlmode = ml;
205 | }
206 |
207 | /* Return true if the terminal name is in the list of terminals we know are
208 | * not able to understand basic escape sequences. */
209 | static int isUnsupportedTerm(void) {
210 | char *term = getenv("TERM");
211 | int j;
212 |
213 | if (term == NULL) return 0;
214 | for (j = 0; unsupported_term[j]; j++)
215 | if (!strcasecmp(term,unsupported_term[j])) return 1;
216 | return 0;
217 | }
218 |
219 | /* Raw mode: 1960 magic shit. */
220 | static int enableRawMode(int fd) {
221 | struct termios raw;
222 |
223 | if (!isatty(STDIN_FILENO)) goto fatal;
224 | if (!atexit_registered) {
225 | atexit(linenoiseAtExit);
226 | atexit_registered = 1;
227 | }
228 | if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
229 |
230 | raw = orig_termios; /* modify the original mode */
231 | /* input modes: no break, no CR to NL, no parity check, no strip char,
232 | * no start/stop output control. */
233 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
234 | /* output modes - disable post processing */
235 | raw.c_oflag &= ~(OPOST);
236 | /* control modes - set 8 bit chars */
237 | raw.c_cflag |= (CS8);
238 | /* local modes - choing off, canonical off, no extended functions,
239 | * no signal chars (^Z,^C) */
240 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
241 | /* control chars - set return condition: min number of bytes and timer.
242 | * We want read to return every single byte, without timeout. */
243 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
244 |
245 | /* put terminal in raw mode after flushing */
246 | if (tcsetattr(fd,TCSADRAIN,&raw) < 0) goto fatal;
247 | rawmode = 1;
248 | return 0;
249 |
250 | fatal:
251 | errno = ENOTTY;
252 | return -1;
253 | }
254 |
255 | static void disableRawMode(int fd) {
256 | /* Don't even check the return value as it's too late. */
257 | if (rawmode && tcsetattr(fd,TCSADRAIN,&orig_termios) != -1)
258 | rawmode = 0;
259 | }
260 |
261 | /* Use the ESC [6n escape sequence to query the horizontal cursor position
262 | * and return it. On error -1 is returned, on success the position of the
263 | * cursor. */
264 | static int getCursorPosition(int ifd, int ofd) {
265 | char buf[32];
266 | int cols, rows;
267 | unsigned int i = 0;
268 |
269 | /* Report cursor location */
270 | if (write(ofd, "\x1b[6n", 4) != 4) return -1;
271 |
272 | /* Read the response: ESC [ rows ; cols R */
273 | while (i < sizeof(buf)-1) {
274 | if (read(ifd,buf+i,1) != 1) break;
275 | if (buf[i] == 'R') break;
276 | i++;
277 | }
278 | buf[i] = '\0';
279 |
280 | /* Parse it. */
281 | if (buf[0] != ESC || buf[1] != '[') return -1;
282 | if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
283 | return cols;
284 | }
285 |
286 | /* Try to get the number of columns in the current terminal, or assume 80
287 | * if it fails. */
288 | static int getColumns(int ifd, int ofd) {
289 | struct winsize ws;
290 |
291 | if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
292 | /* ioctl() failed. Try to query the terminal itself. */
293 | int start, cols;
294 |
295 | /* Get the initial position so we can restore it later. */
296 | start = getCursorPosition(ifd,ofd);
297 | if (start == -1) goto failed;
298 |
299 | /* Go to right margin and get position. */
300 | if (write(ofd,"\x1b[999C",6) != 6) goto failed;
301 | cols = getCursorPosition(ifd,ofd);
302 | if (cols == -1) goto failed;
303 |
304 | /* Restore position. */
305 | if (cols > start) {
306 | char seq[32];
307 | snprintf(seq,32,"\x1b[%dD",cols-start);
308 | if (write(ofd,seq,strlen(seq)) == -1) {
309 | /* Can't recover... */
310 | }
311 | }
312 | return cols;
313 | } else {
314 | return ws.ws_col;
315 | }
316 |
317 | failed:
318 | return 80;
319 | }
320 |
321 | /* Clear the screen. Used to handle ctrl+l */
322 | void linenoiseClearScreen(void) {
323 | if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
324 | /* nothing to do, just to avoid warning. */
325 | }
326 | }
327 |
328 | /* Beep, used for completion when there is nothing to complete or when all
329 | * the choices were already shown. */
330 | static void linenoiseBeep(void) {
331 | fprintf(stderr, "\x7");
332 | fflush(stderr);
333 | }
334 |
335 | /* ============================== Completion ================================ */
336 |
337 | /* Free a list of completion option populated by linenoiseAddCompletion(). */
338 | static void freeCompletions(linenoiseCompletions *lc) {
339 | size_t i;
340 | for (i = 0; i < lc->len; i++)
341 | free(lc->cvec[i]);
342 | if (lc->cvec != NULL)
343 | free(lc->cvec);
344 | }
345 |
346 | /* This is an helper function for linenoiseEdit() and is called when the
347 | * user types the key in order to complete the string currently in the
348 | * input.
349 | *
350 | * The state of the editing is encapsulated into the pointed linenoiseState
351 | * structure as described in the structure definition. */
352 | static int completeLine(struct linenoiseState *ls) {
353 | linenoiseCompletions lc = { 0, NULL };
354 | int nread, nwritten;
355 | char c = 0;
356 |
357 | completionCallback(ls->buf,&lc);
358 | if (lc.len == 0) {
359 | linenoiseBeep();
360 | } else {
361 | size_t stop = 0, i = 0;
362 |
363 | while(!stop) {
364 | /* Show completion or original buffer */
365 | if (i < lc.len) {
366 | struct linenoiseState saved = *ls;
367 |
368 | ls->len = ls->pos = strlen(lc.cvec[i]);
369 | ls->buf = lc.cvec[i];
370 | refreshLine(ls);
371 | ls->len = saved.len;
372 | ls->pos = saved.pos;
373 | ls->buf = saved.buf;
374 | } else {
375 | refreshLine(ls);
376 | }
377 |
378 | nread = read(ls->ifd,&c,1);
379 | if (nread <= 0) {
380 | freeCompletions(&lc);
381 | return -1;
382 | }
383 |
384 | switch(c) {
385 | case 9: /* tab */
386 | i = (i+1) % (lc.len+1);
387 | if (i == lc.len) linenoiseBeep();
388 | break;
389 | case 27: /* escape */
390 | /* Re-show original buffer */
391 | if (i < lc.len) refreshLine(ls);
392 | stop = 1;
393 | break;
394 | default:
395 | /* Update buffer and return */
396 | if (i < lc.len) {
397 | nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
398 | ls->len = ls->pos = nwritten;
399 | }
400 | stop = 1;
401 | break;
402 | }
403 | }
404 | }
405 |
406 | freeCompletions(&lc);
407 | return c; /* Return last read character */
408 | }
409 |
410 | /* Register a callback function to be called for tab-completion. */
411 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
412 | completionCallback = fn;
413 | }
414 |
415 | /* Register a hits function to be called to show hits to the user at the
416 | * right of the prompt. */
417 | void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
418 | hintsCallback = fn;
419 | }
420 |
421 | /* Register a function to free the hints returned by the hints callback
422 | * registered with linenoiseSetHintsCallback(). */
423 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
424 | freeHintsCallback = fn;
425 | }
426 |
427 | /* This function is used by the callback function registered by the user
428 | * in order to add completion options given the input string when the
429 | * user typed . See the example.c source code for a very easy to
430 | * understand example. */
431 | void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
432 | size_t len = strlen(str);
433 | char *copy, **cvec;
434 |
435 | copy = malloc(len+1);
436 | if (copy == NULL) return;
437 | memcpy(copy,str,len+1);
438 | cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
439 | if (cvec == NULL) {
440 | free(copy);
441 | return;
442 | }
443 | lc->cvec = cvec;
444 | lc->cvec[lc->len++] = copy;
445 | }
446 |
447 | /* =========================== Line editing ================================= */
448 |
449 | /* We define a very simple "append buffer" structure, that is an heap
450 | * allocated string where we can append to. This is useful in order to
451 | * write all the escape sequences in a buffer and flush them to the standard
452 | * output in a single call, to avoid flickering effects. */
453 | struct abuf {
454 | char *b;
455 | int len;
456 | };
457 |
458 | static void abInit(struct abuf *ab) {
459 | ab->b = NULL;
460 | ab->len = 0;
461 | }
462 |
463 | static void abAppend(struct abuf *ab, const char *s, int len) {
464 | char *new = realloc(ab->b,ab->len+len);
465 |
466 | if (new == NULL) return;
467 | memcpy(new+ab->len,s,len);
468 | ab->b = new;
469 | ab->len += len;
470 | }
471 |
472 | static void abFree(struct abuf *ab) {
473 | free(ab->b);
474 | }
475 |
476 | /* Helper of refreshSingleLine() and refreshMultiLine() to show hints
477 | * to the right of the prompt. */
478 | void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
479 | char seq[64];
480 | if (hintsCallback && plen+l->len < l->cols) {
481 | int color = -1, bold = 0;
482 | char *hint = hintsCallback(l->buf,&color,&bold);
483 | if (hint) {
484 | int hintlen = strlen(hint);
485 | int hintmaxlen = l->cols-(plen+l->len);
486 | if (hintlen > hintmaxlen) hintlen = hintmaxlen;
487 | if (bold == 1 && color == -1) color = 37;
488 | if (color != -1 || bold != 0)
489 | snprintf(seq,64,"\033[%d;%d;49m",bold,color);
490 | abAppend(ab,seq,strlen(seq));
491 | abAppend(ab,hint,hintlen);
492 | if (color != -1 || bold != 0)
493 | abAppend(ab,"\033[0m",4);
494 | /* Call the function to free the hint returned. */
495 | if (freeHintsCallback) freeHintsCallback(hint);
496 | }
497 | }
498 | }
499 |
500 | /* Single line low level line refresh.
501 | *
502 | * Rewrite the currently edited line accordingly to the buffer content,
503 | * cursor position, and number of columns of the terminal. */
504 | static void refreshSingleLine(struct linenoiseState *l, const char *prompt) {
505 | char seq[64];
506 | size_t plen = strlen(prompt);
507 | int fd = l->ofd;
508 | char *buf = l->buf;
509 | size_t len = l->len;
510 | size_t pos = l->pos;
511 | struct abuf ab;
512 |
513 | if (plen >= l->cols) {
514 | len=0; // not enough room
515 | plen = l->cols;
516 | }
517 |
518 | while((plen+pos) >= l->cols && len>0) {
519 | buf++;
520 | len--;
521 | pos--;
522 | }
523 | while (plen+len > l->cols && len>0) {
524 | len--;
525 | }
526 |
527 | abInit(&ab);
528 | /* Cursor to left edge */
529 | snprintf(seq,64,"\r");
530 | abAppend(&ab,seq,strlen(seq));
531 | /* Write the prompt and the current buffer content */
532 | abAppend(&ab,prompt,plen);
533 | if (len > 0)
534 | abAppend(&ab,buf,len);
535 | /* Show hits if any. */
536 | refreshShowHints(&ab,l,plen);
537 | /* Erase to right */
538 | snprintf(seq,64,"\x1b[0K");
539 | abAppend(&ab,seq,strlen(seq));
540 | /* Move cursor to original position. */
541 | snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
542 | abAppend(&ab,seq,strlen(seq));
543 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
544 | abFree(&ab);
545 | }
546 |
547 | /* Multi line low level line refresh.
548 | *
549 | * Rewrite the currently edited line accordingly to the buffer content,
550 | * cursor position, and number of columns of the terminal. */
551 | static void refreshMultiLine(struct linenoiseState *l, const char *prompt) {
552 | char seq[64];
553 | int plen = strlen(prompt);
554 | int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
555 | int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
556 | int rpos2; /* rpos after refresh. */
557 | int col; /* colum position, zero-based. */
558 | int old_rows = l->maxrows;
559 | size_t pos = l->pos;
560 | int fd = l->ofd, j;
561 | struct abuf ab;
562 |
563 | /* Update maxrows if needed. */
564 | if (rows > (int)l->maxrows) l->maxrows = rows;
565 |
566 | /* First step: clear all the lines used before. To do so start by
567 | * going to the last row. */
568 | abInit(&ab);
569 | if (old_rows-rpos > 0) {
570 | lndebug("go down %d", old_rows-rpos);
571 | snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
572 | abAppend(&ab,seq,strlen(seq));
573 | }
574 |
575 | /* Now for every row clear it, go up. */
576 | for (j = 0; j < old_rows-1; j++) {
577 | lndebug("clear+up");
578 | snprintf(seq,64,"\r\x1b[0K\x1b[1A");
579 | abAppend(&ab,seq,strlen(seq));
580 | }
581 |
582 | /* Clean the top line. */
583 | lndebug("clear");
584 | snprintf(seq,64,"\r\x1b[0K");
585 | abAppend(&ab,seq,strlen(seq));
586 |
587 | /* Write the prompt and the current buffer content */
588 | abAppend(&ab,prompt,strlen(prompt));
589 | abAppend(&ab,l->buf,l->len);
590 |
591 | /* Show hits if any. */
592 | refreshShowHints(&ab,l,plen);
593 |
594 | /* If we are at the very end of the screen with our prompt, we need to
595 | * emit a newline and move the prompt to the first column. */
596 | if (pos &&
597 | pos == l->len &&
598 | (pos+plen) % l->cols == 0)
599 | {
600 | lndebug("");
601 | abAppend(&ab,"\n",1);
602 | snprintf(seq,64,"\r");
603 | abAppend(&ab,seq,strlen(seq));
604 | rows++;
605 | if (rows > (int)l->maxrows) l->maxrows = rows;
606 | }
607 |
608 | /* Move cursor to right position. */
609 | rpos2 = (plen+pos+l->cols)/l->cols; /* current cursor relative row. */
610 | lndebug("rpos2 %d", rpos2);
611 |
612 | /* Go up till we reach the expected positon. */
613 | if (rows-rpos2 > 0) {
614 | lndebug("go-up %d", rows-rpos2);
615 | snprintf(seq,64,"\x1b[%dA", rows-rpos2);
616 | abAppend(&ab,seq,strlen(seq));
617 | }
618 |
619 | /* Set column. */
620 | col = (plen+(int)pos) % (int)l->cols;
621 | lndebug("set col %d", 1+col);
622 | if (col)
623 | snprintf(seq,64,"\r\x1b[%dC", col);
624 | else
625 | snprintf(seq,64,"\r");
626 | abAppend(&ab,seq,strlen(seq));
627 |
628 | lndebug("\n");
629 | l->oldpos = pos;
630 |
631 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
632 | abFree(&ab);
633 | }
634 |
635 | /* Calls the two low level functions refreshSingleLine() or
636 | * refreshMultiLine() according to the selected mode. */
637 | static void refreshLinePrompt(struct linenoiseState *l, const char *prompt) {
638 | if (mlmode)
639 | refreshMultiLine(l, prompt);
640 | else
641 | refreshSingleLine(l, prompt);
642 | }
643 |
644 | static void refreshLine(struct linenoiseState *l) {
645 | refreshLinePrompt(l, l->prompt);
646 | }
647 |
648 | /* Insert the character 'c' at cursor current position.
649 | *
650 | * On error writing to the terminal -1 is returned, otherwise 0. */
651 | int linenoiseEditInsert(struct linenoiseState *l, char c) {
652 | if (l->len < l->buflen) {
653 | if (l->len == l->pos) {
654 | l->buf[l->pos] = c;
655 | l->pos++;
656 | l->len++;
657 | l->buf[l->len] = '\0';
658 | if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
659 | /* Avoid a full update of the line in the
660 | * trivial case. */
661 | if (write(l->ofd,&c,1) == -1) return -1;
662 | } else {
663 | refreshLine(l);
664 | }
665 | } else {
666 | memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
667 | l->buf[l->pos] = c;
668 | l->len++;
669 | l->pos++;
670 | l->buf[l->len] = '\0';
671 | refreshLine(l);
672 | }
673 | }
674 | return 0;
675 | }
676 |
677 | /* Move cursor on the left. */
678 | void linenoiseEditMoveLeft(struct linenoiseState *l) {
679 | if (l->pos > 0) {
680 | l->pos--;
681 | refreshLine(l);
682 | }
683 | }
684 |
685 | /* Move cursor on the right. */
686 | void linenoiseEditMoveRight(struct linenoiseState *l) {
687 | if (l->pos != l->len) {
688 | l->pos++;
689 | refreshLine(l);
690 | }
691 | }
692 |
693 | /* Move cursor on the left */
694 | void linenoiseEditMovePrevWord(struct linenoiseState *l) {
695 | while (l->pos > 0 && l->buf[l->pos-1] == ' ')
696 | l->pos--;
697 | while (l->pos > 0 && l->buf[l->pos-1] != ' ')
698 | l->pos--;
699 | refreshLine(l);
700 | }
701 |
702 | /* Move cursor on the right. */
703 | void linenoiseEditMoveNextWord(struct linenoiseState *l) {
704 | while (l->pos < l->len && l->buf[l->pos-1] == ' ')
705 | l->pos++;
706 | while (l->pos < l->len && l->buf[l->pos-1] != ' ')
707 | l->pos++;
708 | refreshLine(l);
709 | }
710 |
711 | /* Move cursor to the start of the line. */
712 | void linenoiseEditMoveHome(struct linenoiseState *l) {
713 | if (l->pos != 0) {
714 | l->pos = 0;
715 | refreshLine(l);
716 | }
717 | }
718 |
719 | /* Move cursor to the end of the line. */
720 | void linenoiseEditMoveEnd(struct linenoiseState *l) {
721 | if (l->pos != l->len) {
722 | l->pos = l->len;
723 | refreshLine(l);
724 | }
725 | }
726 |
727 | /* Substitute the currently edited line with the next or previous history
728 | * entry as specified by 'dir'. */
729 | #define LINENOISE_HISTORY_NEXT 0
730 | #define LINENOISE_HISTORY_PREV 1
731 | void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
732 | if (history_len > 1) {
733 | /* Update the current history entry before to
734 | * overwrite it with the next one. */
735 | free(history[history_len - 1 - l->history_index]);
736 | history[history_len - 1 - l->history_index] = strdup(l->buf);
737 | /* Show the new entry */
738 | l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
739 | if (l->history_index < 0) {
740 | l->history_index = 0;
741 | return;
742 | } else if (l->history_index >= history_len) {
743 | l->history_index = history_len-1;
744 | return;
745 | }
746 | strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
747 | l->buf[l->buflen-1] = '\0';
748 | l->len = l->pos = strlen(l->buf);
749 | refreshLine(l);
750 | }
751 | }
752 |
753 | /* Delete the character at the right of the cursor without altering the cursor
754 | * position. Basically this is what happens with the "Delete" keyboard key. */
755 | void linenoiseEditDelete(struct linenoiseState *l) {
756 | if (l->len > 0 && l->pos < l->len) {
757 | memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
758 | l->len--;
759 | l->buf[l->len] = '\0';
760 | refreshLine(l);
761 | }
762 | }
763 |
764 | /* Backspace implementation. */
765 | void linenoiseEditBackspace(struct linenoiseState *l) {
766 | if (l->pos > 0 && l->len > 0) {
767 | memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
768 | l->pos--;
769 | l->len--;
770 | l->buf[l->len] = '\0';
771 | refreshLine(l);
772 | }
773 | }
774 |
775 | /* Delete the previous word, maintaining the cursor at the start of the
776 | * current word. */
777 | void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
778 | size_t old_pos = l->pos;
779 | size_t diff;
780 |
781 | while (l->pos > 0 && l->buf[l->pos-1] == ' ')
782 | l->pos--;
783 | while (l->pos > 0 && l->buf[l->pos-1] != ' ')
784 | l->pos--;
785 | diff = old_pos - l->pos;
786 | memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
787 | l->len -= diff;
788 | refreshLine(l);
789 | }
790 |
791 | /* Delete the next word, maintaining the cursor at the same position */
792 | void linenoiseEditDeleteNextWord(struct linenoiseState *l) {
793 | size_t next_pos = l->pos;
794 | size_t diff;
795 |
796 | while (next_pos < l->len && l->buf[next_pos] == ' ')
797 | next_pos++;
798 | while (next_pos < l->len && l->buf[next_pos] != ' ')
799 | next_pos++;
800 | diff = next_pos - l->pos;
801 | memmove(l->buf+l->pos,l->buf+next_pos,l->len-next_pos+1);
802 | l->len -= diff;
803 | refreshLine(l);
804 | }
805 |
806 | void linenoiseReverseIncrementalSearch(struct linenoiseState *l) {
807 |
808 | char search_buf[LINENOISE_MAX_LINE];
809 | char search_prompt[LINENOISE_MAX_LINE];
810 | int search_len = 0;
811 | int search_pos = history_len - 1;
812 | int search_dir = -1;
813 | char* prompt;
814 |
815 | int has_match = 1;
816 |
817 | // backup of current input
818 | char *buf;
819 | {
820 | size_t len = 1+ strlen(l->buf);
821 | buf = malloc(len);
822 | if (buf == NULL) return;
823 | memcpy(buf, l->buf, len);
824 | }
825 |
826 | search_buf[0] = 0;
827 |
828 | while (1) {
829 |
830 | if (!has_match)
831 | prompt = "(failed-reverse-i-search)`%s': ";
832 | else
833 | prompt = "(reverse-i-search)`%s': ";
834 |
835 | if (!snprintf(search_prompt, sizeof(search_prompt), prompt, search_buf)) {
836 | linenoiseBeep();
837 | break;
838 | } else {
839 | search_prompt[sizeof(search_prompt)-1] = 0; // crop
840 | }
841 |
842 | l->pos = 0;
843 | refreshLinePrompt(l, search_prompt);
844 |
845 | char c;
846 | int new_char = 0;
847 |
848 | if (read(l->ifd, &c, 1) <= 0) {
849 | l->pos = l->len = snprintf(l->buf, l->buflen, "%s", buf);
850 | l->buf[l->buflen-1] = 0;
851 | refreshLine(l);
852 | free(buf);
853 | return;
854 | }
855 |
856 | switch(c) {
857 | case BACKSPACE:
858 | case CTRL_H:
859 | if (search_len > 0) {
860 | search_buf[--search_len] = 0;
861 | search_pos = history_len - 1;
862 | } else
863 | linenoiseBeep();
864 | break;
865 | case CTRL_N:
866 | case CTRL_R:
867 | search_dir = -1;
868 | if (search_pos >= history_len)
869 | search_pos = history_len - 1;
870 | break;
871 | case CTRL_P:
872 | search_dir = 1;
873 | if (search_pos < 0)
874 | search_pos = 0;
875 | break;
876 | case ESC:
877 | case CTRL_G:
878 | l->pos = l->len = snprintf(l->buf, l->buflen, "%s", buf);
879 | l->buf[l->buflen-1] = 0;
880 | free(buf);
881 | refreshLine(l);
882 | return;
883 | case ENTER:
884 | free(buf);
885 | l->pos = l->len;
886 | refreshLine(l);
887 | return;
888 | default:
889 | new_char = 1;
890 | search_buf[search_len] = c;
891 | search_buf[++search_len] = 0;
892 | search_pos = history_len - 1;
893 | break;
894 | }
895 |
896 | has_match = 0;
897 |
898 | if (strlen(search_buf) > 0) {
899 | for (; search_pos >= 0 && search_pos < history_len; search_pos += search_dir) {
900 | if (strstr(history[search_pos], search_buf) && (new_char || strcmp(history[search_pos], l->buf))) {
901 | has_match = 1;
902 | l->len = snprintf(l->buf, l->buflen, "%s", history[search_pos]);
903 | break;
904 | }
905 | }
906 | if (!has_match) {
907 | linenoiseBeep();
908 | // forbid writes if the line is too long
909 | if (search_len > 0 && new_char && search_len+1 >= sizeof(search_buf))
910 | search_buf[--search_len] = 0;
911 | }
912 | }
913 | }
914 | }
915 |
916 | /* This function is the core of the line editing capability of linenoise.
917 | * It expects 'fd' to be already in "raw mode" so that every key pressed
918 | * will be returned ASAP to read().
919 | *
920 | * The resulting string is put into 'buf' when the user type enter, or
921 | * when ctrl+d is typed.
922 | *
923 | * The function returns the length of the current buffer. */
924 | static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
925 | {
926 | struct linenoiseState l;
927 |
928 | /* Populate the linenoise state that we pass to functions implementing
929 | * specific editing functionalities. */
930 | l.ifd = stdin_fd;
931 | l.ofd = stdout_fd;
932 | l.buf = buf;
933 | l.buflen = buflen;
934 | l.prompt = prompt;
935 | l.plen = strlen(prompt);
936 | l.oldpos = l.pos = 0;
937 | l.len = 0;
938 | l.cols = getColumns(stdin_fd, stdout_fd);
939 | l.maxrows = 0;
940 | l.history_index = 0;
941 |
942 | /* Buffer starts empty. */
943 | l.buf[0] = '\0';
944 | l.buflen--; /* Make sure there is always space for the nulterm */
945 |
946 | /* The latest history entry is always our current buffer, that
947 | * initially is just an empty string. */
948 | linenoiseHistoryAdd("");
949 |
950 | if (write(l.ofd,prompt,l.plen) == -1) return -1;
951 | while(1) {
952 | char c;
953 | int nread;
954 | char seq[5];
955 |
956 | nread = read(l.ifd,&c,1);
957 | if (nread <= 0) return l.len;
958 |
959 | /* Only autocomplete when the callback is set. It returns < 0 when
960 | * there was an error reading from fd. Otherwise it will return the
961 | * character that should be handled next. */
962 | if (c == 9 && completionCallback != NULL) {
963 | c = completeLine(&l);
964 | /* Return on errors */
965 | if (c < 0) return l.len;
966 | /* Read next character when 0 */
967 | if (c == 0) continue;
968 | }
969 |
970 | switch(c) {
971 | case ENTER: /* enter */
972 | history_len--;
973 | free(history[history_len]);
974 | if (mlmode) linenoiseEditMoveEnd(&l);
975 | if (hintsCallback) {
976 | /* Force a refresh without hints to leave the previous
977 | * line as the user typed it after a newline. */
978 | linenoiseHintsCallback *hc = hintsCallback;
979 | hintsCallback = NULL;
980 | refreshLine(&l);
981 | hintsCallback = hc;
982 | }
983 | return (int)l.len;
984 | case CTRL_C: /* ctrl-c */
985 | errno = EAGAIN;
986 | linenoiseWasInterrupted = 1;
987 | return -1;
988 | case BACKSPACE: /* backspace */
989 | case 8: /* ctrl-h */
990 | linenoiseEditBackspace(&l);
991 | break;
992 | case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
993 | line is empty, act as end-of-file. */
994 | if (l.len > 0) {
995 | linenoiseEditDelete(&l);
996 | } else {
997 | history_len--;
998 | free(history[history_len]);
999 | return -1;
1000 | }
1001 | break;
1002 | case CTRL_T: /* ctrl-t, swaps current character with previous. */
1003 | if (l.pos > 0 && l.pos < l.len) {
1004 | int aux = buf[l.pos-1];
1005 | buf[l.pos-1] = buf[l.pos];
1006 | buf[l.pos] = aux;
1007 | if (l.pos != l.len-1) l.pos++;
1008 | refreshLine(&l);
1009 | }
1010 | break;
1011 | case CTRL_B: /* ctrl-b */
1012 | linenoiseEditMoveLeft(&l);
1013 | break;
1014 | case CTRL_F: /* ctrl-f */
1015 | linenoiseEditMoveRight(&l);
1016 | break;
1017 | case CTRL_P: /* ctrl-p */
1018 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
1019 | break;
1020 | case CTRL_R: /* ctrl-r */
1021 | linenoiseReverseIncrementalSearch(&l);
1022 | break;
1023 | case CTRL_N: /* ctrl-n */
1024 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
1025 | break;
1026 | case ESC: /* escape sequence */
1027 | /* Read the next byte representing the escape sequence */
1028 | if (read(l.ifd,seq,1) == -1) break;
1029 |
1030 | /* alt-b, alt-f, alt-d, alt-backspace */
1031 | if (seq[0] == 'b') {
1032 | linenoiseEditMovePrevWord(&l);
1033 | break;
1034 | } else if (seq[0] == 'f') {
1035 | linenoiseEditMoveNextWord(&l);
1036 | break;
1037 | } else if (seq[0] == 'd') {
1038 | linenoiseEditDeleteNextWord(&l);
1039 | break;
1040 | } else if (seq[0] == 127) { /* backspace */
1041 | linenoiseEditDeletePrevWord(&l);
1042 | break;
1043 | }
1044 |
1045 | /* Read a second byte */
1046 | if (read(l.ifd,seq+1,1) == -1) break;
1047 |
1048 | /* ESC [ sequences. */
1049 | if (seq[0] == '[') {
1050 | if (seq[1] >= '0' && seq[1] <= '9') {
1051 | /* Extended escape, read additional byte. */
1052 | if (read(l.ifd,seq+2,1) == -1) break;
1053 | if (seq[2] == '~') {
1054 | switch(seq[1]) {
1055 | case '3': /* Delete key. */
1056 | linenoiseEditDelete(&l);
1057 | break;
1058 | }
1059 | } else if (seq[2] == ';') {
1060 | /* read additional 2 bytes */
1061 | if (read(l.ifd,seq+3,1) == -1) break;
1062 | if (read(l.ifd,seq+4,1) == -1) break;
1063 | if (seq[3] == '5') {
1064 | switch (seq[4]) {
1065 | case 'D': /* ctrl-left */
1066 | linenoiseEditMovePrevWord(&l);
1067 | break;
1068 | case 'C': /* ctrl-right */
1069 | linenoiseEditMoveNextWord(&l);
1070 | break;
1071 | }
1072 | }
1073 | }
1074 | } else {
1075 | switch(seq[1]) {
1076 | case 'A': /* Up */
1077 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
1078 | break;
1079 | case 'B': /* Down */
1080 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
1081 | break;
1082 | case 'C': /* Right */
1083 | linenoiseEditMoveRight(&l);
1084 | break;
1085 | case 'D': /* Left */
1086 | linenoiseEditMoveLeft(&l);
1087 | break;
1088 | case 'H': /* Home */
1089 | linenoiseEditMoveHome(&l);
1090 | break;
1091 | case 'F': /* End*/
1092 | linenoiseEditMoveEnd(&l);
1093 | break;
1094 | }
1095 | }
1096 | }
1097 |
1098 | /* ESC O sequences. */
1099 | else if (seq[0] == 'O') {
1100 | switch(seq[1]) {
1101 | case 'H': /* Home */
1102 | linenoiseEditMoveHome(&l);
1103 | break;
1104 | case 'F': /* End*/
1105 | linenoiseEditMoveEnd(&l);
1106 | break;
1107 | }
1108 | }
1109 | break;
1110 | default:
1111 | if (linenoiseEditInsert(&l,c)) return -1;
1112 | break;
1113 | case CTRL_U: /* Ctrl+u, delete the whole line. */
1114 | buf[0] = '\0';
1115 | l.pos = l.len = 0;
1116 | refreshLine(&l);
1117 | break;
1118 | case CTRL_K: /* Ctrl+k, delete from current to end of line. */
1119 | buf[l.pos] = '\0';
1120 | l.len = l.pos;
1121 | refreshLine(&l);
1122 | break;
1123 | case CTRL_A: /* Ctrl+a, go to the start of the line */
1124 | linenoiseEditMoveHome(&l);
1125 | break;
1126 | case CTRL_E: /* ctrl+e, go to the end of the line */
1127 | linenoiseEditMoveEnd(&l);
1128 | break;
1129 | case CTRL_L: /* ctrl+l, clear screen */
1130 | linenoiseClearScreen();
1131 | refreshLine(&l);
1132 | break;
1133 | case CTRL_W: /* ctrl+w, delete previous word */
1134 | linenoiseEditDeletePrevWord(&l);
1135 | break;
1136 | }
1137 | }
1138 | return l.len;
1139 | }
1140 |
1141 | /* This special mode is used by linenoise in order to print scan codes
1142 | * on screen for debugging / development purposes. It is implemented
1143 | * by the linenoise_example program using the --keycodes option. */
1144 | void linenoisePrintKeyCodes(void) {
1145 | char quit[4];
1146 |
1147 | printf("Linenoise key codes debugging mode.\n"
1148 | "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
1149 | if (enableRawMode(STDIN_FILENO) == -1) return;
1150 | memset(quit,' ',4);
1151 | while(1) {
1152 | char c;
1153 | int nread;
1154 |
1155 | nread = read(STDIN_FILENO,&c,1);
1156 | if (nread <= 0) continue;
1157 | memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
1158 | quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
1159 | if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
1160 |
1161 | printf("'%c' %02x (%d) (type quit to exit)\n",
1162 | isprint(c) ? c : '?', (int)c, (int)c);
1163 | printf("\r"); /* Go left edge manually, we are in raw mode. */
1164 | fflush(stdout);
1165 | }
1166 | disableRawMode(STDIN_FILENO);
1167 | }
1168 |
1169 | /* This function calls the line editing function linenoiseEdit() using
1170 | * the STDIN file descriptor set in raw mode. */
1171 | static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
1172 | int count;
1173 |
1174 | if (buflen == 0) {
1175 | errno = EINVAL;
1176 | return -1;
1177 | }
1178 |
1179 | if (enableRawMode(STDIN_FILENO) == -1) return -1;
1180 | count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
1181 | disableRawMode(STDIN_FILENO);
1182 | printf("\n");
1183 | return count;
1184 | }
1185 |
1186 | /* This function is called when linenoise() is called with the standard
1187 | * input file descriptor not attached to a TTY. So for example when the
1188 | * program using linenoise is called in pipe or with a file redirected
1189 | * to its standard input. In this case, we want to be able to return the
1190 | * line regardless of its length (by default we are limited to 4k). */
1191 | static char *linenoiseNoTTY(void) {
1192 | char *line = NULL;
1193 | size_t len = 0, maxlen = 0;
1194 |
1195 | while(1) {
1196 | if (len == maxlen) {
1197 | if (maxlen == 0) maxlen = 16;
1198 | maxlen *= 2;
1199 | char *oldval = line;
1200 | line = realloc(line,maxlen);
1201 | if (line == NULL) {
1202 | if (oldval) free(oldval);
1203 | return NULL;
1204 | }
1205 | }
1206 | int c = fgetc(stdin);
1207 | if (c == EOF || c == '\n') {
1208 | if (c == EOF && len == 0) {
1209 | free(line);
1210 | return NULL;
1211 | } else {
1212 | line[len] = '\0';
1213 | return line;
1214 | }
1215 | } else {
1216 | line[len] = c;
1217 | len++;
1218 | }
1219 | }
1220 | }
1221 |
1222 | /* The high level function that is the main API of the linenoise library.
1223 | * This function checks if the terminal has basic capabilities, just checking
1224 | * for a blacklist of stupid terminals, and later either calls the line
1225 | * editing function or uses dummy fgets() so that you will be able to type
1226 | * something even in the most desperate of the conditions. */
1227 | char *linenoise(const char *prompt) {
1228 | char buf[LINENOISE_MAX_LINE];
1229 | int count;
1230 |
1231 | if (!isatty(STDIN_FILENO)) {
1232 | /* Not a tty: read from file / pipe. In this mode we don't want any
1233 | * limit to the line size, so we call a function to handle that. */
1234 | return linenoiseNoTTY();
1235 | } else if (isUnsupportedTerm()) {
1236 | size_t len;
1237 |
1238 | printf("%s",prompt);
1239 | fflush(stdout);
1240 | if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
1241 | len = strlen(buf);
1242 | while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
1243 | len--;
1244 | buf[len] = '\0';
1245 | }
1246 | return strdup(buf);
1247 | } else {
1248 | count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
1249 | if (count == -1) return NULL;
1250 | return strdup(buf);
1251 | }
1252 | }
1253 |
1254 | /* This is just a wrapper the user may want to call in order to make sure
1255 | * the linenoise returned buffer is freed with the same allocator it was
1256 | * created with. Useful when the main program is using an alternative
1257 | * allocator. */
1258 | void linenoiseFree(void *ptr) {
1259 | free(ptr);
1260 | }
1261 |
1262 | /* ================================ History ================================= */
1263 |
1264 | /* Free the history, but does not reset it. Only used when we have to
1265 | * exit() to avoid memory leaks are reported by valgrind & co. */
1266 | static void freeHistory(void) {
1267 | if (history) {
1268 | int j;
1269 |
1270 | for (j = 0; j < history_len; j++)
1271 | free(history[j]);
1272 | free(history);
1273 | }
1274 | }
1275 |
1276 | /* At exit we'll try to fix the terminal to the initial conditions. */
1277 | static void linenoiseAtExit(void) {
1278 | disableRawMode(STDIN_FILENO);
1279 | freeHistory();
1280 | }
1281 |
1282 | /* This is the API call to add a new entry in the linenoise history.
1283 | * It uses a fixed array of char pointers that are shifted (memmoved)
1284 | * when the history max length is reached in order to remove the older
1285 | * entry and make room for the new one, so it is not exactly suitable for huge
1286 | * histories, but will work well for a few hundred of entries.
1287 | *
1288 | * Using a circular buffer is smarter, but a bit more complex to handle. */
1289 | int linenoiseHistoryAdd(const char *line) {
1290 | char *linecopy;
1291 |
1292 | if (history_max_len == 0) return 0;
1293 |
1294 | /* Initialization on first call. */
1295 | if (history == NULL) {
1296 | history = malloc(sizeof(char*)*history_max_len);
1297 | if (history == NULL) return 0;
1298 | memset(history,0,(sizeof(char*)*history_max_len));
1299 | }
1300 |
1301 | /* Don't add duplicated lines. */
1302 | if (history_len && !strcmp(history[history_len-1], line)) return 0;
1303 |
1304 | /* Add an heap allocated copy of the line in the history.
1305 | * If we reached the max length, remove the older line. */
1306 | linecopy = strdup(line);
1307 | if (!linecopy) return 0;
1308 | if (history_len == history_max_len) {
1309 | free(history[0]);
1310 | memmove(history,history+1,sizeof(char*)*(history_max_len-1));
1311 | history_len--;
1312 | }
1313 | history[history_len] = linecopy;
1314 | history_len++;
1315 | return 1;
1316 | }
1317 |
1318 | /* Set the maximum length for the history. This function can be called even
1319 | * if there is already some history, the function will make sure to retain
1320 | * just the latest 'len' elements if the new history length value is smaller
1321 | * than the amount of items already inside the history. */
1322 | int linenoiseHistorySetMaxLen(int len) {
1323 | char **new;
1324 |
1325 | if (len < 1) return 0;
1326 | if (history) {
1327 | int tocopy = history_len;
1328 |
1329 | new = malloc(sizeof(char*)*len);
1330 | if (new == NULL) return 0;
1331 |
1332 | /* If we can't copy everything, free the elements we'll not use. */
1333 | if (len < tocopy) {
1334 | int j;
1335 |
1336 | for (j = 0; j < tocopy-len; j++) free(history[j]);
1337 | tocopy = len;
1338 | }
1339 | memset(new,0,sizeof(char*)*len);
1340 | memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
1341 | free(history);
1342 | history = new;
1343 | }
1344 | history_max_len = len;
1345 | if (history_len > history_max_len)
1346 | history_len = history_max_len;
1347 | return 1;
1348 | }
1349 |
1350 | /* Save the history in the specified file. On success 0 is returned
1351 | * otherwise -1 is returned. */
1352 | int linenoiseHistorySave(const char *filename) {
1353 | mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
1354 | FILE *fp;
1355 | int j;
1356 |
1357 | fp = fopen(filename,"w");
1358 | umask(old_umask);
1359 | if (fp == NULL) return -1;
1360 | chmod(filename,S_IRUSR|S_IWUSR);
1361 | for (j = 0; j < history_len; j++)
1362 | fprintf(fp,"%s\n",history[j]);
1363 | fclose(fp);
1364 | return 0;
1365 | }
1366 |
1367 | /* Load the history from the specified file. If the file does not exist
1368 | * zero is returned and no operation is performed.
1369 | *
1370 | * If the file exists and the operation succeeded 0 is returned, otherwise
1371 | * on error -1 is returned. */
1372 | int linenoiseHistoryLoad(const char *filename) {
1373 | FILE *fp = fopen(filename,"r");
1374 | char buf[LINENOISE_MAX_LINE];
1375 |
1376 | if (fp == NULL) return -1;
1377 |
1378 | while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
1379 | char *p;
1380 |
1381 | p = strchr(buf,'\r');
1382 | if (!p) p = strchr(buf,'\n');
1383 | if (p) *p = '\0';
1384 | linenoiseHistoryAdd(buf);
1385 | }
1386 | fclose(fp);
1387 | return 0;
1388 | }
1389 |
--------------------------------------------------------------------------------
/src/linenoise_src.h:
--------------------------------------------------------------------------------
1 | /* linenoise.h -- VERSION 1.0
2 | *
3 | * Guerrilla line editing library against the idea that a line editing lib
4 | * needs to be 20,000 lines of C code.
5 | *
6 | * See linenoise.c for more information.
7 | *
8 | * ------------------------------------------------------------------------
9 | *
10 | * Copyright (c) 2010-2014, Salvatore Sanfilippo
11 | * Copyright (c) 2010-2013, Pieter Noordhuis
12 | *
13 | * All rights reserved.
14 | *
15 | * Redistribution and use in source and binary forms, with or without
16 | * modification, are permitted provided that the following conditions are
17 | * met:
18 | *
19 | * * Redistributions of source code must retain the above copyright
20 | * notice, this list of conditions and the following disclaimer.
21 | *
22 | * * Redistributions in binary form must reproduce the above copyright
23 | * notice, this list of conditions and the following disclaimer in the
24 | * documentation and/or other materials provided with the distribution.
25 | *
26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 | */
38 |
39 | #ifndef __LINENOISE_H
40 | #define __LINENOISE_H
41 |
42 | #ifdef __cplusplus
43 | extern "C" {
44 | #endif
45 |
46 | extern int linenoiseWasInterrupted; /* boolean signalling if last call was ctrl-c */
47 |
48 | typedef struct linenoiseCompletions {
49 | size_t len;
50 | char **cvec;
51 | } linenoiseCompletions;
52 |
53 | typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
54 | typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
55 | typedef void(linenoiseFreeHintsCallback)(void *);
56 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
57 | void linenoiseSetHintsCallback(linenoiseHintsCallback *);
58 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
59 | void linenoiseAddCompletion(linenoiseCompletions *, const char *);
60 |
61 | char *linenoise(const char *prompt);
62 | void linenoiseFree(void *ptr);
63 | int linenoiseHistoryAdd(const char *line);
64 | int linenoiseHistorySetMaxLen(int len);
65 | int linenoiseHistorySave(const char *filename);
66 | int linenoiseHistoryLoad(const char *filename);
67 | void linenoiseClearScreen(void);
68 | void linenoiseSetMultiLine(int ml);
69 | void linenoisePrintKeyCodes(void);
70 |
71 | #ifdef __cplusplus
72 | }
73 | #endif
74 |
75 | #endif /* __LINENOISE_H */
76 |
--------------------------------------------------------------------------------
/src/linenoise_stubs.c:
--------------------------------------------------------------------------------
1 | // OCaml declarations
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 |
11 | #include "linenoise_src.h"
12 |
13 | // Ripped from ctypes
14 | #define Val_none Val_int(0)
15 | #define Some_val(v) Field(v, 0)
16 |
17 | static value Val_some(value v)
18 | {
19 | CAMLparam1(v);
20 | CAMLlocal1(some);
21 | some = caml_alloc(1, 0);
22 | Store_field(some, 0, v);
23 | CAMLreturn(some);
24 | }
25 |
26 | /* if true, raise Sys.Break on ctrl-c */
27 | static int raise_sys_break = 0;
28 |
29 | CAMLprim value ml_catch_break(value flag)
30 | {
31 | CAMLparam1(flag);
32 | raise_sys_break = Bool_val(flag);
33 | CAMLreturn(Val_unit);
34 | }
35 |
36 | CAMLprim value ml_add_completion(value completions, value new_completion)
37 | {
38 | CAMLparam2(completions, new_completion);
39 | char* c_new_completion = caml_stat_strdup(String_val(new_completion));
40 | linenoiseAddCompletion((linenoiseCompletions *)completions, c_new_completion);
41 | caml_stat_free(c_new_completion);
42 | CAMLreturn(Val_unit);
43 | }
44 |
45 | // this bridge runs with the runtime lock acquired
46 | static void completion_bridge_inner(const char *buf, linenoiseCompletions *lc)
47 | {
48 | CAMLparam0();
49 | CAMLlocal1(str_copy);
50 | str_copy = caml_copy_string(buf);
51 | caml_callback2(*caml_named_value("lnoise_completion_cb"), str_copy, (value)lc);
52 | CAMLreturn0;
53 | }
54 |
55 | static void completion_bridge(const char *buf, linenoiseCompletions *lc)
56 | {
57 | caml_acquire_runtime_system();
58 | completion_bridge_inner(buf, lc);
59 | caml_release_runtime_system();
60 | }
61 |
62 | static char *hints_bridge_inner(const char *buf, int *color, int *bold)
63 | {
64 | CAMLparam0();
65 | CAMLlocal2(str_copy, cb_result);
66 |
67 | str_copy = caml_copy_string(buf);
68 |
69 | cb_result = caml_callback(*caml_named_value("lnoise_hints_cb"), str_copy);
70 | if (cb_result == Val_none) {
71 | CAMLreturnT(char *,NULL);
72 | } else {
73 | char* msg = caml_stat_strdup(String_val(Field(Field(cb_result, 0), 0)));
74 | *color = Int_val(Field(Field(cb_result, 0), 1)) + 31;
75 | *bold = Bool_val(Field(Field(cb_result, 0), 2));
76 | CAMLreturnT(char *,msg);
77 | }
78 | }
79 |
80 | static char *hints_bridge(const char *buf, int *color, int *bold)
81 | {
82 | caml_acquire_runtime_system();
83 | char* res = hints_bridge_inner(buf, color, bold);
84 | caml_release_runtime_system();
85 | return res;
86 | }
87 |
88 |
89 | static void free_hints_bridge(void* data) {
90 | caml_acquire_runtime_system();
91 | caml_stat_free(data);
92 | caml_release_runtime_system();
93 | }
94 |
95 | __attribute__((constructor))
96 | void set_free_hints(void) { linenoiseSetFreeHintsCallback(free); }
97 |
98 | CAMLprim value ml_setup_bridges(value unit) {
99 | CAMLparam1(unit);
100 | linenoiseSetCompletionCallback(completion_bridge);
101 | linenoiseSetHintsCallback(hints_bridge);
102 | linenoiseSetFreeHintsCallback(free_hints_bridge);
103 | CAMLreturn(Val_unit);
104 | }
105 |
106 | CAMLprim value ml_linenoise(value prompt)
107 | {
108 | CAMLparam1(prompt);
109 | CAMLlocal1(lnoise_result);
110 |
111 | linenoiseWasInterrupted = 0; // reset
112 | char* c_prompt = caml_stat_strdup(String_val(prompt));
113 |
114 | caml_release_runtime_system();
115 | const char *result = linenoise(c_prompt);
116 | caml_acquire_runtime_system();
117 |
118 | caml_stat_free(c_prompt);
119 | if (!result) {
120 | if (linenoiseWasInterrupted && raise_sys_break) {
121 | caml_raise_constant(*caml_named_value("sys_break"));
122 | } else {
123 | CAMLreturn(Val_none);
124 | }
125 | }
126 | lnoise_result = caml_copy_string(result);
127 | linenoiseFree((void*)result);
128 | CAMLreturn(Val_some(lnoise_result));
129 | }
130 |
131 | CAMLprim value ml_history_add(value line)
132 | {
133 | CAMLparam1(line);
134 | char* c_line = caml_stat_strdup(String_val(line));
135 | int res = linenoiseHistoryAdd(c_line);
136 | caml_stat_free(c_line);
137 | CAMLreturn(Val_int(res));
138 | }
139 |
140 | CAMLprim value ml_history_set_maxlen(value max)
141 | {
142 | CAMLparam1(max);
143 | CAMLreturn(Val_int(linenoiseHistorySetMaxLen(Int_val(max))));
144 | }
145 |
146 | CAMLprim value ml_history_save(value filename)
147 | {
148 | CAMLparam1(filename);
149 | char* c_filename = caml_stat_strdup(String_val(filename));
150 | int res = linenoiseHistorySave(c_filename);
151 | caml_stat_free(c_filename);
152 | CAMLreturn(Val_int(res));
153 | }
154 |
155 | CAMLprim value ml_history_load(value filename)
156 | {
157 | CAMLparam1(filename);
158 | char* c_filename= caml_stat_strdup(String_val(filename));
159 | int res = linenoiseHistoryLoad(c_filename);
160 | caml_stat_free(c_filename);
161 | CAMLreturn(Val_int(res));
162 | }
163 |
164 | CAMLprim value ml_clearscreen(__attribute__((unused))value unit)
165 | {
166 | CAMLparam0();
167 | linenoiseClearScreen();
168 | CAMLreturn(Val_unit);
169 | }
170 |
171 | CAMLprim value ml_set_multiline(value use_multiline)
172 | {
173 | CAMLparam1(use_multiline);
174 | linenoiseSetMultiLine(Bool_val(use_multiline));
175 | CAMLreturn(Val_unit);
176 | }
177 |
178 | CAMLprim value ml_printkeycodes(void)
179 | {
180 | CAMLparam0();
181 | linenoisePrintKeyCodes();
182 | CAMLreturn(Val_unit);
183 | }
184 |
--------------------------------------------------------------------------------