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