diff --git a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java index 194abb008ce..8ea2e10b37f 100644 --- a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java +++ b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java @@ -54,7 +54,7 @@ import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.fieldassist.ComboContentAdapter; -import org.eclipse.jface.resource.JFaceColors; +import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.text.FindReplaceDocumentAdapter; @@ -68,6 +68,7 @@ import org.eclipse.ui.IWorkingSetManager; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; +import org.eclipse.ui.internal.SearchDecoration; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; @@ -140,8 +141,7 @@ public class TextSearchPage extends DialogPage implements ISearchPage, IReplaceP */ private String[] fPreviousExtensions; private Label fFileNamePatternDescription; - - + private ControlDecoration fPatternDecoration; private static class SearchPatternData { public final boolean isCaseSensitive; public final boolean isRegExSearch; @@ -450,6 +450,7 @@ public void setVisible(boolean visible) { } final void updateOKStatus() { + fPatternDecoration.hide(); boolean regexStatus= validateRegex(); getContainer().setPerformActionEnabled(regexStatus); } @@ -479,24 +480,19 @@ public void createControl(Composite parent) { setControl(result); Dialog.applyDialogFont(result); PlatformUI.getWorkbench().getHelpSystem().setHelp(result, ISearchHelpContextIds.TEXT_SEARCH_PAGE); -} + } private boolean validateRegex() { + if (fIsRegExCheckbox.getSelection()) { try { PatternConstructor.createPattern(fPattern.getText(), fIsCaseSensitive, true); } catch (PatternSyntaxException e) { - String locMessage= e.getLocalizedMessage(); - int i= 0; - while (i < locMessage.length() && "\n\r".indexOf(locMessage.charAt(i)) == -1) { //$NON-NLS-1$ - i++; - } - statusMessage(true, locMessage.substring(0, i)); // only take first line + SearchDecoration.validateRegex(fPattern.getText(), fPatternDecoration); return false; } - statusMessage(false, ""); //$NON-NLS-1$ } else { - statusMessage(false, SearchMessages.SearchPage_containingText_hint); + fPatternDecoration.hide(); } return true; } @@ -512,6 +508,8 @@ private void addTextPatternControls(Composite group) { // Pattern combo fPattern= new Combo(group, SWT.SINGLE | SWT.BORDER); + fPatternDecoration = new ControlDecoration(fPattern, SWT.BOTTOM | SWT.LEFT); + // Not done here to prevent page from resizing // fPattern.setItems(getPreviousSearchPatterns()); fPattern.addSelectionListener(new SelectionAdapter() { @@ -561,7 +559,6 @@ public void widgetSelected(SelectionEvent e) { public void widgetSelected(SelectionEvent e) { fIsRegExSearch= fIsRegExCheckbox.getSelection(); updateOKStatus(); - writeConfiguration(); fPatterFieldContentAssist.setEnabled(fIsRegExSearch); fIsWholeWordCheckbox.setEnabled(!fIsRegExSearch); @@ -860,15 +857,4 @@ private void writeConfiguration() { } - private void statusMessage(boolean error, String message) { - fStatusLabel.setText(message); - if (error) { - fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); - } - else { - // use same color as another label to respect styling - fStatusLabel.setForeground(fFileNamePatternDescription.getForeground()); - } - } - } 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 a6ae0d007f1..907a34d2a49 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 @@ -48,6 +48,7 @@ import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.TextContentAdapter; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; @@ -61,6 +62,7 @@ import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; +import org.eclipse.ui.internal.SearchDecoration; import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; import org.eclipse.ui.internal.findandreplace.FindReplaceMessages; import org.eclipse.ui.internal.findandreplace.HistoryStore; @@ -140,6 +142,7 @@ private final class KeyboardShortcuts { private Color overlayBackgroundColor; private Color normalTextForegroundColor; private boolean positionAtTop = true; + private ControlDecoration searchBarDecoration; private ContentAssistCommandAdapter contentAssistSearchField, contentAssistReplaceField; public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget target) { @@ -469,6 +472,7 @@ private void createRegexSearchButton() { wholeWordSearchButton.setEnabled(findReplaceLogic.isAvailable(SearchOptions.WHOLE_WORD)); updateIncrementalSearch(); updateContentAssistAvailability(); + decorate(); }).withShortcuts(KeyboardShortcuts.OPTION_REGEX).build(); regexSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.REGEX)); } @@ -542,6 +546,7 @@ private void createSearchBar() { HistoryStore searchHistory = new HistoryStore(getDialogSettings(), "searchhistory", //$NON-NLS-1$ HISTORY_SIZE); searchBar = new HistoryTextWrapper(searchHistory, searchBarContainer, SWT.SINGLE); + searchBarDecoration = new ControlDecoration(searchBar, SWT.BOTTOM | SWT.LEFT); GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBar); searchBar.forceFocus(); searchBar.selectAll(); @@ -587,6 +592,9 @@ private void setTextEditorActionsActivated(boolean state) { }); searchBar.setMessage(FindReplaceMessages.FindReplaceOverlay_searchBar_message); contentAssistSearchField = createContentAssistField(searchBar, true); + searchBar.addModifyListener(Event -> { + decorate(); + }); } private void updateIncrementalSearch() { @@ -921,4 +929,12 @@ private void updateContentAssistAvailability() { setContentAssistsEnablement(findReplaceLogic.isAvailableAndActive(SearchOptions.REGEX)); } + private void decorate() { + if (regexSearchButton.getSelection()) { + SearchDecoration.validateRegex(getFindString(), searchBarDecoration); + } else { + searchBarDecoration.hide(); + } + } + } \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java index 2ab91df8d15..d71941d293f 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java @@ -49,6 +49,7 @@ import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.fieldassist.ComboContentAdapter; +import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.resource.JFaceColors; @@ -64,6 +65,7 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; +import org.eclipse.ui.internal.SearchDecoration; import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; import org.eclipse.ui.internal.findandreplace.FindReplaceLogicMessageGenerator; import org.eclipse.ui.internal.findandreplace.FindReplaceMessages; @@ -71,6 +73,7 @@ import org.eclipse.ui.internal.findandreplace.IFindReplaceLogic; import org.eclipse.ui.internal.findandreplace.SearchOptions; import org.eclipse.ui.internal.findandreplace.status.IFindReplaceStatus; +import org.eclipse.ui.internal.findandreplace.status.InvalidRegExStatus; import org.eclipse.ui.internal.texteditor.SWTUtil; /** @@ -147,7 +150,10 @@ public void modifyText(ModifyEvent e) { fIgnoreNextEvent = false; return; } - evaluateFindReplaceStatus(); + modificationHandler.run(); + fFindField.addModifyListener(event -> { + decorate(); + }); updateButtonState(!findReplaceLogic.isActive(SearchOptions.INCREMENTAL)); } @@ -178,6 +184,7 @@ public void modifyText(ModifyEvent e) { private Button fReplaceSelectionButton, fReplaceFindButton, fFindNextButton, fReplaceAllButton, fSelectAllButton; private Combo fFindField, fReplaceField; private InputModifyListener fFindModifyListener, fReplaceModifyListener; + private boolean regexOk = true; /** * Find and replace command adapters. @@ -196,6 +203,7 @@ public void modifyText(ModifyEvent e) { * @since 3.0 */ private boolean fGiveFocusToFindField = true; + private ControlDecoration fFindFieldDecoration; /** * Holds the mnemonic/button pairs for all buttons. @@ -310,6 +318,7 @@ public void widgetSelected(SelectionEvent e) { writeSelection(); updateButtonState(!somethingFound); + updateFindHistory(); evaluateFindReplaceStatus(); } @@ -345,6 +354,7 @@ public void widgetSelected(SelectionEvent e) { evaluateFindReplaceStatus(); } }); + setGridData(fReplaceFindButton, SWT.FILL, false, SWT.FILL, false); fReplaceSelectionButton = makeButton(panel, FindReplaceMessages.FindReplace_ReplaceSelectionButton_label, 104, @@ -634,6 +644,8 @@ private Composite createInputPanel(Composite parent) { FindReplaceDocumentAdapterContentProposalProvider findProposer = new FindReplaceDocumentAdapterContentProposalProvider( true); fFindField = new Combo(panel, SWT.DROP_DOWN | SWT.BORDER); + fFindFieldDecoration = new ControlDecoration(fFindField, SWT.BOTTOM | SWT.LEFT); + fContentAssistFindField = new ContentAssistCommandAdapter(fFindField, contentAdapter, findProposer, ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, new char[0], true); setGridData(fFindField, SWT.FILL, true, SWT.CENTER, false); @@ -750,6 +762,10 @@ public void widgetDefaultSelected(SelectionEvent e) { @Override public void widgetSelected(SelectionEvent e) { boolean newState = fIsRegExCheckBox.getSelection(); + decorate(); + if (!newState) { + regexOk = true; + } setupFindReplaceLogic(); storeSettings(); updateButtonState(); @@ -1050,9 +1066,10 @@ private void addDecorationMargin(Control control) { if (!(layoutData instanceof GridData)) return; GridData gd = (GridData) layoutData; - FieldDecoration dec = FieldDecorationRegistry.getDefault() + + FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL); - gd.horizontalIndent = dec.getImage().getBounds().width; + gd.horizontalIndent = fieldDecoration.getImage().getBounds().width; } /** @@ -1092,8 +1109,9 @@ private void updateButtonState(boolean disableReplace) { || !isRegExSearchAvailableAndActive; fWholeWordCheckBox.setEnabled(findReplaceLogic.isAvailable(SearchOptions.WHOLE_WORD)); - fFindNextButton.setEnabled(enable && isFindStringSet); - fSelectAllButton.setEnabled(enable && isFindStringSet && (target instanceof IFindReplaceTargetExtension4)); + fFindNextButton.setEnabled(enable && isFindStringSet && regexOk); + fSelectAllButton.setEnabled( + enable && isFindStringSet && (target instanceof IFindReplaceTargetExtension4) && regexOk); fReplaceSelectionButton.setEnabled( !disableReplace && enable && isTargetEditable && hasActiveSelection && isSelectionGoodForReplace); fReplaceFindButton.setEnabled(!disableReplace && enable && isTargetEditable && isFindStringSet @@ -1102,7 +1120,6 @@ private void updateButtonState(boolean disableReplace) { } } - /** * Updates the given combo with the given content. * @@ -1335,19 +1352,29 @@ private void activateInFindReplaceLogicIf(SearchOptions option, boolean shouldAc } } - /** - * Evaluate the status of the FindReplaceLogic object. - */ + private void decorate() { + if (fIsRegExCheckBox.getSelection()) { + regexOk = SearchDecoration.validateRegex(fFindField.getText(), fFindFieldDecoration); + updateButtonState(regexOk); + + } else { + fFindFieldDecoration.hide(); + } + } + private void evaluateFindReplaceStatus() { IFindReplaceStatus status = findReplaceLogic.getStatus(); - String dialogMessage = status.accept(new FindReplaceLogicMessageGenerator()); - fStatusLabel.setText(dialogMessage); - if (status.isInputValid()) { - fStatusLabel.setForeground(fReplaceLabel.getForeground()); - } else { - fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); + if (!(status instanceof InvalidRegExStatus)) { + String dialogMessage = status.accept(new FindReplaceLogicMessageGenerator()); + fStatusLabel.setText(dialogMessage); + if (status.isInputValid()) { + fStatusLabel.setForeground(fReplaceLabel.getForeground()); + } else { + fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); + } } + } private String getCurrentSelection() { diff --git a/bundles/org.eclipse.ui/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui/META-INF/MANIFEST.MF index 165143157ed..df02157e273 100644 --- a/bundles/org.eclipse.ui/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui/META-INF/MANIFEST.MF @@ -7,7 +7,7 @@ Bundle-Activator: org.eclipse.ui.internal.UIPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin -Export-Package: org.eclipse.ui.internal;x-internal:=true +Export-Package: org.eclipse.ui.internal;x-friends:="org.eclipse.ui.workbench.texteditor,org.eclipse.search" Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.swt;bundle-version="[3.126.0,4.0.0)";visibility:=reexport, org.eclipse.jface;bundle-version="[3.34.0,4.0.0)";visibility:=reexport, diff --git a/bundles/org.eclipse.ui/src/org/eclipse/ui/internal/SearchDecoration.java b/bundles/org.eclipse.ui/src/org/eclipse/ui/internal/SearchDecoration.java new file mode 100644 index 00000000000..47e4e148356 --- /dev/null +++ b/bundles/org.eclipse.ui/src/org/eclipse/ui/internal/SearchDecoration.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * 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; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.eclipse.jface.fieldassist.ControlDecoration; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.swt.graphics.Image; + +/** + * This class contains methods to validate and decorate search fields. + */ +public class SearchDecoration { + + private SearchDecoration() { + // avoid instantiation + } + + /** + * Validate the given regular expression and change the control decoration + * accordingly. If the expression is invalid then the decoration will show an + * error icon and a message and if the expression is valid then the decoration + * will be hidden. + * + * @param regex The regular expression to be validated. + * @param targetDecoration The control decoration that will show the result of + * the validation. + */ + public static boolean validateRegex(String regex, ControlDecoration targetDecoration) { + String errorMessage = getValidationError(regex); + if (errorMessage.isEmpty()) { + targetDecoration.hide(); + return true; + + } + + Image decorationImage = FieldDecorationRegistry.getDefault() + .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR).getImage(); + targetDecoration.setImage(decorationImage); + targetDecoration.setDescriptionText(errorMessage); + targetDecoration.show(); + return false; + } + + /** + * Validate a regular expression. + * + * @return The appropriate error message if the regex is invalid or an empty + * string if the regex is valid. + */ + private static String getValidationError(String regex) { + try { + Pattern.compile(regex); + return ""; //$NON-NLS-1$ + } catch (PatternSyntaxException e) { + String message = e.getLocalizedMessage(); + + // Only preserve the first line of the original error message. + int i = 0; + while (i < message.length() && "\n\r".indexOf(message.charAt(i)) == -1) { //$NON-NLS-1$ + i++; + } + + return message.substring(0, i); + } + } + +} \ No newline at end of file