1 | //===-- Editline.cpp ------------------------------------------------------===// |
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 | #include <climits> |
10 | #include <iomanip> |
11 | #include <optional> |
12 | |
13 | #include "lldb/Host/ConnectionFileDescriptor.h" |
14 | #include "lldb/Host/Editline.h" |
15 | #include "lldb/Host/FileSystem.h" |
16 | #include "lldb/Host/Host.h" |
17 | #include "lldb/Host/StreamFile.h" |
18 | #include "lldb/Utility/AnsiTerminal.h" |
19 | #include "lldb/Utility/CompletionRequest.h" |
20 | #include "lldb/Utility/FileSpec.h" |
21 | #include "lldb/Utility/LLDBAssert.h" |
22 | #include "lldb/Utility/SelectHelper.h" |
23 | #include "lldb/Utility/Status.h" |
24 | #include "lldb/Utility/StreamString.h" |
25 | #include "lldb/Utility/StringList.h" |
26 | #include "lldb/Utility/Timeout.h" |
27 | #include "lldb/lldb-forward.h" |
28 | #include "llvm/Support/ConvertUTF.h" |
29 | |
30 | #include "llvm/Support/FileSystem.h" |
31 | #include "llvm/Support/Locale.h" |
32 | #include "llvm/Support/Threading.h" |
33 | |
34 | using namespace lldb_private; |
35 | using namespace lldb_private::line_editor; |
36 | |
37 | // Editline uses careful cursor management to achieve the illusion of editing a |
38 | // multi-line block of text with a single line editor. Preserving this |
39 | // illusion requires fairly careful management of cursor state. Read and |
40 | // understand the relationship between DisplayInput(), MoveCursor(), |
41 | // SetCurrentLine(), and SaveEditedLine() before making changes. |
42 | |
43 | /// https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf |
44 | #define ESCAPE "\x1b" |
45 | #define ANSI_CLEAR_BELOW ESCAPE "[J" |
46 | #define ANSI_CLEAR_RIGHT ESCAPE "[K" |
47 | #define ANSI_SET_COLUMN_N ESCAPE "[%dG" |
48 | #define ANSI_UP_N_ROWS ESCAPE "[%dA" |
49 | #define ANSI_DOWN_N_ROWS ESCAPE "[%dB" |
50 | |
51 | #if LLDB_EDITLINE_USE_WCHAR |
52 | |
53 | #define EditLineConstString(str) L##str |
54 | #define EditLineStringFormatSpec "%ls" |
55 | |
56 | #else |
57 | |
58 | #define EditLineConstString(str) str |
59 | #define EditLineStringFormatSpec "%s" |
60 | |
61 | // use #defines so wide version functions and structs will resolve to old |
62 | // versions for case of libedit not built with wide char support |
63 | #define history_w history |
64 | #define history_winit history_init |
65 | #define history_wend history_end |
66 | #define HistoryW History |
67 | #define HistEventW HistEvent |
68 | #define LineInfoW LineInfo |
69 | |
70 | #define el_wgets el_gets |
71 | #define el_wgetc el_getc |
72 | #define el_wpush el_push |
73 | #define el_wparse el_parse |
74 | #define el_wset el_set |
75 | #define el_wget el_get |
76 | #define el_wline el_line |
77 | #define el_winsertstr el_insertstr |
78 | #define el_wdeletestr el_deletestr |
79 | |
80 | #endif // #if LLDB_EDITLINE_USE_WCHAR |
81 | |
82 | template <typename T> class ScopedOptional { |
83 | public: |
84 | template <typename... Args> |
85 | ScopedOptional(std::optional<T> &optional, Args &&...args) |
86 | : m_optional(optional) { |
87 | m_optional.emplace(std::forward<Args>(args)...); |
88 | } |
89 | ~ScopedOptional() { m_optional.reset(); } |
90 | |
91 | private: |
92 | std::optional<T> &m_optional; |
93 | }; |
94 | |
95 | bool IsOnlySpaces(const EditLineStringType &content) { |
96 | for (wchar_t ch : content) { |
97 | if (ch != EditLineCharType(' ')) |
98 | return false; |
99 | } |
100 | return true; |
101 | } |
102 | |
103 | static size_t ColumnWidth(llvm::StringRef str) { |
104 | std::string stripped = ansi::StripAnsiTerminalCodes(str); |
105 | return llvm::sys::locale::columnWidth(s: stripped); |
106 | } |
107 | |
108 | static int GetOperation(HistoryOperation op) { |
109 | // The naming used by editline for the history operations is counter |
110 | // intuitive to how it's used in LLDB's editline implementation. |
111 | // |
112 | // - The H_LAST returns the oldest entry in the history. |
113 | // |
114 | // - The H_PREV operation returns the previous element in the history, which |
115 | // is newer than the current one. |
116 | // |
117 | // - The H_CURR returns the current entry in the history. |
118 | // |
119 | // - The H_NEXT operation returns the next element in the history, which is |
120 | // older than the current one. |
121 | // |
122 | // - The H_FIRST returns the most recent entry in the history. |
123 | // |
124 | // The naming of the enum entries match the semantic meaning. |
125 | switch(op) { |
126 | case HistoryOperation::Oldest: |
127 | return H_LAST; |
128 | case HistoryOperation::Older: |
129 | return H_NEXT; |
130 | case HistoryOperation::Current: |
131 | return H_CURR; |
132 | case HistoryOperation::Newer: |
133 | return H_PREV; |
134 | case HistoryOperation::Newest: |
135 | return H_FIRST; |
136 | } |
137 | llvm_unreachable("Fully covered switch!" ); |
138 | } |
139 | |
140 | |
141 | EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) { |
142 | EditLineStringStreamType combined_stream; |
143 | for (EditLineStringType line : lines) { |
144 | combined_stream << line.c_str() << "\n" ; |
145 | } |
146 | return combined_stream.str(); |
147 | } |
148 | |
149 | std::vector<EditLineStringType> SplitLines(const EditLineStringType &input) { |
150 | std::vector<EditLineStringType> result; |
151 | size_t start = 0; |
152 | while (start < input.length()) { |
153 | size_t end = input.find(c: '\n', pos: start); |
154 | if (end == std::string::npos) { |
155 | result.push_back(x: input.substr(pos: start)); |
156 | break; |
157 | } |
158 | result.push_back(x: input.substr(pos: start, n: end - start)); |
159 | start = end + 1; |
160 | } |
161 | // Treat an empty history session as a single command of zero-length instead |
162 | // of returning an empty vector. |
163 | if (result.empty()) { |
164 | result.emplace_back(); |
165 | } |
166 | return result; |
167 | } |
168 | |
169 | EditLineStringType FixIndentation(const EditLineStringType &line, |
170 | int indent_correction) { |
171 | if (indent_correction == 0) |
172 | return line; |
173 | if (indent_correction < 0) |
174 | return line.substr(pos: -indent_correction); |
175 | return EditLineStringType(indent_correction, EditLineCharType(' ')) + line; |
176 | } |
177 | |
178 | int GetIndentation(const EditLineStringType &line) { |
179 | int space_count = 0; |
180 | for (EditLineCharType ch : line) { |
181 | if (ch != EditLineCharType(' ')) |
182 | break; |
183 | ++space_count; |
184 | } |
185 | return space_count; |
186 | } |
187 | |
188 | bool IsInputPending(FILE *file) { |
189 | // FIXME: This will be broken on Windows if we ever re-enable Editline. You |
190 | // can't use select |
191 | // on something that isn't a socket. This will have to be re-written to not |
192 | // use a FILE*, but instead use some kind of yet-to-be-created abstraction |
193 | // that select-like functionality on non-socket objects. |
194 | const int fd = fileno(stream: file); |
195 | SelectHelper select_helper; |
196 | select_helper.SetTimeout(std::chrono::microseconds(0)); |
197 | select_helper.FDSetRead(fd); |
198 | return select_helper.Select().Success(); |
199 | } |
200 | |
201 | namespace lldb_private { |
202 | namespace line_editor { |
203 | typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP; |
204 | |
205 | // EditlineHistory objects are sometimes shared between multiple Editline |
206 | // instances with the same program name. |
207 | |
208 | class EditlineHistory { |
209 | private: |
210 | // Use static GetHistory() function to get a EditlineHistorySP to one of |
211 | // these objects |
212 | EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries) |
213 | : m_prefix(prefix) { |
214 | m_history = history_winit(); |
215 | history_w(m_history, &m_event, H_SETSIZE, size); |
216 | if (unique_entries) |
217 | history_w(m_history, &m_event, H_SETUNIQUE, 1); |
218 | } |
219 | |
220 | const char *GetHistoryFilePath() { |
221 | // Compute the history path lazily. |
222 | if (m_path.empty() && m_history && !m_prefix.empty()) { |
223 | llvm::SmallString<128> lldb_history_file; |
224 | FileSystem::Instance().GetHomeDirectory(path&: lldb_history_file); |
225 | llvm::sys::path::append(path&: lldb_history_file, a: ".lldb" ); |
226 | |
227 | // LLDB stores its history in ~/.lldb/. If for some reason this directory |
228 | // isn't writable or cannot be created, history won't be available. |
229 | if (!llvm::sys::fs::create_directory(path: lldb_history_file)) { |
230 | #if LLDB_EDITLINE_USE_WCHAR |
231 | std::string filename = m_prefix + "-widehistory" ; |
232 | #else |
233 | std::string filename = m_prefix + "-history" ; |
234 | #endif |
235 | llvm::sys::path::append(path&: lldb_history_file, a: filename); |
236 | m_path = std::string(lldb_history_file.str()); |
237 | } |
238 | } |
239 | |
240 | if (m_path.empty()) |
241 | return nullptr; |
242 | |
243 | return m_path.c_str(); |
244 | } |
245 | |
246 | public: |
247 | ~EditlineHistory() { |
248 | Save(); |
249 | |
250 | if (m_history) { |
251 | history_wend(m_history); |
252 | m_history = nullptr; |
253 | } |
254 | } |
255 | |
256 | static EditlineHistorySP GetHistory(const std::string &prefix) { |
257 | typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap; |
258 | static std::recursive_mutex g_mutex; |
259 | static WeakHistoryMap g_weak_map; |
260 | std::lock_guard<std::recursive_mutex> guard(g_mutex); |
261 | WeakHistoryMap::const_iterator pos = g_weak_map.find(x: prefix); |
262 | EditlineHistorySP history_sp; |
263 | if (pos != g_weak_map.end()) { |
264 | history_sp = pos->second.lock(); |
265 | if (history_sp) |
266 | return history_sp; |
267 | g_weak_map.erase(position: pos); |
268 | } |
269 | history_sp.reset(p: new EditlineHistory(prefix, 800, true)); |
270 | g_weak_map[prefix] = history_sp; |
271 | return history_sp; |
272 | } |
273 | |
274 | bool IsValid() const { return m_history != nullptr; } |
275 | |
276 | HistoryW *GetHistoryPtr() { return m_history; } |
277 | |
278 | void Enter(const EditLineCharType *line_cstr) { |
279 | if (m_history) |
280 | history_w(m_history, &m_event, H_ENTER, line_cstr); |
281 | } |
282 | |
283 | bool Load() { |
284 | if (m_history) { |
285 | const char *path = GetHistoryFilePath(); |
286 | if (path) { |
287 | history_w(m_history, &m_event, H_LOAD, path); |
288 | return true; |
289 | } |
290 | } |
291 | return false; |
292 | } |
293 | |
294 | bool Save() { |
295 | if (m_history) { |
296 | const char *path = GetHistoryFilePath(); |
297 | if (path) { |
298 | history_w(m_history, &m_event, H_SAVE, path); |
299 | return true; |
300 | } |
301 | } |
302 | return false; |
303 | } |
304 | |
305 | protected: |
306 | /// The history object. |
307 | HistoryW *m_history = nullptr; |
308 | /// The history event needed to contain all history events. |
309 | HistEventW m_event; |
310 | /// The prefix name (usually the editline program name) to use when |
311 | /// loading/saving history. |
312 | std::string m_prefix; |
313 | /// Path to the history file. |
314 | std::string m_path; |
315 | }; |
316 | } |
317 | } |
318 | |
319 | // Editline private methods |
320 | |
321 | void Editline::SetBaseLineNumber(int line_number) { |
322 | m_base_line_number = line_number; |
323 | m_line_number_digits = |
324 | std::max<int>(a: 3, b: std::to_string(val: line_number).length() + 1); |
325 | } |
326 | |
327 | std::string Editline::PromptForIndex(int line_index) { |
328 | bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0; |
329 | std::string prompt = m_set_prompt; |
330 | if (use_line_numbers && prompt.length() == 0) |
331 | prompt = ": " ; |
332 | std::string continuation_prompt = prompt; |
333 | if (m_set_continuation_prompt.length() > 0) { |
334 | continuation_prompt = m_set_continuation_prompt; |
335 | // Ensure that both prompts are the same length through space padding |
336 | const size_t prompt_width = ColumnWidth(str: prompt); |
337 | const size_t cont_prompt_width = ColumnWidth(str: continuation_prompt); |
338 | const size_t padded_prompt_width = |
339 | std::max(a: prompt_width, b: cont_prompt_width); |
340 | if (prompt_width < padded_prompt_width) |
341 | prompt += std::string(padded_prompt_width - prompt_width, ' '); |
342 | else if (cont_prompt_width < padded_prompt_width) |
343 | continuation_prompt += |
344 | std::string(padded_prompt_width - cont_prompt_width, ' '); |
345 | } |
346 | |
347 | if (use_line_numbers) { |
348 | StreamString prompt_stream; |
349 | prompt_stream.Printf( |
350 | format: "%*d%s" , m_line_number_digits, m_base_line_number + line_index, |
351 | (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str()); |
352 | return std::string(std::move(prompt_stream.GetString())); |
353 | } |
354 | return (line_index == 0) ? prompt : continuation_prompt; |
355 | } |
356 | |
357 | void Editline::SetCurrentLine(int line_index) { |
358 | m_current_line_index = line_index; |
359 | m_current_prompt = PromptForIndex(line_index); |
360 | } |
361 | |
362 | size_t Editline::GetPromptWidth() { return ColumnWidth(str: PromptForIndex(line_index: 0)); } |
363 | |
364 | bool Editline::IsEmacs() { |
365 | const char *editor; |
366 | el_get(m_editline, EL_EDITOR, &editor); |
367 | return editor[0] == 'e'; |
368 | } |
369 | |
370 | bool Editline::IsOnlySpaces() { |
371 | const LineInfoW *info = el_wline(m_editline); |
372 | for (const EditLineCharType *character = info->buffer; |
373 | character < info->lastchar; character++) { |
374 | if (*character != ' ') |
375 | return false; |
376 | } |
377 | return true; |
378 | } |
379 | |
380 | int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) { |
381 | int line = 0; |
382 | if (location == CursorLocation::EditingPrompt || |
383 | location == CursorLocation::BlockEnd || |
384 | location == CursorLocation::EditingCursor) { |
385 | for (unsigned index = 0; index < m_current_line_index; index++) { |
386 | line += CountRowsForLine(content: m_input_lines[index]); |
387 | } |
388 | if (location == CursorLocation::EditingCursor) { |
389 | line += cursor_row; |
390 | } else if (location == CursorLocation::BlockEnd) { |
391 | for (unsigned index = m_current_line_index; index < m_input_lines.size(); |
392 | index++) { |
393 | line += CountRowsForLine(content: m_input_lines[index]); |
394 | } |
395 | --line; |
396 | } |
397 | } |
398 | return line; |
399 | } |
400 | |
401 | void Editline::MoveCursor(CursorLocation from, CursorLocation to) { |
402 | const LineInfoW *info = el_wline(m_editline); |
403 | int editline_cursor_position = |
404 | (int)((info->cursor - info->buffer) + GetPromptWidth()); |
405 | int editline_cursor_row = editline_cursor_position / m_terminal_width; |
406 | |
407 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
408 | |
409 | // Determine relative starting and ending lines |
410 | int fromLine = GetLineIndexForLocation(location: from, cursor_row: editline_cursor_row); |
411 | int toLine = GetLineIndexForLocation(location: to, cursor_row: editline_cursor_row); |
412 | if (toLine != fromLine) { |
413 | fprintf(stream: locked_stream.GetFile().GetStream(), |
414 | format: (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, |
415 | std::abs(x: toLine - fromLine)); |
416 | } |
417 | |
418 | // Determine target column |
419 | int toColumn = 1; |
420 | if (to == CursorLocation::EditingCursor) { |
421 | toColumn = |
422 | editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1; |
423 | } else if (to == CursorLocation::BlockEnd && !m_input_lines.empty()) { |
424 | toColumn = |
425 | ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % |
426 | 80) + |
427 | 1; |
428 | } |
429 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_SET_COLUMN_N, toColumn); |
430 | } |
431 | |
432 | void Editline::DisplayInput(int firstIndex) { |
433 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
434 | fprintf(stream: locked_stream.GetFile().GetStream(), |
435 | ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1); |
436 | int line_count = (int)m_input_lines.size(); |
437 | for (int index = firstIndex; index < line_count; index++) { |
438 | fprintf(stream: locked_stream.GetFile().GetStream(), |
439 | format: "%s" |
440 | "%s" |
441 | "%s" EditLineStringFormatSpec " " , |
442 | m_prompt_ansi_prefix.c_str(), PromptForIndex(line_index: index).c_str(), |
443 | m_prompt_ansi_suffix.c_str(), m_input_lines[index].c_str()); |
444 | if (index < line_count - 1) |
445 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "\n" ); |
446 | } |
447 | } |
448 | |
449 | int Editline::CountRowsForLine(const EditLineStringType &content) { |
450 | std::string prompt = |
451 | PromptForIndex(line_index: 0); // Prompt width is constant during an edit session |
452 | int line_length = (int)(content.length() + ColumnWidth(str: prompt)); |
453 | return (line_length / m_terminal_width) + 1; |
454 | } |
455 | |
456 | void Editline::SaveEditedLine() { |
457 | const LineInfoW *info = el_wline(m_editline); |
458 | m_input_lines[m_current_line_index] = |
459 | EditLineStringType(info->buffer, info->lastchar - info->buffer); |
460 | } |
461 | |
462 | StringList Editline::GetInputAsStringList(int line_count) { |
463 | StringList lines; |
464 | for (EditLineStringType line : m_input_lines) { |
465 | if (line_count == 0) |
466 | break; |
467 | #if LLDB_EDITLINE_USE_WCHAR |
468 | std::string buffer; |
469 | llvm::convertWideToUTF8(Source: line, Result&: buffer); |
470 | lines.AppendString(s: buffer); |
471 | #else |
472 | lines.AppendString(line); |
473 | #endif |
474 | --line_count; |
475 | } |
476 | return lines; |
477 | } |
478 | |
479 | unsigned char Editline::RecallHistory(HistoryOperation op) { |
480 | assert(op == HistoryOperation::Older || op == HistoryOperation::Newer); |
481 | if (!m_history_sp || !m_history_sp->IsValid()) |
482 | return CC_ERROR; |
483 | |
484 | HistoryW *pHistory = m_history_sp->GetHistoryPtr(); |
485 | HistEventW history_event; |
486 | std::vector<EditLineStringType> new_input_lines; |
487 | |
488 | // Treat moving from the "live" entry differently |
489 | if (!m_in_history) { |
490 | switch (op) { |
491 | case HistoryOperation::Newer: |
492 | return CC_ERROR; // Can't go newer than the "live" entry |
493 | case HistoryOperation::Older: { |
494 | if (history_w(pHistory, &history_event, |
495 | GetOperation(op: HistoryOperation::Newest)) == -1) |
496 | return CC_ERROR; |
497 | // Save any edits to the "live" entry in case we return by moving forward |
498 | // in history (it would be more bash-like to save over any current entry, |
499 | // but libedit doesn't offer the ability to add entries anywhere except |
500 | // the end.) |
501 | SaveEditedLine(); |
502 | m_live_history_lines = m_input_lines; |
503 | m_in_history = true; |
504 | } break; |
505 | default: |
506 | llvm_unreachable("unsupported history direction" ); |
507 | } |
508 | } else { |
509 | if (history_w(pHistory, &history_event, GetOperation(op)) == -1) { |
510 | switch (op) { |
511 | case HistoryOperation::Older: |
512 | // Can't move earlier than the earliest entry. |
513 | return CC_ERROR; |
514 | case HistoryOperation::Newer: |
515 | // Moving to newer-than-the-newest entry yields the "live" entry. |
516 | new_input_lines = m_live_history_lines; |
517 | m_in_history = false; |
518 | break; |
519 | default: |
520 | llvm_unreachable("unsupported history direction" ); |
521 | } |
522 | } |
523 | } |
524 | |
525 | // If we're pulling the lines from history, split them apart |
526 | if (m_in_history) |
527 | new_input_lines = SplitLines(input: history_event.str); |
528 | |
529 | // Erase the current edit session and replace it with a new one |
530 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::BlockStart); |
531 | m_input_lines = new_input_lines; |
532 | DisplayInput(); |
533 | |
534 | // Prepare to edit the last line when moving to previous entry, or the first |
535 | // line when moving to next entry |
536 | switch (op) { |
537 | case HistoryOperation::Older: |
538 | m_current_line_index = (int)m_input_lines.size() - 1; |
539 | break; |
540 | case HistoryOperation::Newer: |
541 | m_current_line_index = 0; |
542 | break; |
543 | default: |
544 | llvm_unreachable("unsupported history direction" ); |
545 | } |
546 | SetCurrentLine(m_current_line_index); |
547 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingPrompt); |
548 | return CC_NEWLINE; |
549 | } |
550 | |
551 | int Editline::GetCharacter(EditLineGetCharType *c) { |
552 | const LineInfoW *info = el_wline(m_editline); |
553 | |
554 | // Paint a ANSI formatted version of the desired prompt over the version |
555 | // libedit draws. (will only be requested if colors are supported) |
556 | if (m_needs_prompt_repaint) { |
557 | ScopedOptional<LockedStreamFile> scope(m_locked_output, |
558 | m_output_stream_sp->Lock()); |
559 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::EditingPrompt); |
560 | fprintf(stream: m_locked_output->GetFile().GetStream(), |
561 | format: "%s" |
562 | "%s" |
563 | "%s" , |
564 | m_prompt_ansi_prefix.c_str(), Prompt(), |
565 | m_prompt_ansi_suffix.c_str()); |
566 | MoveCursor(from: CursorLocation::EditingPrompt, to: CursorLocation::EditingCursor); |
567 | m_needs_prompt_repaint = false; |
568 | } |
569 | |
570 | if (m_multiline_enabled) { |
571 | // Detect when the number of rows used for this input line changes due to |
572 | // an edit |
573 | int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth()); |
574 | int new_line_rows = (lineLength / m_terminal_width) + 1; |
575 | if (m_current_line_rows != -1 && new_line_rows != m_current_line_rows) { |
576 | // Respond by repainting the current state from this line on |
577 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::EditingPrompt); |
578 | SaveEditedLine(); |
579 | DisplayInput(firstIndex: m_current_line_index); |
580 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingCursor); |
581 | } |
582 | m_current_line_rows = new_line_rows; |
583 | } |
584 | |
585 | if (m_terminal_size_has_changed) |
586 | ApplyTerminalSizeChange(); |
587 | |
588 | // This mutex is locked by our caller (GetLine). Unlock it while we read a |
589 | // character (blocking operation), so we do not hold the mutex |
590 | // indefinitely. This gives a chance for someone to interrupt us. After |
591 | // Read returns, immediately lock the mutex again and check if we were |
592 | // interrupted. |
593 | m_locked_output.reset(); |
594 | |
595 | if (m_redraw_callback) |
596 | m_redraw_callback(); |
597 | |
598 | // Read an actual character |
599 | lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; |
600 | char ch = 0; |
601 | int read_count = |
602 | m_input_connection.Read(dst: &ch, dst_len: 1, timeout: std::nullopt, status, error_ptr: nullptr); |
603 | |
604 | // Re-lock the output mutex to protected m_editor_status here and in the |
605 | // switch below. |
606 | m_locked_output.emplace(args: m_output_stream_sp->Lock()); |
607 | if (m_editor_status == EditorStatus::Interrupted) { |
608 | while (read_count > 0 && status == lldb::eConnectionStatusSuccess) |
609 | read_count = |
610 | m_input_connection.Read(dst: &ch, dst_len: 1, timeout: std::nullopt, status, error_ptr: nullptr); |
611 | lldbassert(status == lldb::eConnectionStatusInterrupted); |
612 | return 0; |
613 | } |
614 | |
615 | if (read_count) { |
616 | if (CompleteCharacter(ch, out&: *c)) |
617 | return 1; |
618 | return 0; |
619 | } |
620 | |
621 | switch (status) { |
622 | case lldb::eConnectionStatusSuccess: |
623 | llvm_unreachable("Success should have resulted in positive read_count." ); |
624 | case lldb::eConnectionStatusInterrupted: |
625 | llvm_unreachable("Interrupts should have been handled above." ); |
626 | case lldb::eConnectionStatusError: |
627 | case lldb::eConnectionStatusTimedOut: |
628 | case lldb::eConnectionStatusEndOfFile: |
629 | case lldb::eConnectionStatusNoConnection: |
630 | case lldb::eConnectionStatusLostConnection: |
631 | m_editor_status = EditorStatus::EndOfInput; |
632 | } |
633 | |
634 | return 0; |
635 | } |
636 | |
637 | const char *Editline::Prompt() { |
638 | if (m_color) |
639 | m_needs_prompt_repaint = true; |
640 | return m_current_prompt.c_str(); |
641 | } |
642 | |
643 | unsigned char Editline::BreakLineCommand(int ch) { |
644 | // Preserve any content beyond the cursor, truncate and save the current line |
645 | const LineInfoW *info = el_wline(m_editline); |
646 | auto current_line = |
647 | EditLineStringType(info->buffer, info->cursor - info->buffer); |
648 | auto new_line_fragment = |
649 | EditLineStringType(info->cursor, info->lastchar - info->cursor); |
650 | m_input_lines[m_current_line_index] = current_line; |
651 | |
652 | // Ignore whitespace-only extra fragments when breaking a line |
653 | if (::IsOnlySpaces(content: new_line_fragment)) |
654 | new_line_fragment = EditLineConstString("" ); |
655 | |
656 | // Establish the new cursor position at the start of a line when inserting a |
657 | // line break |
658 | m_revert_cursor_index = 0; |
659 | |
660 | // Don't perform automatic formatting when pasting |
661 | if (!IsInputPending(file: m_input_file)) { |
662 | // Apply smart indentation |
663 | if (m_fix_indentation_callback) { |
664 | StringList lines = GetInputAsStringList(line_count: m_current_line_index + 1); |
665 | #if LLDB_EDITLINE_USE_WCHAR |
666 | std::string buffer; |
667 | llvm::convertWideToUTF8(Source: new_line_fragment, Result&: buffer); |
668 | lines.AppendString(s: buffer); |
669 | #else |
670 | lines.AppendString(new_line_fragment); |
671 | #endif |
672 | |
673 | int indent_correction = m_fix_indentation_callback(this, lines, 0); |
674 | new_line_fragment = FixIndentation(line: new_line_fragment, indent_correction); |
675 | m_revert_cursor_index = GetIndentation(line: new_line_fragment); |
676 | } |
677 | } |
678 | |
679 | // Insert the new line and repaint everything from the split line on down |
680 | m_input_lines.insert(position: m_input_lines.begin() + m_current_line_index + 1, |
681 | x: new_line_fragment); |
682 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::EditingPrompt); |
683 | DisplayInput(firstIndex: m_current_line_index); |
684 | |
685 | // Reposition the cursor to the right line and prepare to edit the new line |
686 | SetCurrentLine(m_current_line_index + 1); |
687 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingPrompt); |
688 | return CC_NEWLINE; |
689 | } |
690 | |
691 | unsigned char Editline::EndOrAddLineCommand(int ch) { |
692 | // Don't perform end of input detection when pasting, always treat this as a |
693 | // line break |
694 | if (IsInputPending(file: m_input_file)) { |
695 | return BreakLineCommand(ch); |
696 | } |
697 | |
698 | // Save any edits to this line |
699 | SaveEditedLine(); |
700 | |
701 | // If this is the end of the last line, consider whether to add a line |
702 | // instead |
703 | const LineInfoW *info = el_wline(m_editline); |
704 | if (m_current_line_index == m_input_lines.size() - 1 && |
705 | info->cursor == info->lastchar) { |
706 | if (m_is_input_complete_callback) { |
707 | auto lines = GetInputAsStringList(); |
708 | if (!m_is_input_complete_callback(this, lines)) { |
709 | return BreakLineCommand(ch); |
710 | } |
711 | |
712 | // The completion test is allowed to change the input lines when complete |
713 | m_input_lines.clear(); |
714 | for (unsigned index = 0; index < lines.GetSize(); index++) { |
715 | #if LLDB_EDITLINE_USE_WCHAR |
716 | std::wstring wbuffer; |
717 | llvm::ConvertUTF8toWide(Source: lines[index], Result&: wbuffer); |
718 | m_input_lines.insert(position: m_input_lines.end(), x: wbuffer); |
719 | #else |
720 | m_input_lines.insert(m_input_lines.end(), lines[index]); |
721 | #endif |
722 | } |
723 | } |
724 | } |
725 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::BlockEnd); |
726 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
727 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "\n" ); |
728 | m_editor_status = EditorStatus::Complete; |
729 | return CC_NEWLINE; |
730 | } |
731 | |
732 | unsigned char Editline::DeleteNextCharCommand(int ch) { |
733 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
734 | LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); |
735 | |
736 | // Just delete the next character normally if possible |
737 | if (info->cursor < info->lastchar) { |
738 | info->cursor++; |
739 | el_deletestr(m_editline, 1); |
740 | return CC_REFRESH; |
741 | } |
742 | |
743 | // Fail when at the end of the last line, except when ^D is pressed on the |
744 | // line is empty, in which case it is treated as EOF |
745 | if (m_current_line_index == m_input_lines.size() - 1) { |
746 | if (ch == 4 && info->buffer == info->lastchar) { |
747 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "^D\n" ); |
748 | m_editor_status = EditorStatus::EndOfInput; |
749 | return CC_EOF; |
750 | } |
751 | return CC_ERROR; |
752 | } |
753 | |
754 | // Prepare to combine this line with the one below |
755 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::EditingPrompt); |
756 | |
757 | // Insert the next line of text at the cursor and restore the cursor position |
758 | const EditLineCharType *cursor = info->cursor; |
759 | el_winsertstr(m_editline, m_input_lines[m_current_line_index + 1].c_str()); |
760 | info->cursor = cursor; |
761 | SaveEditedLine(); |
762 | |
763 | // Delete the extra line |
764 | m_input_lines.erase(position: m_input_lines.begin() + m_current_line_index + 1); |
765 | |
766 | // Clear and repaint from this line on down |
767 | DisplayInput(firstIndex: m_current_line_index); |
768 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingCursor); |
769 | return CC_REFRESH; |
770 | } |
771 | |
772 | unsigned char Editline::DeletePreviousCharCommand(int ch) { |
773 | LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); |
774 | |
775 | // Just delete the previous character normally when not at the start of a |
776 | // line |
777 | if (info->cursor > info->buffer) { |
778 | el_deletestr(m_editline, 1); |
779 | return CC_REFRESH; |
780 | } |
781 | |
782 | // No prior line and no prior character? Let the user know |
783 | if (m_current_line_index == 0) |
784 | return CC_ERROR; |
785 | |
786 | // No prior character, but prior line? Combine with the line above |
787 | SaveEditedLine(); |
788 | SetCurrentLine(m_current_line_index - 1); |
789 | auto priorLine = m_input_lines[m_current_line_index]; |
790 | m_input_lines.erase(position: m_input_lines.begin() + m_current_line_index); |
791 | m_input_lines[m_current_line_index] = |
792 | priorLine + m_input_lines[m_current_line_index]; |
793 | |
794 | // Repaint from the new line down |
795 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
796 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, |
797 | CountRowsForLine(content: priorLine), 1); |
798 | DisplayInput(firstIndex: m_current_line_index); |
799 | |
800 | // Put the cursor back where libedit expects it to be before returning to |
801 | // editing by telling libedit about the newly inserted text |
802 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingPrompt); |
803 | el_winsertstr(m_editline, priorLine.c_str()); |
804 | return CC_REDISPLAY; |
805 | } |
806 | |
807 | unsigned char Editline::PreviousLineCommand(int ch) { |
808 | SaveEditedLine(); |
809 | |
810 | if (m_current_line_index == 0) { |
811 | return RecallHistory(op: HistoryOperation::Older); |
812 | } |
813 | |
814 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
815 | |
816 | // Start from a known location |
817 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::EditingPrompt); |
818 | |
819 | // Treat moving up from a blank last line as a deletion of that line |
820 | if (m_current_line_index == m_input_lines.size() - 1 && IsOnlySpaces()) { |
821 | m_input_lines.erase(position: m_input_lines.begin() + m_current_line_index); |
822 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_CLEAR_BELOW); |
823 | } |
824 | |
825 | SetCurrentLine(m_current_line_index - 1); |
826 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, |
827 | CountRowsForLine(content: m_input_lines[m_current_line_index]), 1); |
828 | return CC_NEWLINE; |
829 | } |
830 | |
831 | unsigned char Editline::NextLineCommand(int ch) { |
832 | SaveEditedLine(); |
833 | |
834 | // Handle attempts to move down from the last line |
835 | if (m_current_line_index == m_input_lines.size() - 1) { |
836 | // Don't add an extra line if the existing last line is blank, move through |
837 | // history instead |
838 | if (IsOnlySpaces()) { |
839 | return RecallHistory(op: HistoryOperation::Newer); |
840 | } |
841 | |
842 | // Determine indentation for the new line |
843 | int indentation = 0; |
844 | if (m_fix_indentation_callback) { |
845 | StringList lines = GetInputAsStringList(); |
846 | lines.AppendString(str: "" ); |
847 | indentation = m_fix_indentation_callback(this, lines, 0); |
848 | } |
849 | m_input_lines.insert( |
850 | position: m_input_lines.end(), |
851 | x: EditLineStringType(indentation, EditLineCharType(' '))); |
852 | } |
853 | |
854 | // Move down past the current line using newlines to force scrolling if |
855 | // needed |
856 | SetCurrentLine(m_current_line_index + 1); |
857 | const LineInfoW *info = el_wline(m_editline); |
858 | int cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth()); |
859 | int cursor_row = cursor_position / m_terminal_width; |
860 | |
861 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
862 | for (int line_count = 0; line_count < m_current_line_rows - cursor_row; |
863 | line_count++) { |
864 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "\n" ); |
865 | } |
866 | return CC_NEWLINE; |
867 | } |
868 | |
869 | unsigned char Editline::PreviousHistoryCommand(int ch) { |
870 | SaveEditedLine(); |
871 | |
872 | return RecallHistory(op: HistoryOperation::Older); |
873 | } |
874 | |
875 | unsigned char Editline::NextHistoryCommand(int ch) { |
876 | SaveEditedLine(); |
877 | |
878 | return RecallHistory(op: HistoryOperation::Newer); |
879 | } |
880 | |
881 | unsigned char Editline::FixIndentationCommand(int ch) { |
882 | if (!m_fix_indentation_callback) |
883 | return CC_NORM; |
884 | |
885 | // Insert the character typed before proceeding |
886 | EditLineCharType inserted[] = {(EditLineCharType)ch, 0}; |
887 | el_winsertstr(m_editline, inserted); |
888 | LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); |
889 | int cursor_position = info->cursor - info->buffer; |
890 | |
891 | // Save the edits and determine the correct indentation level |
892 | SaveEditedLine(); |
893 | StringList lines = GetInputAsStringList(line_count: m_current_line_index + 1); |
894 | int indent_correction = |
895 | m_fix_indentation_callback(this, lines, cursor_position); |
896 | |
897 | // If it is already correct no special work is needed |
898 | if (indent_correction == 0) |
899 | return CC_REFRESH; |
900 | |
901 | // Change the indentation level of the line |
902 | std::string currentLine = lines.GetStringAtIndex(idx: m_current_line_index); |
903 | if (indent_correction > 0) { |
904 | currentLine = currentLine.insert(pos: 0, n: indent_correction, c: ' '); |
905 | } else { |
906 | currentLine = currentLine.erase(pos: 0, n: -indent_correction); |
907 | } |
908 | #if LLDB_EDITLINE_USE_WCHAR |
909 | std::wstring wbuffer; |
910 | llvm::ConvertUTF8toWide(Source: currentLine, Result&: wbuffer); |
911 | m_input_lines[m_current_line_index] = wbuffer; |
912 | #else |
913 | m_input_lines[m_current_line_index] = currentLine; |
914 | #endif |
915 | |
916 | // Update the display to reflect the change |
917 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::EditingPrompt); |
918 | DisplayInput(firstIndex: m_current_line_index); |
919 | |
920 | // Reposition the cursor back on the original line and prepare to restart |
921 | // editing with a new cursor position |
922 | SetCurrentLine(m_current_line_index); |
923 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingPrompt); |
924 | m_revert_cursor_index = cursor_position + indent_correction; |
925 | return CC_NEWLINE; |
926 | } |
927 | |
928 | unsigned char Editline::RevertLineCommand(int ch) { |
929 | el_winsertstr(m_editline, m_input_lines[m_current_line_index].c_str()); |
930 | if (m_revert_cursor_index >= 0) { |
931 | LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); |
932 | info->cursor = info->buffer + m_revert_cursor_index; |
933 | if (info->cursor > info->lastchar) { |
934 | info->cursor = info->lastchar; |
935 | } |
936 | m_revert_cursor_index = -1; |
937 | } |
938 | return CC_REFRESH; |
939 | } |
940 | |
941 | unsigned char Editline::BufferStartCommand(int ch) { |
942 | SaveEditedLine(); |
943 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::BlockStart); |
944 | SetCurrentLine(0); |
945 | m_revert_cursor_index = 0; |
946 | return CC_NEWLINE; |
947 | } |
948 | |
949 | unsigned char Editline::BufferEndCommand(int ch) { |
950 | SaveEditedLine(); |
951 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::BlockEnd); |
952 | SetCurrentLine((int)m_input_lines.size() - 1); |
953 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingPrompt); |
954 | return CC_NEWLINE; |
955 | } |
956 | |
957 | /// Prints completions and their descriptions to the given file. Only the |
958 | /// completions in the interval [start, end) are printed. |
959 | static size_t |
960 | PrintCompletion(FILE *output_file, |
961 | llvm::ArrayRef<CompletionResult::Completion> results, |
962 | size_t max_completion_length, size_t max_length, |
963 | std::optional<size_t> max_height = std::nullopt) { |
964 | constexpr size_t ellipsis_length = 3; |
965 | constexpr size_t padding_length = 8; |
966 | constexpr size_t separator_length = 4; |
967 | |
968 | const size_t description_col = |
969 | std::min(a: max_completion_length + padding_length, b: max_length); |
970 | |
971 | size_t lines_printed = 0; |
972 | size_t results_printed = 0; |
973 | for (const CompletionResult::Completion &c : results) { |
974 | if (max_height && lines_printed >= *max_height) |
975 | break; |
976 | |
977 | results_printed++; |
978 | |
979 | if (c.GetCompletion().empty()) |
980 | continue; |
981 | |
982 | // Print the leading padding. |
983 | fprintf(stream: output_file, format: " " ); |
984 | |
985 | // Print the completion with trailing padding to the description column if |
986 | // that fits on the screen. Otherwise print whatever fits on the screen |
987 | // followed by ellipsis. |
988 | const size_t completion_length = c.GetCompletion().size(); |
989 | if (padding_length + completion_length < max_length) { |
990 | fprintf(stream: output_file, format: "%-*s" , |
991 | static_cast<int>(description_col - padding_length), |
992 | c.GetCompletion().c_str()); |
993 | } else { |
994 | // If the completion doesn't fit on the screen, print ellipsis and don't |
995 | // bother with the description. |
996 | fprintf(stream: output_file, format: "%.*s...\n" , |
997 | static_cast<int>(max_length - padding_length - ellipsis_length), |
998 | c.GetCompletion().c_str()); |
999 | lines_printed++; |
1000 | continue; |
1001 | } |
1002 | |
1003 | // If we don't have a description, or we don't have enough space left to |
1004 | // print the separator followed by the ellipsis, we're done. |
1005 | if (c.GetDescription().empty() || |
1006 | description_col + separator_length + ellipsis_length >= max_length) { |
1007 | fprintf(stream: output_file, format: "\n" ); |
1008 | lines_printed++; |
1009 | continue; |
1010 | } |
1011 | |
1012 | // Print the separator. |
1013 | fprintf(stream: output_file, format: " -- " ); |
1014 | |
1015 | // Descriptions can contain newlines. We want to print them below each |
1016 | // other, aligned after the separator. For example, foo has a |
1017 | // two-line description: |
1018 | // |
1019 | // foo -- Something that fits on the line. |
1020 | // More information below. |
1021 | // |
1022 | // However, as soon as a line exceed the available screen width and |
1023 | // print ellipsis, we don't print the next line. For example, foo has a |
1024 | // three-line description: |
1025 | // |
1026 | // foo -- Something that fits on the line. |
1027 | // Something much longer that doesn't fit... |
1028 | // |
1029 | // Because we had to print ellipsis on line two, we don't print the |
1030 | // third line. |
1031 | bool first = true; |
1032 | for (llvm::StringRef line : llvm::split(Str: c.GetDescription(), Separator: '\n')) { |
1033 | if (line.empty()) |
1034 | break; |
1035 | if (max_height && lines_printed >= *max_height) |
1036 | break; |
1037 | if (!first) |
1038 | fprintf(stream: output_file, format: "%*s" , |
1039 | static_cast<int>(description_col + separator_length), "" ); |
1040 | |
1041 | first = false; |
1042 | const size_t position = description_col + separator_length; |
1043 | const size_t description_length = line.size(); |
1044 | if (position + description_length < max_length) { |
1045 | fprintf(stream: output_file, format: "%.*s\n" , static_cast<int>(description_length), |
1046 | line.data()); |
1047 | lines_printed++; |
1048 | } else { |
1049 | fprintf(stream: output_file, format: "%.*s...\n" , |
1050 | static_cast<int>(max_length - position - ellipsis_length), |
1051 | line.data()); |
1052 | lines_printed++; |
1053 | continue; |
1054 | } |
1055 | } |
1056 | } |
1057 | return results_printed; |
1058 | } |
1059 | |
1060 | void Editline::DisplayCompletions( |
1061 | Editline &editline, llvm::ArrayRef<CompletionResult::Completion> results) { |
1062 | assert(!results.empty()); |
1063 | |
1064 | LockedStreamFile locked_stream = editline.m_output_stream_sp->Lock(); |
1065 | |
1066 | fprintf(stream: locked_stream.GetFile().GetStream(), |
1067 | format: "\n" ANSI_CLEAR_BELOW "Available completions:\n" ); |
1068 | |
1069 | /// Account for the current line, the line showing "Available completions" |
1070 | /// before and the line saying "More" after. |
1071 | const size_t page_size = editline.GetTerminalHeight() - 3; |
1072 | |
1073 | bool all = false; |
1074 | |
1075 | auto longest = |
1076 | std::max_element(first: results.begin(), last: results.end(), comp: [](auto &c1, auto &c2) { |
1077 | return c1.GetCompletion().size() < c2.GetCompletion().size(); |
1078 | }); |
1079 | |
1080 | const size_t max_len = longest->GetCompletion().size(); |
1081 | |
1082 | size_t cur_pos = 0; |
1083 | while (cur_pos < results.size()) { |
1084 | cur_pos += PrintCompletion( |
1085 | output_file: locked_stream.GetFile().GetStream(), results: results.slice(N: cur_pos), max_completion_length: max_len, |
1086 | max_length: editline.GetTerminalWidth(), |
1087 | max_height: all ? std::nullopt : std::optional<size_t>(page_size)); |
1088 | |
1089 | if (cur_pos >= results.size()) |
1090 | break; |
1091 | |
1092 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "More (Y/n/a): " ); |
1093 | // The type for the output and the type for the parameter are different, |
1094 | // to allow interoperability with older versions of libedit. The container |
1095 | // for the reply must be as wide as what our implementation is using, |
1096 | // but libedit may use a narrower type depending on the build |
1097 | // configuration. |
1098 | EditLineGetCharType reply = L'n'; |
1099 | int got_char = el_wgetc(editline.m_editline, |
1100 | reinterpret_cast<EditLineCharType *>(&reply)); |
1101 | // Check for a ^C or other interruption. |
1102 | if (editline.m_editor_status == EditorStatus::Interrupted) { |
1103 | editline.m_editor_status = EditorStatus::Editing; |
1104 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "^C\n" ); |
1105 | break; |
1106 | } |
1107 | |
1108 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "\n" ); |
1109 | if (got_char == -1 || reply == 'n') |
1110 | break; |
1111 | if (reply == 'a') |
1112 | all = true; |
1113 | } |
1114 | } |
1115 | |
1116 | void Editline::UseColor(bool use_color) { m_color = use_color; } |
1117 | |
1118 | unsigned char Editline::TabCommand(int ch) { |
1119 | if (!m_completion_callback) |
1120 | return CC_ERROR; |
1121 | |
1122 | const LineInfo *line_info = el_line(m_editline); |
1123 | |
1124 | llvm::StringRef line(line_info->buffer, |
1125 | line_info->lastchar - line_info->buffer); |
1126 | unsigned cursor_index = line_info->cursor - line_info->buffer; |
1127 | CompletionResult result; |
1128 | CompletionRequest request(line, cursor_index, result); |
1129 | |
1130 | m_completion_callback(request); |
1131 | |
1132 | llvm::ArrayRef<CompletionResult::Completion> results = result.GetResults(); |
1133 | |
1134 | StringList completions; |
1135 | result.GetMatches(matches&: completions); |
1136 | |
1137 | if (results.size() == 0) |
1138 | return CC_ERROR; |
1139 | |
1140 | if (results.size() == 1) { |
1141 | CompletionResult::Completion completion = results.front(); |
1142 | switch (completion.GetMode()) { |
1143 | case CompletionMode::Normal: { |
1144 | std::string to_add = completion.GetCompletion(); |
1145 | // Terminate the current argument with a quote if it started with a quote. |
1146 | Args &parsedLine = request.GetParsedLine(); |
1147 | if (!parsedLine.empty() && request.GetCursorIndex() < parsedLine.size() && |
1148 | request.GetParsedArg().IsQuoted()) { |
1149 | to_add.push_back(c: request.GetParsedArg().GetQuoteChar()); |
1150 | } |
1151 | to_add.push_back(c: ' '); |
1152 | el_deletestr(m_editline, request.GetCursorArgumentPrefix().size()); |
1153 | el_insertstr(m_editline, to_add.c_str()); |
1154 | // Clear all the autosuggestion parts if the only single space can be completed. |
1155 | if (to_add == " " ) |
1156 | return CC_REDISPLAY; |
1157 | return CC_REFRESH; |
1158 | } |
1159 | case CompletionMode::Partial: { |
1160 | std::string to_add = completion.GetCompletion(); |
1161 | to_add = to_add.substr(pos: request.GetCursorArgumentPrefix().size()); |
1162 | el_insertstr(m_editline, to_add.c_str()); |
1163 | break; |
1164 | } |
1165 | case CompletionMode::RewriteLine: { |
1166 | el_deletestr(m_editline, line_info->cursor - line_info->buffer); |
1167 | el_insertstr(m_editline, completion.GetCompletion().c_str()); |
1168 | break; |
1169 | } |
1170 | } |
1171 | return CC_REDISPLAY; |
1172 | } |
1173 | |
1174 | // If we get a longer match display that first. |
1175 | std::string longest_prefix = completions.LongestCommonPrefix(); |
1176 | if (!longest_prefix.empty()) |
1177 | longest_prefix = |
1178 | longest_prefix.substr(pos: request.GetCursorArgumentPrefix().size()); |
1179 | if (!longest_prefix.empty()) { |
1180 | el_insertstr(m_editline, longest_prefix.c_str()); |
1181 | return CC_REDISPLAY; |
1182 | } |
1183 | |
1184 | DisplayCompletions(editline&: *this, results); |
1185 | |
1186 | DisplayInput(); |
1187 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingCursor); |
1188 | return CC_REDISPLAY; |
1189 | } |
1190 | |
1191 | unsigned char Editline::ApplyAutosuggestCommand(int ch) { |
1192 | if (!m_suggestion_callback) { |
1193 | return CC_REDISPLAY; |
1194 | } |
1195 | |
1196 | const LineInfo *line_info = el_line(m_editline); |
1197 | llvm::StringRef line(line_info->buffer, |
1198 | line_info->lastchar - line_info->buffer); |
1199 | |
1200 | if (std::optional<std::string> to_add = m_suggestion_callback(line)) |
1201 | el_insertstr(m_editline, to_add->c_str()); |
1202 | |
1203 | return CC_REDISPLAY; |
1204 | } |
1205 | |
1206 | unsigned char Editline::TypedCharacter(int ch) { |
1207 | std::string typed = std::string(1, ch); |
1208 | el_insertstr(m_editline, typed.c_str()); |
1209 | |
1210 | if (!m_suggestion_callback) { |
1211 | return CC_REDISPLAY; |
1212 | } |
1213 | |
1214 | const LineInfo *line_info = el_line(m_editline); |
1215 | llvm::StringRef line(line_info->buffer, |
1216 | line_info->lastchar - line_info->buffer); |
1217 | |
1218 | if (std::optional<std::string> to_add = m_suggestion_callback(line)) { |
1219 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
1220 | std::string to_add_color = |
1221 | m_suggestion_ansi_prefix + to_add.value() + m_suggestion_ansi_suffix; |
1222 | fputs(s: typed.c_str(), stream: locked_stream.GetFile().GetStream()); |
1223 | fputs(s: to_add_color.c_str(), stream: locked_stream.GetFile().GetStream()); |
1224 | size_t new_autosuggestion_size = line.size() + to_add->length(); |
1225 | // Print spaces to hide any remains of a previous longer autosuggestion. |
1226 | if (new_autosuggestion_size < m_previous_autosuggestion_size) { |
1227 | size_t spaces_to_print = |
1228 | m_previous_autosuggestion_size - new_autosuggestion_size; |
1229 | std::string spaces = std::string(spaces_to_print, ' '); |
1230 | fputs(s: spaces.c_str(), stream: locked_stream.GetFile().GetStream()); |
1231 | } |
1232 | m_previous_autosuggestion_size = new_autosuggestion_size; |
1233 | |
1234 | int editline_cursor_position = |
1235 | (int)((line_info->cursor - line_info->buffer) + GetPromptWidth()); |
1236 | int editline_cursor_row = editline_cursor_position / m_terminal_width; |
1237 | int toColumn = |
1238 | editline_cursor_position - (editline_cursor_row * m_terminal_width); |
1239 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_SET_COLUMN_N, toColumn); |
1240 | return CC_REFRESH; |
1241 | } |
1242 | |
1243 | return CC_REDISPLAY; |
1244 | } |
1245 | |
1246 | void Editline::AddFunctionToEditLine(const EditLineCharType *command, |
1247 | const EditLineCharType *helptext, |
1248 | EditlineCommandCallbackType callbackFn) { |
1249 | el_wset(m_editline, EL_ADDFN, command, helptext, callbackFn); |
1250 | } |
1251 | |
1252 | void Editline::SetEditLinePromptCallback( |
1253 | EditlinePromptCallbackType callbackFn) { |
1254 | el_set(m_editline, EL_PROMPT, callbackFn); |
1255 | } |
1256 | |
1257 | void Editline::SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn) { |
1258 | el_wset(m_editline, EL_GETCFN, callbackFn); |
1259 | } |
1260 | |
1261 | void Editline::ConfigureEditor(bool multiline) { |
1262 | if (m_editline && m_multiline_enabled == multiline) |
1263 | return; |
1264 | m_multiline_enabled = multiline; |
1265 | |
1266 | if (m_editline) { |
1267 | // Disable edit mode to stop the terminal from flushing all input during |
1268 | // the call to el_end() since we expect to have multiple editline instances |
1269 | // in this program. |
1270 | el_set(m_editline, EL_EDITMODE, 0); |
1271 | el_end(m_editline); |
1272 | } |
1273 | |
1274 | LockedStreamFile locked_output_stream = m_output_stream_sp->Lock(); |
1275 | LockedStreamFile locked_error_stream = m_output_stream_sp->Lock(); |
1276 | m_editline = el_init(m_editor_name.c_str(), m_input_file, |
1277 | locked_output_stream.GetFile().GetStream(), |
1278 | locked_error_stream.GetFile().GetStream()); |
1279 | ApplyTerminalSizeChange(); |
1280 | |
1281 | if (m_history_sp && m_history_sp->IsValid()) { |
1282 | if (!m_history_sp->Load()) { |
1283 | fputs(s: "Could not load history file\n." , |
1284 | stream: locked_output_stream.GetFile().GetStream()); |
1285 | } |
1286 | el_wset(m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); |
1287 | } |
1288 | el_set(m_editline, EL_CLIENTDATA, this); |
1289 | el_set(m_editline, EL_SIGNAL, 0); |
1290 | el_set(m_editline, EL_EDITOR, "emacs" ); |
1291 | |
1292 | SetGetCharacterFunction([](EditLine *editline, EditLineGetCharType *c) { |
1293 | return Editline::InstanceFor(editline)->GetCharacter(c); |
1294 | }); |
1295 | |
1296 | SetEditLinePromptCallback([](EditLine *editline) { |
1297 | return Editline::InstanceFor(editline)->Prompt(); |
1298 | }); |
1299 | |
1300 | // Commands used for multiline support, registered whether or not they're |
1301 | // used |
1302 | AddFunctionToEditLine( |
1303 | EditLineConstString("lldb-break-line" ), |
1304 | EditLineConstString("Insert a line break" ), |
1305 | callbackFn: [](EditLine *editline, int ch) { |
1306 | return Editline::InstanceFor(editline)->BreakLineCommand(ch); |
1307 | }); |
1308 | |
1309 | AddFunctionToEditLine( |
1310 | EditLineConstString("lldb-end-or-add-line" ), |
1311 | EditLineConstString("End editing or continue when incomplete" ), |
1312 | callbackFn: [](EditLine *editline, int ch) { |
1313 | return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch); |
1314 | }); |
1315 | AddFunctionToEditLine( |
1316 | EditLineConstString("lldb-delete-next-char" ), |
1317 | EditLineConstString("Delete next character" ), |
1318 | callbackFn: [](EditLine *editline, int ch) { |
1319 | return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch); |
1320 | }); |
1321 | AddFunctionToEditLine( |
1322 | EditLineConstString("lldb-delete-previous-char" ), |
1323 | EditLineConstString("Delete previous character" ), |
1324 | callbackFn: [](EditLine *editline, int ch) { |
1325 | return Editline::InstanceFor(editline)->DeletePreviousCharCommand(ch); |
1326 | }); |
1327 | AddFunctionToEditLine( |
1328 | EditLineConstString("lldb-previous-line" ), |
1329 | EditLineConstString("Move to previous line" ), |
1330 | callbackFn: [](EditLine *editline, int ch) { |
1331 | return Editline::InstanceFor(editline)->PreviousLineCommand(ch); |
1332 | }); |
1333 | AddFunctionToEditLine( |
1334 | EditLineConstString("lldb-next-line" ), |
1335 | EditLineConstString("Move to next line" ), callbackFn: [](EditLine *editline, int ch) { |
1336 | return Editline::InstanceFor(editline)->NextLineCommand(ch); |
1337 | }); |
1338 | AddFunctionToEditLine( |
1339 | EditLineConstString("lldb-previous-history" ), |
1340 | EditLineConstString("Move to previous history" ), |
1341 | callbackFn: [](EditLine *editline, int ch) { |
1342 | return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch); |
1343 | }); |
1344 | AddFunctionToEditLine( |
1345 | EditLineConstString("lldb-next-history" ), |
1346 | EditLineConstString("Move to next history" ), |
1347 | callbackFn: [](EditLine *editline, int ch) { |
1348 | return Editline::InstanceFor(editline)->NextHistoryCommand(ch); |
1349 | }); |
1350 | AddFunctionToEditLine( |
1351 | EditLineConstString("lldb-buffer-start" ), |
1352 | EditLineConstString("Move to start of buffer" ), |
1353 | callbackFn: [](EditLine *editline, int ch) { |
1354 | return Editline::InstanceFor(editline)->BufferStartCommand(ch); |
1355 | }); |
1356 | AddFunctionToEditLine( |
1357 | EditLineConstString("lldb-buffer-end" ), |
1358 | EditLineConstString("Move to end of buffer" ), |
1359 | callbackFn: [](EditLine *editline, int ch) { |
1360 | return Editline::InstanceFor(editline)->BufferEndCommand(ch); |
1361 | }); |
1362 | AddFunctionToEditLine( |
1363 | EditLineConstString("lldb-fix-indentation" ), |
1364 | EditLineConstString("Fix line indentation" ), |
1365 | callbackFn: [](EditLine *editline, int ch) { |
1366 | return Editline::InstanceFor(editline)->FixIndentationCommand(ch); |
1367 | }); |
1368 | |
1369 | // Register the complete callback under two names for compatibility with |
1370 | // older clients using custom .editrc files (largely because libedit has a |
1371 | // bad bug where if you have a bind command that tries to bind to a function |
1372 | // name that doesn't exist, it can corrupt the heap and crash your process |
1373 | // later.) |
1374 | EditlineCommandCallbackType complete_callback = [](EditLine *editline, |
1375 | int ch) { |
1376 | return Editline::InstanceFor(editline)->TabCommand(ch); |
1377 | }; |
1378 | AddFunctionToEditLine(EditLineConstString("lldb-complete" ), |
1379 | EditLineConstString("Invoke completion" ), |
1380 | callbackFn: complete_callback); |
1381 | AddFunctionToEditLine(EditLineConstString("lldb_complete" ), |
1382 | EditLineConstString("Invoke completion" ), |
1383 | callbackFn: complete_callback); |
1384 | |
1385 | // General bindings we don't mind being overridden |
1386 | if (!multiline) { |
1387 | el_set(m_editline, EL_BIND, "^r" , "em-inc-search-prev" , |
1388 | NULL); // Cycle through backwards search, entering string |
1389 | |
1390 | if (m_suggestion_callback) { |
1391 | AddFunctionToEditLine( |
1392 | EditLineConstString("lldb-apply-complete" ), |
1393 | EditLineConstString("Adopt autocompletion" ), |
1394 | callbackFn: [](EditLine *editline, int ch) { |
1395 | return Editline::InstanceFor(editline)->ApplyAutosuggestCommand(ch); |
1396 | }); |
1397 | |
1398 | el_set(m_editline, EL_BIND, "^f" , "lldb-apply-complete" , |
1399 | NULL); // Apply a part that is suggested automatically |
1400 | |
1401 | AddFunctionToEditLine( |
1402 | EditLineConstString("lldb-typed-character" ), |
1403 | EditLineConstString("Typed character" ), |
1404 | callbackFn: [](EditLine *editline, int ch) { |
1405 | return Editline::InstanceFor(editline)->TypedCharacter(ch); |
1406 | }); |
1407 | |
1408 | char bind_key[2] = {0, 0}; |
1409 | llvm::StringRef ascii_chars = |
1410 | "abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXZY1234567890!\"#$%" |
1411 | "&'()*+,./:;<=>?@[]_`{|}~ " ; |
1412 | for (char c : ascii_chars) { |
1413 | bind_key[0] = c; |
1414 | el_set(m_editline, EL_BIND, bind_key, "lldb-typed-character" , NULL); |
1415 | } |
1416 | el_set(m_editline, EL_BIND, "\\-" , "lldb-typed-character" , NULL); |
1417 | el_set(m_editline, EL_BIND, "\\^" , "lldb-typed-character" , NULL); |
1418 | el_set(m_editline, EL_BIND, "\\\\" , "lldb-typed-character" , NULL); |
1419 | } |
1420 | } |
1421 | |
1422 | el_set(m_editline, EL_BIND, "^w" , "ed-delete-prev-word" , |
1423 | NULL); // Delete previous word, behave like bash in emacs mode |
1424 | el_set(m_editline, EL_BIND, "\t" , "lldb-complete" , |
1425 | NULL); // Bind TAB to auto complete |
1426 | |
1427 | // Allow ctrl-left-arrow and ctrl-right-arrow for navigation, behave like |
1428 | // bash in emacs mode. |
1429 | el_set(m_editline, EL_BIND, ESCAPE "[1;5C" , "em-next-word" , NULL); |
1430 | el_set(m_editline, EL_BIND, ESCAPE "[1;5D" , "ed-prev-word" , NULL); |
1431 | el_set(m_editline, EL_BIND, ESCAPE "[5C" , "em-next-word" , NULL); |
1432 | el_set(m_editline, EL_BIND, ESCAPE "[5D" , "ed-prev-word" , NULL); |
1433 | el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[C" , "em-next-word" , NULL); |
1434 | el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[D" , "ed-prev-word" , NULL); |
1435 | |
1436 | // Allow user-specific customization prior to registering bindings we |
1437 | // absolutely require |
1438 | el_source(m_editline, nullptr); |
1439 | |
1440 | // Register an internal binding that external developers shouldn't use |
1441 | AddFunctionToEditLine( |
1442 | EditLineConstString("lldb-revert-line" ), |
1443 | EditLineConstString("Revert line to saved state" ), |
1444 | callbackFn: [](EditLine *editline, int ch) { |
1445 | return Editline::InstanceFor(editline)->RevertLineCommand(ch); |
1446 | }); |
1447 | |
1448 | // Register keys that perform auto-indent correction |
1449 | if (m_fix_indentation_callback && m_fix_indentation_callback_chars) { |
1450 | char bind_key[2] = {0, 0}; |
1451 | const char *indent_chars = m_fix_indentation_callback_chars; |
1452 | while (*indent_chars) { |
1453 | bind_key[0] = *indent_chars; |
1454 | el_set(m_editline, EL_BIND, bind_key, "lldb-fix-indentation" , NULL); |
1455 | ++indent_chars; |
1456 | } |
1457 | } |
1458 | |
1459 | // Multi-line editor bindings |
1460 | if (multiline) { |
1461 | el_set(m_editline, EL_BIND, "\n" , "lldb-end-or-add-line" , NULL); |
1462 | el_set(m_editline, EL_BIND, "\r" , "lldb-end-or-add-line" , NULL); |
1463 | el_set(m_editline, EL_BIND, ESCAPE "\n" , "lldb-break-line" , NULL); |
1464 | el_set(m_editline, EL_BIND, ESCAPE "\r" , "lldb-break-line" , NULL); |
1465 | el_set(m_editline, EL_BIND, "^p" , "lldb-previous-line" , NULL); |
1466 | el_set(m_editline, EL_BIND, "^n" , "lldb-next-line" , NULL); |
1467 | el_set(m_editline, EL_BIND, "^?" , "lldb-delete-previous-char" , NULL); |
1468 | el_set(m_editline, EL_BIND, "^d" , "lldb-delete-next-char" , NULL); |
1469 | el_set(m_editline, EL_BIND, ESCAPE "[3~" , "lldb-delete-next-char" , NULL); |
1470 | el_set(m_editline, EL_BIND, ESCAPE "[\\^" , "lldb-revert-line" , NULL); |
1471 | |
1472 | // Editor-specific bindings |
1473 | if (IsEmacs()) { |
1474 | el_set(m_editline, EL_BIND, ESCAPE "<" , "lldb-buffer-start" , NULL); |
1475 | el_set(m_editline, EL_BIND, ESCAPE ">" , "lldb-buffer-end" , NULL); |
1476 | el_set(m_editline, EL_BIND, ESCAPE "[A" , "lldb-previous-line" , NULL); |
1477 | el_set(m_editline, EL_BIND, ESCAPE "[B" , "lldb-next-line" , NULL); |
1478 | el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A" , "lldb-previous-history" , |
1479 | NULL); |
1480 | el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B" , "lldb-next-history" , |
1481 | NULL); |
1482 | el_set(m_editline, EL_BIND, ESCAPE "[1;3A" , "lldb-previous-history" , |
1483 | NULL); |
1484 | el_set(m_editline, EL_BIND, ESCAPE "[1;3B" , "lldb-next-history" , NULL); |
1485 | } else { |
1486 | el_set(m_editline, EL_BIND, "^H" , "lldb-delete-previous-char" , NULL); |
1487 | |
1488 | el_set(m_editline, EL_BIND, "-a" , ESCAPE "[A" , "lldb-previous-line" , |
1489 | NULL); |
1490 | el_set(m_editline, EL_BIND, "-a" , ESCAPE "[B" , "lldb-next-line" , NULL); |
1491 | el_set(m_editline, EL_BIND, "-a" , "x" , "lldb-delete-next-char" , NULL); |
1492 | el_set(m_editline, EL_BIND, "-a" , "^H" , "lldb-delete-previous-char" , |
1493 | NULL); |
1494 | el_set(m_editline, EL_BIND, "-a" , "^?" , "lldb-delete-previous-char" , |
1495 | NULL); |
1496 | |
1497 | // Escape is absorbed exiting edit mode, so re-register important |
1498 | // sequences without the prefix |
1499 | el_set(m_editline, EL_BIND, "-a" , "[A" , "lldb-previous-line" , NULL); |
1500 | el_set(m_editline, EL_BIND, "-a" , "[B" , "lldb-next-line" , NULL); |
1501 | el_set(m_editline, EL_BIND, "-a" , "[\\^" , "lldb-revert-line" , NULL); |
1502 | } |
1503 | } |
1504 | } |
1505 | |
1506 | // Editline public methods |
1507 | |
1508 | Editline *Editline::InstanceFor(EditLine *editline) { |
1509 | Editline *editor; |
1510 | el_get(editline, EL_CLIENTDATA, &editor); |
1511 | return editor; |
1512 | } |
1513 | |
1514 | Editline::Editline(const char *editline_name, FILE *input_file, |
1515 | lldb::LockableStreamFileSP output_stream_sp, |
1516 | lldb::LockableStreamFileSP error_stream_sp, bool color) |
1517 | : m_editor_status(EditorStatus::Complete), m_input_file(input_file), |
1518 | m_output_stream_sp(output_stream_sp), m_error_stream_sp(error_stream_sp), |
1519 | m_input_connection(fileno(stream: input_file), false), m_color(color) { |
1520 | assert(output_stream_sp && error_stream_sp); |
1521 | // Get a shared history instance |
1522 | m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name; |
1523 | m_history_sp = EditlineHistory::GetHistory(prefix: m_editor_name); |
1524 | } |
1525 | |
1526 | Editline::~Editline() { |
1527 | if (m_editline) { |
1528 | // Disable edit mode to stop the terminal from flushing all input during |
1529 | // the call to el_end() since we expect to have multiple editline instances |
1530 | // in this program. |
1531 | el_set(m_editline, EL_EDITMODE, 0); |
1532 | el_end(m_editline); |
1533 | m_editline = nullptr; |
1534 | } |
1535 | |
1536 | // EditlineHistory objects are sometimes shared between multiple Editline |
1537 | // instances with the same program name. So just release our shared pointer |
1538 | // and if we are the last owner, it will save the history to the history save |
1539 | // file automatically. |
1540 | m_history_sp.reset(); |
1541 | } |
1542 | |
1543 | void Editline::SetPrompt(const char *prompt) { |
1544 | m_set_prompt = prompt == nullptr ? "" : prompt; |
1545 | } |
1546 | |
1547 | void Editline::SetContinuationPrompt(const char *continuation_prompt) { |
1548 | m_set_continuation_prompt = |
1549 | continuation_prompt == nullptr ? "" : continuation_prompt; |
1550 | } |
1551 | |
1552 | void Editline::TerminalSizeChanged() { m_terminal_size_has_changed = 1; } |
1553 | |
1554 | void Editline::ApplyTerminalSizeChange() { |
1555 | if (!m_editline) |
1556 | return; |
1557 | |
1558 | m_terminal_size_has_changed = 0; |
1559 | el_resize(m_editline); |
1560 | int columns; |
1561 | // This function is documenting as taking (const char *, void *) for the |
1562 | // vararg part, but in reality in was consuming arguments until the first |
1563 | // null pointer. This was fixed in libedit in April 2019 |
1564 | // <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>, |
1565 | // but we're keeping the workaround until a version with that fix is more |
1566 | // widely available. |
1567 | if (el_get(m_editline, EL_GETTC, "co" , &columns, nullptr) == 0) { |
1568 | m_terminal_width = columns; |
1569 | if (m_current_line_rows != -1) { |
1570 | const LineInfoW *info = el_wline(m_editline); |
1571 | int lineLength = |
1572 | (int)((info->lastchar - info->buffer) + GetPromptWidth()); |
1573 | m_current_line_rows = (lineLength / columns) + 1; |
1574 | } |
1575 | } else { |
1576 | m_terminal_width = INT_MAX; |
1577 | m_current_line_rows = 1; |
1578 | } |
1579 | |
1580 | int rows; |
1581 | if (el_get(m_editline, EL_GETTC, "li" , &rows, nullptr) == 0) { |
1582 | m_terminal_height = rows; |
1583 | } else { |
1584 | m_terminal_height = INT_MAX; |
1585 | } |
1586 | } |
1587 | |
1588 | const char *Editline::GetPrompt() { return m_set_prompt.c_str(); } |
1589 | |
1590 | uint32_t Editline::GetCurrentLine() { return m_current_line_index; } |
1591 | |
1592 | bool Editline::Interrupt() { |
1593 | bool result = true; |
1594 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
1595 | if (m_editor_status == EditorStatus::Editing) { |
1596 | fprintf(stream: locked_stream.GetFile().GetStream(), format: "^C\n" ); |
1597 | result = m_input_connection.InterruptRead(); |
1598 | } |
1599 | m_editor_status = EditorStatus::Interrupted; |
1600 | return result; |
1601 | } |
1602 | |
1603 | bool Editline::Cancel() { |
1604 | bool result = true; |
1605 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
1606 | if (m_editor_status == EditorStatus::Editing) { |
1607 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::BlockStart); |
1608 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_CLEAR_BELOW); |
1609 | result = m_input_connection.InterruptRead(); |
1610 | } |
1611 | m_editor_status = EditorStatus::Interrupted; |
1612 | return result; |
1613 | } |
1614 | |
1615 | bool Editline::GetLine(std::string &line, bool &interrupted) { |
1616 | ConfigureEditor(multiline: false); |
1617 | m_input_lines = std::vector<EditLineStringType>(); |
1618 | m_input_lines.insert(position: m_input_lines.begin(), EditLineConstString("" )); |
1619 | |
1620 | ScopedOptional<LockedStreamFile> scope(m_locked_output, |
1621 | m_output_stream_sp->Lock()); |
1622 | |
1623 | lldbassert(m_editor_status != EditorStatus::Editing); |
1624 | if (m_editor_status == EditorStatus::Interrupted) { |
1625 | m_editor_status = EditorStatus::Complete; |
1626 | interrupted = true; |
1627 | return true; |
1628 | } |
1629 | |
1630 | SetCurrentLine(0); |
1631 | m_in_history = false; |
1632 | m_editor_status = EditorStatus::Editing; |
1633 | m_revert_cursor_index = -1; |
1634 | |
1635 | int count; |
1636 | auto input = el_wgets(m_editline, &count); |
1637 | |
1638 | interrupted = m_editor_status == EditorStatus::Interrupted; |
1639 | if (!interrupted) { |
1640 | if (input == nullptr) { |
1641 | fprintf(stream: m_locked_output->GetFile().GetStream(), format: "\n" ); |
1642 | m_editor_status = EditorStatus::EndOfInput; |
1643 | } else { |
1644 | m_history_sp->Enter(line_cstr: input); |
1645 | #if LLDB_EDITLINE_USE_WCHAR |
1646 | llvm::convertWideToUTF8(Source: SplitLines(input)[0], Result&: line); |
1647 | #else |
1648 | line = SplitLines(input)[0]; |
1649 | #endif |
1650 | m_editor_status = EditorStatus::Complete; |
1651 | } |
1652 | } |
1653 | return m_editor_status != EditorStatus::EndOfInput; |
1654 | } |
1655 | |
1656 | bool Editline::GetLines(int first_line_number, StringList &lines, |
1657 | bool &interrupted) { |
1658 | ConfigureEditor(multiline: true); |
1659 | |
1660 | // Print the initial input lines, then move the cursor back up to the start |
1661 | // of input |
1662 | SetBaseLineNumber(first_line_number); |
1663 | m_input_lines = std::vector<EditLineStringType>(); |
1664 | m_input_lines.insert(position: m_input_lines.begin(), EditLineConstString("" )); |
1665 | |
1666 | ScopedOptional<LockedStreamFile> scope(m_locked_output, |
1667 | m_output_stream_sp->Lock()); |
1668 | |
1669 | // Begin the line editing loop |
1670 | DisplayInput(); |
1671 | SetCurrentLine(0); |
1672 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::BlockStart); |
1673 | m_editor_status = EditorStatus::Editing; |
1674 | m_in_history = false; |
1675 | |
1676 | m_revert_cursor_index = -1; |
1677 | while (m_editor_status == EditorStatus::Editing) { |
1678 | int count; |
1679 | m_current_line_rows = -1; |
1680 | el_wpush(m_editline, EditLineConstString( |
1681 | "\x1b[^" )); // Revert to the existing line content |
1682 | el_wgets(m_editline, &count); |
1683 | } |
1684 | |
1685 | interrupted = m_editor_status == EditorStatus::Interrupted; |
1686 | if (!interrupted) { |
1687 | // Save the completed entry in history before returning. Don't save empty |
1688 | // input as that just clutters the command history. |
1689 | if (!m_input_lines.empty()) |
1690 | m_history_sp->Enter(line_cstr: CombineLines(lines: m_input_lines).c_str()); |
1691 | |
1692 | lines = GetInputAsStringList(); |
1693 | } |
1694 | return m_editor_status != EditorStatus::EndOfInput; |
1695 | } |
1696 | |
1697 | void Editline::PrintAsync(lldb::LockableStreamFileSP stream_sp, const char *s, |
1698 | size_t len) { |
1699 | LockedStreamFile locked_stream = m_output_stream_sp->Lock(); |
1700 | if (m_editor_status == EditorStatus::Editing) { |
1701 | SaveEditedLine(); |
1702 | MoveCursor(from: CursorLocation::EditingCursor, to: CursorLocation::BlockStart); |
1703 | fprintf(stream: locked_stream.GetFile().GetStream(), ANSI_CLEAR_BELOW); |
1704 | } |
1705 | locked_stream.Write(src: s, src_len: len); |
1706 | if (m_editor_status == EditorStatus::Editing) { |
1707 | DisplayInput(); |
1708 | MoveCursor(from: CursorLocation::BlockEnd, to: CursorLocation::EditingCursor); |
1709 | } |
1710 | } |
1711 | |
1712 | bool Editline::CompleteCharacter(char ch, EditLineGetCharType &out) { |
1713 | #if !LLDB_EDITLINE_USE_WCHAR |
1714 | if (ch == (char)EOF) |
1715 | return false; |
1716 | |
1717 | out = (unsigned char)ch; |
1718 | return true; |
1719 | #else |
1720 | llvm::SmallString<4> input; |
1721 | for (;;) { |
1722 | input.push_back(Elt: ch); |
1723 | auto *cur_ptr = reinterpret_cast<const llvm::UTF8 *>(input.begin()); |
1724 | auto *end_ptr = reinterpret_cast<const llvm::UTF8 *>(input.end()); |
1725 | llvm::UTF32 code_point = 0; |
1726 | llvm::ConversionResult cr = llvm::convertUTF8Sequence( |
1727 | source: &cur_ptr, sourceEnd: end_ptr, target: &code_point, flags: llvm::lenientConversion); |
1728 | switch (cr) { |
1729 | case llvm::conversionOK: |
1730 | out = code_point; |
1731 | return out != (EditLineGetCharType)WEOF; |
1732 | case llvm::targetExhausted: |
1733 | case llvm::sourceIllegal: |
1734 | return false; |
1735 | case llvm::sourceExhausted: |
1736 | lldb::ConnectionStatus status; |
1737 | size_t read_count = m_input_connection.Read( |
1738 | dst: &ch, dst_len: 1, timeout: std::chrono::seconds(0), status, error_ptr: nullptr); |
1739 | if (read_count == 0) |
1740 | return false; |
1741 | break; |
1742 | } |
1743 | } |
1744 | #endif |
1745 | } |
1746 | |