Skip to content

File search: don't re-evaluate match filters in the UI thread - #4338

Open
iloveeclipse wants to merge 1 commit into
eclipse-platform:masterfrom
iloveeclipse:ui_freeze_on_search
Open

File search: don't re-evaluate match filters in the UI thread#4338
iloveeclipse wants to merge 1 commit into
eclipse-platform:masterfrom
iloveeclipse:ui_freeze_on_search

Conversation

@iloveeclipse

Copy link
Copy Markdown
Member

FileTreeContentProvider#elementsChanged(..) evaluated the active match filters again for every match of every updated file. The filter state of a match is however already computed once per match by AbstractTextSearchResult#didAddMatch(..) (and updated when the filters change) and is cached in Match#isFiltered(), which is also what initialize(..) and AbstractTextSearchViewPage#getDisplayedMatchCount(..) use.

Re-evaluating the filters is not only redundant, it is expensive: OuterProjectFileFilter calls IWorkspaceRoot#findFilesForLocationURI(..), which iterates over all projects of the workspace. With a search producing thousands of matches this ran for every match in the UI thread on every batched update and froze the UI. The provider now reads the already computed filter state instead.

The collection of the updated line elements was reworked as well: the matches of a file are enumerated only once, no matter how many lines of that file were updated, only the updated lines are remembered instead of all lines of the touched files, and the enumeration stops as soon as all updated lines are known to have matches. Since line elements have identity semantics (LineElement doesn't implement
equals(..)/hashCode()), identity based sets are used.

OuterProjectFileFilter is still evaluated once per match by the search result, so it now remembers the filter state per file instead of repeating the workspace lookup for every match of that file. The states are kept in a weak map keyed by the file handles the matches hold, so they are collected together with the search result they were computed for and the filter, which is shared by all file search results, doesn't keep anything alive. A resource change listener invalidates the states if projects are added, removed, opened, closed or moved - the only changes that can modify which files represent a location - and deregisters itself again as soon as no states are left. The listener is (un)registered without holding a lock of the filter to avoid a lock order inversion with the workspace notification.

Added test for OuterProjectFileFilter (missed in the original eclipse-platform/eclipse.platform.text#144).

Fixes #4337

Assisted-by: Github Copilot (Claude Opus 5)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cache publication can race with project-change invalidation and retain stale filter states.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Optimizes file-search filtering to prevent UI freezes caused by redundant match-filter evaluation.

Changes:

  • Reuses cached match filter state during tree updates.
  • Caches outer-project filter results with resource-change invalidation.
  • Adds nested-project filtering tests.
File summaries
File Description
FileTreeContentProvider.java Optimizes incremental tree updates.
OuterProjectFileFilter.java Adds per-file filter caching and invalidation.
NestedProjectFilterTest.java Tests nested-project filtering behavior.
AllFileSearchTests.java Registers the new test class.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Test Results

   858 files  ± 0     858 suites  ±0   54m 0s ⏱️ - 4m 13s
 8 222 tests + 6   7 979 ✅ + 6  243 💤 ±0  0 ❌ ±0 
20 568 runs  +18  19 898 ✅ +18  670 💤 ±0  0 ❌ ±0 

Results for commit 4e8f78b. ± Comparison against base commit a680fa8.

♻️ This comment has been updated with latest results.

FileTreeContentProvider#elementsChanged(..) evaluated the active match
filters again for every match of every updated file. The filter state of
a match is however already computed once per match by
AbstractTextSearchResult#didAddMatch(..) (and updated when the filters
change) and is cached in Match#isFiltered(), which is also what
initialize(..) and AbstractTextSearchViewPage#getDisplayedMatchCount(..)
use.

Re-evaluating the filters is not only redundant, it is expensive:
OuterProjectFileFilter calls IWorkspaceRoot#findFilesForLocationURI(..),
which iterates over all projects of the workspace. With a search
producing thousands of matches this ran for every match in the UI thread
on every batched update and froze the UI. The provider now reads the
already computed filter state instead.

The collection of the updated line elements was reworked as well: the
matches of a file are enumerated only once, no matter how many lines of
that file were updated, only the updated lines are remembered instead of
all lines of the touched files, and the enumeration stops as soon as all
updated lines are known to have matches. Since line elements have
identity semantics (LineElement doesn't implement
equals(..)/hashCode()), identity based sets are used.

OuterProjectFileFilter is still evaluated once per match by the search
result, so it now remembers the filter state per file instead of
repeating the workspace lookup for every match of that file. The states
are kept in a weak map keyed by the file handles the matches hold, so
they are collected together with the search result they were computed
for and the filter, which is shared by all file search results, doesn't
keep anything alive. A resource change listener invalidates the states
if projects are added, removed, opened, closed or moved - the only
changes that can modify which files represent a location - and
deregisters itself again as soon as no states are left. The listener is
(un)registered without holding a lock of the filter to avoid a lock
order inversion with the workspace notification.

Added test for OuterProjectFileFilter (missed in the original
eclipse-platform/eclipse.platform.text#144).

Fixes eclipse-platform#4337

Assisted-by: Github Copilot (Claude Opus 5)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cache invalidation, listener lifecycle, and multi-file enumeration still have correctness and performance issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment on lines +57 to +59
private volatile Map<IFile, Boolean> filterStates = newFilterStates();

private final AtomicBoolean isListening = new AtomicBoolean();
Comment on lines +75 to +80
private void ensureListeningToProjectChanges() {
if (!isListening.get() && isListening.compareAndSet(false, true)) {
ResourcesPlugin.getWorkspace().addResourceChangeListener(projectChangeListener,
IResourceChangeEvent.POST_CHANGE);
}
}
Comment on lines +86 to +92
for (IResourceDelta projectDelta : delta.getAffectedChildren()) {
if (projectDelta.getKind() != IResourceDelta.CHANGED) {
return true; // project added or removed
}
int flags= projectDelta.getFlags();
if ((flags & (IResourceDelta.OPEN | IResourceDelta.DESCRIPTION | IResourceDelta.MOVED_FROM
| IResourceDelta.MOVED_TO | IResourceDelta.LOCAL_CHANGED | IResourceDelta.REPLACED)) != 0) {
Comment on lines +340 to +343
LineElement lineElement = ((FileMatch) match).getLineElement();
if (updatedLines.contains(lineElement) && linesWithMatches.add(lineElement)
&& linesWithMatches.size() == updatedLines.size()) {
return linesWithMatches; // all updated lines have matches
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search freezes UI if "Show only most nested match" filter is enabled

2 participants