1 | //===-- Editline.h ----------------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // TODO: wire up window size changes |
10 | |
11 | // If we ever get a private copy of libedit, there are a number of defects that |
12 | // would be nice to fix; |
13 | // a) Sometimes text just disappears while editing. In an 80-column editor |
14 | // paste the following text, without |
15 | // the quotes: |
16 | // "This is a test of the input system missing Hello, World! Do you |
17 | // disappear when it gets to a particular length?" |
18 | // Now press ^A to move to the start and type 3 characters, and you'll see a |
19 | // good amount of the text will |
20 | // disappear. It's still in the buffer, just invisible. |
21 | // b) The prompt printing logic for dealing with ANSI formatting characters is |
22 | // broken, which is why we're working around it here. |
23 | // c) The incremental search uses escape to cancel input, so it's confused by |
24 | // ANSI sequences starting with escape. |
25 | // d) Emoji support is fairly terrible, presumably it doesn't understand |
26 | // composed characters? |
27 | |
28 | #ifndef LLDB_HOST_EDITLINE_H |
29 | #define LLDB_HOST_EDITLINE_H |
30 | #if defined(__cplusplus) |
31 | |
32 | #include "lldb/Host/Config.h" |
33 | |
34 | #if LLDB_EDITLINE_USE_WCHAR |
35 | #include <codecvt> |
36 | #endif |
37 | #include <locale> |
38 | #include <sstream> |
39 | #include <vector> |
40 | |
41 | #include "lldb/lldb-private.h" |
42 | |
43 | #if defined(_WIN32) |
44 | #include "lldb/Host/windows/editlinewin.h" |
45 | #elif !defined(__ANDROID__) |
46 | #include <histedit.h> |
47 | #endif |
48 | |
49 | #include <csignal> |
50 | #include <mutex> |
51 | #include <string> |
52 | #include <vector> |
53 | |
54 | #include "lldb/Host/ConnectionFileDescriptor.h" |
55 | #include "lldb/Utility/CompletionRequest.h" |
56 | #include "lldb/Utility/FileSpec.h" |
57 | #include "lldb/Utility/Predicate.h" |
58 | #include "lldb/Utility/StringList.h" |
59 | |
60 | #include "llvm/ADT/FunctionExtras.h" |
61 | |
62 | namespace lldb_private { |
63 | namespace line_editor { |
64 | |
65 | // type alias's to help manage 8 bit and wide character versions of libedit |
66 | #if LLDB_EDITLINE_USE_WCHAR |
67 | using EditLineStringType = std::wstring; |
68 | using EditLineStringStreamType = std::wstringstream; |
69 | using EditLineCharType = wchar_t; |
70 | #else |
71 | using EditLineStringType = std::string; |
72 | using EditLineStringStreamType = std::stringstream; |
73 | using EditLineCharType = char; |
74 | #endif |
75 | |
76 | // At one point the callback type of el_set getchar callback changed from char |
77 | // to wchar_t. It is not possible to detect differentiate between the two |
78 | // versions exactly, but this is a pretty good approximation and allows us to |
79 | // build against almost any editline version out there. |
80 | #if LLDB_EDITLINE_USE_WCHAR || defined(EL_CLIENTDATA) || LLDB_HAVE_EL_RFUNC_T |
81 | using EditLineGetCharType = wchar_t; |
82 | #else |
83 | using EditLineGetCharType = char; |
84 | #endif |
85 | |
86 | using EditlineGetCharCallbackType = int (*)(::EditLine *editline, |
87 | EditLineGetCharType *c); |
88 | using EditlineCommandCallbackType = unsigned char (*)(::EditLine *editline, |
89 | int ch); |
90 | using EditlinePromptCallbackType = const char *(*)(::EditLine *editline); |
91 | |
92 | class EditlineHistory; |
93 | |
94 | using EditlineHistorySP = std::shared_ptr<EditlineHistory>; |
95 | |
96 | using IsInputCompleteCallbackType = |
97 | llvm::unique_function<bool(Editline *, StringList &)>; |
98 | |
99 | using FixIndentationCallbackType = |
100 | llvm::unique_function<int(Editline *, StringList &, int)>; |
101 | |
102 | using SuggestionCallbackType = |
103 | llvm::unique_function<llvm::Optional<std::string>(llvm::StringRef)>; |
104 | |
105 | using CompleteCallbackType = llvm::unique_function<void(CompletionRequest &)>; |
106 | |
107 | /// Status used to decide when and how to start editing another line in |
108 | /// multi-line sessions |
109 | enum class EditorStatus { |
110 | |
111 | /// The default state proceeds to edit the current line |
112 | Editing, |
113 | |
114 | /// Editing complete, returns the complete set of edited lines |
115 | Complete, |
116 | |
117 | /// End of input reported |
118 | EndOfInput, |
119 | |
120 | /// Editing interrupted |
121 | Interrupted |
122 | }; |
123 | |
124 | /// Established locations that can be easily moved among with MoveCursor |
125 | enum class CursorLocation { |
126 | /// The start of the first line in a multi-line edit session |
127 | BlockStart, |
128 | |
129 | /// The start of the current line in a multi-line edit session |
130 | EditingPrompt, |
131 | |
132 | /// The location of the cursor on the current line in a multi-line edit |
133 | /// session |
134 | EditingCursor, |
135 | |
136 | /// The location immediately after the last character in a multi-line edit |
137 | /// session |
138 | BlockEnd |
139 | }; |
140 | |
141 | /// Operation for the history. |
142 | enum class HistoryOperation { |
143 | Oldest, |
144 | Older, |
145 | Current, |
146 | Newer, |
147 | Newest |
148 | }; |
149 | } |
150 | |
151 | using namespace line_editor; |
152 | |
153 | /// Instances of Editline provide an abstraction over libedit's EditLine |
154 | /// facility. Both |
155 | /// single- and multi-line editing are supported. |
156 | class Editline { |
157 | public: |
158 | Editline(const char *editor_name, FILE *input_file, FILE *output_file, |
159 | FILE *error_file, bool color_prompts); |
160 | |
161 | ~Editline(); |
162 | |
163 | /// Uses the user data storage of EditLine to retrieve an associated instance |
164 | /// of Editline. |
165 | static Editline *InstanceFor(::EditLine *editline); |
166 | |
167 | /// Sets a string to be used as a prompt, or combined with a line number to |
168 | /// form a prompt. |
169 | void SetPrompt(const char *prompt); |
170 | |
171 | /// Sets an alternate string to be used as a prompt for the second line and |
172 | /// beyond in multi-line |
173 | /// editing scenarios. |
174 | void SetContinuationPrompt(const char *continuation_prompt); |
175 | |
176 | /// Call when the terminal size changes |
177 | void TerminalSizeChanged(); |
178 | |
179 | /// Returns the prompt established by SetPrompt() |
180 | const char *GetPrompt(); |
181 | |
182 | /// Returns the index of the line currently being edited |
183 | uint32_t GetCurrentLine(); |
184 | |
185 | /// Interrupt the current edit as if ^C was pressed |
186 | bool Interrupt(); |
187 | |
188 | /// Cancel this edit and oblitarate all trace of it |
189 | bool Cancel(); |
190 | |
191 | /// Register a callback for autosuggestion. |
192 | void SetSuggestionCallback(SuggestionCallbackType callback) { |
193 | m_suggestion_callback = std::move(callback); |
194 | } |
195 | |
196 | /// Register a callback for the tab key |
197 | void SetAutoCompleteCallback(CompleteCallbackType callback) { |
198 | m_completion_callback = std::move(callback); |
199 | } |
200 | |
201 | /// Register a callback for testing whether multi-line input is complete |
202 | void SetIsInputCompleteCallback(IsInputCompleteCallbackType callback) { |
203 | m_is_input_complete_callback = std::move(callback); |
204 | } |
205 | |
206 | /// Register a callback for determining the appropriate indentation for a line |
207 | /// when creating a newline. An optional set of insertable characters can |
208 | /// also trigger the callback. |
209 | void SetFixIndentationCallback(FixIndentationCallbackType callback, |
210 | const char *indent_chars) { |
211 | m_fix_indentation_callback = std::move(callback); |
212 | m_fix_indentation_callback_chars = indent_chars; |
213 | } |
214 | |
215 | /// Prompts for and reads a single line of user input. |
216 | bool GetLine(std::string &line, bool &interrupted); |
217 | |
218 | /// Prompts for and reads a multi-line batch of user input. |
219 | bool GetLines(int first_line_number, StringList &lines, bool &interrupted); |
220 | |
221 | void PrintAsync(Stream *stream, const char *s, size_t len); |
222 | |
223 | private: |
224 | /// Sets the lowest line number for multi-line editing sessions. A value of |
225 | /// zero suppresses |
226 | /// line number printing in the prompt. |
227 | void SetBaseLineNumber(int line_number); |
228 | |
229 | /// Returns the complete prompt by combining the prompt or continuation prompt |
230 | /// with line numbers |
231 | /// as appropriate. The line index is a zero-based index into the current |
232 | /// multi-line session. |
233 | std::string PromptForIndex(int line_index); |
234 | |
235 | /// Sets the current line index between line edits to allow free movement |
236 | /// between lines. Updates |
237 | /// the prompt to match. |
238 | void SetCurrentLine(int line_index); |
239 | |
240 | /// Determines the width of the prompt in characters. The width is guaranteed |
241 | /// to be the same for |
242 | /// all lines of the current multi-line session. |
243 | int GetPromptWidth(); |
244 | |
245 | /// Returns true if the underlying EditLine session's keybindings are |
246 | /// Emacs-based, or false if |
247 | /// they are VI-based. |
248 | bool IsEmacs(); |
249 | |
250 | /// Returns true if the current EditLine buffer contains nothing but spaces, |
251 | /// or is empty. |
252 | bool IsOnlySpaces(); |
253 | |
254 | /// Helper method used by MoveCursor to determine relative line position. |
255 | int GetLineIndexForLocation(CursorLocation location, int cursor_row); |
256 | |
257 | /// Move the cursor from one well-established location to another using |
258 | /// relative line positioning |
259 | /// and absolute column positioning. |
260 | void MoveCursor(CursorLocation from, CursorLocation to); |
261 | |
262 | /// Clear from cursor position to bottom of screen and print input lines |
263 | /// including prompts, optionally |
264 | /// starting from a specific line. Lines are drawn with an extra space at the |
265 | /// end to reserve room for |
266 | /// the rightmost cursor position. |
267 | void DisplayInput(int firstIndex = 0); |
268 | |
269 | /// Counts the number of rows a given line of content will end up occupying, |
270 | /// taking into account both |
271 | /// the preceding prompt and a single trailing space occupied by a cursor when |
272 | /// at the end of the line. |
273 | int CountRowsForLine(const EditLineStringType &content); |
274 | |
275 | /// Save the line currently being edited |
276 | void SaveEditedLine(); |
277 | |
278 | /// Convert the current input lines into a UTF8 StringList |
279 | StringList GetInputAsStringList(int line_count = UINT32_MAX); |
280 | |
281 | /// Replaces the current multi-line session with the next entry from history. |
282 | unsigned char RecallHistory(HistoryOperation op); |
283 | |
284 | /// Character reading implementation for EditLine that supports our multi-line |
285 | /// editing trickery. |
286 | int GetCharacter(EditLineGetCharType *c); |
287 | |
288 | /// Prompt implementation for EditLine. |
289 | const char *Prompt(); |
290 | |
291 | /// Line break command used when meta+return is pressed in multi-line mode. |
292 | unsigned char BreakLineCommand(int ch); |
293 | |
294 | /// Command used when return is pressed in multi-line mode. |
295 | unsigned char EndOrAddLineCommand(int ch); |
296 | |
297 | /// Delete command used when delete is pressed in multi-line mode. |
298 | unsigned char DeleteNextCharCommand(int ch); |
299 | |
300 | /// Delete command used when backspace is pressed in multi-line mode. |
301 | unsigned char DeletePreviousCharCommand(int ch); |
302 | |
303 | /// Line navigation command used when ^P or up arrow are pressed in multi-line |
304 | /// mode. |
305 | unsigned char PreviousLineCommand(int ch); |
306 | |
307 | /// Line navigation command used when ^N or down arrow are pressed in |
308 | /// multi-line mode. |
309 | unsigned char NextLineCommand(int ch); |
310 | |
311 | /// History navigation command used when Alt + up arrow is pressed in |
312 | /// multi-line mode. |
313 | unsigned char PreviousHistoryCommand(int ch); |
314 | |
315 | /// History navigation command used when Alt + down arrow is pressed in |
316 | /// multi-line mode. |
317 | unsigned char NextHistoryCommand(int ch); |
318 | |
319 | /// Buffer start command used when Esc < is typed in multi-line emacs mode. |
320 | unsigned char BufferStartCommand(int ch); |
321 | |
322 | /// Buffer end command used when Esc > is typed in multi-line emacs mode. |
323 | unsigned char BufferEndCommand(int ch); |
324 | |
325 | /// Context-sensitive tab insertion or code completion command used when the |
326 | /// tab key is typed. |
327 | unsigned char TabCommand(int ch); |
328 | |
329 | /// Apply autosuggestion part in gray as editline. |
330 | unsigned char ApplyAutosuggestCommand(int ch); |
331 | |
332 | /// Command used when a character is typed. |
333 | unsigned char TypedCharacter(int ch); |
334 | |
335 | /// Respond to normal character insertion by fixing line indentation |
336 | unsigned char FixIndentationCommand(int ch); |
337 | |
338 | /// Revert line command used when moving between lines. |
339 | unsigned char RevertLineCommand(int ch); |
340 | |
341 | /// Ensures that the current EditLine instance is properly configured for |
342 | /// single or multi-line editing. |
343 | void ConfigureEditor(bool multiline); |
344 | |
345 | bool CompleteCharacter(char ch, EditLineGetCharType &out); |
346 | |
347 | void ApplyTerminalSizeChange(); |
348 | |
349 | // The following set various editline parameters. It's not any less |
350 | // verbose to put the editline calls into a function, but it |
351 | // provides type safety, since the editline functions take varargs |
352 | // parameters. |
353 | void AddFunctionToEditLine(const EditLineCharType *command, |
354 | const EditLineCharType *helptext, |
355 | EditlineCommandCallbackType callbackFn); |
356 | void SetEditLinePromptCallback(EditlinePromptCallbackType callbackFn); |
357 | void SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn); |
358 | |
359 | #if LLDB_EDITLINE_USE_WCHAR |
360 | std::wstring_convert<std::codecvt_utf8<wchar_t>> m_utf8conv; |
361 | #endif |
362 | ::EditLine *m_editline = nullptr; |
363 | EditlineHistorySP m_history_sp; |
364 | bool m_in_history = false; |
365 | std::vector<EditLineStringType> m_live_history_lines; |
366 | bool m_multiline_enabled = false; |
367 | std::vector<EditLineStringType> m_input_lines; |
368 | EditorStatus m_editor_status; |
369 | bool m_color_prompts = true; |
370 | int m_terminal_width = 0; |
371 | int m_base_line_number = 0; |
372 | unsigned m_current_line_index = 0; |
373 | int m_current_line_rows = -1; |
374 | int m_revert_cursor_index = 0; |
375 | int m_line_number_digits = 3; |
376 | std::string m_set_prompt; |
377 | std::string m_set_continuation_prompt; |
378 | std::string m_current_prompt; |
379 | bool m_needs_prompt_repaint = false; |
380 | volatile std::sig_atomic_t m_terminal_size_has_changed = 0; |
381 | std::string m_editor_name; |
382 | FILE *m_input_file; |
383 | FILE *m_output_file; |
384 | FILE *m_error_file; |
385 | ConnectionFileDescriptor m_input_connection; |
386 | |
387 | IsInputCompleteCallbackType m_is_input_complete_callback; |
388 | |
389 | FixIndentationCallbackType m_fix_indentation_callback; |
390 | const char *m_fix_indentation_callback_chars = nullptr; |
391 | |
392 | CompleteCallbackType m_completion_callback; |
393 | |
394 | SuggestionCallbackType m_suggestion_callback; |
395 | |
396 | std::size_t m_previous_autosuggestion_size = 0; |
397 | std::mutex m_output_mutex; |
398 | }; |
399 | } |
400 | |
401 | #endif // #if defined(__cplusplus) |
402 | #endif // LLDB_HOST_EDITLINE_H |
403 | |