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