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