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 | |
31 | #include "lldb/Host/Config.h" |
32 | |
33 | #if LLDB_EDITLINE_USE_WCHAR |
34 | #include <codecvt> |
35 | #endif |
36 | #include <locale> |
37 | #include <sstream> |
38 | #include <vector> |
39 | |
40 | #include "lldb/lldb-private.h" |
41 | |
42 | #if !defined(_WIN32) && !defined(__ANDROID__) |
43 | #include <histedit.h> |
44 | #endif |
45 | |
46 | #include <csignal> |
47 | #include <mutex> |
48 | #include <optional> |
49 | #include <string> |
50 | #include <vector> |
51 | |
52 | #include "lldb/Host/ConnectionFileDescriptor.h" |
53 | #include "lldb/Utility/CompletionRequest.h" |
54 | #include "lldb/Utility/FileSpec.h" |
55 | #include "lldb/Utility/Predicate.h" |
56 | #include "lldb/Utility/StringList.h" |
57 | |
58 | #include "llvm/ADT/FunctionExtras.h" |
59 | |
60 | namespace lldb_private { |
61 | namespace line_editor { |
62 | |
63 | // type alias's to help manage 8 bit and wide character versions of libedit |
64 | #if LLDB_EDITLINE_USE_WCHAR |
65 | using EditLineStringType = std::wstring; |
66 | using EditLineStringStreamType = std::wstringstream; |
67 | using EditLineCharType = wchar_t; |
68 | #else |
69 | using EditLineStringType = std::string; |
70 | using EditLineStringStreamType = std::stringstream; |
71 | using EditLineCharType = char; |
72 | #endif |
73 | |
74 | // At one point the callback type of el_set getchar callback changed from char |
75 | // to wchar_t. It is not possible to detect differentiate between the two |
76 | // versions exactly, but this is a pretty good approximation and allows us to |
77 | // build against almost any editline version out there. |
78 | // It does, however, require extra care when invoking el_getc, as the type |
79 | // of the input is a single char buffer, but the callback will write a wchar_t. |
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<std::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 single- and multi-line editing are supported. |
155 | class Editline { |
156 | public: |
157 | Editline(const char *editor_name, FILE *input_file, FILE *output_file, |
158 | FILE *error_file, std::recursive_mutex &output_mutex); |
159 | |
160 | ~Editline(); |
161 | |
162 | /// Uses the user data storage of EditLine to retrieve an associated instance |
163 | /// of Editline. |
164 | static Editline *InstanceFor(::EditLine *editline); |
165 | |
166 | static void |
167 | DisplayCompletions(Editline &editline, |
168 | llvm::ArrayRef<CompletionResult::Completion> results); |
169 | |
170 | /// Sets a string to be used as a prompt, or combined with a line number to |
171 | /// form a prompt. |
172 | void SetPrompt(const char *prompt); |
173 | |
174 | /// Sets an alternate string to be used as a prompt for the second line and |
175 | /// beyond in multi-line editing scenarios. |
176 | void SetContinuationPrompt(const char *continuation_prompt); |
177 | |
178 | /// Call when the terminal size changes. |
179 | void TerminalSizeChanged(); |
180 | |
181 | /// Returns the prompt established by SetPrompt. |
182 | const char *GetPrompt(); |
183 | |
184 | /// Returns the index of the line currently being edited. |
185 | uint32_t GetCurrentLine(); |
186 | |
187 | /// Interrupt the current edit as if ^C was pressed. |
188 | bool Interrupt(); |
189 | |
190 | /// Cancel this edit and obliterate all trace of it. |
191 | bool Cancel(); |
192 | |
193 | /// Register a callback for autosuggestion. |
194 | void SetSuggestionCallback(SuggestionCallbackType callback) { |
195 | m_suggestion_callback = std::move(callback); |
196 | } |
197 | |
198 | /// Register a callback for the tab key |
199 | void SetAutoCompleteCallback(CompleteCallbackType callback) { |
200 | m_completion_callback = std::move(callback); |
201 | } |
202 | |
203 | /// Register a callback for testing whether multi-line input is complete |
204 | void SetIsInputCompleteCallback(IsInputCompleteCallbackType callback) { |
205 | m_is_input_complete_callback = std::move(callback); |
206 | } |
207 | |
208 | /// Register a callback for determining the appropriate indentation for a line |
209 | /// when creating a newline. An optional set of insertable characters can |
210 | /// also trigger the callback. |
211 | void SetFixIndentationCallback(FixIndentationCallbackType callback, |
212 | const char *indent_chars) { |
213 | m_fix_indentation_callback = std::move(callback); |
214 | m_fix_indentation_callback_chars = indent_chars; |
215 | } |
216 | |
217 | void SetPromptAnsiPrefix(std::string prefix) { |
218 | m_prompt_ansi_prefix = std::move(prefix); |
219 | } |
220 | |
221 | void SetPromptAnsiSuffix(std::string suffix) { |
222 | m_prompt_ansi_suffix = std::move(suffix); |
223 | } |
224 | |
225 | void SetSuggestionAnsiPrefix(std::string prefix) { |
226 | m_suggestion_ansi_prefix = std::move(prefix); |
227 | } |
228 | |
229 | void SetSuggestionAnsiSuffix(std::string suffix) { |
230 | m_suggestion_ansi_suffix = std::move(suffix); |
231 | } |
232 | |
233 | /// Prompts for and reads a single line of user input. |
234 | bool GetLine(std::string &line, bool &interrupted); |
235 | |
236 | /// Prompts for and reads a multi-line batch of user input. |
237 | bool GetLines(int first_line_number, StringList &lines, bool &interrupted); |
238 | |
239 | void PrintAsync(Stream *stream, const char *s, size_t len); |
240 | |
241 | /// Convert the current input lines into a UTF8 StringList |
242 | StringList GetInputAsStringList(int line_count = UINT32_MAX); |
243 | |
244 | private: |
245 | /// Sets the lowest line number for multi-line editing sessions. A value of |
246 | /// zero suppresses line number printing in the prompt. |
247 | void SetBaseLineNumber(int line_number); |
248 | |
249 | /// Returns the complete prompt by combining the prompt or continuation prompt |
250 | /// with line numbers as appropriate. The line index is a zero-based index |
251 | /// into the current multi-line session. |
252 | std::string PromptForIndex(int line_index); |
253 | |
254 | /// Sets the current line index between line edits to allow free movement |
255 | /// between lines. Updates the prompt to match. |
256 | void SetCurrentLine(int line_index); |
257 | |
258 | /// Determines the width of the prompt in characters. The width is guaranteed |
259 | /// to be the same for all lines of the current multi-line session. |
260 | size_t GetPromptWidth(); |
261 | |
262 | /// Returns true if the underlying EditLine session's keybindings are |
263 | /// Emacs-based, or false if they are VI-based. |
264 | bool IsEmacs(); |
265 | |
266 | /// Returns true if the current EditLine buffer contains nothing but spaces, |
267 | /// or is empty. |
268 | bool IsOnlySpaces(); |
269 | |
270 | /// Helper method used by MoveCursor to determine relative line position. |
271 | int GetLineIndexForLocation(CursorLocation location, int cursor_row); |
272 | |
273 | /// Move the cursor from one well-established location to another using |
274 | /// relative line positioning and absolute column positioning. |
275 | void MoveCursor(CursorLocation from, CursorLocation to); |
276 | |
277 | /// Clear from cursor position to bottom of screen and print input lines |
278 | /// including prompts, optionally starting from a specific line. Lines are |
279 | /// drawn with an extra space at the end to reserve room for the rightmost |
280 | /// cursor position. |
281 | void DisplayInput(int firstIndex = 0); |
282 | |
283 | /// Counts the number of rows a given line of content will end up occupying, |
284 | /// taking into account both the preceding prompt and a single trailing space |
285 | /// occupied by a cursor when at the end of the line. |
286 | int CountRowsForLine(const EditLineStringType &content); |
287 | |
288 | /// Save the line currently being edited. |
289 | void SaveEditedLine(); |
290 | |
291 | /// Replaces the current multi-line session with the next entry from history. |
292 | unsigned char RecallHistory(HistoryOperation op); |
293 | |
294 | /// Character reading implementation for EditLine that supports our multi-line |
295 | /// editing trickery. |
296 | int GetCharacter(EditLineGetCharType *c); |
297 | |
298 | /// Prompt implementation for EditLine. |
299 | const char *Prompt(); |
300 | |
301 | /// Line break command used when meta+return is pressed in multi-line mode. |
302 | unsigned char BreakLineCommand(int ch); |
303 | |
304 | /// Command used when return is pressed in multi-line mode. |
305 | unsigned char EndOrAddLineCommand(int ch); |
306 | |
307 | /// Delete command used when delete is pressed in multi-line mode. |
308 | unsigned char DeleteNextCharCommand(int ch); |
309 | |
310 | /// Delete command used when backspace is pressed in multi-line mode. |
311 | unsigned char DeletePreviousCharCommand(int ch); |
312 | |
313 | /// Line navigation command used when ^P or up arrow are pressed in multi-line |
314 | /// mode. |
315 | unsigned char PreviousLineCommand(int ch); |
316 | |
317 | /// Line navigation command used when ^N or down arrow are pressed in |
318 | /// multi-line mode. |
319 | unsigned char NextLineCommand(int ch); |
320 | |
321 | /// History navigation command used when Alt + up arrow is pressed in |
322 | /// multi-line mode. |
323 | unsigned char PreviousHistoryCommand(int ch); |
324 | |
325 | /// History navigation command used when Alt + down arrow is pressed in |
326 | /// multi-line mode. |
327 | unsigned char NextHistoryCommand(int ch); |
328 | |
329 | /// Buffer start command used when Esc < is typed in multi-line emacs mode. |
330 | unsigned char BufferStartCommand(int ch); |
331 | |
332 | /// Buffer end command used when Esc > is typed in multi-line emacs mode. |
333 | unsigned char BufferEndCommand(int ch); |
334 | |
335 | /// Context-sensitive tab insertion or code completion command used when the |
336 | /// tab key is typed. |
337 | unsigned char TabCommand(int ch); |
338 | |
339 | /// Apply autosuggestion part in gray as editline. |
340 | unsigned char ApplyAutosuggestCommand(int ch); |
341 | |
342 | /// Command used when a character is typed. |
343 | unsigned char TypedCharacter(int ch); |
344 | |
345 | /// Respond to normal character insertion by fixing line indentation |
346 | unsigned char FixIndentationCommand(int ch); |
347 | |
348 | /// Revert line command used when moving between lines. |
349 | unsigned char RevertLineCommand(int ch); |
350 | |
351 | /// Ensures that the current EditLine instance is properly configured for |
352 | /// single or multi-line editing. |
353 | void ConfigureEditor(bool multiline); |
354 | |
355 | bool CompleteCharacter(char ch, EditLineGetCharType &out); |
356 | |
357 | void ApplyTerminalSizeChange(); |
358 | |
359 | // The following set various editline parameters. It's not any less |
360 | // verbose to put the editline calls into a function, but it |
361 | // provides type safety, since the editline functions take varargs |
362 | // parameters. |
363 | void AddFunctionToEditLine(const EditLineCharType *command, |
364 | const EditLineCharType *helptext, |
365 | EditlineCommandCallbackType callbackFn); |
366 | void SetEditLinePromptCallback(EditlinePromptCallbackType callbackFn); |
367 | void SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn); |
368 | |
369 | #if LLDB_EDITLINE_USE_WCHAR |
370 | std::wstring_convert<std::codecvt_utf8<wchar_t>> m_utf8conv; |
371 | #endif |
372 | ::EditLine *m_editline = nullptr; |
373 | EditlineHistorySP m_history_sp; |
374 | bool m_in_history = false; |
375 | std::vector<EditLineStringType> m_live_history_lines; |
376 | bool m_multiline_enabled = false; |
377 | std::vector<EditLineStringType> m_input_lines; |
378 | EditorStatus m_editor_status; |
379 | int m_terminal_width = 0; |
380 | int m_base_line_number = 0; |
381 | unsigned m_current_line_index = 0; |
382 | int m_current_line_rows = -1; |
383 | int m_revert_cursor_index = 0; |
384 | int m_line_number_digits = 3; |
385 | std::string m_set_prompt; |
386 | std::string m_set_continuation_prompt; |
387 | std::string m_current_prompt; |
388 | bool m_needs_prompt_repaint = false; |
389 | volatile std::sig_atomic_t m_terminal_size_has_changed = 0; |
390 | std::string m_editor_name; |
391 | FILE *m_input_file; |
392 | FILE *m_output_file; |
393 | FILE *m_error_file; |
394 | ConnectionFileDescriptor m_input_connection; |
395 | |
396 | IsInputCompleteCallbackType m_is_input_complete_callback; |
397 | |
398 | FixIndentationCallbackType m_fix_indentation_callback; |
399 | const char *m_fix_indentation_callback_chars = nullptr; |
400 | |
401 | CompleteCallbackType m_completion_callback; |
402 | SuggestionCallbackType m_suggestion_callback; |
403 | |
404 | std::string m_prompt_ansi_prefix; |
405 | std::string m_prompt_ansi_suffix; |
406 | std::string m_suggestion_ansi_prefix; |
407 | std::string m_suggestion_ansi_suffix; |
408 | |
409 | std::size_t m_previous_autosuggestion_size = 0; |
410 | std::recursive_mutex &m_output_mutex; |
411 | }; |
412 | } |
413 | |
414 | #endif // LLDB_HOST_EDITLINE_H |
415 | |