From 14cfedb287f7bc680dbfcf64616581be927e0fdc Mon Sep 17 00:00:00 2001 From: Allan Odgaard Date: Thu, 7 Mar 2013 16:14:49 +0100 Subject: [PATCH] =?UTF-8?q?Implement=20Replace=20[and=20Find]=20bound=20to?= =?UTF-8?q?=20=E2=8C=A5=E2=8C=98G?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently this is implemented in OakTextView which means that it doesn’t work for macros. Some refactoring is in order so that the implementation can be shared (lack of sharing has to do with how OakTextView reports status to Find dialog or via tool tips, and macros want none of that). There is also no check to see if the current state of the editor is the result of a find operation, i.e. you can invoke “replace” regardless of wether or not “find” was the last action. Finally, doing a multi-file search and using “find next” at the end of one document, which brings you to the first match of next document (part of the results), will not update “captures” from a potential regular expression search, meaning that if you then do “replace”, and your replacement string is a format string that references the match (via $1-n) then it will not be correctly expanded. Closes #104. --- .../resources/English.lproj/MainMenu.xib | 4 +- Frameworks/Find/src/FindWindowController.mm | 2 +- Frameworks/OakTextView/src/OakTextView.mm | 58 +++++++++++++------ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Applications/TextMate/resources/English.lproj/MainMenu.xib b/Applications/TextMate/resources/English.lproj/MainMenu.xib index 94bf0515..0138884c 100644 --- a/Applications/TextMate/resources/English.lproj/MainMenu.xib +++ b/Applications/TextMate/resources/English.lproj/MainMenu.xib @@ -948,7 +948,7 @@ Replace & Find - f + g 1572864 2147483647 @@ -2471,7 +2471,7 @@ {269, 102} - {{0, 0}, {1440, 878}} + {{0, 0}, {2560, 1578}} {269, 124} {10000000000000, 10000000000000} Go to Line diff --git a/Frameworks/Find/src/FindWindowController.mm b/Frameworks/Find/src/FindWindowController.mm index a171eea6..c572dd91 100644 --- a/Frameworks/Find/src/FindWindowController.mm +++ b/Frameworks/Find/src/FindWindowController.mm @@ -301,7 +301,6 @@ static NSButton* OakCreateButton (NSString* label, NSBezelStyle bezel = NSRounde self.findAllButton.action = @selector(findAll:); self.replaceAllButton.action = @selector(replaceAll:); self.replaceAndFindButton.action = @selector(replaceAndFind:); - self.replaceAndFindButton.enabled = NO; self.findPreviousButton.action = @selector(findPrevious:); self.findNextButton.action = @selector(findNext:); @@ -321,6 +320,7 @@ static NSButton* OakCreateButton (NSString* label, NSBezelStyle bezel = NSRounde [self.wrapAroundCheckBox bind:NSValueBinding toObject:_objectController withKeyPath:@"content.wrapAround" options:nil]; [self.ignoreWhitespaceCheckBox bind:NSEnabledBinding toObject:_objectController withKeyPath:@"content.canIgnoreWhitespace" options:nil]; [self.statusTextField bind:NSValueBinding toObject:_objectController withKeyPath:@"content.statusString" options:nil]; + [self.replaceAndFindButton bind:NSEnabledBinding toObject:_objectController withKeyPath:@"content.folderSearch" options:@{ NSValueTransformerNameBindingOption: @"NSNegateBoolean" }]; NSView* contentView = self.window.contentView; for(NSView* view in [self.allViews allValues]) diff --git a/Frameworks/OakTextView/src/OakTextView.mm b/Frameworks/OakTextView/src/OakTextView.mm index 5d87fdc9..2c5b08e2 100644 --- a/Frameworks/OakTextView/src/OakTextView.mm +++ b/Frameworks/OakTextView/src/OakTextView.mm @@ -1575,15 +1575,34 @@ static void update_menu_key_equivalents (NSMenu* menu, action_to_key_t const& ac // TODO If aFindServer != self then we should record findWithOptions: instead of find{Next,Previous,All,…}: + find_operation_t findOperation = aFindServer.findOperation; + if(findOperation == kFindOperationReplace || findOperation == kFindOperationReplaceAndFind) + { + std::string replacement = to_s(aFindServer.replaceString); + if(NSDictionary* captures = [OakPasteboard pasteboardWithName:NSReplacePboard].auxiliaryOptionsForCurrent) + { + std::map variables; + for(NSString* key in [captures allKeys]) + variables.insert(std::make_pair(to_s(key), to_s((NSString*)captures[key]))); + replacement = format_string::expand(replacement, variables); + } + editor->insert(replacement, true); + + [self recordSelector:@selector(replace:) withArgument:nil]; + if(findOperation == kFindOperationReplaceAndFind) + findOperation = kFindOperationFind; + } + bool onlyInSelection = false; - switch(aFindServer.findOperation) + switch(findOperation) { case kFindOperationFindInSelection: case kFindOperationCountInSelection: onlyInSelection = editor->has_selection(); case kFindOperationFind: case kFindOperationCount: { - bool isCounting = aFindServer.findOperation == kFindOperationCount || aFindServer.findOperation == kFindOperationCountInSelection; + [OakPasteboard pasteboardWithName:NSReplacePboard].auxiliaryOptionsForCurrent = nil; + bool isCounting = findOperation == kFindOperationCount || findOperation == kFindOperationCountInSelection; std::string const findStr = to_s(aFindServer.findString); find::options_t options = aFindServer.findOptions; @@ -1592,15 +1611,15 @@ static void update_menu_key_equivalents (NSMenu* menu, action_to_key_t const& ac if(documents && [documents count] > 1) options &= ~find::wrap_around; - ng::ranges_t res; - citerate(pair, ng::find(document->buffer(), editor->ranges(), findStr, options, onlyInSelection ? editor->ranges() : ng::ranges_t())) - res.push_back(pair->first); + auto allMatches = ng::find(document->buffer(), editor->ranges(), findStr, options, onlyInSelection ? editor->ranges() : ng::ranges_t()); + ng::ranges_t res; + std::transform(allMatches.begin(), allMatches.end(), std::back_inserter(res), [](decltype(allMatches)::value_type const& p){ return p.first; }); if(onlyInSelection && res.sorted() == editor->ranges().sorted()) { res = ng::ranges_t(); - citerate(pair, ng::find(document->buffer(), editor->ranges(), findStr, options, ng::ranges_t())) - res.push_back(pair->first); + allMatches = ng::find(document->buffer(), editor->ranges(), findStr, options, ng::ranges_t()); + std::transform(allMatches.begin(), allMatches.end(), std::back_inserter(res), [](decltype(allMatches)::value_type const& p){ return p.first; }); } if(res.empty() && !isCounting && documents && [documents count] > 1) @@ -1633,21 +1652,30 @@ static void update_menu_key_equivalents (NSMenu* menu, action_to_key_t const& ac } else { - [self recordSelector:(options & find::all_matches) ? (aFindServer.findOperation == kFindOperationFind ? @selector(findAll:) : @selector(findAllInSelection:)) : ((options & find::backwards) ? @selector(findPrevious:) : @selector(findNext:)) withArgument:nil]; + [self recordSelector:(options & find::all_matches) ? (findOperation == kFindOperationFind ? @selector(findAll:) : @selector(findAllInSelection:)) : ((options & find::backwards) ? @selector(findPrevious:) : @selector(findNext:)) withArgument:nil]; std::set alreadySelected; citerate(range, editor->ranges()) alreadySelected.insert(*range); ng::ranges_t newSelection; - iterate(range, res) + for(auto range : res) { - if(alreadySelected.find(range->sorted()) == alreadySelected.end()) - newSelection.push_back(range->sorted()); + if(alreadySelected.find(range.sorted()) == alreadySelected.end()) + newSelection.push_back(range.sorted()); } if(!res.empty()) + { editor->set_selections(res); + if(res.size() == 1 && (options & find::regular_expression)) + { + NSMutableDictionary* captures = [NSMutableDictionary dictionary]; + for(auto pair : allMatches[res.last()]) + captures[[NSString stringWithCxxString:pair.first]] = [NSString stringWithCxxString:pair.second]; + [OakPasteboard pasteboardWithName:NSReplacePboard].auxiliaryOptionsForCurrent = captures; + } + } [self highlightRanges:newSelection]; [aFindServer didFind:newSelection.size() occurrencesOf:aFindServer.findString atPosition:res.size() == 1 ? document->buffer().convert(res.last().min().index) : text::pos_t::undefined]; @@ -1655,19 +1683,15 @@ static void update_menu_key_equivalents (NSMenu* menu, action_to_key_t const& ac } break; - case kFindOperationReplace: - case kFindOperationReplaceAndFind: - break; - case kFindOperationReplaceAll: case kFindOperationReplaceAllInSelection: { std::string const findStr = to_s(aFindServer.findString); std::string const replaceStr = to_s(aFindServer.replaceString); find::options_t options = aFindServer.findOptions; - [self recordSelector:(options & find::all_matches) ? (aFindServer.findOperation == kFindOperationReplaceAll ? @selector(replaceAll:) : @selector(replaceAllInSelection:)) : @selector(replace:) withArgument:nil]; + [self recordSelector:(options & find::all_matches) ? (findOperation == kFindOperationReplaceAll ? @selector(replaceAll:) : @selector(replaceAllInSelection:)) : @selector(replace:) withArgument:nil]; - ng::ranges_t const res = editor->replace_all(findStr, replaceStr, options, aFindServer.findOperation == kFindOperationReplaceAllInSelection); + ng::ranges_t const res = editor->replace_all(findStr, replaceStr, options, findOperation == kFindOperationReplaceAllInSelection); [aFindServer didReplace:res.size() occurrencesOf:aFindServer.findString with:aFindServer.replaceString]; } break;