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;
- }
-}