Editline.h revision 360784
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
23//    working around it here.
24// c) When resizing the terminal window, if the cursor moves between rows
25// libedit will get confused. d) The incremental search uses escape to cancel
26// input, so it's confused by
27// ANSI sequences starting with escape.
28// e) Emoji support is fairly terrible, presumably it doesn't understand
29// composed characters?
30
31#ifndef liblldb_Editline_h_
32#define liblldb_Editline_h_
33#if defined(__cplusplus)
34
35#include "lldb/Host/Config.h"
36
37#if LLDB_EDITLINE_USE_WCHAR
38#include <codecvt>
39#endif
40#include <locale>
41#include <sstream>
42#include <vector>
43
44#include "lldb/Host/ConnectionFileDescriptor.h"
45#include "lldb/lldb-private.h"
46
47#if defined(_WIN32)
48#include "lldb/Host/windows/editlinewin.h"
49#elif !defined(__ANDROID__)
50#include <histedit.h>
51#endif
52
53#include <mutex>
54#include <string>
55#include <vector>
56
57#include "lldb/Host/ConnectionFileDescriptor.h"
58#include "lldb/Utility/CompletionRequest.h"
59#include "lldb/Utility/FileSpec.h"
60#include "lldb/Utility/Predicate.h"
61
62namespace lldb_private {
63namespace line_editor {
64
65// type alias's to help manage 8 bit and wide character versions of libedit
66#if LLDB_EDITLINE_USE_WCHAR
67using EditLineStringType = std::wstring;
68using EditLineStringStreamType = std::wstringstream;
69using EditLineCharType = wchar_t;
70#else
71using EditLineStringType = std::string;
72using EditLineStringStreamType = std::stringstream;
73using 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
81using EditLineGetCharType = wchar_t;
82#else
83using EditLineGetCharType = char;
84#endif
85
86typedef int (*EditlineGetCharCallbackType)(::EditLine *editline,
87                                           EditLineGetCharType *c);
88typedef unsigned char (*EditlineCommandCallbackType)(::EditLine *editline,
89                                                     int ch);
90typedef const char *(*EditlinePromptCallbackType)(::EditLine *editline);
91
92class EditlineHistory;
93
94typedef std::shared_ptr<EditlineHistory> EditlineHistorySP;
95
96typedef bool (*IsInputCompleteCallbackType)(Editline *editline,
97                                            StringList &lines, void *baton);
98
99typedef int (*FixIndentationCallbackType)(Editline *editline,
100                                          const StringList &lines,
101                                          int cursor_position, void *baton);
102
103typedef void (*CompleteCallbackType)(CompletionRequest &request, void *baton);
104
105/// Status used to decide when and how to start editing another line in
106/// multi-line sessions
107enum 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
123enum 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.
140enum class HistoryOperation {
141  Oldest,
142  Older,
143  Current,
144  Newer,
145  Newest
146};
147}
148
149using namespace line_editor;
150
151/// Instances of Editline provide an abstraction over libedit's EditLine
152/// facility.  Both
153/// single- and multi-line editing are supported.
154class Editline {
155public:
156  Editline(const char *editor_name, FILE *input_file, FILE *output_file,
157           FILE *error_file, bool color_prompts);
158
159  ~Editline();
160
161  /// Uses the user data storage of EditLine to retrieve an associated instance
162  /// of Editline.
163  static Editline *InstanceFor(::EditLine *editline);
164
165  /// Sets a string to be used as a prompt, or combined with a line number to
166  /// form a prompt.
167  void SetPrompt(const char *prompt);
168
169  /// Sets an alternate string to be used as a prompt for the second line and
170  /// beyond in multi-line
171  /// editing scenarios.
172  void SetContinuationPrompt(const char *continuation_prompt);
173
174  /// Required to update the width of the terminal registered for I/O.  It is
175  /// critical that this
176  /// be correct at all times.
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 the tab key
192  void SetAutoCompleteCallback(CompleteCallbackType callback, void *baton);
193
194  /// Register a callback for testing whether multi-line input is complete
195  void SetIsInputCompleteCallback(IsInputCompleteCallbackType callback,
196                                  void *baton);
197
198  /// Register a callback for determining the appropriate indentation for a line
199  /// when creating a newline.  An optional set of insertable characters can
200  /// also
201  /// trigger the callback.
202  bool SetFixIndentationCallback(FixIndentationCallbackType callback,
203                                 void *baton, const char *indent_chars);
204
205  /// Prompts for and reads a single line of user input.
206  bool GetLine(std::string &line, bool &interrupted);
207
208  /// Prompts for and reads a multi-line batch of user input.
209  bool GetLines(int first_line_number, StringList &lines, bool &interrupted);
210
211  void PrintAsync(Stream *stream, const char *s, size_t len);
212
213private:
214  /// Sets the lowest line number for multi-line editing sessions.  A value of
215  /// zero suppresses
216  /// line number printing in the prompt.
217  void SetBaseLineNumber(int line_number);
218
219  /// Returns the complete prompt by combining the prompt or continuation prompt
220  /// with line numbers
221  /// as appropriate.  The line index is a zero-based index into the current
222  /// multi-line session.
223  std::string PromptForIndex(int line_index);
224
225  /// Sets the current line index between line edits to allow free movement
226  /// between lines.  Updates
227  /// the prompt to match.
228  void SetCurrentLine(int line_index);
229
230  /// Determines the width of the prompt in characters.  The width is guaranteed
231  /// to be the same for
232  /// all lines of the current multi-line session.
233  int GetPromptWidth();
234
235  /// Returns true if the underlying EditLine session's keybindings are
236  /// Emacs-based, or false if
237  /// they are VI-based.
238  bool IsEmacs();
239
240  /// Returns true if the current EditLine buffer contains nothing but spaces,
241  /// or is empty.
242  bool IsOnlySpaces();
243
244  /// Helper method used by MoveCursor to determine relative line position.
245  int GetLineIndexForLocation(CursorLocation location, int cursor_row);
246
247  /// Move the cursor from one well-established location to another using
248  /// relative line positioning
249  /// and absolute column positioning.
250  void MoveCursor(CursorLocation from, CursorLocation to);
251
252  /// Clear from cursor position to bottom of screen and print input lines
253  /// including prompts, optionally
254  /// starting from a specific line.  Lines are drawn with an extra space at the
255  /// end to reserve room for
256  /// the rightmost cursor position.
257  void DisplayInput(int firstIndex = 0);
258
259  /// Counts the number of rows a given line of content will end up occupying,
260  /// taking into account both
261  /// the preceding prompt and a single trailing space occupied by a cursor when
262  /// at the end of the line.
263  int CountRowsForLine(const EditLineStringType &content);
264
265  /// Save the line currently being edited
266  void SaveEditedLine();
267
268  /// Convert the current input lines into a UTF8 StringList
269  StringList GetInputAsStringList(int line_count = UINT32_MAX);
270
271  /// Replaces the current multi-line session with the next entry from history.
272  unsigned char RecallHistory(HistoryOperation op);
273
274  /// Character reading implementation for EditLine that supports our multi-line
275  /// editing trickery.
276  int GetCharacter(EditLineGetCharType *c);
277
278  /// Prompt implementation for EditLine.
279  const char *Prompt();
280
281  /// Line break command used when meta+return is pressed in multi-line mode.
282  unsigned char BreakLineCommand(int ch);
283
284  /// Command used when return is pressed in multi-line mode.
285  unsigned char EndOrAddLineCommand(int ch);
286
287  /// Delete command used when delete is pressed in multi-line mode.
288  unsigned char DeleteNextCharCommand(int ch);
289
290  /// Delete command used when backspace is pressed in multi-line mode.
291  unsigned char DeletePreviousCharCommand(int ch);
292
293  /// Line navigation command used when ^P or up arrow are pressed in multi-line
294  /// mode.
295  unsigned char PreviousLineCommand(int ch);
296
297  /// Line navigation command used when ^N or down arrow are pressed in
298  /// multi-line mode.
299  unsigned char NextLineCommand(int ch);
300
301  /// History navigation command used when Alt + up arrow is pressed in
302  /// multi-line mode.
303  unsigned char PreviousHistoryCommand(int ch);
304
305  /// History navigation command used when Alt + down arrow is pressed in
306  /// multi-line mode.
307  unsigned char NextHistoryCommand(int ch);
308
309  /// Buffer start command used when Esc < is typed in multi-line emacs mode.
310  unsigned char BufferStartCommand(int ch);
311
312  /// Buffer end command used when Esc > is typed in multi-line emacs mode.
313  unsigned char BufferEndCommand(int ch);
314
315  /// Context-sensitive tab insertion or code completion command used when the
316  /// tab key is typed.
317  unsigned char TabCommand(int ch);
318
319  /// Respond to normal character insertion by fixing line indentation
320  unsigned char FixIndentationCommand(int ch);
321
322  /// Revert line command used when moving between lines.
323  unsigned char RevertLineCommand(int ch);
324
325  /// Ensures that the current EditLine instance is properly configured for
326  /// single or multi-line editing.
327  void ConfigureEditor(bool multiline);
328
329  bool CompleteCharacter(char ch, EditLineGetCharType &out);
330
331private:
332#if LLDB_EDITLINE_USE_WCHAR
333  std::wstring_convert<std::codecvt_utf8<wchar_t>> m_utf8conv;
334#endif
335  ::EditLine *m_editline = nullptr;
336  EditlineHistorySP m_history_sp;
337  bool m_in_history = false;
338  std::vector<EditLineStringType> m_live_history_lines;
339  bool m_multiline_enabled = false;
340  std::vector<EditLineStringType> m_input_lines;
341  EditorStatus m_editor_status;
342  bool m_color_prompts = true;
343  int m_terminal_width = 0;
344  int m_base_line_number = 0;
345  unsigned m_current_line_index = 0;
346  int m_current_line_rows = -1;
347  int m_revert_cursor_index = 0;
348  int m_line_number_digits = 3;
349  std::string m_set_prompt;
350  std::string m_set_continuation_prompt;
351  std::string m_current_prompt;
352  bool m_needs_prompt_repaint = false;
353  std::string m_editor_name;
354  FILE *m_input_file;
355  FILE *m_output_file;
356  FILE *m_error_file;
357  ConnectionFileDescriptor m_input_connection;
358  IsInputCompleteCallbackType m_is_input_complete_callback = nullptr;
359  void *m_is_input_complete_callback_baton = nullptr;
360  FixIndentationCallbackType m_fix_indentation_callback = nullptr;
361  void *m_fix_indentation_callback_baton = nullptr;
362  const char *m_fix_indentation_callback_chars = nullptr;
363  CompleteCallbackType m_completion_callback = nullptr;
364  void *m_completion_callback_baton = nullptr;
365
366  std::mutex m_output_mutex;
367};
368}
369
370#endif // #if defined(__cplusplus)
371#endif // liblldb_Editline_h_
372