/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/include/lldb/Host/Editline.h
Line | Count | Source (jump to first uncovered line) |
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 | | #if LLDB_EDITLINE_USE_WCHAR || defined(EL_CLIENTDATA) || LLDB_HAVE_EL_RFUNC_T |
79 | | using EditLineGetCharType = wchar_t; |
80 | | #else |
81 | | using EditLineGetCharType = char; |
82 | | #endif |
83 | | |
84 | | using EditlineGetCharCallbackType = int (*)(::EditLine *editline, |
85 | | EditLineGetCharType *c); |
86 | | using EditlineCommandCallbackType = unsigned char (*)(::EditLine *editline, |
87 | | int ch); |
88 | | using EditlinePromptCallbackType = const char *(*)(::EditLine *editline); |
89 | | |
90 | | class EditlineHistory; |
91 | | |
92 | | using EditlineHistorySP = std::shared_ptr<EditlineHistory>; |
93 | | |
94 | | using IsInputCompleteCallbackType = |
95 | | llvm::unique_function<bool(Editline *, StringList &)>; |
96 | | |
97 | | using FixIndentationCallbackType = |
98 | | llvm::unique_function<int(Editline *, StringList &, int)>; |
99 | | |
100 | | using SuggestionCallbackType = |
101 | | llvm::unique_function<std::optional<std::string>(llvm::StringRef)>; |
102 | | |
103 | | using CompleteCallbackType = llvm::unique_function<void(CompletionRequest &)>; |
104 | | |
105 | | /// Status used to decide when and how to start editing another line in |
106 | | /// multi-line sessions. |
107 | | enum class EditorStatus { |
108 | | |
109 | | /// The default state proceeds to edit the current line. |
110 | | Editing, |
111 | | |
112 | | /// Editing complete, returns the complete set of edited lines. |
113 | | Complete, |
114 | | |
115 | | /// End of input reported. |
116 | | EndOfInput, |
117 | | |
118 | | /// Editing interrupted. |
119 | | Interrupted |
120 | | }; |
121 | | |
122 | | /// Established locations that can be easily moved among with MoveCursor. |
123 | | enum class CursorLocation { |
124 | | /// The start of the first line in a multi-line edit session. |
125 | | BlockStart, |
126 | | |
127 | | /// The start of the current line in a multi-line edit session. |
128 | | EditingPrompt, |
129 | | |
130 | | /// The location of the cursor on the current line in a multi-line edit |
131 | | /// session. |
132 | | EditingCursor, |
133 | | |
134 | | /// The location immediately after the last character in a multi-line edit |
135 | | /// session. |
136 | | BlockEnd |
137 | | }; |
138 | | |
139 | | /// Operation for the history. |
140 | | enum class HistoryOperation { |
141 | | Oldest, |
142 | | Older, |
143 | | Current, |
144 | | Newer, |
145 | | Newest |
146 | | }; |
147 | | } |
148 | | |
149 | | using namespace line_editor; |
150 | | |
151 | | /// Instances of Editline provide an abstraction over libedit's EditLine |
152 | | /// facility. Both single- and multi-line editing are supported. |
153 | | class Editline { |
154 | | public: |
155 | | Editline(const char *editor_name, FILE *input_file, FILE *output_file, |
156 | | FILE *error_file, std::recursive_mutex &output_mutex); |
157 | | |
158 | | ~Editline(); |
159 | | |
160 | | /// Uses the user data storage of EditLine to retrieve an associated instance |
161 | | /// of Editline. |
162 | | static Editline *InstanceFor(::EditLine *editline); |
163 | | |
164 | | /// Sets a string to be used as a prompt, or combined with a line number to |
165 | | /// form a prompt. |
166 | | void SetPrompt(const char *prompt); |
167 | | |
168 | | /// Sets an alternate string to be used as a prompt for the second line and |
169 | | /// beyond in multi-line editing scenarios. |
170 | | void SetContinuationPrompt(const char *continuation_prompt); |
171 | | |
172 | | /// Call when the terminal size changes. |
173 | | void TerminalSizeChanged(); |
174 | | |
175 | | /// Returns the prompt established by SetPrompt. |
176 | | const char *GetPrompt(); |
177 | | |
178 | | /// Returns the index of the line currently being edited. |
179 | | uint32_t GetCurrentLine(); |
180 | | |
181 | | /// Interrupt the current edit as if ^C was pressed. |
182 | | bool Interrupt(); |
183 | | |
184 | | /// Cancel this edit and obliterate all trace of it. |
185 | | bool Cancel(); |
186 | | |
187 | | /// Register a callback for autosuggestion. |
188 | 0 | void SetSuggestionCallback(SuggestionCallbackType callback) { |
189 | 0 | m_suggestion_callback = std::move(callback); |
190 | 0 | } |
191 | | |
192 | | /// Register a callback for the tab key |
193 | 12 | void SetAutoCompleteCallback(CompleteCallbackType callback) { |
194 | 12 | m_completion_callback = std::move(callback); |
195 | 12 | } |
196 | | |
197 | | /// Register a callback for testing whether multi-line input is complete |
198 | 14 | void SetIsInputCompleteCallback(IsInputCompleteCallbackType callback) { |
199 | 14 | m_is_input_complete_callback = std::move(callback); |
200 | 14 | } |
201 | | |
202 | | /// Register a callback for determining the appropriate indentation for a line |
203 | | /// when creating a newline. An optional set of insertable characters can |
204 | | /// also trigger the callback. |
205 | | void SetFixIndentationCallback(FixIndentationCallbackType callback, |
206 | 0 | const char *indent_chars) { |
207 | 0 | m_fix_indentation_callback = std::move(callback); |
208 | 0 | m_fix_indentation_callback_chars = indent_chars; |
209 | 0 | } |
210 | | |
211 | 7 | void SetPromptAnsiPrefix(std::string prefix) { |
212 | 7 | m_prompt_ansi_prefix = std::move(prefix); |
213 | 7 | } |
214 | | |
215 | 7 | void SetPromptAnsiSuffix(std::string suffix) { |
216 | 7 | m_prompt_ansi_suffix = std::move(suffix); |
217 | 7 | } |
218 | | |
219 | 0 | void SetSuggestionAnsiPrefix(std::string prefix) { |
220 | 0 | m_suggestion_ansi_prefix = std::move(prefix); |
221 | 0 | } |
222 | | |
223 | 0 | void SetSuggestionAnsiSuffix(std::string suffix) { |
224 | 0 | m_suggestion_ansi_suffix = std::move(suffix); |
225 | 0 | } |
226 | | |
227 | | /// Prompts for and reads a single line of user input. |
228 | | bool GetLine(std::string &line, bool &interrupted); |
229 | | |
230 | | /// Prompts for and reads a multi-line batch of user input. |
231 | | bool GetLines(int first_line_number, StringList &lines, bool &interrupted); |
232 | | |
233 | | void PrintAsync(Stream *stream, const char *s, size_t len); |
234 | | |
235 | | /// Convert the current input lines into a UTF8 StringList |
236 | | StringList GetInputAsStringList(int line_count = UINT32_MAX); |
237 | | |
238 | | private: |
239 | | /// Sets the lowest line number for multi-line editing sessions. A value of |
240 | | /// zero suppresses line number printing in the prompt. |
241 | | void SetBaseLineNumber(int line_number); |
242 | | |
243 | | /// Returns the complete prompt by combining the prompt or continuation prompt |
244 | | /// with line numbers as appropriate. The line index is a zero-based index |
245 | | /// into the current multi-line session. |
246 | | std::string PromptForIndex(int line_index); |
247 | | |
248 | | /// Sets the current line index between line edits to allow free movement |
249 | | /// between lines. Updates the prompt to match. |
250 | | void SetCurrentLine(int line_index); |
251 | | |
252 | | /// Determines the width of the prompt in characters. The width is guaranteed |
253 | | /// to be the same for all lines of the current multi-line session. |
254 | | size_t GetPromptWidth(); |
255 | | |
256 | | /// Returns true if the underlying EditLine session's keybindings are |
257 | | /// Emacs-based, or false if they are VI-based. |
258 | | bool IsEmacs(); |
259 | | |
260 | | /// Returns true if the current EditLine buffer contains nothing but spaces, |
261 | | /// or is empty. |
262 | | bool IsOnlySpaces(); |
263 | | |
264 | | /// Helper method used by MoveCursor to determine relative line position. |
265 | | int GetLineIndexForLocation(CursorLocation location, int cursor_row); |
266 | | |
267 | | /// Move the cursor from one well-established location to another using |
268 | | /// relative line positioning and absolute column positioning. |
269 | | void MoveCursor(CursorLocation from, CursorLocation to); |
270 | | |
271 | | /// Clear from cursor position to bottom of screen and print input lines |
272 | | /// including prompts, optionally starting from a specific line. Lines are |
273 | | /// drawn with an extra space at the end to reserve room for the rightmost |
274 | | /// cursor position. |
275 | | void DisplayInput(int firstIndex = 0); |
276 | | |
277 | | /// Counts the number of rows a given line of content will end up occupying, |
278 | | /// taking into account both the preceding prompt and a single trailing space |
279 | | /// occupied by a cursor when at the end of the line. |
280 | | int CountRowsForLine(const EditLineStringType &content); |
281 | | |
282 | | /// Save the line currently being edited. |
283 | | void SaveEditedLine(); |
284 | | |
285 | | /// Replaces the current multi-line session with the next entry from history. |
286 | | unsigned char RecallHistory(HistoryOperation op); |
287 | | |
288 | | /// Character reading implementation for EditLine that supports our multi-line |
289 | | /// editing trickery. |
290 | | int GetCharacter(EditLineGetCharType *c); |
291 | | |
292 | | /// Prompt implementation for EditLine. |
293 | | const char *Prompt(); |
294 | | |
295 | | /// Line break command used when meta+return is pressed in multi-line mode. |
296 | | unsigned char BreakLineCommand(int ch); |
297 | | |
298 | | /// Command used when return is pressed in multi-line mode. |
299 | | unsigned char EndOrAddLineCommand(int ch); |
300 | | |
301 | | /// Delete command used when delete is pressed in multi-line mode. |
302 | | unsigned char DeleteNextCharCommand(int ch); |
303 | | |
304 | | /// Delete command used when backspace is pressed in multi-line mode. |
305 | | unsigned char DeletePreviousCharCommand(int ch); |
306 | | |
307 | | /// Line navigation command used when ^P or up arrow are pressed in multi-line |
308 | | /// mode. |
309 | | unsigned char PreviousLineCommand(int ch); |
310 | | |
311 | | /// Line navigation command used when ^N or down arrow are pressed in |
312 | | /// multi-line mode. |
313 | | unsigned char NextLineCommand(int ch); |
314 | | |
315 | | /// History navigation command used when Alt + up arrow is pressed in |
316 | | /// multi-line mode. |
317 | | unsigned char PreviousHistoryCommand(int ch); |
318 | | |
319 | | /// History navigation command used when Alt + down arrow is pressed in |
320 | | /// multi-line mode. |
321 | | unsigned char NextHistoryCommand(int ch); |
322 | | |
323 | | /// Buffer start command used when Esc < is typed in multi-line emacs mode. |
324 | | unsigned char BufferStartCommand(int ch); |
325 | | |
326 | | /// Buffer end command used when Esc > is typed in multi-line emacs mode. |
327 | | unsigned char BufferEndCommand(int ch); |
328 | | |
329 | | /// Context-sensitive tab insertion or code completion command used when the |
330 | | /// tab key is typed. |
331 | | unsigned char TabCommand(int ch); |
332 | | |
333 | | /// Apply autosuggestion part in gray as editline. |
334 | | unsigned char ApplyAutosuggestCommand(int ch); |
335 | | |
336 | | /// Command used when a character is typed. |
337 | | unsigned char TypedCharacter(int ch); |
338 | | |
339 | | /// Respond to normal character insertion by fixing line indentation |
340 | | unsigned char FixIndentationCommand(int ch); |
341 | | |
342 | | /// Revert line command used when moving between lines. |
343 | | unsigned char RevertLineCommand(int ch); |
344 | | |
345 | | /// Ensures that the current EditLine instance is properly configured for |
346 | | /// single or multi-line editing. |
347 | | void ConfigureEditor(bool multiline); |
348 | | |
349 | | bool CompleteCharacter(char ch, EditLineGetCharType &out); |
350 | | |
351 | | void ApplyTerminalSizeChange(); |
352 | | |
353 | | // The following set various editline parameters. It's not any less |
354 | | // verbose to put the editline calls into a function, but it |
355 | | // provides type safety, since the editline functions take varargs |
356 | | // parameters. |
357 | | void AddFunctionToEditLine(const EditLineCharType *command, |
358 | | const EditLineCharType *helptext, |
359 | | EditlineCommandCallbackType callbackFn); |
360 | | void SetEditLinePromptCallback(EditlinePromptCallbackType callbackFn); |
361 | | void SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn); |
362 | | |
363 | | #if LLDB_EDITLINE_USE_WCHAR |
364 | | std::wstring_convert<std::codecvt_utf8<wchar_t>> m_utf8conv; |
365 | | #endif |
366 | | ::EditLine *m_editline = nullptr; |
367 | | EditlineHistorySP m_history_sp; |
368 | | bool m_in_history = false; |
369 | | std::vector<EditLineStringType> m_live_history_lines; |
370 | | bool m_multiline_enabled = false; |
371 | | std::vector<EditLineStringType> m_input_lines; |
372 | | EditorStatus m_editor_status; |
373 | | int m_terminal_width = 0; |
374 | | int m_base_line_number = 0; |
375 | | unsigned m_current_line_index = 0; |
376 | | int m_current_line_rows = -1; |
377 | | int m_revert_cursor_index = 0; |
378 | | int m_line_number_digits = 3; |
379 | | std::string m_set_prompt; |
380 | | std::string m_set_continuation_prompt; |
381 | | std::string m_current_prompt; |
382 | | bool m_needs_prompt_repaint = false; |
383 | | volatile std::sig_atomic_t m_terminal_size_has_changed = 0; |
384 | | std::string m_editor_name; |
385 | | FILE *m_input_file; |
386 | | FILE *m_output_file; |
387 | | FILE *m_error_file; |
388 | | ConnectionFileDescriptor m_input_connection; |
389 | | |
390 | | IsInputCompleteCallbackType m_is_input_complete_callback; |
391 | | |
392 | | FixIndentationCallbackType m_fix_indentation_callback; |
393 | | const char *m_fix_indentation_callback_chars = nullptr; |
394 | | |
395 | | CompleteCallbackType m_completion_callback; |
396 | | SuggestionCallbackType m_suggestion_callback; |
397 | | |
398 | | std::string m_prompt_ansi_prefix; |
399 | | std::string m_prompt_ansi_suffix; |
400 | | std::string m_suggestion_ansi_prefix; |
401 | | std::string m_suggestion_ansi_suffix; |
402 | | |
403 | | std::size_t m_previous_autosuggestion_size = 0; |
404 | | std::recursive_mutex &m_output_mutex; |
405 | | }; |
406 | | } |
407 | | |
408 | | #endif // LLDB_HOST_EDITLINE_H |