From d61da37edacfa41137032adbcb6b32a41360d5a8 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 17 Jul 2014 18:08:54 +0400 Subject: [PATCH] IDEA-122076, IDEA-122172 Multicaret actions in quick find mode --- .../find/FindInEditorMultiCaretTest.java | 222 ++++++++++++++++++ .../intellij/find/EditorSearchComponent.java | 34 ++- .../src/com/intellij/find/FindUtil.java | 70 +++++- .../AddOccurrenceAction.java | 46 ++++ .../EditorHeaderAction.java | 21 +- .../NextOccurrenceAction.java | 17 +- .../PrevOccurrenceAction.java | 17 +- .../RemoveOccurrenceAction.java | 49 ++++ .../RestorePreviousSettingsAction.java | 5 +- .../editorHeaderActions/SelectAllAction.java | 53 +++++ .../find/impl/livePreview/LivePreview.java | 45 +--- .../livePreview/LivePreviewController.java | 19 +- .../find/impl/livePreview/SearchResults.java | 145 ++++++------ .../impl/livePreview/SelectionManager.java | 90 +++++++ .../actions/SelectAllOccurrencesAction.java | 53 +++-- .../actions/SelectNextOccurrenceAction.java | 27 +-- .../SelectOccurrencesActionHandler.java | 34 ++- .../UnselectPreviousOccurrenceAction.java | 10 + .../src/messages/ActionsBundle.properties | 2 +- 19 files changed, 782 insertions(+), 177 deletions(-) create mode 100644 java/java-tests/testSrc/com/intellij/find/FindInEditorMultiCaretTest.java create mode 100644 platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java create mode 100644 platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java create mode 100644 platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java create mode 100644 platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java diff --git a/java/java-tests/testSrc/com/intellij/find/FindInEditorMultiCaretTest.java b/java/java-tests/testSrc/com/intellij/find/FindInEditorMultiCaretTest.java new file mode 100644 index 000000000000..02f8c589c43b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/find/FindInEditorMultiCaretTest.java @@ -0,0 +1,222 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.find; + +import com.intellij.find.editorHeaderActions.*; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.util.Getter; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; + +import javax.swing.text.JTextComponent; +import java.io.IOException; + +public class FindInEditorMultiCaretTest extends LightPlatformCodeInsightFixtureTestCase { + public void testBasic() throws IOException { + init("abc\n" + + "abc\n" + + "abc"); + initFind(); + setTextToFind("b"); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + addOccurrence(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + nextOccurrence(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + prevOccurrence(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + removeOccurrence(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + allOccurrences(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + assertNull(getEditorSearchComponent()); + } + + public void testActionsWorkFromEditor() throws IOException { + init("abc\n" + + "abc\n" + + "abc"); + initFind(); + setTextToFind("b"); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + addOccurrenceFromEditor(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + nextOccurrenceFromEditor(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + prevOccurrenceFromEditor(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + removeOccurrenceFromEditor(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + allOccurrencesFromEditor(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + assertNull(getEditorSearchComponent()); + } + + public void testCloseRetainsMulticaretSelection() throws IOException { + init("abc\n" + + "abc\n" + + "abc"); + initFind(); + setTextToFind("b"); + addOccurrence(); + closeFind(); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + } + + public void testTextModificationRemovesOldSelections() throws IOException { + init("abc\n" + + "abc\n" + + "abc"); + initFind(); + setTextToFind("b"); + addOccurrence(); + setTextToFind("bc"); + + assertEquals(1, myFixture.getEditor().getCaretModel().getCaretCount()); + assertEquals("bc", myFixture.getEditor().getSelectionModel().getSelectedText()); + } + + public void testSecondFindNavigatesToTheSameOccurrence() throws IOException { + init("abc\n" + + "abc\n" + + "abc"); + initFind(); + setTextToFind("abc"); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + closeFind(); + initFind(); + setTextToFind("abc"); + checkResultByText("abc\n" + + "abc\n" + + "abc"); + } + + private void setTextToFind(String text) { + EditorSearchComponent editorSearchComponent = getEditorSearchComponent(); + assertNotNull(editorSearchComponent); + JTextComponent searchField = editorSearchComponent.getSearchField(); + assertNotNull(searchField); + for (int i = 0; i <= text.length(); i++) { + searchField.setText(text.substring(0, i)); // emulate typing chars one by one + } + } + + private void nextOccurrence() { + final EditorSearchComponent editorSearchComponent = getEditorSearchComponent(); + executeAction(new NextOccurrenceAction(editorSearchComponent, new Getter() { + @Override + public JTextComponent get() { + return editorSearchComponent.getSearchField(); + } + })); + } + + private void prevOccurrence() { + final EditorSearchComponent editorSearchComponent = getEditorSearchComponent(); + executeAction(new PrevOccurrenceAction(editorSearchComponent, new Getter() { + @Override + public JTextComponent get() { + return editorSearchComponent.getSearchField(); + } + })); + } + + private void addOccurrence() { + executeAction(new AddOccurrenceAction(getEditorSearchComponent())); + } + + private void removeOccurrence() { + executeAction(new RemoveOccurrenceAction(getEditorSearchComponent())); + } + + private void allOccurrences() { + executeAction(new SelectAllAction(getEditorSearchComponent())); + } + + private void nextOccurrenceFromEditor() { + myFixture.performEditorAction(IdeActions.ACTION_FIND_NEXT); + } + + private void prevOccurrenceFromEditor() { + myFixture.performEditorAction(IdeActions.ACTION_FIND_PREVIOUS); + } + + private void addOccurrenceFromEditor() { + myFixture.performEditorAction(IdeActions.ACTION_SELECT_NEXT_OCCURENCE); + } + + private void removeOccurrenceFromEditor() { + myFixture.performEditorAction(IdeActions.ACTION_UNSELECT_PREVIOUS_OCCURENCE); + } + + private void allOccurrencesFromEditor() { + myFixture.performEditorAction(IdeActions.ACTION_SELECT_ALL_OCCURRENCES); + } + + private void closeFind() { + EditorSearchComponent editorSearchComponent = getEditorSearchComponent(); + executeAction(new CloseOnESCAction(editorSearchComponent, editorSearchComponent.getSearchField())); + } + + private static void executeAction(EditorHeaderAction action) { + action.actionPerformed(AnActionEvent.createFromInputEvent(action, null, ActionPlaces.EDITOR_TOOLBAR)); + } + + private void initFind() { + myFixture.performEditorAction("Find"); + } + + private EditorSearchComponent getEditorSearchComponent() { + return (EditorSearchComponent)myFixture.getEditor().getHeaderComponent(); + } + + private void init(String text) { + myFixture.configureByText(getTestName(false) + ".txt", text); + } + + private void checkResultByText(String text) { + myFixture.checkResult(text); + } +} diff --git a/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java b/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java index 8c5864c6569b..8a36638d993c 100644 --- a/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java +++ b/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -68,11 +69,12 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data private final Project myProject; private ActionToolbar myActionsToolbar; - + @NotNull public Editor getEditor() { return myEditor; } + @NotNull private final Editor myEditor; public JTextComponent getSearchField() { @@ -173,7 +175,7 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data return findModel; } - public EditorSearchComponent(Editor editor, Project project) { + public EditorSearchComponent(@NotNull Editor editor, Project project) { this(editor, project, createDefaultFindModel(project, editor)); } @@ -225,7 +227,7 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data } @Override - public void cursorMoved(boolean toChangeSelection) { + public void cursorMoved() { updateExcludeStatus(); } @@ -233,10 +235,7 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data public void updateFinished() { } - @Override - public void editorChanged(SearchResults sr, Editor oldEditor) { } - - public EditorSearchComponent(final Editor editor, final Project project, FindModel findModel) { + public EditorSearchComponent(@NotNull final Editor editor, final Project project, FindModel findModel) { myFindModel = findModel; myProject = project; @@ -367,6 +366,9 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data actionGroup.add(new ShowHistoryAction(mySearchFieldGetter, this)); actionGroup.add(new PrevOccurrenceAction(this, mySearchFieldGetter)); actionGroup.add(new NextOccurrenceAction(this, mySearchFieldGetter)); + actionGroup.add(new AddOccurrenceAction(this)); + actionGroup.add(new RemoveOccurrenceAction(this)); + actionGroup.add(new SelectAllAction(this)); actionGroup.add(new FindAllAction(this)); actionGroup.add(new ToggleMultiline(this)); actionGroup.add(new ToggleMatchCase(this)); @@ -803,10 +805,6 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data } public void close() { - if (myEditor.getSelectionModel().hasSelection()) { - myEditor.getCaretModel().moveToOffset(myEditor.getSelectionModel().getSelectionStart()); - myEditor.getSelectionModel().removeSelection(); - } IdeFocusManager.getInstance(myProject).requestFocus(myEditor.getContentComponent(), false); myLivePreviewController.dispose(); @@ -937,6 +935,18 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data return insets; } + public void selectAllOccurrences() { + FindUtil.selectSearchResultsInEditor(myEditor, mySearchResults.getOccurrences().iterator(), -1); + } + + public void removeOccurrence() { + mySearchResults.prevOccurrence(true); + } + + public void addNextOccurrence() { + mySearchResults.nextOccurrence(true); + } + private static class MyUndoProvider extends TextComponentUndoProvider { private boolean myEnabled = true; public MyUndoProvider(JTextComponent textComponent) { diff --git a/platform/lang-impl/src/com/intellij/find/FindUtil.java b/platform/lang-impl/src/com/intellij/find/FindUtil.java index 7907d34c1228..edc906481398 100644 --- a/platform/lang-impl/src/com/intellij/find/FindUtil.java +++ b/platform/lang-impl/src/com/intellij/find/FindUtil.java @@ -31,6 +31,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.editor.actions.IncrementalFindAction; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; @@ -70,10 +71,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; public class FindUtil { private static final Key KEY = Key.create("FindUtil.KEY"); @@ -960,4 +958,68 @@ public class FindUtil { }); return view; } + + /** + * Creates a selection in editor per each search result. Existing carets and selections in editor are discarded. + * + * @param caretShiftFromSelectionStart if non-negative, defines caret position relative to selection start, for each created selection. + * if negative, carets will be positioned at selection ends + */ + public static void selectSearchResultsInEditor(@NotNull Editor editor, + @NotNull Iterator resultIterator, + int caretShiftFromSelectionStart) { + if (!editor.getCaretModel().supportsMultipleCarets()) { + return; + } + ArrayList caretStates = new ArrayList(); + while (resultIterator.hasNext()) { + FindResult findResult = resultIterator.next(); + int caretOffset = getCaretPosition(findResult, caretShiftFromSelectionStart); + int selectionStartOffset = findResult.getStartOffset(); + int selectionEndOffset = findResult.getEndOffset(); + EditorActionUtil.makePositionVisible(editor, caretOffset); + EditorActionUtil.makePositionVisible(editor, selectionStartOffset); + EditorActionUtil.makePositionVisible(editor, selectionEndOffset); + caretStates.add(new CaretState(editor.offsetToLogicalPosition(caretOffset), + editor.offsetToLogicalPosition(selectionStartOffset), + editor.offsetToLogicalPosition(selectionEndOffset))); + } + if (caretStates.isEmpty()) { + return; + } + editor.getCaretModel().setCaretsAndSelections(caretStates); + } + + /** + * Attempts to add a new caret to editor, with selection corresponding to given search result. + * + * @param caretShiftFromSelectionStart if non-negative, defines caret position relative to selection start, for each created selection. + * if negative, caret will be positioned at selection end + * @return true if caret was added successfully, false if it cannot be done, e.g. because a caret already + * exists at target position + */ + public static boolean selectSearchResultInEditor(@NotNull Editor editor, @NotNull FindResult result, int caretShiftFromSelectionStart) { + if (!editor.getCaretModel().supportsMultipleCarets()) { + return false; + } + int caretOffset = getCaretPosition(result, caretShiftFromSelectionStart); + EditorActionUtil.makePositionVisible(editor, caretOffset); + Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(caretOffset)); + if (newCaret == null) { + return false; + } + else { + int selectionStartOffset = result.getStartOffset(); + int selectionEndOffset = result.getEndOffset(); + EditorActionUtil.makePositionVisible(editor, selectionStartOffset); + EditorActionUtil.makePositionVisible(editor, selectionEndOffset); + newCaret.setSelection(selectionStartOffset, selectionEndOffset); + return true; + } + } + + private static int getCaretPosition(FindResult findResult, int caretShiftFromSelectionStart) { + return caretShiftFromSelectionStart < 0 + ? findResult.getEndOffset() : Math.min(findResult.getStartOffset() + caretShiftFromSelectionStart, findResult.getEndOffset()); + } } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java new file mode 100644 index 000000000000..9064113d9c9b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.find.editorHeaderActions; + +import com.intellij.find.EditorSearchComponent; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; + +import java.util.Arrays; + +public class AddOccurrenceAction extends EditorHeaderAction implements DumbAware { + public AddOccurrenceAction(EditorSearchComponent editorSearchComponent) { + super(editorSearchComponent); + + copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_SELECT_NEXT_OCCURENCE)); + getTemplatePresentation().setIcon(AllIcons.General.Add); + + registerShortcutsForComponent(Arrays.asList(getShortcutSet().getShortcuts()), editorSearchComponent.getSearchField()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + getEditorSearchComponent().addNextOccurrence(); + } + + @Override + public void update(AnActionEvent e) { + boolean isFind = !getEditorSearchComponent().getFindModel().isReplaceState(); + boolean hasMatches = getEditorSearchComponent().hasMatches(); + e.getPresentation().setVisible(isFind); + e.getPresentation().setEnabled(isFind && hasMatches); + }} diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java index 6cdba4b1e6eb..15348730a41d 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java @@ -1,21 +1,34 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.find.editorHeaderActions; import com.intellij.find.EditorSearchComponent; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.CustomShortcutSet; -import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.actionSystem.Shortcut; import javax.swing.*; -import java.util.ArrayList; import java.util.List; public abstract class EditorHeaderAction extends AnAction { private final EditorSearchComponent myEditorSearchComponent; - protected static void registerShortcutsForComponent(List shortcuts, JComponent component, AnAction a) { - a.registerCustomShortcutSet( + protected void registerShortcutsForComponent(List shortcuts, JComponent component) { + registerCustomShortcutSet( new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])), component); } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java index 1eb933cf2d03..0e6e67168521 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.find.editorHeaderActions; import com.intellij.find.EditorSearchComponent; @@ -35,7 +50,7 @@ public class NextOccurrenceAction extends EditorHeaderAction implements DumbAwar shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), null)); } - registerShortcutsForComponent(shortcuts, editorTextField.get(), this); + registerShortcutsForComponent(shortcuts, editorTextField.get()); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java index f5a2d3911b43..586f90a835d1 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.find.editorHeaderActions; import com.intellij.find.EditorSearchComponent; @@ -34,7 +49,7 @@ public class PrevOccurrenceAction extends EditorHeaderAction implements DumbAwar shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK), null)); } - registerShortcutsForComponent(shortcuts, editorTextField.get(), this); + registerShortcutsForComponent(shortcuts, editorTextField.get()); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java new file mode 100644 index 000000000000..b739f942991d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.find.editorHeaderActions; + +import com.intellij.find.EditorSearchComponent; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.project.DumbAware; + +import java.util.Arrays; + +public class RemoveOccurrenceAction extends EditorHeaderAction implements DumbAware { + public RemoveOccurrenceAction(EditorSearchComponent editorSearchComponent) { + super(editorSearchComponent); + + copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_UNSELECT_PREVIOUS_OCCURENCE)); + getTemplatePresentation().setIcon(AllIcons.General.Remove); + + registerShortcutsForComponent(Arrays.asList(getShortcutSet().getShortcuts()), editorSearchComponent.getSearchField()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + getEditorSearchComponent().removeOccurrence(); + } + + @Override + public void update(AnActionEvent e) { + boolean isFind = !getEditorSearchComponent().getFindModel().isReplaceState(); + boolean hasMatches = getEditorSearchComponent().hasMatches(); + e.getPresentation().setVisible(isFind); + e.getPresentation().setEnabled(isFind && hasMatches); + } +} diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java index cad2be2ddc20..2607ebf0557b 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,8 +39,7 @@ public class RestorePreviousSettingsAction extends EditorHeaderAction implements public RestorePreviousSettingsAction(EditorSearchComponent editorSearchComponent, JTextComponent textField) { super(editorSearchComponent); myTextField = textField; - registerShortcutsForComponent(Collections.singletonList(SHORTCUT), - textField, this); + registerShortcutsForComponent(Collections.singletonList(SHORTCUT), textField); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java new file mode 100644 index 000000000000..0e52566f6fbc --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.find.editorHeaderActions; + +import com.intellij.find.EditorSearchComponent; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; +import com.intellij.util.containers.ContainerUtil; + +import java.util.ArrayList; +import java.util.List; + +public class SelectAllAction extends EditorHeaderAction implements DumbAware { + public SelectAllAction(EditorSearchComponent editorSearchComponent) { + super(editorSearchComponent); + + copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_SELECT_ALL_OCCURRENCES)); + getTemplatePresentation().setIcon(AllIcons.Actions.Selectall); + + List shortcuts = new ArrayList(); + ContainerUtil.addAll(shortcuts, getShortcutSet().getShortcuts()); + ContainerUtil.addAll(shortcuts, CommonShortcuts.ALT_ENTER.getShortcuts()); + registerShortcutsForComponent(shortcuts, editorSearchComponent.getSearchField()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + getEditorSearchComponent().selectAllOccurrences(); + getEditorSearchComponent().close(); + } + + @Override + public void update(AnActionEvent e) { + boolean isFind = !getEditorSearchComponent().getFindModel().isReplaceState(); + boolean hasMatches = getEditorSearchComponent().hasMatches(); + e.getPresentation().setVisible(isFind); + e.getPresentation().setEnabled(isFind && hasMatches); + } +} diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java index c20b5925721d..07cdde4fb256 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -121,7 +121,7 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search } highlightUsages(); - updateCursorHighlighting(false); + updateCursorHighlighting(); if (myInSmartUpdate) { clearUnusedHightlighters(); myInSmartUpdate = false; @@ -218,9 +218,9 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search } @Override - public void cursorMoved(boolean toChangeSelection) { + public void cursorMoved() { updateInSelectionHighlighters(); - updateCursorHighlighting(toChangeSelection); + updateCursorHighlighting(); } @Override @@ -228,14 +228,7 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search dumpState(); } - @Override - public void editorChanged(SearchResults sr, Editor oldEditor) { - removeFromEditor(); - oldEditor.getDocument().removeDocumentListener(this); - mySearchResults.getEditor().getDocument().addDocumentListener(this); - } - - private void updateCursorHighlighting(boolean scroll) { + private void updateCursorHighlighting() { hideBalloon(); if (myCursorHighlighter != null) { @@ -245,7 +238,6 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search final FindResult cursor = mySearchResults.getCursor(); Editor editor = mySearchResults.getEditor(); - SelectionModel selection = editor.getSelectionModel(); if (cursor != null) { Set dummy = new HashSet(); highlightRange(cursor, new TextAttributes(null, null, Color.BLACK, EffectType.ROUNDED_BOX, 0), dummy); @@ -253,33 +245,6 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search myCursorHighlighter = dummy.iterator().next(); } - if (scroll) { - if (mySearchResults.getFindModel().isGlobal()) { - FoldingModel foldingModel = editor.getFoldingModel(); - final FoldRegion[] allRegions = editor.getFoldingModel().getAllFoldRegions(); - - foldingModel.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - for (FoldRegion region : allRegions) { - if (!region.isValid()) continue; - if (cursor.intersects(TextRange.create(region))) { - region.setExpanded(true); - } - } - } - }); - selection.setSelection(cursor.getStartOffset(), cursor.getEndOffset()); - - editor.getCaretModel().moveToOffset(cursor.getEndOffset()); - editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); - } else { - if (!SearchResults.insideVisibleArea(editor, cursor)) { - LogicalPosition pos = editor.offsetToLogicalPosition(cursor.getStartOffset()); - editor.getScrollingModel().scrollTo(pos, ScrollType.CENTER); - } - } - } editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() { @Override public void run() { diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java index 8368f1b2f2aa..94a6c3f9c1bc 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.find.impl.livePreview; import com.intellij.find.*; @@ -83,9 +98,9 @@ public class LivePreviewController implements LivePreview.Delegate, FindUtil.Rep public void moveCursor(SearchResults.Direction direction) { if (direction == SearchResults.Direction.UP) { - mySearchResults.prevOccurrence(); + mySearchResults.prevOccurrence(false); } else { - mySearchResults.nextOccurrence(); + mySearchResults.nextOccurrence(false); } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java index dff4989bb2f6..39b17dd2acc8 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java @@ -21,9 +21,7 @@ import com.intellij.find.FindModel; import com.intellij.find.FindResult; import com.intellij.find.FindUtil; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -38,6 +36,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.Stack; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -70,6 +69,7 @@ public class SearchResults implements DocumentListener { private @Nullable FindResult myCursor; + @NotNull private List myOccurrences = new ArrayList(); private final Set myExcluded = new HashSet(); @@ -90,6 +90,8 @@ public class SearchResults implements DocumentListener { private final Stack> myCursorPositions = new Stack>(); + private final SelectionManager mySelectionManager = new SelectionManager(this); + public SearchResults(Editor editor, Project project) { myEditor = editor; myProject = project; @@ -127,9 +129,8 @@ public class SearchResults implements DocumentListener { public void exclude(FindResult occurrence) { boolean include = false; - final TextRange r = occurrence; for (RangeMarker rangeMarker : myExcluded) { - if (TextRange.areSegmentsEqual(rangeMarker, r)) { + if (TextRange.areSegmentsEqual(rangeMarker, occurrence)) { myExcluded.remove(rangeMarker); rangeMarker.dispose(); include = true; @@ -137,7 +138,7 @@ public class SearchResults implements DocumentListener { } } if (!include) { - myExcluded.add(myEditor.getDocument().createRangeMarker(r.getStartOffset(), r.getEndOffset(), true)); + myExcluded.add(myEditor.getDocument().createRangeMarker(occurrence.getStartOffset(), occurrence.getEndOffset(), true)); } notifyChanged(); } @@ -149,8 +150,7 @@ public class SearchResults implements DocumentListener { public interface SearchResultsListener { void searchResultsUpdated(SearchResults sr); - void editorChanged(SearchResults sr, Editor oldEditor); - void cursorMoved(boolean toChangeSelection); + void cursorMoved(); void updateFinished(); } @@ -175,6 +175,7 @@ public class SearchResults implements DocumentListener { return myCursor; } + @NotNull public List getOccurrences() { return myOccurrences; } @@ -184,19 +185,7 @@ public class SearchResults implements DocumentListener { return myProject; } - public synchronized void setEditor(Editor editor) { - Editor oldOne = myEditor; - myEditor = editor; - notifyEditorChanged(oldOne); - } - - private void notifyEditorChanged(Editor oldOne) { - for (SearchResultsListener listener : myListeners) { - listener.editorChanged(this, oldOne); - } - } - - public synchronized Editor getEditor() { + public Editor getEditor() { return myEditor; } @@ -334,7 +323,7 @@ public class SearchResults implements DocumentListener { myEditor.getDocument().removeDocumentListener(this); } - private void searchCompleted(List occurrences, Editor editor, @Nullable FindModel findModel, + private void searchCompleted(@NotNull List occurrences, Editor editor, @Nullable FindModel findModel, boolean toChangeSelection, @Nullable TextRange next, int stamp) { if (stamp < myLastUpdatedStamp){ return; @@ -344,7 +333,7 @@ public class SearchResults implements DocumentListener { return; } myOccurrences = occurrences; - final TextRange oldCursorRange = myCursor != null ? myCursor : null; + final TextRange oldCursorRange = myCursor; Collections.sort(myOccurrences, new Comparator() { @Override public int compare(FindResult findResult, FindResult findResult1) { @@ -357,7 +346,10 @@ public class SearchResults implements DocumentListener { updateExcluded(); notifyChanged(); if (oldCursorRange == null || myCursor == null || !myCursor.equals(oldCursorRange)) { - notifyCursorMoved(toChangeSelection); + if (toChangeSelection) { + mySelectionManager.updateSelection(true, true); + } + notifyCursorMoved(); } dumpIfNeeded(); } @@ -389,7 +381,7 @@ public class SearchResults implements DocumentListener { myCursor = firstOccurrenceAfterOffset(oldCursorRange.getEndOffset()); } else { if (justReplaced) { - nextOccurrence(false, next, false, justReplaced); + nextOccurrence(false, next, false, true, false); } else { FindResult afterCaret = oldCursorRange == null ? firstOccurrenceAtOrAfterCaret() : firstOccurrenceAfterCaret(); if (afterCaret != null) { @@ -405,7 +397,7 @@ public class SearchResults implements DocumentListener { } } if (!justReplaced && myCursor == null && hasMatches()) { - nextOccurrence(true, oldCursorRange, false, false); + nextOccurrence(true, oldCursorRange, false, false, false); } if (toPush && myCursor != null){ push(); @@ -445,6 +437,13 @@ public class SearchResults implements DocumentListener { return occurrence; } } + int selectionStartOffset = getEditor().getSelectionModel().getSelectionStart(); + int selectionEndOffset = getEditor().getSelectionModel().getSelectionEnd(); + for (FindResult occurrence : myOccurrences) { + if (selectionEndOffset >= occurrence.getEndOffset() && selectionStartOffset <= occurrence.getStartOffset()) { + return occurrence; + } + } return firstOccurrenceAfterCaret(); } @@ -461,25 +460,6 @@ public class SearchResults implements DocumentListener { return visibleArea.contains(point); } - @Nullable - private FindResult firstVisibleOccurrence() { - int offset = Integer.MAX_VALUE; - FindResult firstOccurrence = null; - FindResult firstVisibleOccurrence = null; - for (FindResult o : getOccurrences()) { - if (insideVisibleArea(myEditor, o)) { - if (firstVisibleOccurrence == null || o.getStartOffset() < firstVisibleOccurrence.getStartOffset()) { - firstVisibleOccurrence = o; - } - } - if (o.getStartOffset() < offset) { - offset = o.getStartOffset(); - firstOccurrence = o; - } - } - return firstVisibleOccurrence != null ? firstVisibleOccurrence : firstOccurrence; - } - @Nullable private FindResult firstOccurrenceBeforeCaret() { int offset = getEditor().getCaretModel().getOffset(); @@ -554,32 +534,45 @@ public class SearchResults implements DocumentListener { return null; } - public void prevOccurrence() { - FindResult next = null; - if (myFindModel == null) return; - boolean processFromTheBeginning = false; - if (myNotFoundState) { - myNotFoundState = false; - processFromTheBeginning = true; - } - if (!myFindModel.isGlobal()) { - if (myCursor != null) { - next = prevOccurrence(myCursor); + public void prevOccurrence(boolean findSelected) { + if (findSelected) { + if (mySelectionManager.removeCurrentSelection()) { + myCursor = firstOccurrenceAtOrAfterCaret(); } - } else { - next = firstOccurrenceBeforeCaret(); + else { + myCursor = null; + } + notifyCursorMoved(); } - if (next == null) { - if (processFromTheBeginning) { - if (hasMatches()) { - next = getOccurrences().get(getOccurrences().size()-1); + else { + FindResult next = null; + if (myFindModel == null) return; + boolean processFromTheBeginning = false; + if (myNotFoundState) { + myNotFoundState = false; + processFromTheBeginning = true; + } + if (!myFindModel.isGlobal()) { + if (myCursor != null) { + next = prevOccurrence(myCursor); + } + } + else { + next = firstOccurrenceBeforeCaret(); + } + if (next == null) { + if (processFromTheBeginning) { + if (hasMatches()) { + next = getOccurrences().get(getOccurrences().size() - 1); + } + } + else { + setNotFoundState(false); } - } else { - setNotFoundState(false); } - } - moveCursorTo(next); + moveCursorTo(next, false); + } push(); } @@ -587,13 +580,13 @@ public class SearchResults implements DocumentListener { myCursorPositions.push(Pair.create(myFindModel, myCursor)); } - public void nextOccurrence() { + public void nextOccurrence(boolean retainOldSelection) { if (myFindModel == null) return; - nextOccurrence(false, myCursor != null ? myCursor : null, true, false); + nextOccurrence(false, myCursor, true, false, retainOldSelection); push(); } - private void nextOccurrence(boolean processFromTheBeginning, TextRange cursor, boolean toNotify, boolean justReplaced) { + private void nextOccurrence(boolean processFromTheBeginning, TextRange cursor, boolean toNotify, boolean justReplaced, boolean retainOldSelection) { FindResult next; if (myNotFoundState) { myNotFoundState = false; @@ -614,22 +607,24 @@ public class SearchResults implements DocumentListener { } } if (toNotify) { - moveCursorTo(next); + moveCursorTo(next, retainOldSelection); } else { myCursor = next; } } - public void moveCursorTo(FindResult next) { - if (next != null) { + public void moveCursorTo(FindResult next, boolean retainOldSelection) { + if (next != null && !mySelectionManager.isSelected(next)) { + retainOldSelection &= (myCursor != null && mySelectionManager.isSelected(myCursor)); myCursor = next; - notifyCursorMoved(true); + mySelectionManager.updateSelection(!retainOldSelection, false); + notifyCursorMoved(); } } - private void notifyCursorMoved(boolean toChangeSelection) { + private void notifyCursorMoved() { for (SearchResultsListener listener : myListeners) { - listener.cursorMoved(toChangeSelection); + listener.cursorMoved(); } } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java new file mode 100644 index 000000000000..50f37927c506 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.find.impl.livePreview; + +import com.intellij.find.FindResult; +import com.intellij.find.FindUtil; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; + +public class SelectionManager { + @NotNull private final SearchResults mySearchResults; + + public SelectionManager(@NotNull SearchResults results) { + mySearchResults = results; + } + + public void updateSelection(boolean removePreviousSelection, boolean removeAllPreviousSelections) { + Editor editor = mySearchResults.getEditor(); + if (removeAllPreviousSelections) { + editor.getCaretModel().removeSecondaryCarets(); + } + final FindResult cursor = mySearchResults.getCursor(); + if (cursor == null) { + return; + } + if (mySearchResults.getFindModel().isGlobal()) { + if (removePreviousSelection || removeAllPreviousSelections) { + FoldingModel foldingModel = editor.getFoldingModel(); + final FoldRegion[] allRegions = editor.getFoldingModel().getAllFoldRegions(); + + foldingModel.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + for (FoldRegion region : allRegions) { + if (!region.isValid()) continue; + if (cursor.intersects(TextRange.create(region))) { + region.setExpanded(true); + } + } + } + }); + editor.getSelectionModel().setSelection(cursor.getStartOffset(), cursor.getEndOffset()); + editor.getCaretModel().moveToOffset(cursor.getEndOffset()); + } + else { + FindUtil.selectSearchResultInEditor(editor, cursor, -1); + } + editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); + } else { + if (!SearchResults.insideVisibleArea(editor, cursor)) { + LogicalPosition pos = editor.offsetToLogicalPosition(cursor.getStartOffset()); + editor.getScrollingModel().scrollTo(pos, ScrollType.CENTER); + } + } + } + + public boolean removeCurrentSelection() { + Editor editor = mySearchResults.getEditor(); + CaretModel caretModel = editor.getCaretModel(); + Caret primaryCaret = caretModel.getPrimaryCaret(); + if (caretModel.getCaretCount() > 1) { + caretModel.removeCaret(primaryCaret); + return true; + } + else { + primaryCaret.moveToOffset(primaryCaret.getSelectionStart()); + primaryCaret.removeSelection(); + return false; + } + } + + public boolean isSelected(@NotNull FindResult result) { + Editor editor = mySearchResults.getEditor(); + return editor.getCaretModel().getCaretAt(editor.offsetToVisualPosition(result.getEndOffset())) != null; + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java index d135b528f5e2..3e284ff6b2cc 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java @@ -15,9 +15,9 @@ */ package com.intellij.openapi.editor.actions; -import com.intellij.find.FindManager; -import com.intellij.find.FindModel; -import com.intellij.find.FindResult; +import com.intellij.find.*; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; +import com.intellij.find.editorHeaderActions.SelectAllAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; @@ -27,6 +27,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.Nullable; +import java.util.Iterator; + public class SelectAllOccurrencesAction extends EditorAction { protected SelectAllOccurrencesAction() { super(new Handler()); @@ -39,7 +41,9 @@ public class SelectAllOccurrencesAction extends EditorAction { } @Override - public void doExecute(Editor editor, @Nullable Caret c, DataContext dataContext) { + public void doExecute(final Editor editor, @Nullable Caret c, DataContext dataContext) { + if (executeEquivalentFindPanelAction(editor, dataContext)) return; + Caret caret = c == null ? editor.getCaretModel().getPrimaryCaret() : c; boolean wholeWordsSearch = false; @@ -58,25 +62,36 @@ public class SelectAllOccurrencesAction extends EditorAction { } int caretShiftFromSelectionStart = caret.getOffset() - caret.getSelectionStart(); - FindManager findManager = FindManager.getInstance(project); + final FindManager findManager = FindManager.getInstance(project); - FindModel model = new FindModel(); - model.setStringToFind(selectedText); - model.setCaseSensitive(true); - model.setWholeWordsOnly(wholeWordsSearch); + final FindModel model = getFindModel(selectedText, wholeWordsSearch); - int searchStartOffset = 0; - FindResult findResult = findManager.findString(editor.getDocument().getCharsSequence(), searchStartOffset, model); - while (findResult.isStringFound()) { - int newCaretOffset = caretShiftFromSelectionStart + findResult.getStartOffset(); - EditorActionUtil.makePositionVisible(editor, newCaretOffset); - Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(newCaretOffset)); - if (newCaret != null) { - setSelection(editor, newCaret, findResult); + FindUtil.selectSearchResultsInEditor(editor, new Iterator() { + FindResult findResult = findManager.findString(editor.getDocument().getCharsSequence(), 0, model); + + @Override + public boolean hasNext() { + return findResult.isStringFound(); } - findResult = findManager.findString(editor.getDocument().getCharsSequence(), findResult.getEndOffset(), model); - } + + @Override + public FindResult next() { + FindResult result = findResult; + findResult = findManager.findString(editor.getDocument().getCharsSequence(), findResult.getEndOffset(), model); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }, caretShiftFromSelectionStart); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } + + @Override + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return new SelectAllAction(searchComponent); + } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java index d4767b8f1274..7d3c408ee152 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java @@ -15,9 +15,9 @@ */ package com.intellij.openapi.editor.actions; -import com.intellij.find.FindManager; -import com.intellij.find.FindModel; -import com.intellij.find.FindResult; +import com.intellij.find.*; +import com.intellij.find.editorHeaderActions.AddOccurrenceAction; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; @@ -40,6 +40,8 @@ public class SelectNextOccurrenceAction extends EditorAction { @Override public void doExecute(Editor editor, @Nullable Caret c, DataContext dataContext) { + if (executeEquivalentFindPanelAction(editor, dataContext)) return; + Caret caret = c == null ? editor.getCaretModel().getPrimaryCaret() : c; TextRange wordSelectionRange = getSelectionRange(editor, caret); boolean notFoundPreviously = getAndResetNotFoundStatus(editor); @@ -52,10 +54,7 @@ public class SelectNextOccurrenceAction extends EditorAction { } FindManager findManager = FindManager.getInstance(project); - FindModel model = new FindModel(); - model.setStringToFind(selectedText); - model.setCaseSensitive(true); - model.setWholeWordsOnly(wholeWordSearch); + FindModel model = getFindModel(selectedText, wholeWordSearch); findManager.setFindWasPerformed(); findManager.setFindNextModel(model); @@ -63,18 +62,13 @@ public class SelectNextOccurrenceAction extends EditorAction { int searchStartOffset = notFoundPreviously ? 0 : caret.getSelectionEnd(); FindResult findResult = findManager.findString(editor.getDocument().getCharsSequence(), searchStartOffset, model); if (findResult.isStringFound()) { - int newCaretOffset = caret.getOffset() - caret.getSelectionStart() + findResult.getStartOffset(); - EditorActionUtil.makePositionVisible(editor, newCaretOffset); - Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(newCaretOffset)); - if (newCaret == null) { + boolean caretAdded = FindUtil.selectSearchResultInEditor(editor, findResult, caret.getOffset() - caret.getSelectionStart()); + if (!caretAdded) { // this means that the found occurence is already selected if (notFoundPreviously) { setNotFoundStatus(editor); // to make sure we won't show hint anymore if there are no more occurrences } } - else { - setSelection(editor, newCaret, findResult); - } } else { setNotFoundStatus(editor); @@ -90,5 +84,10 @@ public class SelectNextOccurrenceAction extends EditorAction { } editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } + + @Override + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return new AddOccurrenceAction(searchComponent); + } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java index 477dc9ed9e56..7b189281dfd0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java @@ -19,8 +19,11 @@ import com.intellij.codeInsight.editorActions.SelectWordUtil; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.hint.HintManagerImpl; import com.intellij.codeInsight.hint.HintUtil; +import com.intellij.find.EditorSearchComponent; import com.intellij.find.FindBundle; -import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.find.FindModel; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorLastActionTracker; @@ -96,4 +99,33 @@ abstract public class SelectOccurrencesActionHandler extends EditorActionHandler String lastActionId = EditorLastActionTracker.getInstance().getLastActionId(); return SELECT_ACTIONS.contains(lastActionId); } + + protected static FindModel getFindModel(String text, boolean wholeWords) { + FindModel model = new FindModel(); + model.setStringToFind(text); + model.setCaseSensitive(true); + model.setWholeWordsOnly(wholeWords); + return model; + } + + protected boolean executeEquivalentFindPanelAction(Editor editor, DataContext context) { + if (editor.getHeaderComponent() instanceof EditorSearchComponent) { + EditorSearchComponent searchComponent = (EditorSearchComponent)editor.getHeaderComponent(); + EditorHeaderAction action = getEquivalentFindPanelAction(searchComponent); + if (action != null) { + Presentation presentation = new Presentation(); + AnActionEvent event = new AnActionEvent(null, context, ActionPlaces.MAIN_MENU, presentation, ActionManager.getInstance(), 0); + action.update(event); + if (presentation.isEnabled()) { + action.actionPerformed(event); + return true; + } + } + } + return false; + } + + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return null; + } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java index a5657ff89b27..fffff56b12dc 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java @@ -15,6 +15,9 @@ */ package com.intellij.openapi.editor.actions; +import com.intellij.find.EditorSearchComponent; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; +import com.intellij.find.editorHeaderActions.RemoveOccurrenceAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; @@ -35,6 +38,8 @@ public class UnselectPreviousOccurrenceAction extends EditorAction { @Override public void doExecute(Editor editor, @Nullable Caret caret, DataContext dataContext) { + if (executeEquivalentFindPanelAction(editor, dataContext)) return; + if (editor.getCaretModel().getCaretCount() > 1) { editor.getCaretModel().removeCaret(editor.getCaretModel().getPrimaryCaret()); } @@ -44,5 +49,10 @@ public class UnselectPreviousOccurrenceAction extends EditorAction { getAndResetNotFoundStatus(editor); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } + + @Override + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return new RemoveOccurrenceAction(searchComponent); + } } } diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 710c29e77522..1722b46d655a 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -110,7 +110,7 @@ action.EditorMoveDownAndScrollWithSelection.text=Move Down and Scroll with Selec action.EditorAddOrRemoveCaret.text=Add or Remove Caret action.EditorCloneCaretBelow.text=Clone Caret Below action.EditorCloneCaretAbove.text=Clone Caret Above -action.SelectNextOccurrence.text=Select Next Occurrence +action.SelectNextOccurrence.text=Add Selection for Next Occurrence action.SelectAllOccurrences.text=Select All Occurrences action.UnselectPreviousOccurrence.text=Unselect Occurrence action.EditorToggleStickySelection.text=Toggle Sticky Selection