1 //===-- CommandCompletions.cpp ----------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "llvm/ADT/SmallString.h"
10 #include "llvm/ADT/StringSet.h"
12 #include "lldb/Core/FileSpecList.h"
13 #include "lldb/Core/Module.h"
14 #include "lldb/Core/PluginManager.h"
15 #include "lldb/Host/FileSystem.h"
16 #include "lldb/Interpreter/CommandCompletions.h"
17 #include "lldb/Interpreter/CommandInterpreter.h"
18 #include "lldb/Interpreter/OptionValueProperties.h"
19 #include "lldb/Symbol/CompileUnit.h"
20 #include "lldb/Symbol/Variable.h"
21 #include "lldb/Utility/FileSpec.h"
22 #include "lldb/Utility/StreamString.h"
23 #include "lldb/Utility/TildeExpressionResolver.h"
25 #include "llvm/Support/FileSystem.h"
26 #include "llvm/Support/Path.h"
28 using namespace lldb_private
;
30 CommandCompletions::CommonCompletionElement
31 CommandCompletions::g_common_completions
[] = {
32 {eCustomCompletion
, nullptr},
33 {eSourceFileCompletion
, CommandCompletions::SourceFiles
},
34 {eDiskFileCompletion
, CommandCompletions::DiskFiles
},
35 {eDiskDirectoryCompletion
, CommandCompletions::DiskDirectories
},
36 {eSymbolCompletion
, CommandCompletions::Symbols
},
37 {eModuleCompletion
, CommandCompletions::Modules
},
38 {eSettingsNameCompletion
, CommandCompletions::SettingsNames
},
39 {ePlatformPluginCompletion
, CommandCompletions::PlatformPluginNames
},
40 {eArchitectureCompletion
, CommandCompletions::ArchitectureNames
},
41 {eVariablePathCompletion
, CommandCompletions::VariablePath
},
42 {eNoCompletion
, nullptr} // This one has to be last in the list.
45 bool CommandCompletions::InvokeCommonCompletionCallbacks(
46 CommandInterpreter
&interpreter
, uint32_t completion_mask
,
47 CompletionRequest
&request
, SearchFilter
*searcher
) {
50 if (completion_mask
& eCustomCompletion
)
53 for (int i
= 0;; i
++) {
54 if (g_common_completions
[i
].type
== eNoCompletion
)
56 else if ((g_common_completions
[i
].type
& completion_mask
) ==
57 g_common_completions
[i
].type
&&
58 g_common_completions
[i
].callback
!= nullptr) {
60 g_common_completions
[i
].callback(interpreter
, request
, searcher
);
66 void CommandCompletions::SourceFiles(CommandInterpreter
&interpreter
,
67 CompletionRequest
&request
,
68 SearchFilter
*searcher
) {
69 // Find some way to switch "include support files..."
70 SourceFileCompleter
completer(interpreter
, false, request
);
72 if (searcher
== nullptr) {
73 lldb::TargetSP target_sp
= interpreter
.GetDebugger().GetSelectedTarget();
74 SearchFilterForUnconstrainedSearches
null_searcher(target_sp
);
75 completer
.DoCompletion(&null_searcher
);
77 completer
.DoCompletion(searcher
);
81 static void DiskFilesOrDirectories(const llvm::Twine
&partial_name
,
82 bool only_directories
,
83 CompletionRequest
&request
,
84 TildeExpressionResolver
&Resolver
) {
85 llvm::SmallString
<256> CompletionBuffer
;
86 llvm::SmallString
<256> Storage
;
87 partial_name
.toVector(CompletionBuffer
);
89 if (CompletionBuffer
.size() >= PATH_MAX
)
92 namespace path
= llvm::sys::path
;
94 llvm::StringRef SearchDir
;
95 llvm::StringRef PartialItem
;
97 if (CompletionBuffer
.startswith("~")) {
98 llvm::StringRef
Buffer(CompletionBuffer
);
100 Buffer
.find_if([](char c
) { return path::is_separator(c
); });
102 llvm::StringRef Username
= Buffer
.take_front(FirstSep
);
103 llvm::StringRef Remainder
;
104 if (FirstSep
!= llvm::StringRef::npos
)
105 Remainder
= Buffer
.drop_front(FirstSep
+ 1);
107 llvm::SmallString
<256> Resolved
;
108 if (!Resolver
.ResolveExact(Username
, Resolved
)) {
109 // We couldn't resolve it as a full username. If there were no slashes
110 // then this might be a partial username. We try to resolve it as such
111 // but after that, we're done regardless of any matches.
112 if (FirstSep
== llvm::StringRef::npos
) {
113 llvm::StringSet
<> MatchSet
;
114 Resolver
.ResolvePartial(Username
, MatchSet
);
115 for (const auto &S
: MatchSet
) {
116 Resolved
= S
.getKey();
117 path::append(Resolved
, path::get_separator());
118 request
.AddCompletion(Resolved
, "", CompletionMode::Partial
);
124 // If there was no trailing slash, then we're done as soon as we resolve
125 // the expression to the correct directory. Otherwise we need to continue
126 // looking for matches within that directory.
127 if (FirstSep
== llvm::StringRef::npos
) {
128 // Make sure it ends with a separator.
129 path::append(CompletionBuffer
, path::get_separator());
130 request
.AddCompletion(CompletionBuffer
, "", CompletionMode::Partial
);
134 // We want to keep the form the user typed, so we special case this to
135 // search in the fully resolved directory, but CompletionBuffer keeps the
136 // unmodified form that the user typed.
138 llvm::StringRef RemainderDir
= path::parent_path(Remainder
);
139 if (!RemainderDir
.empty()) {
140 // Append the remaining path to the resolved directory.
141 Storage
.append(path::get_separator());
142 Storage
.append(RemainderDir
);
146 SearchDir
= path::parent_path(CompletionBuffer
);
149 size_t FullPrefixLen
= CompletionBuffer
.size();
151 PartialItem
= path::filename(CompletionBuffer
);
153 // path::filename() will return "." when the passed path ends with a
154 // directory separator. We have to filter those out, but only when the
155 // "." doesn't come from the completion request itself.
156 if (PartialItem
== "." && path::is_separator(CompletionBuffer
.back()))
157 PartialItem
= llvm::StringRef();
159 if (SearchDir
.empty()) {
160 llvm::sys::fs::current_path(Storage
);
163 assert(!PartialItem
.contains(path::get_separator()));
165 // SearchDir now contains the directory to search in, and Prefix contains the
166 // text we want to match against items in that directory.
168 FileSystem
&fs
= FileSystem::Instance();
170 llvm::vfs::directory_iterator Iter
= fs
.DirBegin(SearchDir
, EC
);
171 llvm::vfs::directory_iterator End
;
172 for (; Iter
!= End
&& !EC
; Iter
.increment(EC
)) {
174 llvm::ErrorOr
<llvm::vfs::Status
> Status
= fs
.GetStatus(Entry
.path());
179 auto Name
= path::filename(Entry
.path());
182 if (Name
== "." || Name
== ".." || !Name
.startswith(PartialItem
))
185 bool is_dir
= Status
->isDirectory();
187 // If it's a symlink, then we treat it as a directory as long as the target
189 if (Status
->isSymlink()) {
190 FileSpec
symlink_filespec(Entry
.path());
191 FileSpec resolved_filespec
;
192 auto error
= fs
.ResolveSymbolicLink(symlink_filespec
, resolved_filespec
);
194 is_dir
= fs
.IsDirectory(symlink_filespec
);
197 if (only_directories
&& !is_dir
)
200 // Shrink it back down so that it just has the original prefix the user
201 // typed and remove the part of the name which is common to the located
202 // item and what the user typed.
203 CompletionBuffer
.resize(FullPrefixLen
);
204 Name
= Name
.drop_front(PartialItem
.size());
205 CompletionBuffer
.append(Name
);
208 path::append(CompletionBuffer
, path::get_separator());
211 CompletionMode mode
=
212 is_dir
? CompletionMode::Partial
: CompletionMode::Normal
;
213 request
.AddCompletion(CompletionBuffer
, "", mode
);
217 static void DiskFilesOrDirectories(const llvm::Twine
&partial_name
,
218 bool only_directories
, StringList
&matches
,
219 TildeExpressionResolver
&Resolver
) {
220 CompletionResult result
;
221 std::string partial_name_str
= partial_name
.str();
222 CompletionRequest
request(partial_name_str
, partial_name_str
.size(), result
);
223 DiskFilesOrDirectories(partial_name
, only_directories
, request
, Resolver
);
224 result
.GetMatches(matches
);
227 static void DiskFilesOrDirectories(CompletionRequest
&request
,
228 bool only_directories
) {
229 StandardTildeExpressionResolver resolver
;
230 DiskFilesOrDirectories(request
.GetCursorArgumentPrefix(), only_directories
,
234 void CommandCompletions::DiskFiles(CommandInterpreter
&interpreter
,
235 CompletionRequest
&request
,
236 SearchFilter
*searcher
) {
237 DiskFilesOrDirectories(request
, /*only_dirs*/ false);
240 void CommandCompletions::DiskFiles(const llvm::Twine
&partial_file_name
,
242 TildeExpressionResolver
&Resolver
) {
243 DiskFilesOrDirectories(partial_file_name
, false, matches
, Resolver
);
246 void CommandCompletions::DiskDirectories(CommandInterpreter
&interpreter
,
247 CompletionRequest
&request
,
248 SearchFilter
*searcher
) {
249 DiskFilesOrDirectories(request
, /*only_dirs*/ true);
252 void CommandCompletions::DiskDirectories(const llvm::Twine
&partial_file_name
,
254 TildeExpressionResolver
&Resolver
) {
255 DiskFilesOrDirectories(partial_file_name
, true, matches
, Resolver
);
258 void CommandCompletions::Modules(CommandInterpreter
&interpreter
,
259 CompletionRequest
&request
,
260 SearchFilter
*searcher
) {
261 ModuleCompleter
completer(interpreter
, request
);
263 if (searcher
== nullptr) {
264 lldb::TargetSP target_sp
= interpreter
.GetDebugger().GetSelectedTarget();
265 SearchFilterForUnconstrainedSearches
null_searcher(target_sp
);
266 completer
.DoCompletion(&null_searcher
);
268 completer
.DoCompletion(searcher
);
272 void CommandCompletions::Symbols(CommandInterpreter
&interpreter
,
273 CompletionRequest
&request
,
274 SearchFilter
*searcher
) {
275 SymbolCompleter
completer(interpreter
, request
);
277 if (searcher
== nullptr) {
278 lldb::TargetSP target_sp
= interpreter
.GetDebugger().GetSelectedTarget();
279 SearchFilterForUnconstrainedSearches
null_searcher(target_sp
);
280 completer
.DoCompletion(&null_searcher
);
282 completer
.DoCompletion(searcher
);
286 void CommandCompletions::SettingsNames(CommandInterpreter
&interpreter
,
287 CompletionRequest
&request
,
288 SearchFilter
*searcher
) {
289 // Cache the full setting name list
290 static StringList g_property_names
;
291 if (g_property_names
.GetSize() == 0) {
292 // Generate the full setting name list on demand
293 lldb::OptionValuePropertiesSP
properties_sp(
294 interpreter
.GetDebugger().GetValueProperties());
297 properties_sp
->DumpValue(nullptr, strm
, OptionValue::eDumpOptionName
);
298 const std::string
&str
= strm
.GetString();
299 g_property_names
.SplitIntoLines(str
.c_str(), str
.size());
303 for (const std::string
&s
: g_property_names
)
304 request
.TryCompleteCurrentArg(s
);
307 void CommandCompletions::PlatformPluginNames(CommandInterpreter
&interpreter
,
308 CompletionRequest
&request
,
309 SearchFilter
*searcher
) {
310 PluginManager::AutoCompletePlatformName(request
.GetCursorArgumentPrefix(),
314 void CommandCompletions::ArchitectureNames(CommandInterpreter
&interpreter
,
315 CompletionRequest
&request
,
316 SearchFilter
*searcher
) {
317 ArchSpec::AutoComplete(request
);
320 void CommandCompletions::VariablePath(CommandInterpreter
&interpreter
,
321 CompletionRequest
&request
,
322 SearchFilter
*searcher
) {
323 Variable::AutoComplete(interpreter
.GetExecutionContext(), request
);
326 CommandCompletions::Completer::Completer(CommandInterpreter
&interpreter
,
327 CompletionRequest
&request
)
328 : m_interpreter(interpreter
), m_request(request
) {}
330 CommandCompletions::Completer::~Completer() = default;
332 // SourceFileCompleter
334 CommandCompletions::SourceFileCompleter::SourceFileCompleter(
335 CommandInterpreter
&interpreter
, bool include_support_files
,
336 CompletionRequest
&request
)
337 : CommandCompletions::Completer(interpreter
, request
),
338 m_include_support_files(include_support_files
), m_matching_files() {
339 FileSpec
partial_spec(m_request
.GetCursorArgumentPrefix());
340 m_file_name
= partial_spec
.GetFilename().GetCString();
341 m_dir_name
= partial_spec
.GetDirectory().GetCString();
344 lldb::SearchDepth
CommandCompletions::SourceFileCompleter::GetDepth() {
345 return lldb::eSearchDepthCompUnit
;
348 Searcher::CallbackReturn
349 CommandCompletions::SourceFileCompleter::SearchCallback(SearchFilter
&filter
,
350 SymbolContext
&context
,
352 if (context
.comp_unit
!= nullptr) {
353 if (m_include_support_files
) {
354 FileSpecList supporting_files
= context
.comp_unit
->GetSupportFiles();
355 for (size_t sfiles
= 0; sfiles
< supporting_files
.GetSize(); sfiles
++) {
356 const FileSpec
&sfile_spec
=
357 supporting_files
.GetFileSpecAtIndex(sfiles
);
358 const char *sfile_file_name
= sfile_spec
.GetFilename().GetCString();
359 const char *sfile_dir_name
= sfile_spec
.GetFilename().GetCString();
361 if (m_file_name
&& sfile_file_name
&&
362 strstr(sfile_file_name
, m_file_name
) == sfile_file_name
)
364 if (match
&& m_dir_name
&& sfile_dir_name
&&
365 strstr(sfile_dir_name
, m_dir_name
) != sfile_dir_name
)
369 m_matching_files
.AppendIfUnique(sfile_spec
);
373 const char *cur_file_name
=
374 context
.comp_unit
->GetPrimaryFile().GetFilename().GetCString();
375 const char *cur_dir_name
=
376 context
.comp_unit
->GetPrimaryFile().GetDirectory().GetCString();
379 if (m_file_name
&& cur_file_name
&&
380 strstr(cur_file_name
, m_file_name
) == cur_file_name
)
383 if (match
&& m_dir_name
&& cur_dir_name
&&
384 strstr(cur_dir_name
, m_dir_name
) != cur_dir_name
)
388 m_matching_files
.AppendIfUnique(context
.comp_unit
->GetPrimaryFile());
392 return Searcher::eCallbackReturnContinue
;
395 void CommandCompletions::SourceFileCompleter::DoCompletion(
396 SearchFilter
*filter
) {
397 filter
->Search(*this);
398 // Now convert the filelist to completions:
399 for (size_t i
= 0; i
< m_matching_files
.GetSize(); i
++) {
400 m_request
.AddCompletion(
401 m_matching_files
.GetFileSpecAtIndex(i
).GetFilename().GetCString());
407 static bool regex_chars(const char comp
) {
408 return llvm::StringRef("[](){}+.*|^$\\?").contains(comp
);
411 CommandCompletions::SymbolCompleter::SymbolCompleter(
412 CommandInterpreter
&interpreter
, CompletionRequest
&request
)
413 : CommandCompletions::Completer(interpreter
, request
) {
414 std::string regex_str
;
415 if (!m_request
.GetCursorArgumentPrefix().empty()) {
416 regex_str
.append("^");
417 regex_str
.append(m_request
.GetCursorArgumentPrefix());
419 // Match anything since the completion string is empty
420 regex_str
.append(".");
422 std::string::iterator pos
=
423 find_if(regex_str
.begin() + 1, regex_str
.end(), regex_chars
);
424 while (pos
< regex_str
.end()) {
425 pos
= regex_str
.insert(pos
, '\\');
426 pos
= find_if(pos
+ 2, regex_str
.end(), regex_chars
);
428 m_regex
= RegularExpression(regex_str
);
431 lldb::SearchDepth
CommandCompletions::SymbolCompleter::GetDepth() {
432 return lldb::eSearchDepthModule
;
435 Searcher::CallbackReturn
CommandCompletions::SymbolCompleter::SearchCallback(
436 SearchFilter
&filter
, SymbolContext
&context
, Address
*addr
) {
437 if (context
.module_sp
) {
438 SymbolContextList sc_list
;
439 const bool include_symbols
= true;
440 const bool include_inlines
= true;
441 context
.module_sp
->FindFunctions(m_regex
, include_symbols
, include_inlines
,
445 // Now add the functions & symbols to the list - only add if unique:
446 for (uint32_t i
= 0; i
< sc_list
.GetSize(); i
++) {
447 if (sc_list
.GetContextAtIndex(i
, sc
)) {
448 ConstString func_name
= sc
.GetFunctionName(Mangled::ePreferDemangled
);
449 // Ensure that the function name matches the regex. This is more than a
450 // sanity check. It is possible that the demangled function name does
451 // not start with the prefix, for example when it's in an anonymous
453 if (!func_name
.IsEmpty() && m_regex
.Execute(func_name
.GetStringRef()))
454 m_match_set
.insert(func_name
);
458 return Searcher::eCallbackReturnContinue
;
461 void CommandCompletions::SymbolCompleter::DoCompletion(SearchFilter
*filter
) {
462 filter
->Search(*this);
463 collection::iterator pos
= m_match_set
.begin(), end
= m_match_set
.end();
464 for (pos
= m_match_set
.begin(); pos
!= end
; pos
++)
465 m_request
.AddCompletion((*pos
).GetCString());
469 CommandCompletions::ModuleCompleter::ModuleCompleter(
470 CommandInterpreter
&interpreter
, CompletionRequest
&request
)
471 : CommandCompletions::Completer(interpreter
, request
) {
472 FileSpec
partial_spec(m_request
.GetCursorArgumentPrefix());
473 m_file_name
= partial_spec
.GetFilename().GetCString();
474 m_dir_name
= partial_spec
.GetDirectory().GetCString();
477 lldb::SearchDepth
CommandCompletions::ModuleCompleter::GetDepth() {
478 return lldb::eSearchDepthModule
;
481 Searcher::CallbackReturn
CommandCompletions::ModuleCompleter::SearchCallback(
482 SearchFilter
&filter
, SymbolContext
&context
, Address
*addr
) {
483 if (context
.module_sp
) {
484 const char *cur_file_name
=
485 context
.module_sp
->GetFileSpec().GetFilename().GetCString();
486 const char *cur_dir_name
=
487 context
.module_sp
->GetFileSpec().GetDirectory().GetCString();
490 if (m_file_name
&& cur_file_name
&&
491 strstr(cur_file_name
, m_file_name
) == cur_file_name
)
494 if (match
&& m_dir_name
&& cur_dir_name
&&
495 strstr(cur_dir_name
, m_dir_name
) != cur_dir_name
)
499 m_request
.AddCompletion(cur_file_name
);
502 return Searcher::eCallbackReturnContinue
;
505 void CommandCompletions::ModuleCompleter::DoCompletion(SearchFilter
*filter
) {
506 filter
->Search(*this);