diff --git a/org.eclipse.search/META-INF/MANIFEST.MF b/org.eclipse.search/META-INF/MANIFEST.MF index 4249dc0a2..4259ddac5 100644 --- a/org.eclipse.search/META-INF/MANIFEST.MF +++ b/org.eclipse.search/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.search; singleton:=true -Bundle-Version: 3.14.200.qualifier +Bundle-Version: 3.15.100.qualifier Bundle-Activator: org.eclipse.search.internal.ui.SearchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/org.eclipse.search/new search/org/eclipse/search/ui/IResearchQuery.java b/org.eclipse.search/new search/org/eclipse/search/ui/IResearchQuery.java new file mode 100644 index 000000000..fdeee5318 --- /dev/null +++ b/org.eclipse.search/new search/org/eclipse/search/ui/IResearchQuery.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2022 Joerg Kubitz 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: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.search.ui; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Represents a particular search query that can be rerun in background with an + * updated search text + *

+ * Clients may implement this interface. + *

+ * + * @since 3.15 + */ +public interface IResearchQuery extends ISearchQuery { + /** + * @return the text the user was searching for + */ + String getSearchString(); + + /** + * Sets the search text for the next run + * + * @param s + * the text the user is searching for + * @see org.eclipse.search.ui.ISearchQuery#run(IProgressMonitor) + */ + void setSearchString(String s); + + @Override + public default boolean canRerun() { + return true; + } + + @Override + public default boolean canRunInBackground() { + return true; + } + +} diff --git a/org.eclipse.search/new search/org/eclipse/search/ui/NewSearchUI.java b/org.eclipse.search/new search/org/eclipse/search/ui/NewSearchUI.java index 2431df106..540e8d5bd 100644 --- a/org.eclipse.search/new search/org/eclipse/search/ui/NewSearchUI.java +++ b/org.eclipse.search/new search/org/eclipse/search/ui/NewSearchUI.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,6 +13,8 @@ *******************************************************************************/ package org.eclipse.search.ui; +import java.util.Objects; + import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.dialogs.ErrorDialog; @@ -243,9 +245,12 @@ public static boolean isQueryRunning(ISearchQuery query) { /** * Sends a 'cancel' command to the given query running in background. * The call has no effect if the query is not running, not in background or is not cancelable. + * + * The query may still running. * * @param query * the query + * @see #waitFinished * @since 3.1 */ public static void cancelQuery(ISearchQuery query) { @@ -255,6 +260,19 @@ public static void cancelQuery(ISearchQuery query) { InternalSearchUI.getInstance().cancelSearch(query); } + /** + * waits till the query is finished + * + * @param query + * the query + * @see #cancelQuery + * @since 3.15 + */ + public static void waitFinished(ISearchQuery query) { + Objects.requireNonNull(query, "query must not be null"); //$NON-NLS-1$ + InternalSearchUI.getInstance().waitFinished(query); + } + /** * Removes the given search query. * diff --git a/org.eclipse.search/new search/org/eclipse/search2/internal/ui/InternalSearchUI.java b/org.eclipse.search/new search/org/eclipse/search2/internal/ui/InternalSearchUI.java index 78c080055..52d4ddc9b 100644 --- a/org.eclipse.search/new search/org/eclipse/search2/internal/ui/InternalSearchUI.java +++ b/org.eclipse.search/new search/org/eclipse/search2/internal/ui/InternalSearchUI.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -15,8 +15,9 @@ package org.eclipse.search2.internal.ui; import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; @@ -39,6 +40,7 @@ import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.ISearchResult; import org.eclipse.search.ui.ISearchResultViewPart; +import org.eclipse.search.ui.NewSearchUI; import org.eclipse.search2.internal.ui.text.PositionTracker; @@ -48,31 +50,28 @@ public class InternalSearchUI { private static InternalSearchUI fgInstance; // contains all running jobs - private HashMap fSearchJobs; + private final Map fSearchJobs; - private QueryManager fSearchResultsManager; - private PositionTracker fPositionTracker; + private final QueryManager fSearchResultsManager; + private final PositionTracker fPositionTracker; - private SearchViewManager fSearchViewManager; + private final SearchViewManager fSearchViewManager; public static final Object FAMILY_SEARCH = new Object(); private static class SearchJobRecord { - public ISearchQuery query; - public Job job; - public boolean isRunning; + public final ISearchQuery query; + public volatile Job job; public SearchJobRecord(ISearchQuery job) { this.query= job; - this.isRunning= false; this.job= null; } } - private class InternalSearchJob extends Job { - private SearchJobRecord fSearchJobRecord; + private final SearchJobRecord fSearchJobRecord; public InternalSearchJob(SearchJobRecord sjr) { super(sjr.query.getLabel()); @@ -102,6 +101,7 @@ protected IStatus run(IProgressMonitor monitor) { fSearchJobRecord.job= null; return status; } + @Override public boolean belongsTo(Object family) { return family == InternalSearchUI.FAMILY_SEARCH; @@ -110,12 +110,10 @@ public boolean belongsTo(Object family) { } private void searchJobStarted(SearchJobRecord record) { - record.isRunning= true; getSearchManager().queryStarting(record.query); } private void searchJobFinished(SearchJobRecord record) { - record.isRunning= false; fSearchJobs.remove(record.query); getSearchManager().queryFinished(record.query); } @@ -125,7 +123,7 @@ private void searchJobFinished(SearchJobRecord record) { */ public InternalSearchUI() { fgInstance= this; - fSearchJobs= new HashMap<>(); + fSearchJobs= new ConcurrentHashMap<>(); fSearchResultsManager= new QueryManager(); fPositionTracker= new PositionTracker(); @@ -137,7 +135,7 @@ public InternalSearchUI() { /** * @return returns the shared instance. */ - public static InternalSearchUI getInstance() { + public static synchronized InternalSearchUI getInstance() { if (fgInstance ==null) fgInstance= new InternalSearchUI(); return fgInstance; @@ -158,8 +156,13 @@ private IWorkbenchSiteProgressService getProgressService() { } public boolean runSearchInBackground(ISearchQuery query, ISearchResultViewPart view) { - if (isQueryRunning(query)) + SearchJobRecord sjr = new SearchJobRecord(query); + // do not rerun same query in parallel (avoid MT issues): + NewSearchUI.waitFinished(query); // blocks UI :-( + SearchJobRecord queryRunning = fSearchJobs.putIfAbsent(query, sjr); + if (queryRunning != null) { return false; + } // prepare view if (view == null) { @@ -170,8 +173,6 @@ public boolean runSearchInBackground(ISearchQuery query, ISearchResultViewPart v addQuery(query); - SearchJobRecord sjr= new SearchJobRecord(query); - fSearchJobs.put(query, sjr); Job job= new InternalSearchJob(sjr); job.setPriority(Job.BUILD); @@ -188,12 +189,13 @@ public boolean runSearchInBackground(ISearchQuery query, ISearchResultViewPart v } public boolean isQueryRunning(ISearchQuery query) { - SearchJobRecord sjr= fSearchJobs.get(query); - return sjr != null && sjr.isRunning; + return fSearchJobs.containsKey(query); } public IStatus runSearchInForeground(IRunnableContext context, final ISearchQuery query, ISearchResultViewPart view) { - if (isQueryRunning(query)) { + SearchJobRecord sjr = new SearchJobRecord(query); + SearchJobRecord queryRunning = fSearchJobs.putIfAbsent(query, sjr); + if (queryRunning != null) { return Status.CANCEL_STATUS; } @@ -206,9 +208,6 @@ public IStatus runSearchInForeground(IRunnableContext context, final ISearchQuer addQuery(query); - SearchJobRecord sjr= new SearchJobRecord(query); - fSearchJobs.put(query, sjr); - if (context == null) context= new ProgressMonitorDialog(null); @@ -264,13 +263,30 @@ private void doShutdown() { } - public void cancelSearch(ISearchQuery job) { - SearchJobRecord rec= fSearchJobs.get(job); - if (rec != null && rec.job != null) - rec.job.cancel(); + public void cancelSearch(ISearchQuery query) { + SearchJobRecord rec = fSearchJobs.get(query); + Job job = rec == null ? null : rec.job; + if (job != null) { + job.cancel(); + // note that the job may still be running for some time + // which can cause multithreading issues. + } } - + public boolean waitFinished(ISearchQuery query) { + SearchJobRecord rec = fSearchJobs.get(query); + Job job = rec == null ? null : rec.job; + if (job != null) { + try { + job.cancel(); + job.join(); + return true; + } catch (InterruptedException e) { + // ignore + } + } + return false; + } public QueryManager getSearchManager() { return fSearchResultsManager; @@ -348,5 +364,4 @@ private void showSearchResult(SearchView searchView, ISearchResult result) { searchView.showSearchResult(result); } - } diff --git a/org.eclipse.search/new search/org/eclipse/search2/internal/ui/SearchView.java b/org.eclipse.search/new search/org/eclipse/search2/internal/ui/SearchView.java index 1294c9423..7c385ba9e 100644 --- a/org.eclipse.search/new search/org/eclipse/search2/internal/ui/SearchView.java +++ b/org.eclipse.search/new search/org/eclipse/search2/internal/ui/SearchView.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -18,12 +18,19 @@ package org.eclipse.search2.internal.ui; import java.text.MessageFormat; +import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; +import java.util.Objects; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; @@ -34,6 +41,7 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Text; import org.eclipse.core.commands.operations.IUndoContext; @@ -49,7 +57,13 @@ import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.Throttler; import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; + +import org.eclipse.jface.text.ITextSelection; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IMemento; @@ -81,6 +95,7 @@ import org.eclipse.search.internal.ui.SearchPlugin; import org.eclipse.search.ui.IContextMenuConstants; import org.eclipse.search.ui.IQueryListener; +import org.eclipse.search.ui.IResearchQuery; import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.ISearchResult; import org.eclipse.search.ui.ISearchResultPage; @@ -112,6 +127,7 @@ public class SearchView extends PageBookView implements ISearchResultViewPart, I private Composite fPageContent; private Link fDescription; + private Text filterText; private Composite fDescriptionComposite; /** @@ -429,6 +445,7 @@ private void internalShowSearchPage(ISearchResultPage page, ISearchResult search } updatePartName(); updateLabel(); + updateFilter(); updateCancelAction(); updateHelpContextID(page); @@ -477,18 +494,163 @@ public void updateLabel() { fDescriptionComposite.setLayout(layout); fDescriptionComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - fDescription= new Link(fDescriptionComposite, SWT.NONE); - GridData gridData= new GridData(SWT.FILL, SWT.CENTER, true, false); - gridData.horizontalIndent= 5; + fDescription = new Link(fDescriptionComposite, SWT.NONE); + GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false); + gridData.horizontalIndent = 5; fDescription.setLayoutData(gridData); - fDescription.setText(label); - Label separator= new Label(fDescriptionComposite, SWT.SEPARATOR | SWT.HORIZONTAL); + Label separator = new Label(fDescriptionComposite, SWT.SEPARATOR | SWT.HORIZONTAL); separator.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + ISearchQuery query = fCurrentSearch.getQuery(); + if (query instanceof IResearchQuery) { + filterText = new Text(fDescriptionComposite, + SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH); + filterText.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.character == SWT.CR) + openSearch(); + } + // optional + // filterText.setMessage(WorkbenchMessages.FilteredTree_FilterMessage); + }); + filterText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + } else { + if (filterText != null) { + filterText.dispose(); + filterText = null; + } + } + fPageContent.layout(); - } else { - fDescription.setText(label); } + fDescription.setText(label); + } + } + } + + public void updateFilter() { + if (filterText != null && !filterText.isDisposed()) { + String searchString = null; + ISearchQuery query = fCurrentSearch.getQuery(); + if (query instanceof IResearchQuery) { + searchString = ((IResearchQuery) query).getSearchString(); + } + if (searchString != null) { + searchString = LegacyActionTools.escapeMnemonics(searchString); + } + filterText.setVisible(searchString != null); + if (searchString != null && !Objects.equals(filterText.getText(), searchString)) { + filterText.setText(searchString); + } + // after setting text to avoid initial modification fires event + ModifyListener inputListener = this::textChanged; + filterText.removeModifyListener(inputListener); + filterText.addModifyListener(inputListener); + } + } + + private void openSearch() { + ISelectionProvider backup = getSite().getSelectionProvider(); + try { + String searchText = filterText.getText(); + getSite().setSelectionProvider(new SelectionProviderAdapter(searchText)); + new OpenSearchDialogAction().run(); // uses selection + } finally { + getSite().setSelectionProvider(backup); + } + } + + private Throttler researchThrottled = new Throttler(PlatformUI.getWorkbench().getDisplay(), Duration.ofMillis(50), + this::research); + + private void research() { + String searchText = filterText.getText(); + ISearchQuery query = fCurrentSearch.getQuery(); + if (query instanceof IResearchQuery) { + ((IResearchQuery) query).setSearchString(searchText); + fSearchAgainAction.run(); + } + } + + private void textChanged(@SuppressWarnings("unused") ModifyEvent ignored) { + researchThrottled.throttledExec(); + } + + private class SelectionProviderAdapter implements ISelectionProvider, ISelectionChangedListener { + private final String fakeSelection; + + private final class StringSelection implements ITextSelection { + private final String text; + + public StringSelection(String text) { + this.text = text; + } + + @Override + public boolean isEmpty() { + return text.isEmpty(); + } + + @Override + public int getOffset() { + return 0; + } + + @Override + public int getLength() { + return text.length(); + } + + @Override + public int getStartLine() { + return 0; + } + + @Override + public int getEndLine() { + return 0; + } + + @Override + public String getText() { + return text; + } + } + + private final ArrayList fListeners = new ArrayList<>(); + + public SelectionProviderAdapter(String searchText) { + this.fakeSelection = searchText; + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + fListeners.add(listener); + } + + @Override + public ISelection getSelection() { + return new StringSelection(fakeSelection); + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + fListeners.remove(listener); + } + + @Override + public void setSelection(ISelection selection) { + // ignore + } + + @Override + public void selectionChanged(SelectionChangedEvent event) { + // forward to my listeners + SelectionChangedEvent wrappedEvent = new SelectionChangedEvent(this, event.getSelection()); + for (ISelectionChangedListener listener : fListeners) { + listener.selectionChanged(wrappedEvent); } } } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java b/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java index 9a174e40c..3ee9ff3d7 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java @@ -333,38 +333,45 @@ public IStatus search(IFile[] files, IProgressMonitor monitor) { long startTime= TRACING ? System.currentTimeMillis() : 0; Job monitorUpdateJob= new Job(SearchMessages.TextSearchVisitor_progress_updating_job) { - private int fLastNumberOfScannedFiles= 0; @Override public IStatus run(IProgressMonitor inner) { - while (!inner.isCanceled()) { - // Propagate user cancellation to the JobGroup. - if (fProgressMonitor.isCanceled()) { - jobGroup.cancel(); - break; - } + int fLastNumberOfScannedFiles = 0; + try { + while (!inner.isCanceled()) { + // Propagate user cancellation to the JobGroup. + if (fProgressMonitor.isCanceled()) { + break; + } - IFile file; - int numberOfScannedFiles; - synchronized (fLock) { - file= fCurrentFile; - numberOfScannedFiles= fNumberOfScannedFiles; - } - if (file != null) { - String fileName= file.getName(); - Object[] args= { fileName, Integer.valueOf(numberOfScannedFiles), Integer.valueOf(fNumberOfFilesToScan)}; - fProgressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args)); - int steps= numberOfScannedFiles - fLastNumberOfScannedFiles; - fProgressMonitor.worked(steps); - fLastNumberOfScannedFiles += steps; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return Status.OK_STATUS; + IFile file; + int numberOfScannedFiles; + synchronized (fLock) { + file = fCurrentFile; + numberOfScannedFiles = fNumberOfScannedFiles; + } + if (numberOfScannedFiles == fNumberOfFilesToScan) { + return Status.OK_STATUS; + } + if (file != null) { + String fileName = file.getName(); + Object[] args = { fileName, Integer.valueOf(numberOfScannedFiles), + Integer.valueOf(fNumberOfFilesToScan) }; + fProgressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args)); + int steps = numberOfScannedFiles - fLastNumberOfScannedFiles; + fProgressMonitor.worked(steps); + fLastNumberOfScannedFiles += steps; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + return Status.OK_STATUS; + } } + return Status.OK_STATUS; + } finally { + jobGroup.cancel(); } - return Status.OK_STATUS; } }; @@ -373,8 +380,6 @@ public IStatus run(IProgressMonitor inner) { ? SearchMessages.TextSearchVisitor_filesearch_task_label : ""; //$NON-NLS-1$ fProgressMonitor.beginTask(taskName, fNumberOfFilesToScan); - monitorUpdateJob.setSystem(true); - monitorUpdateJob.schedule(); try { fCollector.beginReporting(); Map documentsInEditors= PlatformUI.isWorkbenchRunning() ? evalNonFileBufferDocuments() : Collections.emptyMap(); @@ -384,13 +389,15 @@ public IStatus run(IProgressMonitor inner) { Map> localFilesByLocation = new LinkedHashMap<>(); Map> remotFilesByLocation = new LinkedHashMap<>(); - for (IFile file : files) { - IPath path = file.getLocation(); - String key = path == null ? file.getLocationURI().toString() : path.toString(); - Map> filesByLocation = (path != null) ? localFilesByLocation - : remotFilesByLocation; - filesByLocation.computeIfAbsent(key, k -> new ArrayList<>()).add(file); + if (!fProgressMonitor.isCanceled()) { + for (IFile file : files) { + IPath path = file.getLocation(); + String key = path == null ? file.getLocationURI().toString() : path.toString(); + Map> filesByLocation = (path != null) ? localFilesByLocation + : remotFilesByLocation; + filesByLocation.computeIfAbsent(key, k -> new ArrayList<>()).add(file); + } } localFilesByLocation.values().forEach(fileBatches::offer); remotFilesByLocation.values().forEach(fileBatches::offer); @@ -400,10 +407,22 @@ public IStatus run(IProgressMonitor inner) { job.setJobGroup(jobGroup); job.schedule(); } + monitorUpdateJob.setSystem(true); + monitorUpdateJob.schedule(); // The monitorUpdateJob is managing progress and cancellation, // so it is ok to pass a null monitor into the job group. - jobGroup.join(0, null); + try { + // avoid stale jobs + monitorUpdateJob.join(0, null); + } catch (InterruptedException e) { + // ignore + } + jobGroup.join(0, null); // XXX can deadlock, but using + // fProgressMonitor shows wrong + // progress. no guarantee + // monitorUpdateJob is running in + // another worker if (fProgressMonitor.isCanceled()) throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); @@ -412,7 +431,6 @@ public IStatus run(IProgressMonitor inner) { } catch (InterruptedException e) { throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); } finally { - monitorUpdateJob.cancel(); fileBatches.clear(); } } finally { diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java index e29d18ae7..6e320ed67 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java @@ -160,6 +160,10 @@ private SearchMessages() { public static String FileSearchQuery_singularLabel; public static String FileSearchQuery_singularLabel_fileNameSearch; public static String FileSearchQuery_pluralPattern_fileNameSearch; + public static String FileResearchQuery_pluralPattern; + public static String FileResearchQuery_singularLabel; + public static String FileResearchQuery_singularLabel_fileNameSearch; + public static String FileResearchQuery_pluralPattern_fileNameSearch; public static String OpenSearchDialogAction_label; public static String OpenSearchDialogAction_tooltip; public static String FileTypeEditor_typeDelimiter; @@ -220,4 +224,6 @@ private SearchMessages() { public static String TextSearchEngineRegistry_defaulttextsearch_label; public static String FileSearchQuery_singularPatternWithFileExt; public static String FileSearchQuery_pluralPatternWithFileExt; + public static String FileResearchQuery_singularPatternWithFileExt; + public static String FileResearchQuery_pluralPatternWithFileExt; } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties index a7c3ed874..4ec17b3a9 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties @@ -152,6 +152,13 @@ FileSearchQuery_singularPatternWithFileExt= ''{0}'' - 1 match in {1} ({2}) FileSearchQuery_singularLabel_fileNameSearch=1 file name matching ''{0}'' in {1} FileSearchQuery_pluralPattern_fileNameSearch={1} file names matching ''{0}'' in {2} +FileResearchQuery_pluralPattern= {1} matches in {2} +FileResearchQuery_singularLabel= 1 match in {1} +FileResearchQuery_pluralPatternWithFileExt= {1} matches in {2} ({3}) +FileResearchQuery_singularPatternWithFileExt= 1 match in {1} ({2}) +FileResearchQuery_singularLabel_fileNameSearch=1 file name matching ''{0}'' in {1} +FileResearchQuery_pluralPattern_fileNameSearch={1} file names matching ''{0}'' in {2} + OpenSearchDialogAction_label= Search OpenSearchDialogAction_tooltip= Search diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java index a3fe73456..46b01e46f 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -38,6 +38,7 @@ import org.eclipse.search.internal.core.text.PatternConstructor; import org.eclipse.search.internal.ui.Messages; import org.eclipse.search.internal.ui.SearchMessages; +import org.eclipse.search.ui.IResearchQuery; import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.ISearchResult; import org.eclipse.search.ui.text.AbstractTextSearchResult; @@ -45,7 +46,7 @@ import org.eclipse.search.ui.text.Match; -public class FileSearchQuery implements ISearchQuery { +public class FileSearchQuery implements ISearchQuery, IResearchQuery { private final static class TextSearchResultCollector extends TextSearchRequestor { @@ -195,7 +196,7 @@ private void flushMatches() { } private final FileTextSearchScope fScope; - private final String fSearchText; + private volatile String fSearchText; private final boolean fIsRegEx; private final boolean fIsCaseSensitive; private final boolean fIsWholeWord; @@ -220,11 +221,6 @@ public FileTextSearchScope getSearchScope() { return fScope; } - @Override - public boolean canRunInBackground() { - return true; - } - @Override public IStatus run(final IProgressMonitor monitor) { AbstractTextSearchResult textResult= (AbstractTextSearchResult) getSearchResult(); @@ -251,43 +247,55 @@ private boolean isScopeAllFileTypes() { @Override public String getLabel() { - Pattern searchPattern = getSearchPattern(); - return searchPattern.pattern().isEmpty() ? SearchMessages.FileSearchQuery_label + return isFileNameSearch() ? SearchMessages.FileSearchQuery_label : Messages.format(SearchMessages.TextSearchVisitor_textsearch_task_label, getSearchString()); } + @Override public String getSearchString() { return fSearchText; } + @Override + public void setSearchString(String s) { + this.fSearchText = s; + } + public String getResultLabel(int nMatches) { String searchString= getSearchString(); + boolean research = true; /// TODO? if (!searchString.isEmpty()) { // text search if (isScopeAllFileTypes()) { // search all file extensions if (nMatches == 1) { Object[] args= { searchString, fScope.getDescription() }; - return Messages.format(SearchMessages.FileSearchQuery_singularLabel, args); + return Messages.format(research ? SearchMessages.FileResearchQuery_singularLabel + : SearchMessages.FileSearchQuery_singularLabel, args); } Object[] args= { searchString, Integer.valueOf(nMatches), fScope.getDescription() }; - return Messages.format(SearchMessages.FileSearchQuery_pluralPattern, args); + return Messages.format(research ? SearchMessages.FileResearchQuery_pluralPattern + : SearchMessages.FileSearchQuery_pluralPattern, args); } // search selected file extensions if (nMatches == 1) { Object[] args= { searchString, fScope.getDescription(), fScope.getFilterDescription() }; - return Messages.format(SearchMessages.FileSearchQuery_singularPatternWithFileExt, args); + return Messages.format(research ? SearchMessages.FileResearchQuery_singularPatternWithFileExt + : SearchMessages.FileSearchQuery_singularPatternWithFileExt, args); } Object[] args= { searchString, Integer.valueOf(nMatches), fScope.getDescription(), fScope.getFilterDescription() }; - return Messages.format(SearchMessages.FileSearchQuery_pluralPatternWithFileExt, args); + return Messages.format(research ? SearchMessages.FileResearchQuery_pluralPatternWithFileExt + : SearchMessages.FileSearchQuery_pluralPatternWithFileExt, args); } // file search if (nMatches == 1) { Object[] args= { fScope.getFilterDescription(), fScope.getDescription() }; - return Messages.format(SearchMessages.FileSearchQuery_singularLabel_fileNameSearch, args); + return Messages.format(research ? SearchMessages.FileResearchQuery_singularLabel_fileNameSearch + : SearchMessages.FileSearchQuery_singularLabel_fileNameSearch, args); } Object[] args= { fScope.getFilterDescription(), Integer.valueOf(nMatches), fScope.getDescription() }; - return Messages.format(SearchMessages.FileSearchQuery_pluralPattern_fileNameSearch, args); + return Messages.format(research ? SearchMessages.FileResearchQuery_pluralPattern_fileNameSearch + : SearchMessages.FileSearchQuery_pluralPattern_fileNameSearch, args); } /** @@ -325,11 +333,6 @@ public boolean isWholeWord() { return fIsWholeWord; } - @Override - public boolean canRerun() { - return true; - } - @Override public ISearchResult getSearchResult() { if (fResult == null) { diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java index 75bb63ea7..df90b0baf 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/TextSearchPage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2017 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -20,6 +20,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; @@ -110,6 +111,7 @@ public class TextSearchPage extends DialogPage implements ISearchPage, IReplaceP * @since 3.6 */ private static final String STORE_EXTENSIONS= "EXTENSIONS"; //$NON-NLS-1$ + private static WeakReference lastQuery; private List fPreviousSearchPatterns= new ArrayList<>(HISTORY_SIZE); @@ -274,7 +276,13 @@ private ISearchQuery newQuery() throws CoreException { @Override public boolean performAction() { try { - NewSearchUI.runQueryInBackground(newQuery()); + ISearchQuery last = lastQuery == null ? null : lastQuery.get(); + if (last != null) { + NewSearchUI.cancelQuery(last); + } + ISearchQuery newQuery = newQuery(); + lastQuery=new WeakReference<>(newQuery); + NewSearchUI.runQueryInBackground(newQuery); } catch (CoreException e) { ErrorDialog.openError(getShell(), SearchMessages.TextSearchPage_replace_searchproblems_title, SearchMessages.TextSearchPage_replace_searchproblems_message, e.getStatus()); return false;