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