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 00000000000..d3620887168 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history.png differ 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 00000000000..d047a63ba09 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/open_history@2x.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java index 1e30529ea75..3ecacadf4b9 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java @@ -72,6 +72,8 @@ private FindReplaceMessages() { public static String FindReplaceOverlay_searchBar_message; public static String FindReplaceOverlay_replaceBar_message; public static String FindReplaceOverlay_replaceToggle_toolTip; + public static String FindReplaceOverlay_searchHistory_toolTip; + public static String FindReplaceOverlay_replaceHistory_toolTip; public static String FindReplaceOverlayFirstTimePopup_FindReplaceOverlayFirstTimePopup_message; public static String FindReplaceOverlayFirstTimePopup_FindReplaceOverlayFirstTimePopup_title; } diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties index da945bbceb3..25669d2daba 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties @@ -45,7 +45,7 @@ FindReplace_ReplaceAllButton_label=Replace &All FindReplace_SelectAllButton_label=&Select All FindReplace_CloseButton_label=Close -# Messages for the "new" Find-Replace-Overlay +# Messages for the Find-Replace-Overlay FindReplaceOverlay_upSearchButton_toolTip=Search backward (Shift + Enter) FindReplaceOverlay_downSearchButton_toolTip=Search forward (Enter) FindReplaceOverlay_searchAllButton_toolTip=Search all (Ctrl + Enter) @@ -55,8 +55,10 @@ FindReplaceOverlay_caseSensitiveButton_toolTip=Match case (Ctrl + Shift + C) FindReplaceOverlay_wholeWordsButton_toolTip=Match whole word (Ctrl + Shift + W) FindReplaceOverlay_replaceButton_toolTip=Replace (Enter) FindReplaceOverlay_replaceAllButton_toolTip=Replace all (Ctrl + Enter) -FindReplaceOverlay_searchBar_message=Find -FindReplaceOverlay_replaceBar_message=Replace +FindReplaceOverlay_searchBar_message=Find (\u2195 for history) +FindReplaceOverlay_replaceBar_message=Replace (\u2195 for history) FindReplaceOverlay_replaceToggle_toolTip=Toggle input for replace (Ctrl + R) +FindReplaceOverlay_searchHistory_toolTip=Show search history +FindReplaceOverlay_replaceHistory_toolTip=Show replace history FindReplaceOverlayFirstTimePopup_FindReplaceOverlayFirstTimePopup_message=Find and replace can now be done using an overlay embedded inside the editor. If you prefer the dialog, you can disable the overlay in the preferences or disable 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"); }