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