From 26c5880277a3f518ae9d83a9b4579334f5f678d7 Mon Sep 17 00:00:00 2001 From: Maximilian Wittmer Date: Wed, 29 May 2024 21:39:00 +0200 Subject: [PATCH] Find/Replace Overlay: Add a search history Add a search history for the Find/Replace overlay, displayed as a dropdown below the find/replace inputs. fixes #1907 --- .../META-INF/MANIFEST.MF | 1 + .../icons/full/elcl16/open_history.png | Bin 0 -> 331 bytes .../icons/full/elcl16/open_history@2x.png | Bin 0 -> 556 bytes .../findandreplace/FindReplaceMessages.java | 2 + .../FindReplaceMessages.properties | 8 +- .../internal/findandreplace/HistoryStore.java | 4 + .../overlay/AccessibleToolBar.java | 2 +- .../overlay/AccessibleToolItemBuilder.java | 2 +- .../overlay/FindReplaceOverlay.java | 60 ++++-- .../FindReplaceOverlayFirstTimePopup.java | 2 + .../overlay/FindReplaceOverlayImages.java | 29 +-- .../overlay/HistoryTextWrapper.java | 198 ++++++++++++++++++ .../overlay/SearchHistoryMenu.java | 101 +++++++++ .../texteditor/tests/OverlayAccess.java | 18 +- 14 files changed, 379 insertions(+), 48 deletions(-) create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history@2x.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/HistoryTextWrapper.java create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/SearchHistoryMenu.java diff --git a/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF index 557cfe4b1a4..8deeafb7867 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Export-Package: org.eclipse.ui.contentassist, org.eclipse.ui.internal.findandreplace;x-friends:="org.eclipse.ui.workbench.texteditor.tests", org.eclipse.ui.internal.findandreplace.status;x-friends:="org.eclipse.ui.workbench.texteditor.tests", + org.eclipse.ui.internal.findandreplace.overlay;x-friends="org.eclipse.ui.workbench.texteditor.tests", org.eclipse.ui.internal.texteditor;texteditor=split;mandatory:=texteditor;x-friends:="org.eclipse.ui.editors", org.eclipse.ui.internal.texteditor.codemining;x-internal:=true, org.eclipse.ui.internal.texteditor.quickdiff;x-internal:=true, diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history.png new file mode 100644 index 0000000000000000000000000000000000000000..d3620887168e3a03a05bd5726901fa7b8df13caa GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(q^T^vI!dhbrM%{uHL(0YH3 z&d!kO*VbmuSD1do%(WwU7iU~^q-)1jt~-(9Cseu9sdgIXk`M%_S4tJ4}1N8w=ia3*2IGYzr5|mTNM{Kkg2C zb=C5F8LwW~qez!+XZDyJ+rr?mJiX-HUV)2qX8KM`y<6{l?|10^?}d|8=0|1Qy*tms z5HRiOoSEicEBg(2>UXbO%}^7a{eAiEZYG8kpZ{obGbP0l+XkKX>*9P literal 0 HcmV?d00001 diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history@2x.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d047a63ba0910c9a30b49f09830084623b37fe37 GIT binary patch literal 556 zcmV+{0@MA8P)6fqdZ-%A!7K~S&~8!N$1*V?nYUQj^<8?VwvSA@w7A)91p#q9*EgR>LF zf1t(LS&705hqc`mHns}7lf>oe-F>dwy{fpC{vMi5xI-N}b*GaB4DQ^q_09xzsRaLD5*bqXz+qu~Q0PlS_gfLDi z9TGxxl~Ny?<~AC^w7mCMQcCLpUXdK9lpdy(9>*B(X|1chWGw^$0M^gggm3j-`m2k$d#0a}0-pauBv0R8~F4z6_kX$XD*0000disable it now. FindReplaceOverlayFirstTimePopup_FindReplaceOverlayFirstTimePopup_title=New Find/Replace Overlay diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java index 5d091011028..15054c9a73f 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java @@ -109,4 +109,8 @@ private void writeHistory() { settingsManager.put(sectionName, names); } + public List asList() { + return new ArrayList<>(history); + } + } diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolBar.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolBar.java index cece438cab3..5a621c77dbd 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolBar.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/AccessibleToolBar.java @@ -33,7 +33,7 @@ * activation behavior, we listen for it manually and send according events if * necessary. */ -public class AccessibleToolBar extends Composite { +class AccessibleToolBar extends Composite { private List toolBars = new ArrayList<>(); 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 640ac1c9ce0..b0ea0dbc425 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 @@ -23,7 +23,7 @@ /** * Builder for ToolItems for {@link AccessibleToolBar}. */ -public class AccessibleToolItemBuilder { +class AccessibleToolItemBuilder { private final AccessibleToolBar accessibleToolBar; private int styleBits = SWT.NONE; private Image image; 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 55f17c7ad9d..93e17de2396 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 @@ -45,6 +45,8 @@ import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; +import org.eclipse.core.runtime.Adapters; + import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; @@ -62,6 +64,7 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; import org.eclipse.ui.internal.findandreplace.FindReplaceMessages; +import org.eclipse.ui.internal.findandreplace.HistoryStore; import org.eclipse.ui.internal.findandreplace.SearchOptions; import org.eclipse.ui.internal.findandreplace.status.IFindReplaceStatus; @@ -90,27 +93,34 @@ public class FindReplaceOverlay extends Dialog { private Composite searchContainer; private Composite searchBarContainer; - private Text searchBar; + private HistoryTextWrapper searchBar; private AccessibleToolBar searchTools; - private ToolItem searchInSelectionButton; private ToolItem wholeWordSearchButton; private ToolItem caseSensitiveSearchButton; private ToolItem regexSearchButton; + + @SuppressWarnings("unused") private ToolItem searchUpButton; private ToolItem searchDownButton; + + @SuppressWarnings("unused") private ToolItem searchAllButton; private Composite replaceContainer; private Composite replaceBarContainer; - private Text replaceBar; + private HistoryTextWrapper replaceBar; private AccessibleToolBar replaceTools; + + @SuppressWarnings("unused") private ToolItem replaceButton; + @SuppressWarnings("unused") private ToolItem replaceAllButton; private Color backgroundToUse; private Color normalTextForegroundColor; private boolean positionAtTop = true; + private static final int HISTORY_SIZE = 15; public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget target) { super(parent); @@ -119,7 +129,6 @@ public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget setShellStyle(SWT.MODELESS); setBlockOnOpen(false); targetPart = part; - } @Override @@ -143,11 +152,14 @@ private void createFindReplaceLogic(IFindReplaceTarget target) { private void performReplaceAll() { BusyIndicator.showWhile(getShell() != null ? getShell().getDisplay() : Display.getCurrent(), () -> findReplaceLogic.performReplaceAll(getFindString(), getReplaceString())); + replaceBar.storeHistory(); + searchBar.storeHistory(); } private void performSelectAll() { BusyIndicator.showWhile(getShell() != null ? getShell().getDisplay() : Display.getCurrent(), () -> findReplaceLogic.performSelectAll(getFindString())); + searchBar.storeHistory(); } private KeyListener shortcuts = KeyListener.keyPressedAdapter(e -> { @@ -350,31 +362,22 @@ private void restoreOverlaySettings() { private void applyOverlayColors(Color color, boolean tryToColorReplaceBar) { searchTools.setBackground(color); - searchInSelectionButton.setBackground(color); - wholeWordSearchButton.setBackground(color); - regexSearchButton.setBackground(color); - caseSensitiveSearchButton.setBackground(color); - searchAllButton.setBackground(color); - searchUpButton.setBackground(color); - searchDownButton.setBackground(color); - searchBarContainer.setBackground(color); searchBar.setBackground(color); searchContainer.setBackground(color); if (replaceBarOpen && tryToColorReplaceBar) { replaceContainer.setBackground(color); - replaceBar.setBackground(color); replaceBarContainer.setBackground(color); - replaceAllButton.setBackground(color); - replaceButton.setBackground(color); + replaceTools.setBackground(color); + replaceBar.setBackground(color); } } private void unbindListeners() { getShell().removeShellListener(overlayDeactivationListener); if (targetPart != null && targetPart instanceof StatusTextEditor textEditor) { - Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); + Control targetWidget = Adapters.adapt(textEditor, ITextViewer.class).getTextWidget(); if (targetWidget != null) { targetWidget.getShell().removeControlListener(shellMovementListener); targetWidget.removePaintListener(widgetMovementListener); @@ -386,7 +389,7 @@ private void unbindListeners() { private void bindListeners() { getShell().addShellListener(overlayDeactivationListener); if (targetPart instanceof StatusTextEditor textEditor) { - Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); + Control targetWidget = Adapters.adapt(textEditor, ITextViewer.class).getTextWidget(); targetWidget.getShell().addControlListener(shellMovementListener); targetWidget.addPaintListener(widgetMovementListener); @@ -433,17 +436,20 @@ private void retrieveBackgroundColor() { textBarForRetrievingTheRightColor.dispose(); } + private void createSearchTools() { searchTools = new AccessibleToolBar(searchContainer); GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(searchTools); + @SuppressWarnings("unused") + ToolItem separator = searchTools.createToolItem(SWT.SEPARATOR); + createWholeWordsButton(); createCaseSensitiveButton(); createRegexSearchButton(); createAreaSearchButton(); - @SuppressWarnings("unused") - ToolItem separator = searchTools.createToolItem(SWT.SEPARATOR); + separator = searchTools.createToolItem(SWT.SEPARATOR); searchUpButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV)) @@ -519,6 +525,10 @@ private void createReplaceTools() { Color warningColor = JFaceColors.getErrorText(getShell().getDisplay()); replaceTools = new AccessibleToolBar(replaceContainer); + + @SuppressWarnings("unused") + ToolItem separator = replaceTools.createToolItem(SWT.SEPARATOR); + GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(replaceTools); replaceButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE)) @@ -546,7 +556,9 @@ private void createReplaceTools() { } private void createSearchBar() { - searchBar = new Text(searchBarContainer, SWT.SINGLE); + HistoryStore searchHistory = new HistoryStore(getDialogSettings(), "searchhistory", //$NON-NLS-1$ + HISTORY_SIZE); + searchBar = new HistoryTextWrapper(searchHistory, searchBarContainer, SWT.SINGLE); GridDataFactory.fillDefaults().grab(true, false).align(GridData.FILL, GridData.END).applyTo(searchBar); searchBar.forceFocus(); searchBar.selectAll(); @@ -591,7 +603,8 @@ private void updateIncrementalSearch() { } private void createReplaceBar() { - replaceBar = new Text(replaceBarContainer, SWT.SINGLE); + HistoryStore replaceHistory = new HistoryStore(getDialogSettings(), "replacehistory", HISTORY_SIZE); //$NON-NLS-1$ + replaceBar = new HistoryTextWrapper(replaceHistory, replaceBarContainer, SWT.SINGLE); GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.END).applyTo(replaceBar); replaceBar.setMessage(FindReplaceMessages.FindReplaceOverlay_replaceBar_message); replaceBar.addFocusListener(FocusListener.focusLostAdapter(e -> { @@ -782,7 +795,7 @@ private void positionToPart() { } StatusTextEditor textEditor = (StatusTextEditor) targetPart; - Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); + Control targetWidget = Adapters.adapt(textEditor, ITextViewer.class).getTextWidget(); if (!okayToUse(targetWidget)) { this.close(); return; @@ -817,6 +830,8 @@ private String getReplaceString() { private void performSingleReplace() { findReplaceLogic.performReplaceAndFind(getFindString(), getReplaceString()); + replaceBar.storeHistory(); + searchBar.storeHistory(); } private void performSearch(boolean forward) { @@ -826,6 +841,7 @@ private void performSearch(boolean forward) { findReplaceLogic.performSearch(getFindString()); activateInFindReplacerIf(SearchOptions.FORWARD, oldForwardSearchSetting); findReplaceLogic.activate(SearchOptions.INCREMENTAL); + searchBar.storeHistory(); } private void updateFromTargetSelection() { diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayFirstTimePopup.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayFirstTimePopup.java index 5b8a924b9a9..6655e3f1873 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayFirstTimePopup.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayFirstTimePopup.java @@ -41,6 +41,8 @@ * shown, informing the user about the new functionality. This class will track * whether the popup was already shown and will only show the Overlay on the * first time the popup was shown. + * + * @since 3.17 */ public class FindReplaceOverlayFirstTimePopup { diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayImages.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayImages.java index ce0cb4a7853..6ead87842cc 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayImages.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayImages.java @@ -31,21 +31,24 @@ /** * Provides Icons for the editor overlay used for performing * find/replace-operations. + * + * @since 3.17 */ -public class FindReplaceOverlayImages { +class FindReplaceOverlayImages { private static final String PREFIX_ELCL = TextEditorPlugin.PLUGIN_ID + ".elcl."; //$NON-NLS-1$ - public static final String KEY_FIND_NEXT = PREFIX_ELCL + "select_next"; //$NON-NLS-1$ - public static final String KEY_FIND_PREV = PREFIX_ELCL + "select_prev"; //$NON-NLS-1$ - public static final String KEY_FIND_REGEX = PREFIX_ELCL + "regex"; //$NON-NLS-1$ - public static final String KEY_REPLACE = PREFIX_ELCL + "replace"; //$NON-NLS-1$ - public static final String KEY_REPLACE_ALL = PREFIX_ELCL + "replace_all"; //$NON-NLS-1$ - public static final String KEY_WHOLE_WORD = PREFIX_ELCL + "whole_word"; //$NON-NLS-1$ - public static final String KEY_CASE_SENSITIVE = PREFIX_ELCL + "case_sensitive"; //$NON-NLS-1$ - public static final String KEY_SEARCH_ALL = PREFIX_ELCL + "search_all"; //$NON-NLS-1$ - public static final String KEY_SEARCH_IN_AREA = PREFIX_ELCL + "search_in_selection"; //$NON-NLS-1$ - public static final String KEY_OPEN_REPLACE_AREA = PREFIX_ELCL + "open_replace"; //$NON-NLS-1$ - public static final String KEY_CLOSE_REPLACE_AREA = PREFIX_ELCL + "close_replace"; //$NON-NLS-1$ + static final String KEY_FIND_NEXT = PREFIX_ELCL + "select_next"; //$NON-NLS-1$ + static final String KEY_FIND_PREV = PREFIX_ELCL + "select_prev"; //$NON-NLS-1$ + static final String KEY_FIND_REGEX = PREFIX_ELCL + "regex"; //$NON-NLS-1$ + static final String KEY_REPLACE = PREFIX_ELCL + "replace"; //$NON-NLS-1$ + static final String KEY_REPLACE_ALL = PREFIX_ELCL + "replace_all"; //$NON-NLS-1$ + static final String KEY_WHOLE_WORD = PREFIX_ELCL + "whole_word"; //$NON-NLS-1$ + static final String KEY_CASE_SENSITIVE = PREFIX_ELCL + "case_sensitive"; //$NON-NLS-1$ + static final String KEY_SEARCH_ALL = PREFIX_ELCL + "search_all"; //$NON-NLS-1$ + static final String KEY_SEARCH_IN_AREA = PREFIX_ELCL + "search_in_selection"; //$NON-NLS-1$ + static final String KEY_OPEN_REPLACE_AREA = PREFIX_ELCL + "open_replace"; //$NON-NLS-1$ + static final String KEY_CLOSE_REPLACE_AREA = PREFIX_ELCL + "close_replace"; //$NON-NLS-1$ + static final String KEY_OPEN_HISTORY = "open_history"; //$NON-NLS-1$ /** * The image registry containing {@link Image images}. @@ -56,6 +59,7 @@ public class FindReplaceOverlayImages { private final static String ELCL = ICONS_PATH + "elcl16/"; //$NON-NLS-1$ + /** * Declare all images */ @@ -71,6 +75,7 @@ private static void declareImages() { declareRegistryImage(KEY_SEARCH_IN_AREA, ELCL + "search_in_area.png"); //$NON-NLS-1$ declareRegistryImage(KEY_OPEN_REPLACE_AREA, ELCL + "open_replace.png"); //$NON-NLS-1$ declareRegistryImage(KEY_CLOSE_REPLACE_AREA, ELCL + "close_replace.png"); //$NON-NLS-1$ + declareRegistryImage(KEY_OPEN_HISTORY, ELCL + "open_history.png"); //$NON-NLS-1$ } /** diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/HistoryTextWrapper.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/HistoryTextWrapper.java new file mode 100644 index 00000000000..185158256fc --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/HistoryTextWrapper.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * 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 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace.overlay; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.swt.widgets.Widget; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; + +import org.eclipse.ui.internal.findandreplace.FindReplaceMessages; +import org.eclipse.ui.internal.findandreplace.HistoryStore; + +/** + * Wrap a Text Bar and a ToolItem to add an input history. The text is only + * stored to history when requested by the Client code, the history is stored in + * a {@code HistoryStore} provided by the client. The history bar behaves like a + * normal {@code Text}. + */ +class HistoryTextWrapper extends Composite { + private Text textBar; + private AccessibleToolBar tools; + private ToolItem dropDown; + private SearchHistoryMenu menu; + private HistoryStore history; + + public HistoryTextWrapper(HistoryStore history, Composite parent, int style) { + super(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().numColumns(2).applyTo(this); + + this.history = history; + + textBar = new Text(this, style); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.CENTER).applyTo(textBar); + tools = new AccessibleToolBar(this); + dropDown = new AccessibleToolItemBuilder(tools).withStyleBits(SWT.PUSH) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchHistory_toolTip) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_OPEN_HISTORY)) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> createHistoryMenuDropdown())) + .build(); + + listenForKeyboardHistoryNavigation(); + } + + private void listenForKeyboardHistoryNavigation() { + addKeyListener(KeyListener.keyPressedAdapter(e -> { + if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) { + int stepDirection = e.keyCode == SWT.ARROW_UP ? 1 : -1; + navigateInHistory(stepDirection); + } + })); + } + + private void createHistoryMenuDropdown() { + if (menu != null && okayToUse(menu.getShell()) || !dropDown.isEnabled()) { + return; + } + + dropDown.setEnabled(false); + menu = new SearchHistoryMenu(getShell(), history, SelectionListener.widgetSelectedAdapter(f -> { + Table table = (Table) f.widget; + TableItem[] selection = table.getSelection(); + String text = selection[0].getText(); + if (text != null) { + textBar.setText(text); + } + })); + + Point barPosition = textBar.toDisplay(0, 0); + menu.setPosition(barPosition.x, barPosition.y + dropDown.getWidth(), textBar.getSize().x + dropDown.getWidth()); + menu.open(); + + menu.getShell().addDisposeListener(new DisposeListener() { + + @Override + public void widgetDisposed(DisposeEvent e) { + getShell().getDisplay().timerExec(100, HistoryTextWrapper.this::enableDropDown); + } + }); + } + + private void enableDropDown() { + dropDown.setEnabled(true); + } + + private void navigateInHistory(int navigationOffset) { + int offset = history.asList().indexOf(textBar.getText()); + + offset += navigationOffset; + offset = offset % history.asList().size(); + + if (offset + navigationOffset < 0) { + offset = history.asList().size() - 1; + } + + textBar.setText(history.get(offset)); + } + + public void storeHistory() { + String string = textBar.getText(); + history.remove(string); // ensure findString is now on the newest index of the history + history.add(string); + } + + private static boolean okayToUse(final Widget widget) { + return widget != null && !widget.isDisposed(); + } + + public void selectAll() { + textBar.selectAll(); + } + + public void addModifyListener(final ModifyListener listener) { + textBar.addModifyListener(listener); + } + + @Override + public void addFocusListener(final FocusListener listener) { + textBar.addFocusListener(listener); + } + + @Override + public void addKeyListener(final KeyListener listener) { + textBar.addKeyListener(listener); + } + + public void setMessage(final String message) { + textBar.setMessage(message); + } + + public void setSelection(int i, int j) { + textBar.setSelection(i, j); + } + + @Override + public boolean isFocusControl() { + return textBar.isFocusControl(); + } + + public String getText() { + return textBar.getText(); + } + + public void setText(String str) { + textBar.setText(str); + } + + @Override + public void setBackground(Color color) { + super.setBackground(color); + + textBar.setBackground(color); + tools.setBackground(color); + } + + @Override + public void setForeground(Color color) { + super.setForeground(color); + + textBar.setForeground(color); + tools.setForeground(color); + } + + @Override + public boolean forceFocus() { + return textBar.forceFocus(); + } + + public Text getTextBar() { + return textBar; + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/SearchHistoryMenu.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/SearchHistoryMenu.java new file mode 100644 index 00000000000..63f14962e62 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/SearchHistoryMenu.java @@ -0,0 +1,101 @@ +package org.eclipse.ui.internal.findandreplace.overlay; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.events.ShellListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.layout.GridDataFactory; + +import org.eclipse.ui.internal.findandreplace.HistoryStore; + +/** + * Menu dropdown for the search history in the find/replace overlay + */ +class SearchHistoryMenu extends Dialog { + private final SelectionListener selectionListener; + private final HistoryStore history; + private final ShellListener shellFocusListener = new ShellAdapter() { + @Override + public void shellDeactivated(ShellEvent e) { + if (!getShell().isDisposed()) { + getShell().getDisplay().asyncExec(SearchHistoryMenu.this::close); + } + } + }; + private Point location; + private int width; + + public SearchHistoryMenu(Shell parent, HistoryStore history, SelectionListener menuItemSelectionListener) { + super(parent); + setShellStyle(SWT.NONE); + setBlockOnOpen(false); + + this.selectionListener = menuItemSelectionListener; + this.history = history; + } + + public void setPosition(int x, int y, int width) { + location = new Point(x, y); + this.width = width; + } + + @Override + public Control createContents(Composite parent) { + Table table = new Table(parent, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(table); + TableColumn column = new TableColumn(table, SWT.NONE); + + for (String entry : history.get()) { + TableItem item = new TableItem(table, SWT.NONE); + item.setText(entry); + } + + table.addSelectionListener(selectionListener); + table.addMouseListener(MouseListener.mouseDownAdapter(e -> { + table.notifyListeners(SWT.Selection, null); + close(); + })); + table.addKeyListener(KeyListener.keyPressedAdapter(e -> { + if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { + close(); + } + })); + getShell().layout(); + + positionShell(table, column); + return table; + } + + private void positionShell(Table table, TableColumn column) { + if (location != null) { + getShell().setBounds(location.x, location.y, width, + Math.min(table.getItemHeight() * 7, table.getItemHeight() * (table.getItemCount() + 2))); + } + int columnSize = table.getSize().x; + if (table.getVerticalBar() != null) { + columnSize -= table.getVerticalBar().getSize().x; + } + column.setWidth(columnSize); + } + + @Override + public int open() { + int code = super.open(); + + getShell().addShellListener(shellFocusListener); + + return code; + } +} diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/OverlayAccess.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/OverlayAccess.java index 575d4e67c87..84755693c34 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/OverlayAccess.java +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/OverlayAccess.java @@ -27,7 +27,6 @@ import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.text.tests.Accessor; @@ -38,13 +37,14 @@ import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; import org.eclipse.ui.internal.findandreplace.IFindReplaceLogic; import org.eclipse.ui.internal.findandreplace.SearchOptions; +import org.eclipse.ui.internal.findandreplace.overlay.HistoryTextWrapper; class OverlayAccess implements IFindReplaceUIAccess { FindReplaceLogic findReplaceLogic; - Text find; + HistoryTextWrapper find; - Text replace; + HistoryTextWrapper replace; ToolItem inSelection; @@ -73,8 +73,8 @@ class OverlayAccess implements IFindReplaceUIAccess { OverlayAccess(Accessor findReplaceOverlayAccessor) { dialogAccessor= findReplaceOverlayAccessor; findReplaceLogic= (FindReplaceLogic) findReplaceOverlayAccessor.get("findReplaceLogic"); - find= (Text) findReplaceOverlayAccessor.get("searchBar"); - replace= (Text) findReplaceOverlayAccessor.get("replaceBar"); + find= (HistoryTextWrapper) findReplaceOverlayAccessor.get("searchBar"); + replace= (HistoryTextWrapper) findReplaceOverlayAccessor.get("replaceBar"); caseSensitive= (ToolItem) findReplaceOverlayAccessor.get("caseSensitiveSearchButton"); wholeWord= (ToolItem) findReplaceOverlayAccessor.get("wholeWordSearchButton"); regEx= (ToolItem) findReplaceOverlayAccessor.get("regexSearchButton"); @@ -152,8 +152,8 @@ public void simulateKeyPressInFindInputField(int keyCode, boolean shiftPressed) if (shiftPressed) { event.stateMask= SWT.SHIFT; } - find.notifyListeners(SWT.KeyDown, event); - find.traverse(SWT.TRAVERSE_RETURN, event); + find.getTextBar().notifyListeners(SWT.KeyDown, event); + find.getTextBar().traverse(SWT.TRAVERSE_RETURN, event); FindReplaceTestUtil.runEventQueue(); } @@ -170,7 +170,7 @@ public String getReplaceText() { @Override public void setFindText(String text) { find.setText(text); - find.notifyListeners(SWT.Modify, null); + find.getTextBar().notifyListeners(SWT.Modify, null); } @Override @@ -251,7 +251,7 @@ public boolean isReplaceDialogOpen() { public void openReplaceDialog() { if (!isReplaceDialogOpen() && Objects.nonNull(openReplaceDialog)) { openReplaceDialog.notifyListeners(SWT.Selection, null); - replace= (Text) dialogAccessor.get("replaceBar"); + replace= (HistoryTextWrapper) dialogAccessor.get("replaceBar"); replaceButton= (ToolItem) dialogAccessor.get("replaceButton"); replaceAllButton= (ToolItem) dialogAccessor.get("replaceAllButton"); }