1 //===-- CommandCompletions.cpp ----------------------------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 // C Includes
11 #include <sys/stat.h>
12 #if defined(__APPLE__) || defined(__linux__)
13 #include <pwd.h>
14 #endif
15 
16 // C++ Includes
17 // Other libraries and framework includes
18 #include "llvm/ADT/SmallString.h"
19 
20 // Project includes
21 #include "lldb/Core/FileSpecList.h"
22 #include "lldb/Core/Module.h"
23 #include "lldb/Core/PluginManager.h"
24 #include "lldb/Host/FileSpec.h"
25 #include "lldb/Host/FileSystem.h"
26 #include "lldb/Interpreter/Args.h"
27 #include "lldb/Interpreter/CommandCompletions.h"
28 #include "lldb/Interpreter/CommandInterpreter.h"
29 #include "lldb/Interpreter/OptionValueProperties.h"
30 #include "lldb/Symbol/CompileUnit.h"
31 #include "lldb/Symbol/Variable.h"
32 #include "lldb/Target/Target.h"
33 #include "lldb/Utility/CleanUp.h"
34 
35 #include "llvm/ADT/SmallString.h"
36 
37 using namespace lldb_private;
38 
39 CommandCompletions::CommonCompletionElement
40 CommandCompletions::g_common_completions[] =
41 {
42     {eCustomCompletion,          nullptr},
43     {eSourceFileCompletion,      CommandCompletions::SourceFiles},
44     {eDiskFileCompletion,        CommandCompletions::DiskFiles},
45     {eDiskDirectoryCompletion,   CommandCompletions::DiskDirectories},
46     {eSymbolCompletion,          CommandCompletions::Symbols},
47     {eModuleCompletion,          CommandCompletions::Modules},
48     {eSettingsNameCompletion,    CommandCompletions::SettingsNames},
49     {ePlatformPluginCompletion,  CommandCompletions::PlatformPluginNames},
50     {eArchitectureCompletion,    CommandCompletions::ArchitectureNames},
51     {eVariablePathCompletion,    CommandCompletions::VariablePath},
52     {eNoCompletion,              nullptr}      // This one has to be last in the list.
53 };
54 
55 bool
56 CommandCompletions::InvokeCommonCompletionCallbacks(CommandInterpreter &interpreter,
57                                                     uint32_t completion_mask,
58                                                     const char *completion_str,
59                                                     int match_start_point,
60                                                     int max_return_elements,
61                                                     SearchFilter *searcher,
62                                                     bool &word_complete,
63                                                     StringList &matches)
64 {
65     bool handled = false;
66 
67     if (completion_mask & eCustomCompletion)
68         return false;
69 
70     for (int i = 0; ; i++)
71     {
72         if (g_common_completions[i].type == eNoCompletion)
73             break;
74          else if ((g_common_completions[i].type & completion_mask) == g_common_completions[i].type
75                    && g_common_completions[i].callback != nullptr)
76          {
77             handled = true;
78             g_common_completions[i].callback (interpreter,
79                                               completion_str,
80                                               match_start_point,
81                                               max_return_elements,
82                                               searcher,
83                                               word_complete,
84                                               matches);
85         }
86     }
87     return handled;
88 }
89 
90 int
91 CommandCompletions::SourceFiles(CommandInterpreter &interpreter,
92                                 const char *partial_file_name,
93                                 int match_start_point,
94                                 int max_return_elements,
95                                 SearchFilter *searcher,
96                                 bool &word_complete,
97                                 StringList &matches)
98 {
99     word_complete = true;
100     // Find some way to switch "include support files..."
101     SourceFileCompleter completer (interpreter,
102                                    false,
103                                    partial_file_name,
104                                    match_start_point,
105                                    max_return_elements,
106                                    matches);
107 
108     if (searcher == nullptr)
109     {
110         lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
111         SearchFilterForUnconstrainedSearches null_searcher (target_sp);
112         completer.DoCompletion (&null_searcher);
113     }
114     else
115     {
116         completer.DoCompletion (searcher);
117     }
118     return matches.GetSize();
119 }
120 
121 typedef struct DiskFilesOrDirectoriesBaton
122 {
123     const char *remainder;
124     char *partial_name_copy;
125     bool only_directories;
126     bool *saw_directory;
127     StringList *matches;
128     char *end_ptr;
129     size_t baselen;
130 } DiskFilesOrDirectoriesBaton;
131 
132 FileSpec::EnumerateDirectoryResult DiskFilesOrDirectoriesCallback(void *baton, FileSpec::FileType file_type, const FileSpec &spec)
133 {
134     const char *name = spec.GetFilename().AsCString();
135 
136     const DiskFilesOrDirectoriesBaton *parameters = (DiskFilesOrDirectoriesBaton*)baton;
137     char *end_ptr = parameters->end_ptr;
138     char *partial_name_copy = parameters->partial_name_copy;
139     const char *remainder = parameters->remainder;
140 
141     // Omit ".", ".." and any . files if the match string doesn't start with .
142     if (name[0] == '.')
143     {
144         if (name[1] == '\0')
145             return FileSpec::eEnumerateDirectoryResultNext;
146         else if (name[1] == '.' && name[2] == '\0')
147             return FileSpec::eEnumerateDirectoryResultNext;
148         else if (remainder[0] != '.')
149             return FileSpec::eEnumerateDirectoryResultNext;
150     }
151 
152     // If we found a directory, we put a "/" at the end of the name.
153 
154     if (remainder[0] == '\0' || strstr(name, remainder) == name)
155     {
156         if (strlen(name) + parameters->baselen >= PATH_MAX)
157             return FileSpec::eEnumerateDirectoryResultNext;
158 
159         strcpy(end_ptr, name);
160 
161         bool isa_directory = false;
162         if (file_type == FileSpec::eFileTypeDirectory)
163             isa_directory = true;
164         else if (file_type == FileSpec::eFileTypeSymbolicLink)
165         {
166             if (FileSpec(partial_name_copy, false).IsDirectory())
167                 isa_directory = true;
168         }
169 
170         if (isa_directory)
171         {
172             *parameters->saw_directory = true;
173             size_t len = strlen(parameters->partial_name_copy);
174             partial_name_copy[len] = '/';
175             partial_name_copy[len + 1] = '\0';
176         }
177         if (parameters->only_directories && !isa_directory)
178             return FileSpec::eEnumerateDirectoryResultNext;
179         parameters->matches->AppendString(partial_name_copy);
180     }
181 
182     return FileSpec::eEnumerateDirectoryResultNext;
183 }
184 
185 static int
186 DiskFilesOrDirectories(const char *partial_file_name,
187                        bool only_directories,
188                        bool &saw_directory,
189                        StringList &matches)
190 {
191     // I'm going to  use the "glob" function with GLOB_TILDE for user directory expansion.
192     // If it is not defined on your host system, you'll need to implement it yourself...
193 
194     size_t partial_name_len = strlen(partial_file_name);
195 
196     if (partial_name_len >= PATH_MAX)
197         return matches.GetSize();
198 
199     // This copy of the string will be cut up into the directory part, and the remainder.  end_ptr
200     // below will point to the place of the remainder in this string.  Then when we've resolved the
201     // containing directory, and opened it, we'll read the directory contents and overwrite the
202     // partial_name_copy starting from end_ptr with each of the matches.  Thus we will preserve
203     // the form the user originally typed.
204 
205     char partial_name_copy[PATH_MAX];
206     memcpy(partial_name_copy, partial_file_name, partial_name_len);
207     partial_name_copy[partial_name_len] = '\0';
208 
209     // We'll need to save a copy of the remainder for comparison, which we do here.
210     char remainder[PATH_MAX];
211 
212     // end_ptr will point past the last / in partial_name_copy, or if there is no slash to the beginning of the string.
213     char *end_ptr;
214 
215     end_ptr = strrchr(partial_name_copy, '/');
216 
217     // This will store the resolved form of the containing directory
218     llvm::SmallString<64> containing_part;
219 
220     if (end_ptr == nullptr)
221     {
222         // There's no directory.  If the thing begins with a "~" then this is a bare
223         // user name.
224         if (*partial_name_copy == '~')
225         {
226             // Nothing here but the user name.  We could just put a slash on the end,
227             // but for completeness sake we'll resolve the user name and only put a slash
228             // on the end if it exists.
229             llvm::SmallString<64> resolved_username(partial_name_copy);
230             FileSpec::ResolveUsername (resolved_username);
231 
232            // Not sure how this would happen, a username longer than PATH_MAX?  Still...
233             if (resolved_username.size() == 0)
234             {
235                 // The user name didn't resolve, let's look in the password database for matches.
236                 // The user name database contains duplicates, and is not in alphabetical order, so
237                 // we'll use a set to manage that for us.
238                 FileSpec::ResolvePartialUsername (partial_name_copy, matches);
239                 if (matches.GetSize() > 0)
240                     saw_directory = true;
241                 return matches.GetSize();
242             }
243             else
244             {
245                 //The thing exists, put a '/' on the end, and return it...
246                 // FIXME: complete user names here:
247                 partial_name_copy[partial_name_len] = '/';
248                 partial_name_copy[partial_name_len+1] = '\0';
249                 matches.AppendString(partial_name_copy);
250                 saw_directory = true;
251                 return matches.GetSize();
252             }
253         }
254         else
255         {
256             // The containing part is the CWD, and the whole string is the remainder.
257             containing_part = ".";
258             strcpy(remainder, partial_name_copy);
259             end_ptr = partial_name_copy;
260         }
261     }
262     else
263     {
264         if (end_ptr == partial_name_copy)
265         {
266             // We're completing a file or directory in the root volume.
267             containing_part = "/";
268         }
269         else
270         {
271             containing_part.append(partial_name_copy, end_ptr);
272         }
273         // Push end_ptr past the final "/" and set remainder.
274         end_ptr++;
275         strcpy(remainder, end_ptr);
276     }
277 
278     // Look for a user name in the containing part, and if it's there, resolve it and stick the
279     // result back into the containing_part:
280 
281     if (*partial_name_copy == '~')
282     {
283         FileSpec::ResolveUsername(containing_part);
284         // User name doesn't exist, we're not getting any further...
285         if (containing_part.empty())
286             return matches.GetSize();
287     }
288 
289     // Okay, containing_part is now the directory we want to open and look for files:
290 
291     size_t baselen = end_ptr - partial_name_copy;
292 
293     DiskFilesOrDirectoriesBaton parameters;
294     parameters.remainder = remainder;
295     parameters.partial_name_copy = partial_name_copy;
296     parameters.only_directories = only_directories;
297     parameters.saw_directory = &saw_directory;
298     parameters.matches = &matches;
299     parameters.end_ptr = end_ptr;
300     parameters.baselen = baselen;
301 
302     FileSpec::EnumerateDirectory(containing_part.c_str(), true, true, true, DiskFilesOrDirectoriesCallback, &parameters);
303 
304     return matches.GetSize();
305 }
306 
307 int
308 CommandCompletions::DiskFiles(CommandInterpreter &interpreter,
309                               const char *partial_file_name,
310                               int match_start_point,
311                               int max_return_elements,
312                               SearchFilter *searcher,
313                               bool &word_complete,
314                               StringList &matches)
315 {
316     int ret_val = DiskFilesOrDirectories (partial_file_name,
317                                           false,
318                                           word_complete,
319                                           matches);
320     word_complete = !word_complete;
321     return ret_val;
322 }
323 
324 int
325 CommandCompletions::DiskDirectories(CommandInterpreter &interpreter,
326                                     const char *partial_file_name,
327                                     int match_start_point,
328                                     int max_return_elements,
329                                     SearchFilter *searcher,
330                                     bool &word_complete,
331                                     StringList &matches)
332 {
333     int ret_val =  DiskFilesOrDirectories (partial_file_name,
334                                            true,
335                                            word_complete,
336                                            matches);
337     word_complete = false;
338     return ret_val;
339 }
340 
341 int
342 CommandCompletions::Modules(CommandInterpreter &interpreter,
343                             const char *partial_file_name,
344                             int match_start_point,
345                             int max_return_elements,
346                             SearchFilter *searcher,
347                             bool &word_complete,
348                             StringList &matches)
349 {
350     word_complete = true;
351     ModuleCompleter completer (interpreter,
352                                partial_file_name,
353                                match_start_point,
354                                max_return_elements,
355                                matches);
356 
357     if (searcher == nullptr)
358     {
359         lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
360         SearchFilterForUnconstrainedSearches null_searcher (target_sp);
361         completer.DoCompletion (&null_searcher);
362     }
363     else
364     {
365         completer.DoCompletion (searcher);
366     }
367     return matches.GetSize();
368 }
369 
370 int
371 CommandCompletions::Symbols(CommandInterpreter &interpreter,
372                             const char *partial_file_name,
373                             int match_start_point,
374                             int max_return_elements,
375                             SearchFilter *searcher,
376                             bool &word_complete,
377                             StringList &matches)
378 {
379     word_complete = true;
380     SymbolCompleter completer (interpreter,
381                                partial_file_name,
382                                match_start_point,
383                                max_return_elements,
384                                matches);
385 
386     if (searcher == nullptr)
387     {
388         lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
389         SearchFilterForUnconstrainedSearches null_searcher (target_sp);
390         completer.DoCompletion (&null_searcher);
391     }
392     else
393     {
394         completer.DoCompletion (searcher);
395     }
396     return matches.GetSize();
397 }
398 
399 int
400 CommandCompletions::SettingsNames (CommandInterpreter &interpreter,
401                                    const char *partial_setting_name,
402                                    int match_start_point,
403                                    int max_return_elements,
404                                    SearchFilter *searcher,
405                                    bool &word_complete,
406                                    StringList &matches)
407 {
408     // Cache the full setting name list
409     static StringList g_property_names;
410     if (g_property_names.GetSize() == 0)
411     {
412         // Generate the full setting name list on demand
413         lldb::OptionValuePropertiesSP properties_sp (interpreter.GetDebugger().GetValueProperties());
414         if (properties_sp)
415         {
416             StreamString strm;
417             properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName);
418             const std::string &str = strm.GetString();
419             g_property_names.SplitIntoLines(str.c_str(), str.size());
420         }
421     }
422 
423     size_t exact_matches_idx = SIZE_MAX;
424     const size_t num_matches = g_property_names.AutoComplete (partial_setting_name, matches, exact_matches_idx);
425     word_complete = exact_matches_idx != SIZE_MAX;
426     return num_matches;
427 }
428 
429 int
430 CommandCompletions::PlatformPluginNames (CommandInterpreter &interpreter,
431                                          const char *partial_name,
432                                          int match_start_point,
433                                          int max_return_elements,
434                                          SearchFilter *searcher,
435                                          bool &word_complete,
436                                          lldb_private::StringList &matches)
437 {
438     const uint32_t num_matches = PluginManager::AutoCompletePlatformName(partial_name, matches);
439     word_complete = num_matches == 1;
440     return num_matches;
441 }
442 
443 int
444 CommandCompletions::ArchitectureNames (CommandInterpreter &interpreter,
445                                        const char *partial_name,
446                                        int match_start_point,
447                                        int max_return_elements,
448                                        SearchFilter *searcher,
449                                        bool &word_complete,
450                                        lldb_private::StringList &matches)
451 {
452     const uint32_t num_matches = ArchSpec::AutoComplete (partial_name, matches);
453     word_complete = num_matches == 1;
454     return num_matches;
455 }
456 
457 int
458 CommandCompletions::VariablePath (CommandInterpreter &interpreter,
459                                   const char *partial_name,
460                                   int match_start_point,
461                                   int max_return_elements,
462                                   SearchFilter *searcher,
463                                   bool &word_complete,
464                                   lldb_private::StringList &matches)
465 {
466     return Variable::AutoComplete (interpreter.GetExecutionContext(), partial_name, matches, word_complete);
467 }
468 
469 CommandCompletions::Completer::Completer(CommandInterpreter &interpreter,
470                                          const char *completion_str,
471                                          int match_start_point,
472                                          int max_return_elements,
473                                          StringList &matches) :
474     m_interpreter (interpreter),
475     m_completion_str (completion_str),
476     m_match_start_point (match_start_point),
477     m_max_return_elements (max_return_elements),
478     m_matches (matches)
479 {
480 }
481 
482 CommandCompletions::Completer::~Completer() = default;
483 
484 //----------------------------------------------------------------------
485 // SourceFileCompleter
486 //----------------------------------------------------------------------
487 
488 CommandCompletions::SourceFileCompleter::SourceFileCompleter(CommandInterpreter &interpreter,
489                                                              bool include_support_files,
490                                                              const char *completion_str,
491                                                              int match_start_point,
492                                                              int max_return_elements,
493                                                              StringList &matches) :
494     CommandCompletions::Completer (interpreter, completion_str, match_start_point, max_return_elements, matches),
495     m_include_support_files (include_support_files),
496     m_matching_files()
497 {
498     FileSpec partial_spec (m_completion_str.c_str(), false);
499     m_file_name = partial_spec.GetFilename().GetCString();
500     m_dir_name = partial_spec.GetDirectory().GetCString();
501 }
502 
503 Searcher::Depth
504 CommandCompletions::SourceFileCompleter::GetDepth()
505 {
506     return eDepthCompUnit;
507 }
508 
509 Searcher::CallbackReturn
510 CommandCompletions::SourceFileCompleter::SearchCallback(SearchFilter &filter,
511                                                         SymbolContext &context,
512                                                         Address *addr,
513                                                         bool complete)
514 {
515     if (context.comp_unit != nullptr)
516     {
517         if (m_include_support_files)
518         {
519             FileSpecList supporting_files = context.comp_unit->GetSupportFiles();
520             for (size_t sfiles = 0; sfiles < supporting_files.GetSize(); sfiles++)
521             {
522                 const FileSpec &sfile_spec = supporting_files.GetFileSpecAtIndex(sfiles);
523                 const char *sfile_file_name = sfile_spec.GetFilename().GetCString();
524                 const char *sfile_dir_name = sfile_spec.GetFilename().GetCString();
525                 bool match = false;
526                 if (m_file_name && sfile_file_name
527                     && strstr (sfile_file_name, m_file_name) == sfile_file_name)
528                     match = true;
529                 if (match && m_dir_name && sfile_dir_name
530                     && strstr (sfile_dir_name, m_dir_name) != sfile_dir_name)
531                     match = false;
532 
533                 if (match)
534                 {
535                     m_matching_files.AppendIfUnique(sfile_spec);
536                 }
537             }
538         }
539         else
540         {
541             const char *cur_file_name = context.comp_unit->GetFilename().GetCString();
542             const char *cur_dir_name = context.comp_unit->GetDirectory().GetCString();
543 
544             bool match = false;
545             if (m_file_name && cur_file_name
546                 && strstr (cur_file_name, m_file_name) == cur_file_name)
547                 match = true;
548 
549             if (match && m_dir_name && cur_dir_name
550                 && strstr (cur_dir_name, m_dir_name) != cur_dir_name)
551                 match = false;
552 
553             if (match)
554             {
555                 m_matching_files.AppendIfUnique(context.comp_unit);
556             }
557         }
558     }
559     return Searcher::eCallbackReturnContinue;
560 }
561 
562 size_t
563 CommandCompletions::SourceFileCompleter::DoCompletion (SearchFilter *filter)
564 {
565     filter->Search (*this);
566     // Now convert the filelist to completions:
567     for (size_t i = 0; i < m_matching_files.GetSize(); i++)
568     {
569         m_matches.AppendString (m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString());
570     }
571     return m_matches.GetSize();
572 }
573 
574 //----------------------------------------------------------------------
575 // SymbolCompleter
576 //----------------------------------------------------------------------
577 
578 static bool
579 regex_chars (const char comp)
580 {
581     return (comp == '[' || comp == ']' ||
582             comp == '(' || comp == ')' ||
583             comp == '{' || comp == '}' ||
584             comp == '+' ||
585             comp == '.' ||
586             comp == '*' ||
587             comp == '|' ||
588             comp == '^' ||
589             comp == '$' ||
590             comp == '\\' ||
591             comp == '?');
592 }
593 
594 CommandCompletions::SymbolCompleter::SymbolCompleter(CommandInterpreter &interpreter,
595                                                      const char *completion_str,
596                                                      int match_start_point,
597                                                      int max_return_elements,
598                                                      StringList &matches) :
599     CommandCompletions::Completer (interpreter, completion_str, match_start_point, max_return_elements, matches)
600 {
601     std::string regex_str;
602     if (completion_str && completion_str[0])
603     {
604         regex_str.append("^");
605         regex_str.append(completion_str);
606     }
607     else
608     {
609         // Match anything since the completion string is empty
610         regex_str.append(".");
611     }
612     std::string::iterator pos = find_if(regex_str.begin() + 1, regex_str.end(), regex_chars);
613     while (pos < regex_str.end())
614     {
615         pos = regex_str.insert(pos, '\\');
616         pos = find_if(pos + 2, regex_str.end(), regex_chars);
617     }
618     m_regex.Compile(regex_str.c_str());
619 }
620 
621 Searcher::Depth
622 CommandCompletions::SymbolCompleter::GetDepth()
623 {
624     return eDepthModule;
625 }
626 
627 Searcher::CallbackReturn
628 CommandCompletions::SymbolCompleter::SearchCallback(SearchFilter &filter,
629                                                     SymbolContext &context,
630                                                     Address *addr,
631                                                     bool complete)
632 {
633     if (context.module_sp)
634     {
635         SymbolContextList sc_list;
636         const bool include_symbols = true;
637         const bool include_inlines = true;
638         const bool append = true;
639         context.module_sp->FindFunctions (m_regex, include_symbols, include_inlines, append, sc_list);
640 
641         SymbolContext sc;
642         // Now add the functions & symbols to the list - only add if unique:
643         for (uint32_t i = 0; i < sc_list.GetSize(); i++)
644         {
645             if (sc_list.GetContextAtIndex(i, sc))
646             {
647                 ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled);
648                 if (!func_name.IsEmpty())
649                     m_match_set.insert (func_name);
650             }
651         }
652     }
653     return Searcher::eCallbackReturnContinue;
654 }
655 
656 size_t
657 CommandCompletions::SymbolCompleter::DoCompletion (SearchFilter *filter)
658 {
659     filter->Search (*this);
660     collection::iterator pos = m_match_set.begin(), end = m_match_set.end();
661     for (pos = m_match_set.begin(); pos != end; pos++)
662         m_matches.AppendString((*pos).GetCString());
663 
664     return m_matches.GetSize();
665 }
666 
667 //----------------------------------------------------------------------
668 // ModuleCompleter
669 //----------------------------------------------------------------------
670 CommandCompletions::ModuleCompleter::ModuleCompleter(CommandInterpreter &interpreter,
671                                                      const char *completion_str,
672                                                      int match_start_point,
673                                                      int max_return_elements,
674                                                      StringList &matches) :
675     CommandCompletions::Completer (interpreter, completion_str, match_start_point, max_return_elements, matches)
676 {
677     FileSpec partial_spec (m_completion_str.c_str(), false);
678     m_file_name = partial_spec.GetFilename().GetCString();
679     m_dir_name = partial_spec.GetDirectory().GetCString();
680 }
681 
682 Searcher::Depth
683 CommandCompletions::ModuleCompleter::GetDepth()
684 {
685     return eDepthModule;
686 }
687 
688 Searcher::CallbackReturn
689 CommandCompletions::ModuleCompleter::SearchCallback(SearchFilter &filter,
690                                                     SymbolContext &context,
691                                                     Address *addr,
692                                                     bool complete)
693 {
694     if (context.module_sp)
695     {
696         const char *cur_file_name = context.module_sp->GetFileSpec().GetFilename().GetCString();
697         const char *cur_dir_name = context.module_sp->GetFileSpec().GetDirectory().GetCString();
698 
699         bool match = false;
700         if (m_file_name && cur_file_name
701             && strstr (cur_file_name, m_file_name) == cur_file_name)
702             match = true;
703 
704         if (match && m_dir_name && cur_dir_name
705             && strstr (cur_dir_name, m_dir_name) != cur_dir_name)
706             match = false;
707 
708         if (match)
709         {
710             m_matches.AppendString (cur_file_name);
711         }
712     }
713     return Searcher::eCallbackReturnContinue;
714 }
715 
716 size_t
717 CommandCompletions::ModuleCompleter::DoCompletion (SearchFilter *filter)
718 {
719     filter->Search (*this);
720     return m_matches.GetSize();
721 }
722