1 | //===-- LineEditor.cpp - line editor --------------------------------------===// |
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 "llvm/LineEditor/LineEditor.h" |
10 | #include "llvm/ADT/STLExtras.h" |
11 | #include "llvm/ADT/SmallString.h" |
12 | #include "llvm/Config/config.h" |
13 | #include "llvm/Support/Path.h" |
14 | #include "llvm/Support/raw_ostream.h" |
15 | #include <algorithm> |
16 | #include <cassert> |
17 | #include <cstdio> |
18 | #ifdef HAVE_LIBEDIT |
19 | #include <histedit.h> |
20 | #endif |
21 | |
22 | using namespace llvm; |
23 | |
24 | std::string LineEditor::getDefaultHistoryPath(StringRef ProgName) { |
25 | SmallString<32> Path; |
26 | if (sys::path::home_directory(result&: Path)) { |
27 | sys::path::append(path&: Path, a: "." + ProgName + "-history" ); |
28 | return std::string(Path); |
29 | } |
30 | return std::string(); |
31 | } |
32 | |
33 | LineEditor::CompleterConcept::~CompleterConcept() = default; |
34 | LineEditor::ListCompleterConcept::~ListCompleterConcept() = default; |
35 | |
36 | std::string LineEditor::ListCompleterConcept::getCommonPrefix( |
37 | const std::vector<Completion> &Comps) { |
38 | assert(!Comps.empty()); |
39 | |
40 | std::string CommonPrefix = Comps[0].TypedText; |
41 | for (const Completion &C : llvm::drop_begin(RangeOrContainer: Comps)) { |
42 | size_t Len = std::min(a: CommonPrefix.size(), b: C.TypedText.size()); |
43 | size_t CommonLen = 0; |
44 | for (; CommonLen != Len; ++CommonLen) { |
45 | if (CommonPrefix[CommonLen] != C.TypedText[CommonLen]) |
46 | break; |
47 | } |
48 | CommonPrefix.resize(n: CommonLen); |
49 | } |
50 | return CommonPrefix; |
51 | } |
52 | |
53 | LineEditor::CompletionAction |
54 | LineEditor::ListCompleterConcept::complete(StringRef Buffer, size_t Pos) const { |
55 | CompletionAction Action; |
56 | std::vector<Completion> Comps = getCompletions(Buffer, Pos); |
57 | if (Comps.empty()) { |
58 | Action.Kind = CompletionAction::AK_ShowCompletions; |
59 | return Action; |
60 | } |
61 | |
62 | std::string CommonPrefix = getCommonPrefix(Comps); |
63 | |
64 | // If the common prefix is non-empty we can simply insert it. If there is a |
65 | // single completion, this will insert the full completion. If there is more |
66 | // than one, this might be enough information to jog the user's memory but if |
67 | // not the user can also hit tab again to see the completions because the |
68 | // common prefix will then be empty. |
69 | if (CommonPrefix.empty()) { |
70 | Action.Kind = CompletionAction::AK_ShowCompletions; |
71 | for (const Completion &Comp : Comps) |
72 | Action.Completions.push_back(x: Comp.DisplayText); |
73 | } else { |
74 | Action.Kind = CompletionAction::AK_Insert; |
75 | Action.Text = CommonPrefix; |
76 | } |
77 | |
78 | return Action; |
79 | } |
80 | |
81 | LineEditor::CompletionAction LineEditor::getCompletionAction(StringRef Buffer, |
82 | size_t Pos) const { |
83 | if (!Completer) { |
84 | CompletionAction Action; |
85 | Action.Kind = CompletionAction::AK_ShowCompletions; |
86 | return Action; |
87 | } |
88 | |
89 | return Completer->complete(Buffer, Pos); |
90 | } |
91 | |
92 | #ifdef HAVE_LIBEDIT |
93 | |
94 | // libedit-based implementation. |
95 | |
96 | struct LineEditor::InternalData { |
97 | LineEditor *LE; |
98 | |
99 | History *Hist; |
100 | EditLine *EL; |
101 | |
102 | unsigned PrevCount; |
103 | std::string ContinuationOutput; |
104 | |
105 | FILE *Out; |
106 | }; |
107 | |
108 | namespace { |
109 | |
110 | const char *ElGetPromptFn(EditLine *EL) { |
111 | LineEditor::InternalData *Data; |
112 | if (el_get(EL, EL_CLIENTDATA, &Data) == 0) |
113 | return Data->LE->getPrompt().c_str(); |
114 | return "> " ; |
115 | } |
116 | |
117 | // Handles tab completion. |
118 | // |
119 | // This function is really horrible. But since the alternative is to get into |
120 | // the line editor business, here we are. |
121 | unsigned char ElCompletionFn(EditLine *EL, int ch) { |
122 | LineEditor::InternalData *Data; |
123 | if (el_get(EL, EL_CLIENTDATA, &Data) == 0) { |
124 | if (!Data->ContinuationOutput.empty()) { |
125 | // This is the continuation of the AK_ShowCompletions branch below. |
126 | FILE *Out = Data->Out; |
127 | |
128 | // Print the required output (see below). |
129 | ::fwrite(Data->ContinuationOutput.c_str(), |
130 | Data->ContinuationOutput.size(), 1, Out); |
131 | |
132 | // Push a sequence of Ctrl-B characters to move the cursor back to its |
133 | // original position. |
134 | std::string Prevs(Data->PrevCount, '\02'); |
135 | ::el_push(EL, const_cast<char *>(Prevs.c_str())); |
136 | |
137 | Data->ContinuationOutput.clear(); |
138 | |
139 | return CC_REFRESH; |
140 | } |
141 | |
142 | const LineInfo *LI = ::el_line(EL); |
143 | LineEditor::CompletionAction Action = Data->LE->getCompletionAction( |
144 | StringRef(LI->buffer, LI->lastchar - LI->buffer), |
145 | LI->cursor - LI->buffer); |
146 | switch (Action.Kind) { |
147 | case LineEditor::CompletionAction::AK_Insert: |
148 | ::el_insertstr(EL, Action.Text.c_str()); |
149 | return CC_REFRESH; |
150 | |
151 | case LineEditor::CompletionAction::AK_ShowCompletions: |
152 | if (Action.Completions.empty()) { |
153 | return CC_REFRESH_BEEP; |
154 | } else { |
155 | // Push a Ctrl-E and a tab. The Ctrl-E causes libedit to move the cursor |
156 | // to the end of the line, so that when we emit a newline we will be on |
157 | // a new blank line. The tab causes libedit to call this function again |
158 | // after moving the cursor. There doesn't seem to be anything we can do |
159 | // from here to cause libedit to move the cursor immediately. This will |
160 | // break horribly if the user has rebound their keys, so for now we do |
161 | // not permit user rebinding. |
162 | ::el_push(EL, const_cast<char *>("\05\t" )); |
163 | |
164 | // This assembles the output for the continuation block above. |
165 | raw_string_ostream OS(Data->ContinuationOutput); |
166 | |
167 | // Move cursor to a blank line. |
168 | OS << "\n" ; |
169 | |
170 | // Emit the completions. |
171 | for (const std::string &Completion : Action.Completions) |
172 | OS << Completion << "\n" ; |
173 | |
174 | // Fool libedit into thinking nothing has changed. Reprint its prompt |
175 | // and the user input. Note that the cursor will remain at the end of |
176 | // the line after this. |
177 | OS << Data->LE->getPrompt() |
178 | << StringRef(LI->buffer, LI->lastchar - LI->buffer); |
179 | |
180 | // This is the number of characters we need to tell libedit to go back: |
181 | // the distance between end of line and the original cursor position. |
182 | Data->PrevCount = LI->lastchar - LI->cursor; |
183 | |
184 | return CC_REFRESH; |
185 | } |
186 | } |
187 | } |
188 | return CC_ERROR; |
189 | } |
190 | |
191 | } // end anonymous namespace |
192 | |
193 | LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In, |
194 | FILE *Out, FILE *Err) |
195 | : Prompt((ProgName + "> " ).str()), HistoryPath(std::string(HistoryPath)), |
196 | Data(new InternalData) { |
197 | if (HistoryPath.empty()) |
198 | this->HistoryPath = getDefaultHistoryPath(ProgName); |
199 | |
200 | Data->LE = this; |
201 | Data->Out = Out; |
202 | |
203 | Data->Hist = ::history_init(); |
204 | assert(Data->Hist); |
205 | |
206 | Data->EL = ::el_init(ProgName.str().c_str(), In, Out, Err); |
207 | assert(Data->EL); |
208 | |
209 | ::el_set(Data->EL, EL_PROMPT, ElGetPromptFn); |
210 | ::el_set(Data->EL, EL_EDITOR, "emacs" ); |
211 | ::el_set(Data->EL, EL_HIST, history, Data->Hist); |
212 | ::el_set(Data->EL, EL_ADDFN, "tab_complete" , "Tab completion function" , |
213 | ElCompletionFn); |
214 | ::el_set(Data->EL, EL_BIND, "\t" , "tab_complete" , NULL); |
215 | ::el_set(Data->EL, EL_BIND, "^r" , "em-inc-search-prev" , |
216 | NULL); // Cycle through backwards search, entering string |
217 | ::el_set(Data->EL, EL_BIND, "^w" , "ed-delete-prev-word" , |
218 | NULL); // Delete previous word, behave like bash does. |
219 | ::el_set(Data->EL, EL_BIND, "\033[3~" , "ed-delete-next-char" , |
220 | NULL); // Fix the delete key. |
221 | ::el_set(Data->EL, EL_CLIENTDATA, Data.get()); |
222 | |
223 | HistEvent HE; |
224 | ::history(Data->Hist, &HE, H_SETSIZE, 800); |
225 | ::history(Data->Hist, &HE, H_SETUNIQUE, 1); |
226 | loadHistory(); |
227 | } |
228 | |
229 | LineEditor::~LineEditor() { |
230 | saveHistory(); |
231 | |
232 | ::history_end(Data->Hist); |
233 | ::el_end(Data->EL); |
234 | ::fwrite("\n" , 1, 1, Data->Out); |
235 | } |
236 | |
237 | void LineEditor::saveHistory() { |
238 | if (!HistoryPath.empty()) { |
239 | HistEvent HE; |
240 | ::history(Data->Hist, &HE, H_SAVE, HistoryPath.c_str()); |
241 | } |
242 | } |
243 | |
244 | void LineEditor::loadHistory() { |
245 | if (!HistoryPath.empty()) { |
246 | HistEvent HE; |
247 | ::history(Data->Hist, &HE, H_LOAD, HistoryPath.c_str()); |
248 | } |
249 | } |
250 | |
251 | std::optional<std::string> LineEditor::readLine() const { |
252 | // Call el_gets to prompt the user and read the user's input. |
253 | int LineLen = 0; |
254 | const char *Line = ::el_gets(Data->EL, &LineLen); |
255 | |
256 | // Either of these may mean end-of-file. |
257 | if (!Line || LineLen == 0) |
258 | return std::nullopt; |
259 | |
260 | // Strip any newlines off the end of the string. |
261 | while (LineLen > 0 && |
262 | (Line[LineLen - 1] == '\n' || Line[LineLen - 1] == '\r')) |
263 | --LineLen; |
264 | |
265 | HistEvent HE; |
266 | if (LineLen > 0) |
267 | ::history(Data->Hist, &HE, H_ENTER, Line); |
268 | |
269 | return std::string(Line, LineLen); |
270 | } |
271 | |
272 | #else // HAVE_LIBEDIT |
273 | |
274 | // Simple fgets-based implementation. |
275 | |
276 | struct LineEditor::InternalData { |
277 | FILE *In; |
278 | FILE *Out; |
279 | }; |
280 | |
281 | LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In, |
282 | FILE *Out, FILE *Err) |
283 | : Prompt((ProgName + "> " ).str()), Data(new InternalData) { |
284 | Data->In = In; |
285 | Data->Out = Out; |
286 | } |
287 | |
288 | LineEditor::~LineEditor() { |
289 | ::fwrite(ptr: "\n" , size: 1, n: 1, s: Data->Out); |
290 | } |
291 | |
292 | void LineEditor::saveHistory() {} |
293 | void LineEditor::loadHistory() {} |
294 | |
295 | std::optional<std::string> LineEditor::readLine() const { |
296 | ::fprintf(stream: Data->Out, format: "%s" , Prompt.c_str()); |
297 | |
298 | std::string Line; |
299 | do { |
300 | char Buf[64]; |
301 | char *Res = ::fgets(s: Buf, n: sizeof(Buf), stream: Data->In); |
302 | if (!Res) { |
303 | if (Line.empty()) |
304 | return std::nullopt; |
305 | else |
306 | return Line; |
307 | } |
308 | Line.append(s: Buf); |
309 | } while (Line.empty() || |
310 | (Line[Line.size() - 1] != '\n' && Line[Line.size() - 1] != '\r')); |
311 | |
312 | while (!Line.empty() && |
313 | (Line[Line.size() - 1] == '\n' || Line[Line.size() - 1] == '\r')) |
314 | Line.resize(n: Line.size() - 1); |
315 | |
316 | return Line; |
317 | } |
318 | |
319 | #endif // HAVE_LIBEDIT |
320 | |