changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/ffi/readline/readline.lisp

changeset 658: 804b5ee20a46
parent: 386d51cf61ca
child: da507f0274b3
author: Richard Westhaver <ellis@rwest.io>
date: Thu, 19 Sep 2024 23:23:02 -0400
permissions: -rw-r--r--
description: zstd completed (besides zdict), working on readline
1 ;;; readline/readline.lisp --- Readline Alien Routines
2 
3 ;; This implementation is based on Vindarel's cl-readline: https://github.com/vindarel/cl-readline
4 
5 ;;; Code:
6 (in-package :readline)
7 
8 (define-alien-enum (rl-completion-type int :test eq)
9  :standard-completion 9
10  :display-and-perform 33
11  :insert-all 42
12  :list-all 63
13  :not-list-cmn-prefix 64)
14 
15 (define-alien-type rl-history-entry (struct rl-history-entry
16  (line (* t))
17  (time (* t))
18  (data (* t))))
19 (macrolet ((def-rl-var (name var type)
20  `(define-alien-variable (,name ,var) ,type)))
21  (def-rl-var "rl_line_buffer" *line-buffer* c-string)
22  (def-rl-var "rl_point" *point* int)
23  (def-rl-var "rl_end" *end* int)
24  (def-rl-var "rl_mark" *mark* int)
25  (def-rl-var "rl_done" *point* boolean)
26  (def-rl-var "rl_num_chars_to_read" *num-chars-to-read* int)
27  (def-rl-var "rl_pending_input" *pending-input* int)
28  (def-rl-var "rl_dispatching" *point* boolean)
29  (def-rl-var "rl_erase_empty_line" *erase-empty-line* boolean)
30  (def-rl-var "rl_prompt" *prompt* c-string)
31  (def-rl-var "rl_display_prompt" *display-prompt* c-string)
32  (def-rl-var "rl_already_prompted" *already-prompted* boolean)
33  (def-rl-var "rl_library_version" *library-version* c-string)
34  (def-rl-var "rl_readline_version" *readline-version* int)
35  (def-rl-var "rl_gnu_readline_p" *gnu-readline-p* boolean)
36  (def-rl-var "rl_terminal_name" *terminal-name* c-string)
37  (def-rl-var "rl_readline_name" *readline-name* c-string)
38  (def-rl-var "rl_instream" *instream* (* t))
39  (def-rl-var "rl_outstream" *outstream* (* t))
40  (def-rl-var "rl_prefer_env_winsize" *prefer-env-winsize* boolean)
41  (def-rl-var "rl_last_func" *last-func* (* t))
42  (def-rl-var "rl_startup_hook" *startup-hook* (* t))
43  (def-rl-var "rl_pre_input_hook" *pre-input-hook* (* t))
44  (def-rl-var "rl_event_hook" *event-hook* (* t))
45  (def-rl-var "rl_getc_function" *getc-function* (* t))
46  (def-rl-var "rl_signal_event_hook" *signal-event-hook* (* t))
47  (def-rl-var "rl_input-available_hook" *input-available-hook* (* t))
48  (def-rl-var "rl_redisplay_function" *redisplay-function* (* t))
49  (def-rl-var "rl_prep_term_function" *prep-term-function* (* t))
50  (def-rl-var "rl_deprep_term_function" *deprep-term-function* (* t))
51  (def-rl-var "rl_executing_keymap" *executing-keymap* (* t))
52  (def-rl-var "rl_binding_keymap" *binding-keymap* (* t))
53  (def-rl-var "rl_executing_macro" *executing-macro* c-string)
54  (def-rl-var "rl_executing_key" *executing-key* char)
55  (def-rl-var "rl_executing_keyseq" *executing-keyseq* c-string)
56  (def-rl-var "rl_key_sequence_length" *key-sequence-length* int)
57  (def-rl-var "rl_readline_state" *readline-state* int)
58  (def-rl-var "rl_explicit_arg" *explicit-arg* boolean)
59  (def-rl-var "rl_numeric_arg" *numeric-arg* int)
60  (def-rl-var "rl_editing_mode" *editing-mode* int)
61  (def-rl-var "rl_catch_sigwinch" *catch-sigwinch* boolean)
62  (def-rl-var "rl_change_environment" *change-environment* boolean)
63  (def-rl-var "rl_attempted_completion_function" *attempted-completion-function* (* t))
64  (def-rl-var "rl_completion_display_matches_hook" *completion-display-matches-hook* (* t))
65  (def-rl-var "rl_basic_word_break_characters" *basic-word-break-characters* c-string)
66  (def-rl-var "rl_completer_word_break_character" *completer-word-break-characters* c-string)
67  (def-rl-var "rl_completion_query_items" *completer-query-items* int)
68  (def-rl-var "rl_completion_append_character" *completion-append-character* char)
69  (def-rl-var "rl_ignore_completion_duplicates" *ignore-completion-duplicates* boolean)
70  (def-rl-var "rl_attempted_completion_over" *attempted-completion-over* boolean)
71  (def-rl-var "rl_sort_completion_matches" *sort-completion-matches* boolean)
72  (def-rl-var "rl_completion_type" *completion-type* rl-completion-type)
73  (def-rl-var "rl_inhibit_completion" *inhibit-completion* boolean)
74  (def-rl-var "history_base" *history-base* int)
75  (def-rl-var "history_length" *history-length* int))
76 
77 (defvar *states*
78  '(:initializing ; 0x0000001 initializing
79  :initialized ; 0x0000002 initialization done
80  :termprepped ; 0x0000004 terminal is prepped
81  :readcmd ; 0x0000008 reading a command key
82  :metanext ; 0x0000010 reading input after ESC
83  :dispatching ; 0x0000020 dispatching to a command
84  :moreinput ; 0x0000040 reading more input in a command function
85  :isearch ; 0x0000080 doing incremental search
86  :nsearch ; 0x0000100 doing non-incremental search
87  :search ; 0x0000200 doing a history search
88  :numericarg ; 0x0000400 reading numeric argument
89  :macroinput ; 0x0000800 getting input from a macro
90  :macrodef ; 0x0001000 defining keyboard macro
91  :overwrite ; 0x0002000 overwrite mode
92  :completing ; 0x0004000 doing completion
93  :sighandler ; 0x0008000 in readline sighandler
94  :undoing ; 0x0010000 doing an undo
95  :inputpending ; 0x0020000 rl_execute_next called
96  :ttycsaved ; 0x0040000 tty special chars saved
97  :callback ; 0x0080000 using the callback interface
98  :vimotion ; 0x0100000 reading vi motion arg
99  :multikey ; 0x0200000 reading multiple-key command
100  :vicmdonce ; 0x0400000 entered vi command mode at least once
101  :redisplaying ; 0x0800000 updating terminal display
102  :done) ; 0x1000000 done; accepted line
103  "Possible state values for `+readline-state+'.")
104 
105 (defvar +c-buffer-size+ 256
106  "How many bytes to allocate per Lisp string when converting list of
107 Lisp strings into array of C strings.")
108 
109 (defun decode-version (version)
110  "Transform VERSION into two values representing major and minor numbers of
111 Readline library version."
112  (values (ldb (byte 8 8) version)
113  (ldb (byte 8 0) version)))
114 
115 ;; (defun decode-state (state)
116 ;; "Transform Readline state STATE into list of keywords. See `+states+' for
117 ;; list of components that can appear in result list."
118 ;; (mapcan (lambda (index keyword)
119 ;; (when (logbitp index state)
120 ;; (list keyword)))
121 ;; (iota (length +states+))
122 ;; +states+))
123 
124 (defmacro produce-callback (function return-type &optional func-arg-list)
125  "Return pointer to callback that calls FUNCTION. RETURN-TYPE specifies
126 return type of the function and FUNC-ARG-LIST is list of argument types (it
127 can be ommited if FUNCTION doesn't take any arguments)."
128  (let ((gensymed-list (mapcar (lambda (x) (list (gensym) x))
129  func-arg-list)))
130  (std:with-gensyms (temp)
131  `(when ,function
132  (progn
133  (define-alien-callable ,temp ,return-type ,gensymed-list
134  (funcall ,function ,@(mapcar #'car gensymed-list)))
135  (alien-callable-function ',temp))))))
136 
137 (defun produce-callback* (function return-type &optional func-arg-list)
138  "Variant of PRODUCE-CALLBACK that should hopefully be more portable.
139 This avoids using a GENSYM as the name of a callback, and is also funcallable."
140  (let ((gensymed-list (mapcar (lambda (x) (list (gensym) x))
141  func-arg-list)))
142  (std:with-gensyms (temp)
143  (when function
144  (progn
145  (eval `(define-alien-callable ,temp ,return-type ,gensymed-list
146  (funcall ,function ,@(mapcar #'car gensymed-list))))
147  (alien-callable-function temp))))))
148 
149 ;;; cl-readline
150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
151 
152 (defun recent-history-line-satisfies-p (predicate)
153  "Check if the most recent history line satisfies given predicate
154 PREDICATE. Return T if there is no history saved."
155  (if (zerop *history-length*)
156  t
157  (with-alien ((s rl-history-entry))
158  (funcall predicate
159  ;; TODO 2024-09-19: does SBCL know how to conver this to a lisp string automatically?
160  (with-alien-slots
161  (line)
162  ;; (alien-funcall "history_get"
163  ;; :int
164  ;; (1- (+ *history-base*
165  ;; *history-length*)))
166  s
167  line)))))
168 
169 (define-alien-routine "readline" (* t) (prompt c-string))
170 (define-alien-routine "add_history" void (line c-string))
171 
172 (defun rl (&key
173  prompt
174  already-prompted
175  num-chars
176  erase-empty-line
177  add-history
178  novelty-check)
179  "Get a line from user with editing. PROMPT, if supplied, is printed before
180 reading of input. Non-NIL value of ALREADY-PROMPTED will tell Readline that
181 the application has printed prompt already. However, PROMPT must be supplied
182 in this case too, so redisplay functions can update the display properly. If
183 NUM-CHARS argument is a positive number, Readline will return after
184 accepting that many characters. If ERASE-EMPTY-LINE is not NIL, `readline'
185 will completely erase the current line, including any prompt, any time a
186 newline is typed as the only character on an otherwise-empty line. The
187 cursor is moved to the beginning of the newly-blank line. Supplying
188 ADD-HISTORY tells Readline that user's input should be added to
189 history. However, blank lines don't get into history anyway. NOVELTY-CHECK,
190 if given, must be a predicate that takes two strings: the actual line and
191 the most recent history line. Only when the predicate evaluates to non-NIL
192 value new line will be added to the history. Return value on success is the
193 actual string and NIL on failure."
194  (setf *already-prompted* already-prompted
195  *num-chars-to-read* (or num-chars 0)
196  *erase-empty-line* erase-empty-line)
197  (let* ((prompt (if prompt (string prompt) ""))
198  (ptr (readline prompt)))
199  (unless (null ptr)
200  (unwind-protect
201  (let ((str ptr))
202  (when (and add-history
203  (not (sequence:emptyp str))
204  (or (not novelty-check)
205  (recent-history-line-satisfies-p
206  (std:curry novelty-check str))))
207  (add-history str))
208  str)
209  (free-alien ptr)))))
210 
211 ;; (defun ensure-initialization ()
212 ;; "Make sure that Readline is initialized. If it's not initialized yet,
213 ;; initialize it."
214 ;; (unless (find :initialized *readline-state*)
215 ;; (initialize)))
216 
217 ;; (defmacro with-possible-redirection (filename append &body body)
218 ;; "If FILENAME is not NIL, try to create C file named FILENAME,
219 ;; temporarily reassign `*outstream*' to pointer to this file, perform BODY,
220 ;; then close the file and assign `*outstream*' the old value. If APPEND is not
221 ;; NIL, output will be appended to the file. Returns NIL on success and T on
222 ;; failure."
223 ;; (std:with-gensyms (temp-outstream file-pointer body-fnc)
224 ;; `(flet ((,body-fnc ()
225 ;; ,@body))
226 ;; (if ,filename
227 ;; (let ((,temp-outstream *outstream*)
228 ;; (,file-pointer (foreign-funcall "fopen"
229 ;; :string ,filename
230 ;; :string (if ,append "a" "w")
231 ;; :pointer)))
232 ;; (if (null-alien ,file-pointer)
233 ;; t
234 ;; (unwind-protect
235 ;; (progn
236 ;; (setf *outstream* ,file-pointer)
237 ;; (,body-fnc))
238 ;; (foreign-funcall "fclose"
239 ;; :pointer ,file-pointer
240 ;; :boolean)
241 ;; (setf *outstream* ,temp-outstream))))
242 ;; (,body-fnc)))))