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