diff --git a/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties b/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties index 309a98d7429..c71ab7f0fb9 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties +++ b/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties @@ -206,6 +206,21 @@ command.joinLines.name = Join Lines command.openHyperlink.name= Open Hyperlink command.openHyperlink.description= Opens the hyperlink at the caret location or opens a chooser if more than one hyperlink is available +command.findReplaceOverlay.searchForward.name= Find and Replace: Search Forward +command.findReplaceOverlay.searchBackward.name= Find and Replace: Search Backward +command.findReplaceOverlay.selectAll.name= Find and Replace: Select All Matches +command.findReplaceOverlay.replaceOne.name= Find and Replace: Replace +command.findReplaceOverlay.replaceAll.name= Find and Replace: Replace All +command.findReplaceOverlay.close.name= Find and Replace: Close +command.findReplaceOverlay.toggleReplace.name= Find and Replace: Toggle Replace Bar +command.findReplaceOverlay.toggleCaseSensitive.name= Find and Replace: Toggle Case Sensitive +command.findReplaceOverlay.toggleWholeWord.name= Find and Replace: Toggle Whole Word +command.findReplaceOverlay.toggleRegex.name= Find and Replace: Toggle Regular Expression +command.findReplaceOverlay.toggleSearchInSelection.name= Find and Replace: Toggle Search in Selection +context.findReplaceOverlay.name= Find and replace +context.findReplaceOverlay.searchFocused.name= Find and replace (search input focused) +context.findReplaceOverlay.replaceFocused.name= Find and replace (replace input focused) + Color.rangeIndicator= Range indicator color Color.rangeIndicatorDesc= The color that is used for the ruler that marks the range of the current selected text block. diff --git a/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml b/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml index aaf9928b511..47d2f1a1a34 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml +++ b/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml @@ -507,6 +507,61 @@ description="%command.openHyperlink.description" categoryId="org.eclipse.ui.category.textEditor" id="org.eclipse.ui.edit.text.open.hyperlink"> + + + + + + + + + + + + + + + + + + + + + + @@ -1500,4 +1555,82 @@ label="%CodeMining.show.ZWSP"> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java index 4eebe0a448a..30a7a7adb1b 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java @@ -13,7 +13,6 @@ *******************************************************************************/ package org.eclipse.ui.internal.findandreplace.overlay; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.osgi.framework.FrameworkUtil; @@ -43,7 +42,6 @@ import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; -import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.fieldassist.ControlDecoration; @@ -57,6 +55,7 @@ import org.eclipse.jface.text.IFindReplaceTarget; import org.eclipse.jface.text.ITextViewer; +import org.eclipse.ui.IWorkbenchCommandConstants; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; @@ -73,29 +72,10 @@ import org.eclipse.ui.texteditor.StatusTextEditor; public class FindReplaceOverlay { - private static final class KeyboardShortcuts { - private static final List SEARCH_FORWARD = List.of( // - KeyStroke.getInstance(SWT.CR), KeyStroke.getInstance(SWT.KEYPAD_CR)); - private static final List SEARCH_BACKWARD = List.of( // - KeyStroke.getInstance(SWT.SHIFT, SWT.CR), KeyStroke.getInstance(SWT.SHIFT, SWT.KEYPAD_CR)); - private static final List SEARCH_ALL = List.of( // - KeyStroke.getInstance(SWT.MOD1, SWT.CR), KeyStroke.getInstance(SWT.MOD1, SWT.KEYPAD_CR)); - private static final List OPTION_CASE_SENSITIVE = List.of( // - KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'C'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'c')); - private static final List OPTION_WHOLE_WORD = List.of( // - KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'D'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'd')); - private static final List OPTION_REGEX = List.of( // - KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'P'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'p')); - private static final List OPTION_SEARCH_IN_SELECTION = List.of( // - KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'I'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'i')); - private static final List CLOSE = List.of( // - KeyStroke.getInstance(SWT.ESC), KeyStroke.getInstance(SWT.MOD1, 'F'), - KeyStroke.getInstance(SWT.MOD1, 'f')); - private static final List TOGGLE_REPLACE = List.of( // - KeyStroke.getInstance(SWT.MOD1, 'R'), KeyStroke.getInstance(SWT.MOD1, 'r')); - } public static final String ID_DATA_KEY = "org.eclipse.ui.internal.findreplace.overlay.FindReplaceOverlay.id"; //$NON-NLS-1$ + private static final String SEARCH_BAR_FOCUS_ID = "org.eclipse.ui.internal.findandreplace.overlay.searchBar"; //$NON-NLS-1$ + private static final String REPLACE_BAR_FOCUS_ID = "org.eclipse.ui.internal.findandreplace.overlay.replaceBar"; //$NON-NLS-1$ private static final String REPLACE_BAR_OPEN_DIALOG_SETTING = "replaceBarOpen"; //$NON-NLS-1$ private static final double WORST_CASE_RATIO_EDITOR_TO_OVERLAY = 0.95; @@ -133,7 +113,7 @@ private static final class KeyboardShortcuts { private ControlDecoration searchBarDecoration; private ContentAssistCommandAdapter contentAssistSearchField, contentAssistReplaceField; - private FindReplaceOverlayCommandSupport commandSupport; + private final FindReplaceOverlayCommandSupport commandSupport; private final FocusListener targetActionActivationHandling = new FocusListener() { @Override @@ -232,6 +212,7 @@ public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget targetControl = getTargetControl(parent, part); findReplaceLogic = createFindReplaceLogic(target); createContainerAndSearchControls(targetControl); + commandSupport.setContainerControl(containerControl); containerControl.setVisible(false); PlatformUI.getWorkbench().getHelpSystem().setHelp(containerControl, IAbstractTextEditorHelpContextIds.FIND_REPLACE_OVERLAY); @@ -377,16 +358,9 @@ private void createContainerAndSearchControls(Composite parent) { } retrieveColors(); createMainContainer(parent); - initializeSearchShortcutHandlers(); - containerControl.layout(); } - private void initializeSearchShortcutHandlers() { - commandSupport.registerCommonActionShortcutsAtControl(searchBar); - commandSupport.registerSearchActionShortcutsAtControl(searchBar); - } - /** * HACK: In order to not introduce a hard-coded color, we need to retrieve the * background color of text widgets and composite to color those widgets that @@ -458,10 +432,9 @@ private void createReplaceToggle() { .applyTo(replaceToggleTools); replaceToggleTools.addMouseListener(MouseListener.mouseDownAdapter(__ -> setReplaceVisible(!replaceBarOpen))); - FindReplaceOverlayAction replaceToggleAction = new FindReplaceOverlayAction(() -> setReplaceVisible(!replaceBarOpen)); - replaceToggleAction.addShortcuts(KeyboardShortcuts.TOGGLE_REPLACE); + FindReplaceOverlayAction replaceToggleAction = new FindReplaceOverlayAction( + () -> setReplaceVisible(!replaceBarOpen), FindReplaceOverlayCommandSupport.CMD_TOGGLE_REPLACE); commandSupport.registerCommonAction(replaceToggleAction); - replaceToggle = new AccessibleToolItemBuilder(replaceToggleTools) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_OPEN_REPLACE_AREA)) .withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceToggle_toolTip) @@ -476,6 +449,7 @@ private void createContentsContainer() { GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(contentGroup); createSearchContainer(); + commandSupport.trackFocusControl(searchBar.getTextBar(), SEARCH_BAR_FOCUS_ID); } private void createSearchTools() { @@ -491,8 +465,8 @@ private void createSearchTools() { searchTools.createToolItem(SWT.SEPARATOR); - FindReplaceOverlayAction searchBackwardAction = new FindReplaceOverlayAction(() -> performSearch(false)); - searchBackwardAction.addShortcuts(KeyboardShortcuts.SEARCH_BACKWARD); + FindReplaceOverlayAction searchBackwardAction = new FindReplaceOverlayAction(() -> performSearch(false), + FindReplaceOverlayCommandSupport.CMD_SEARCH_BACKWARD); commandSupport.registerSearchAction(searchBackwardAction); ToolItem searchBackwardButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV)) @@ -500,8 +474,8 @@ private void createSearchTools() { .withAction(searchBackwardAction).build(); searchBackwardButton.setData(ID_DATA_KEY, "searchBackward"); //$NON-NLS-1$ - FindReplaceOverlayAction searchForwardAction = new FindReplaceOverlayAction(() -> performSearch(true)); - searchForwardAction.addShortcuts(KeyboardShortcuts.SEARCH_FORWARD); + FindReplaceOverlayAction searchForwardAction = new FindReplaceOverlayAction(() -> performSearch(true), + FindReplaceOverlayCommandSupport.CMD_SEARCH_FORWARD); commandSupport.registerSearchAction(searchForwardAction); ToolItem searchForwardButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT)) @@ -509,8 +483,8 @@ private void createSearchTools() { .withAction(searchForwardAction).build(); searchForwardButton.setData(ID_DATA_KEY, "searchForward"); //$NON-NLS-1$ - FindReplaceOverlayAction selectAllAction = new FindReplaceOverlayAction(this::performSelectAll); - selectAllAction.addShortcuts(KeyboardShortcuts.SEARCH_ALL); + FindReplaceOverlayAction selectAllAction = new FindReplaceOverlayAction(this::performSelectAll, + FindReplaceOverlayCommandSupport.CMD_SELECT_ALL); commandSupport.registerSearchAction(selectAllAction); ToolItem selectAllButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL)) @@ -523,9 +497,12 @@ private void createCloseTools() { closeTools = new AccessibleToolBar(searchContainer); GridDataFactory.fillDefaults().grab(false, true).align(GridData.END, GridData.END).applyTo(closeTools); - FindReplaceOverlayAction closeAction = new FindReplaceOverlayAction(this::close); - closeAction.addShortcuts(KeyboardShortcuts.CLOSE); + FindReplaceOverlayAction closeAction = new FindReplaceOverlayAction(this::close, + FindReplaceOverlayCommandSupport.CMD_CLOSE); commandSupport.registerCommonAction(closeAction); + // Also close on Ctrl+F: otherwise it would reopen (a duplicate of) this very overlay. + commandSupport.registerCommonAction( + new FindReplaceOverlayAction(this::close, IWorkbenchCommandConstants.EDIT_FIND_AND_REPLACE)); // Close button new AccessibleToolItemBuilder(closeTools).withStyleBits(SWT.PUSH) @@ -535,9 +512,10 @@ private void createCloseTools() { } private void createAreaSearchButton() { - FindReplaceOverlaySearchOptionAction searchInSelectionAction = new FindReplaceOverlaySearchOptionAction(SearchOptions.GLOBAL, findReplaceLogic); + FindReplaceOverlaySearchOptionAction searchInSelectionAction = new FindReplaceOverlaySearchOptionAction( + SearchOptions.GLOBAL, findReplaceLogic, + FindReplaceOverlayCommandSupport.CMD_TOGGLE_SEARCH_IN_SELECTION); searchInSelectionAction.addExecutionListener(this::updateIncrementalSearch); - searchInSelectionAction.addShortcuts(KeyboardShortcuts.OPTION_SEARCH_IN_SELECTION); commandSupport.registerCommonAction(searchInSelectionAction); ToolItem searchInSelectionButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA)) @@ -548,9 +526,8 @@ private void createAreaSearchButton() { private void createRegexSearchButton() { FindReplaceOverlaySearchOptionAction regexAction = new FindReplaceOverlaySearchOptionAction(SearchOptions.REGEX, - findReplaceLogic); + findReplaceLogic, FindReplaceOverlayCommandSupport.CMD_TOGGLE_REGEX); regexAction.addExecutionListener(this::updateIncrementalSearch); - regexAction.addShortcuts(KeyboardShortcuts.OPTION_REGEX); commandSupport.registerCommonAction(regexAction); ToolItem regexSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX)) @@ -565,9 +542,9 @@ private void createRegexSearchButton() { private void createCaseSensitiveButton() { FindReplaceOverlaySearchOptionAction caseSensitiveAction = new FindReplaceOverlaySearchOptionAction( - SearchOptions.CASE_SENSITIVE, findReplaceLogic); + SearchOptions.CASE_SENSITIVE, findReplaceLogic, + FindReplaceOverlayCommandSupport.CMD_TOGGLE_CASE_SENSITIVE); caseSensitiveAction.addExecutionListener(this::updateIncrementalSearch); - caseSensitiveAction.addShortcuts(KeyboardShortcuts.OPTION_CASE_SENSITIVE); commandSupport.registerCommonAction(caseSensitiveAction); ToolItem caseSensitiveSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE)) @@ -578,9 +555,8 @@ private void createCaseSensitiveButton() { private void createWholeWordsButton() { FindReplaceOverlaySearchOptionAction wholeWordAction = new FindReplaceOverlaySearchOptionAction( - SearchOptions.WHOLE_WORD, findReplaceLogic); + SearchOptions.WHOLE_WORD, findReplaceLogic, FindReplaceOverlayCommandSupport.CMD_TOGGLE_WHOLE_WORD); wholeWordAction.addExecutionListener(this::updateIncrementalSearch); - wholeWordAction.addShortcuts(KeyboardShortcuts.OPTION_WHOLE_WORD); commandSupport.registerCommonAction(wholeWordAction); ToolItem wholeWordSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD)) @@ -601,8 +577,7 @@ private void createReplaceTools() { return; } performSingleReplace(); - }); - replaceAction.addShortcuts(KeyboardShortcuts.SEARCH_FORWARD); + }, FindReplaceOverlayCommandSupport.CMD_REPLACE_FORWARD); commandSupport.registerReplaceAction(replaceAction); ToolItem replaceButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE)) @@ -616,8 +591,7 @@ private void createReplaceTools() { return; } performReplaceAll(); - }); - replaceAllAction.addShortcuts(KeyboardShortcuts.SEARCH_ALL); + }, FindReplaceOverlayCommandSupport.CMD_REPLACE_ALL); commandSupport.registerReplaceAction(replaceAllAction); ToolItem replaceAllButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL)) @@ -741,18 +715,13 @@ private void createReplaceDialog() { } replaceBarOpen = true; createReplaceContainer(); - initializeReplaceShortcutHandlers(); + commandSupport.trackFocusControl(replaceBar.getTextBar(), REPLACE_BAR_FOCUS_ID); updatePlacementAndVisibility(); replaceBar.forceFocus(); customFocusOrder.apply(); } - private void initializeReplaceShortcutHandlers() { - commandSupport.registerCommonActionShortcutsAtControl(replaceBar); - commandSupport.registerReplaceActionShortcutsAtControl(replaceBar); - } - private void enableSearchTools(boolean enable) { ((GridData) searchTools.getLayoutData()).exclude = !enable; searchTools.setVisible(enable); diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayAction.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayAction.java index 25d9b74d045..5ac5dd3ef36 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayAction.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayAction.java @@ -11,30 +11,37 @@ package org.eclipse.ui.internal.findandreplace.overlay; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; -import org.eclipse.jface.bindings.keys.KeyStroke; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.keys.IBindingService; +/** + * Encapsulates an overlay operation together with its optional Eclipse command + * binding. Extends {@link AbstractHandler} so that it can be registered + * directly as a handler by {@link FindReplaceOverlayCommandSupport}, and its + * enablement state is propagated to the Eclipse command infrastructure and to + * any observing UI elements (e.g. buttons via {@link AccessibleToolItem#setAction}). + */ class FindReplaceOverlayAction extends AbstractHandler { private final Runnable operation; + private final String commandId; private final List executionListeners = new ArrayList<>(); private final List> shortcutHintListeners = new ArrayList<>(); - private final List shortcuts = new ArrayList<>(); - - FindReplaceOverlayAction(Runnable operation) { + FindReplaceOverlayAction(Runnable operation, String commandId) { this.operation = operation; + this.commandId = commandId; } - void addShortcuts(List shortcutsToAdd) { - this.shortcuts.addAll(shortcutsToAdd); + FindReplaceOverlayAction(Runnable operation) { + this(operation, null); } @Override @@ -52,18 +59,6 @@ void setAvailable(boolean available) { setBaseEnabled(available); } - List getShortcuts() { - return Collections.unmodifiableList(shortcuts); - } - - boolean executeIfMatching(KeyStroke keystroke) { - if (shortcuts.stream().anyMatch(keystroke::equals)) { - execute(); - return true; - } - return false; - } - void addExecutionListener(Runnable listener) { executionListeners.add(listener); } @@ -78,19 +73,20 @@ void addShortcutHintListener(Consumer listener) { shortcutHintListeners.add(listener); } - void activateKeyBinding() { - shortcutHintListeners.forEach(listener -> listener.accept(getShortcutHint())); + String getCommandId() { + return commandId; } - private String getShortcutHint() { - if (shortcuts.isEmpty()) { - return ""; //$NON-NLS-1$ + void updateHint() { + if (commandId == null) { + return; } - return shortcuts.get(0).format(); // $NON-NLS-1$ - } - - void deactivateKeyBinding() { - shortcutHintListeners.forEach(listener -> listener.accept("")); //$NON-NLS-1$ + IBindingService bindingService = PlatformUI.getWorkbench().getService(IBindingService.class); + if (bindingService == null) { + return; + } + String hint = bindingService.getBestActiveBindingFormattedFor(commandId); + shortcutHintListeners.forEach(l -> l.accept(hint == null ? "" : hint)); //$NON-NLS-1$ } } diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayCommandSupport.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayCommandSupport.java index a10933c6c9e..7ace697d849 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayCommandSupport.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayCommandSupport.java @@ -16,86 +16,212 @@ import java.util.List; import java.util.Map; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +import org.eclipse.core.expressions.EvaluationResult; +import org.eclipse.core.expressions.Expression; +import org.eclipse.core.expressions.ExpressionInfo; +import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.runtime.ILog; import org.eclipse.jface.action.IAction; import org.eclipse.ui.IActionBars; +import org.eclipse.ui.ISources; import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.contexts.IContextActivation; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.keys.IBindingService; import org.eclipse.ui.part.MultiPageEditorSite; +import org.eclipse.ui.swt.IFocusService; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.ITextEditorActionConstants; /** - * Owns the Find/Replace overlay's command-related infrastructure. Currently - * encapsulates the workaround that disables the target text editor's global - * action handlers while the overlay has focus, preventing them from consuming - * key events that are meant for the overlay's text fields. + * Owns the Find/Replace overlay's command infrastructure, including context + * activation, handler activation, and key-binding hint updates. + *

+ * The overlay's own commands are activated as handlers once, scoped by + * {@link #overlayFocusedExpression}, rather than imperatively + * activated/deactivated on every focus change. That expression relies on + * {@link IFocusService} tracking the search/replace bar controls so that + * {@code ACTIVE_FOCUS_CONTROL} reflects them. Context activation (which + * drives key binding resolution and has no expression-based equivalent) + * remains imperative and is updated directly from the overlay's focus + * listeners. */ class FindReplaceOverlayCommandSupport { + static final String CMD_CLOSE = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.close"; //$NON-NLS-1$ + static final String CMD_TOGGLE_REPLACE = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.toggleReplace"; //$NON-NLS-1$ + static final String CMD_TOGGLE_CASE_SENSITIVE = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.toggleCaseSensitive"; //$NON-NLS-1$ + static final String CMD_TOGGLE_WHOLE_WORD = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.toggleWholeWord"; //$NON-NLS-1$ + static final String CMD_TOGGLE_REGEX = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.toggleRegex"; //$NON-NLS-1$ + static final String CMD_TOGGLE_SEARCH_IN_SELECTION = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.toggleSearchInSelection"; //$NON-NLS-1$ + static final String CMD_SEARCH_FORWARD = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.searchForward"; //$NON-NLS-1$ + static final String CMD_SEARCH_BACKWARD = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.searchBackward"; //$NON-NLS-1$ + static final String CMD_SELECT_ALL = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.selectAll"; //$NON-NLS-1$ + static final String CMD_REPLACE_FORWARD = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.replaceOne"; //$NON-NLS-1$ + static final String CMD_REPLACE_ALL = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.replaceAll"; //$NON-NLS-1$ + + private static final String OVERLAY_CONTEXT_ID = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay"; //$NON-NLS-1$ + private static final String OVERLAY_SEARCH_CONTEXT_ID = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.searchFocused"; //$NON-NLS-1$ + private static final String OVERLAY_REPLACE_CONTEXT_ID = + "org.eclipse.ui.workbench.texteditor.findReplaceOverlay.replaceFocused"; //$NON-NLS-1$ + + private Composite containerControl; private final IWorkbenchPart targetPart; - private DeactivateGlobalActionHandlers globalActionHandlerDeaction; + private final Expression overlayFocusedExpression; - private final List commonActions = new ArrayList<>(); - private final List searchActions = new ArrayList<>(); + /** Workbench-scoped action activations that live for the whole overlay lifetime. */ + private final List permanentActionActivations = new ArrayList<>(); + /** Workbench-scoped action activations tied to the replace bar's lifecycle. */ + private final List replaceActionActivations = new ArrayList<>(); + + /** All registered actions, whose shortcut hints depend on which context is currently active. */ + private final List registeredActions = new ArrayList<>(); + /** The subset of {@link #registeredActions} tied to the replace bar's lifecycle. */ private final List replaceActions = new ArrayList<>(); + private IContextActivation overlayContextActivation; + private IContextActivation searchContextActivation; + private IContextActivation replaceContextActivation; + + private DeactivateGlobalActionHandlers globalActionHandlerDeaction; + FindReplaceOverlayCommandSupport(IWorkbenchPart targetPart) { this.targetPart = targetPart; + this.overlayFocusedExpression = createOverlayFocusedExpression(); + } + + void setContainerControl(Composite containerControl) { + this.containerControl = containerControl; + containerControl.addDisposeListener(__ -> deregisterActionActivations()); } void registerCommonAction(FindReplaceOverlayAction action) { - this.commonActions.add(action); + registerPermanentAction(action); } void registerSearchAction(FindReplaceOverlayAction action) { - this.searchActions.add(action); + registerPermanentAction(action); } - void registerReplaceAction(FindReplaceOverlayAction action) { - this.replaceActions.add(action); + private void registerPermanentAction(FindReplaceOverlayAction action) { + IHandlerActivation activation = activateAction(action); + if (activation != null) { + permanentActionActivations.add(activation); + } + registeredActions.add(action); + action.updateHint(); } - void unregisterReplaceActions() { - this.replaceActions.clear(); + void registerReplaceAction(FindReplaceOverlayAction action) { + IHandlerActivation activation = activateAction(action); + if (activation != null) { + replaceActionActivations.add(activation); + } + registeredActions.add(action); + replaceActions.add(action); + action.updateHint(); } - void registerCommonActionShortcutsAtControl(Control control) { - commonActions.forEach(action -> FindReplaceShortcutUtil.registerActionShortcutsAtControl(action, control)); + /** + * Activates the given action as a handler for its command, scoped by + * {@link #overlayFocusedExpression}, on the workbench-level + * {@link IHandlerService}. The overlay's own commands are private to it, so + * there is no competing handler to outrank; using the workbench-level service + * (rather than the target part's) keeps them reliably reachable regardless + * of the target part's own context nesting. + */ + private IHandlerActivation activateAction(FindReplaceOverlayAction action) { + String commandId = action.getCommandId(); + IHandlerService handlerService = getWorkbenchHandlerService(); + if (commandId == null || handlerService == null) { + return null; + } + return handlerService.activateHandler(commandId, action, overlayFocusedExpression); } - void registerSearchActionShortcutsAtControl(Control control) { - searchActions.forEach(action -> FindReplaceShortcutUtil.registerActionShortcutsAtControl(action, control)); + void unregisterReplaceActions() { + IHandlerService handlerService = getWorkbenchHandlerService(); + if (handlerService != null) { + handlerService.deactivateHandlers(replaceActionActivations); + } + replaceActionActivations.clear(); + registeredActions.removeAll(replaceActions); + replaceActions.clear(); } - void registerReplaceActionShortcutsAtControl(Control control) { - replaceActions.forEach(action -> FindReplaceShortcutUtil.registerActionShortcutsAtControl(action, control)); + void trackFocusControl(Text text, String focusId) { + IFocusService focusService = PlatformUI.getWorkbench().getService(IFocusService.class); + if (focusService != null) { + focusService.addFocusTracker(text, focusId); + } } void searchBarActivated() { searchOrReplaceBarActivated(); - searchActions.forEach(FindReplaceOverlayAction::activateKeyBinding); + deactivateSubContexts(); + IContextService contextService = getWorkbenchContextService(); + if (contextService != null) { + searchContextActivation = contextService.activateContext(OVERLAY_SEARCH_CONTEXT_ID); + } + refreshShortcutHints(); } void replaceBarActivated() { searchOrReplaceBarActivated(); - replaceActions.forEach(FindReplaceOverlayAction::activateKeyBinding); + deactivateSubContexts(); + IContextService contextService = getWorkbenchContextService(); + if (contextService != null) { + replaceContextActivation = contextService.activateContext(OVERLAY_REPLACE_CONTEXT_ID); + } + refreshShortcutHints(); } private void searchOrReplaceBarActivated() { setTextEditorActionsActivated(false); - commonActions.forEach(FindReplaceOverlayAction::activateKeyBinding); + activateOverlayContext(); } void searchOrReplaceBarDeactivated() { - commonActions.forEach(FindReplaceOverlayAction::deactivateKeyBinding); - searchActions.forEach(FindReplaceOverlayAction::deactivateKeyBinding); - replaceActions.forEach(FindReplaceOverlayAction::deactivateKeyBinding); + deactivateSubContexts(); + deactivateOverlayContext(); setTextEditorActionsActivated(true); + refreshShortcutHints(); + } + + /** + * Recomputes every registered action's shortcut hint. Handler activation is + * permanent, but {@link IBindingService#getBestActiveBindingFormattedFor} + * depends on which context is currently active, so the hint itself must + * still be refreshed on every focus transition, just like before. + */ + private void refreshShortcutHints() { + for (FindReplaceOverlayAction action : registeredActions) { + action.updateHint(); + } } /* @@ -147,4 +273,80 @@ void reactivate() { } } + private void activateOverlayContext() { + IContextService contextService = getWorkbenchContextService(); + if (contextService != null) { + overlayContextActivation = contextService.activateContext(OVERLAY_CONTEXT_ID); + } + } + + private void deactivateOverlayContext() { + if (overlayContextActivation != null) { + IContextService contextService = getWorkbenchContextService(); + if (contextService != null) { + contextService.deactivateContext(overlayContextActivation); + } + overlayContextActivation = null; + } + } + + private void deactivateSubContexts() { + IContextService contextService = getWorkbenchContextService(); + if (contextService != null) { + if (searchContextActivation != null) { + contextService.deactivateContext(searchContextActivation); + searchContextActivation = null; + } + if (replaceContextActivation != null) { + contextService.deactivateContext(replaceContextActivation); + replaceContextActivation = null; + } + } + } + + private void deregisterActionActivations() { + List allActivations = new ArrayList<>(permanentActionActivations); + allActivations.addAll(replaceActionActivations); + if (allActivations.isEmpty()) { + return; + } + IHandlerService handlerService = getWorkbenchHandlerService(); + if (handlerService != null) { + handlerService.deactivateHandlers(allActivations); + } + permanentActionActivations.clear(); + replaceActionActivations.clear(); + } + + private static IHandlerService getWorkbenchHandlerService() { + return PlatformUI.getWorkbench().getService(IHandlerService.class); + } + + private static IContextService getWorkbenchContextService() { + return PlatformUI.getWorkbench().getService(IContextService.class); + } + + private Expression createOverlayFocusedExpression() { + return new Expression() { + @Override + public EvaluationResult evaluate(IEvaluationContext context) { + Object focusControl = context.getVariable(ISources.ACTIVE_FOCUS_CONTROL_NAME); + if (focusControl instanceof Control control) { + Control current = control; + while (current != null) { + if (current == containerControl) { + return EvaluationResult.TRUE; + } + current = current.getParent(); + } + } + return EvaluationResult.FALSE; + } + + @Override + public void collectExpressionInfo(ExpressionInfo info) { + info.addVariableNameAccess(ISources.ACTIVE_FOCUS_CONTROL_NAME); + } + }; + } } diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlaySearchOptionAction.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlaySearchOptionAction.java index 87a12266168..747833289d1 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlaySearchOptionAction.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlaySearchOptionAction.java @@ -19,8 +19,9 @@ class FindReplaceOverlaySearchOptionAction extends FindReplaceOverlayAction { private final IFindReplaceLogic findReplaceLogic; - FindReplaceOverlaySearchOptionAction(SearchOptions searchOption, IFindReplaceLogic findReplaceLogic) { - super(() -> findReplaceLogic.toggle(searchOption)); + FindReplaceOverlaySearchOptionAction(SearchOptions searchOption, IFindReplaceLogic findReplaceLogic, + String commandId) { + super(() -> findReplaceLogic.toggle(searchOption), commandId); this.searchOption = searchOption; this.findReplaceLogic = findReplaceLogic; } diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceShortcutUtil.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceShortcutUtil.java deleted file mode 100644 index 0dd3a3a2810..00000000000 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceShortcutUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2024 Vector Informatik GmbH and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.ui.internal.findandreplace.overlay; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.widgets.Control; - -import org.eclipse.jface.bindings.keys.KeyStroke; - -public final class FindReplaceShortcutUtil { - private FindReplaceShortcutUtil() { - } - - static void registerActionShortcutsAtControl(FindReplaceOverlayAction action, Control control) { - control.addKeyListener(KeyListener.keyPressedAdapter(event -> { - KeyStroke actualStroke = extractKeyStroke(event); - if (action.executeIfMatching(actualStroke)) { - event.doit = false; - } - })); - } - - static private KeyStroke extractKeyStroke(KeyEvent e) { - char character = e.character; - boolean ctrlDown = (e.stateMask & SWT.CTRL) != 0; - if (ctrlDown && e.character != e.keyCode && e.character < 0x20 && (e.keyCode & SWT.KEYCODE_BIT) == 0) { - character += 0x40; - } - KeyStroke actualStroke = KeyStroke.getInstance(e.stateMask & (SWT.MOD1 | SWT.SHIFT), - character == 0 ? e.keyCode : character); - return actualStroke; - } -}