From 494d0bca124694bc96e04cf1bbfe424c86180d9a Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Thu, 9 Jul 2026 23:29:26 +0200 Subject: [PATCH] Find/Replace overlay: encapsulate handling of search option changes The configuration and implementation of search option modifications in the find/replace overlay was scattered across the overlay implementation as well the implementation of logic and the specific UI (tool) items. With this change, the state and execution handling of search option changes is further encapsulated in a specialization of the existing FindReplaceOverlayAction. The UI just listens to changes/executions of this action or the underlying models. In addition, the builder for the tool items for changing the search options is enhanced to a fluent builder that does not implicitly require specific combinations of properties being set. Contributes to https://github.com/eclipse-platform/eclipse.platform.ui/issues/1912 --- .../overlay/AccessibleToolItemBuilder.java | 102 ++++++++++-------- .../overlay/FindReplaceOverlay.java | 35 +++--- .../overlay/FindReplaceOverlayAction.java | 13 +++ .../FindReplaceOverlaySearchOptionAction.java | 36 +++++++ 4 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlaySearchOptionAction.java diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolItemBuilder.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolItemBuilder.java index a7078045fdd..186fe5eb7c0 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolItemBuilder.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolItemBuilder.java @@ -30,10 +30,6 @@ class AccessibleToolItemBuilder { private int styleBits = SWT.NONE; private Image image; private String toolTipText; - private FindReplaceOverlayAction action; - private SearchOptions searchOption; - private IFindReplaceLogic findReplaceLogic; - private boolean invertSearchOption; public AccessibleToolItemBuilder(AccessibleToolBar accessibleToolBar) { this.accessibleToolBar = Objects.requireNonNull(accessibleToolBar); @@ -54,51 +50,53 @@ public AccessibleToolItemBuilder withToolTipText(String newToolTipText) { return this; } - public AccessibleToolItemBuilder withAction(FindReplaceOverlayAction newAction) { - this.action = newAction; - return this; + public AccessibleToolItemForActionBuilder withAction(FindReplaceOverlayAction action) { + return new AccessibleToolItemForActionBuilder(this, action); } - /** - * Binds a {@link SearchOptions} value to this item. When built, the item's - * selection state is initialized from the logic's current activation state and - * kept in sync automatically. The item's enabled state is also initialized from - * and kept in sync with the option's availability. - */ - public AccessibleToolItemBuilder withSearchOption(SearchOptions option, IFindReplaceLogic logic) { - this.searchOption = option; - this.findReplaceLogic = logic; - this.invertSearchOption = false; - return this; + public class AccessibleToolItemForActionBuilder { + private final AccessibleToolItemBuilder parentBuilder; + + private final FindReplaceOverlayAction action; + + private AccessibleToolItemForActionBuilder(AccessibleToolItemBuilder parentBuilder, + FindReplaceOverlayAction action) { + this.parentBuilder = parentBuilder; + this.action = action; + } + + public ToolItem build() { + AccessibleToolItem accessibleToolItem = parentBuilder.buildAccessibleToolItem(); + accessibleToolItem.setAction(action); + return accessibleToolItem.getToolItem(); + } } - /** - * Like {@link #withSearchOption(SearchOptions, IFindReplaceLogic)} but inverts - * the selection mapping: the item is selected when the option is - * inactive. Useful for options like {@link SearchOptions#GLOBAL} where - * a "search in selection" button should be selected when searching globally is - * turned off. - */ - public AccessibleToolItemBuilder withInvertedSearchOption(SearchOptions option, IFindReplaceLogic logic) { - this.searchOption = option; - this.findReplaceLogic = logic; - this.invertSearchOption = true; - return this; + public AccessibleToolItemForSearchOptionBuilder withAction(FindReplaceOverlaySearchOptionAction action) { + return new AccessibleToolItemForSearchOptionBuilder(this, action); } - public ToolItem build() { - AccessibleToolItem accessibleToolItem = accessibleToolBar.createToolItem(styleBits); - if (image != null) { - accessibleToolItem.setImage(image); - } - if (toolTipText != null) { - accessibleToolItem.setToolTipText(toolTipText); + public class AccessibleToolItemForSearchOptionBuilder extends AccessibleToolItemForActionBuilder { + private final FindReplaceOverlaySearchOptionAction searchOptionAction; + + private boolean invertSearchOption; + + private AccessibleToolItemForSearchOptionBuilder(AccessibleToolItemBuilder parentBuilder, + FindReplaceOverlaySearchOptionAction searchOptionAction) { + super(parentBuilder, searchOptionAction); + this.searchOptionAction = searchOptionAction; } - if (action != null) { - accessibleToolItem.setAction(action); + + public AccessibleToolItemForSearchOptionBuilder displayInverted() { + this.invertSearchOption = true; + return this; } - ToolItem toolItem = accessibleToolItem.getToolItem(); - if (searchOption != null) { + + @Override + public ToolItem build() { + ToolItem toolItem = super.build(); + IFindReplaceLogic findReplaceLogic = searchOptionAction.getFindReplaceLogic(); + SearchOptions searchOption = searchOptionAction.getSearchOption(); boolean initial = findReplaceLogic.isActive(searchOption); toolItem.setSelection(invertSearchOption ? !initial : initial); findReplaceLogic.addSearchOptionActivationChangedListener(searchOption, state -> { @@ -107,12 +105,28 @@ public ToolItem build() { } }); toolItem.setEnabled(findReplaceLogic.isAvailable(searchOption)); - findReplaceLogic.addSearchOptionAvailabilityChangedListener(searchOption, available -> { + findReplaceLogic.addSearchOptionAvailabilityChangedListener(searchOption, state -> { if (!toolItem.isDisposed()) { - toolItem.setEnabled(available); + toolItem.setEnabled(state); } }); + return toolItem; } - return toolItem; } + + public ToolItem build() { + return buildAccessibleToolItem().getToolItem(); + } + + private AccessibleToolItem buildAccessibleToolItem() { + AccessibleToolItem accessibleToolItem = accessibleToolBar.createToolItem(styleBits); + if (image != null) { + accessibleToolItem.setImage(image); + } + if (toolTipText != null) { + accessibleToolItem.setToolTipText(toolTipText); + } + return accessibleToolItem; + } + } 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 ae88160cc86..efb18ffe6da 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 @@ -570,30 +570,25 @@ private void createCloseTools() { } private void createAreaSearchButton() { - FindReplaceOverlayAction searchInSelectionAction = new FindReplaceOverlayAction(() -> { - findReplaceLogic.toggle(SearchOptions.GLOBAL); - updateIncrementalSearch(); - }); + FindReplaceOverlaySearchOptionAction searchInSelectionAction = new FindReplaceOverlaySearchOptionAction(SearchOptions.GLOBAL, findReplaceLogic); + searchInSelectionAction.addExecutionListener(this::updateIncrementalSearch); searchInSelectionAction.addShortcuts(KeyboardShortcuts.OPTION_SEARCH_IN_SELECTION); commonActions.add(searchInSelectionAction); searchInSelectionButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA)) .withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip) - .withInvertedSearchOption(SearchOptions.GLOBAL, findReplaceLogic) - .withAction(searchInSelectionAction).build(); + .withAction(searchInSelectionAction).displayInverted().build(); } private void createRegexSearchButton() { - FindReplaceOverlayAction regexAction = new FindReplaceOverlayAction(() -> { - findReplaceLogic.toggle(SearchOptions.REGEX); - updateIncrementalSearch(); - }); + FindReplaceOverlaySearchOptionAction regexAction = new FindReplaceOverlaySearchOptionAction(SearchOptions.REGEX, + findReplaceLogic); + regexAction.addExecutionListener(this::updateIncrementalSearch); regexAction.addShortcuts(KeyboardShortcuts.OPTION_REGEX); commonActions.add(regexAction); regexSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX)) .withToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip) - .withSearchOption(SearchOptions.REGEX, findReplaceLogic) .withAction(regexAction).build(); findReplaceLogic.addSearchOptionActivationChangedListener(SearchOptions.REGEX, activated -> { updateContentAssistAvailability(); @@ -602,30 +597,26 @@ private void createRegexSearchButton() { } private void createCaseSensitiveButton() { - FindReplaceOverlayAction caseSensitiveAction = new FindReplaceOverlayAction(() -> { - findReplaceLogic.toggle(SearchOptions.CASE_SENSITIVE); - updateIncrementalSearch(); - }); + FindReplaceOverlaySearchOptionAction caseSensitiveAction = new FindReplaceOverlaySearchOptionAction( + SearchOptions.CASE_SENSITIVE, findReplaceLogic); + caseSensitiveAction.addExecutionListener(this::updateIncrementalSearch); caseSensitiveAction.addShortcuts(KeyboardShortcuts.OPTION_CASE_SENSITIVE); commonActions.add(caseSensitiveAction); caseSensitiveSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE)) .withToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip) - .withSearchOption(SearchOptions.CASE_SENSITIVE, findReplaceLogic) .withAction(caseSensitiveAction).build(); } private void createWholeWordsButton() { - FindReplaceOverlayAction wholeWordAction = new FindReplaceOverlayAction(() -> { - findReplaceLogic.toggle(SearchOptions.WHOLE_WORD); - updateIncrementalSearch(); - }); + FindReplaceOverlaySearchOptionAction wholeWordAction = new FindReplaceOverlaySearchOptionAction( + SearchOptions.WHOLE_WORD, findReplaceLogic); + wholeWordAction.addExecutionListener(this::updateIncrementalSearch); wholeWordAction.addShortcuts(KeyboardShortcuts.OPTION_WHOLE_WORD); commonActions.add(wholeWordAction); wholeWordSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD)) .withToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip) - .withSearchOption(SearchOptions.WHOLE_WORD, findReplaceLogic) .withAction(wholeWordAction).build(); } @@ -1055,4 +1046,4 @@ private void decorate() { } } -} \ No newline at end of file +} 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 03e5368e426..28d66801bef 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 @@ -19,6 +19,8 @@ class FindReplaceOverlayAction { private final Runnable operation; + private final List executionListeners = new ArrayList<>(); + private final List shortcuts = new ArrayList<>(); FindReplaceOverlayAction(Runnable operation) { @@ -31,6 +33,7 @@ void addShortcuts(List shortcutsToAdd) { void execute() { operation.run(); + notifyExecutionListeners(); } List getShortcuts() { @@ -52,4 +55,14 @@ String addShortcutHintToTooltipText(String originalTooltipText) { return originalTooltipText + " (" + shortcuts.get(0).format() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } + void addExecutionListener(Runnable listener) { + executionListeners.add(listener); + } + + void notifyExecutionListeners() { + for (Runnable listener : executionListeners) { + listener.run(); + } + } + } 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 new file mode 100644 index 00000000000..87a12266168 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlaySearchOptionAction.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2026 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.ui.internal.findandreplace.IFindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.SearchOptions; + +class FindReplaceOverlaySearchOptionAction extends FindReplaceOverlayAction { + + private final SearchOptions searchOption; + + private final IFindReplaceLogic findReplaceLogic; + + FindReplaceOverlaySearchOptionAction(SearchOptions searchOption, IFindReplaceLogic findReplaceLogic) { + super(() -> findReplaceLogic.toggle(searchOption)); + this.searchOption = searchOption; + this.findReplaceLogic = findReplaceLogic; + } + + SearchOptions getSearchOption() { + return searchOption; + } + + IFindReplaceLogic getFindReplaceLogic() { + return findReplaceLogic; + } + +}