From abe598500dbadfea9733534dc7c54eb69c64dc75 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Mon, 14 Sep 2026 18:01:49 +0200 Subject: [PATCH] Compare the matcher context once per content type lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ContentTypeCatalog compared the matcher's scope context against the default context for every content type it visited. For a project matcher that comparison resolves the project's content type preferences, which walks preference nodes and the workspace tree, so a single file lookup did hundreds of tree lookups and froze the UI when opening editors. The comparison now happens once in internalFindContentTypesSorted and its result is passed down to ContentType.hasFileSpec. With ContentDescriptionPerformanceTest and its 300 extra xml content types, 1000 project matcher look-ups drop from about 60 ms to 35 ms and the cold description pass over 5000 files from about 1 s to 0.65 s. Assisted-by: multiple AI agents and layers of automated tooling 🤖 --- .../core/internal/content/ContentType.java | 9 +++++++- .../internal/content/ContentTypeCatalog.java | 22 ++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java b/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java index 83791e5425f..81c54a739cf 100644 --- a/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java +++ b/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java @@ -394,7 +394,14 @@ boolean hasBuiltInAssociations() { } boolean hasFileSpec(IScopeContext context, String text, int typeMask) { - if (context.equals(manager.getContext()) || (typeMask & IGNORE_USER_DEFINED) != 0) { + return hasFileSpec(context, context.equals(manager.getContext()), text, typeMask); + } + + /** + * Variant for callers that already know whether the context is the default one. + */ + boolean hasFileSpec(IScopeContext context, boolean defaultContext, String text, int typeMask) { + if (defaultContext || (typeMask & IGNORE_USER_DEFINED) != 0) { return hasFileSpec(text, typeMask, false); } String[] fileSpecs = ContentTypeSettings.getFileSpecs(context, id, typeMask); diff --git a/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java b/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java index f5411eeb252..dd5f0af082d 100644 --- a/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java +++ b/runtime/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java @@ -606,19 +606,21 @@ private IContentType[] internalFindContentTypesFor(ContentTypeMatcher matcher, I */ synchronized private IContentType[][] internalFindContentTypesSorted(ContentTypeMatcher matcher, final String fileName, Comparator sortingPolicy, boolean quickFinish) { IScopeContext context = matcher.getContext(); + // comparing a project scope context is expensive, so do it once per lookup + final boolean defaultContext = context.equals(manager.getContext()); IContentType[][] result = { NO_CONTENT_TYPES, NO_CONTENT_TYPES, NO_CONTENT_TYPES }; Set existing = new HashSet<>(); final Set allByFileName; - if (context.equals(manager.getContext())) { + if (defaultContext) { allByFileName = getDirectlyAssociated(fileName, IContentTypeSettings.FILE_NAME_SPEC); } else { allByFileName = new HashSet<>(getDirectlyAssociated(fileName, IContentTypeSettings.FILE_NAME_SPEC | IContentType.IGNORE_USER_DEFINED)); allByFileName.addAll(matcher.getDirectlyAssociated(this, fileName, IContentTypeSettings.FILE_NAME_SPEC)); } - Set selectedByName = selectMatchingByName(context, allByFileName, Collections.emptySet(), fileName, - IContentType.FILE_NAME_SPEC); + Set selectedByName = selectMatchingByName(context, defaultContext, allByFileName, + Collections.emptySet(), fileName, IContentType.FILE_NAME_SPEC); existing.addAll(selectedByName); result[0] = selectedByName.toArray(new IContentType[selectedByName.size()]); if (result[0].length > 1) { @@ -631,13 +633,14 @@ synchronized private IContentType[][] internalFindContentTypesSorted(ContentType final String fileExtension = ContentTypeManager.getFileExtension(fileName); if (fileExtension != null) { final Set allByFileExtension; - if (context.equals(manager.getContext())) { + if (defaultContext) { allByFileExtension = getDirectlyAssociated(fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC); } else { allByFileExtension = new HashSet<>(getDirectlyAssociated(fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC | IContentType.IGNORE_USER_DEFINED)); allByFileExtension.addAll(matcher.getDirectlyAssociated(this, fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC)); } - Set selectedByExtension = selectMatchingByName(context, allByFileExtension, selectedByName, fileExtension, IContentType.FILE_EXTENSION_SPEC); + Set selectedByExtension = selectMatchingByName(context, defaultContext, allByFileExtension, + selectedByName, fileExtension, IContentType.FILE_EXTENSION_SPEC); existing.addAll(selectedByExtension); if (!selectedByExtension.isEmpty()) { result[1] = selectedByExtension.toArray(new IContentType[selectedByExtension.size()]); @@ -651,7 +654,7 @@ synchronized private IContentType[][] internalFindContentTypesSorted(ContentType } final Set allByFilePattern; - if (context.equals(manager.getContext())) { + if (defaultContext) { allByFilePattern = getMatchingRegexpAssociated(fileName, IContentTypeSettings.FILE_PATTERN_SPEC); } else { allByFilePattern = new HashSet<>(getMatchingRegexpAssociated(fileName, @@ -787,7 +790,9 @@ synchronized protected void organize() { * Processes all content types in source, adding those matching the given file spec to the * destination collection. */ - private Set selectMatchingByName(final IScopeContext context, Collection source, final Collection existing, final String fileSpecText, final int fileSpecType) { + private Set selectMatchingByName(final IScopeContext context, final boolean defaultContext, + Collection source, final Collection existing, final String fileSpecText, + final int fileSpecType) { if (source == null || source.isEmpty()) { return Collections.EMPTY_SET; } @@ -801,7 +806,8 @@ private Set selectMatchingByName(final IScopeContext context, Colle // this content type has built-in associations - visit it later as root return ContentTypeVisitor.RETURN; } - if (contentType == root && !contentType.hasFileSpec(context, fileSpecText, fileSpecType)) { + if (contentType == root + && !contentType.hasFileSpec(context, defaultContext, fileSpecText, fileSpecType)) { // it is the root and does not match the file name - do not add it nor look into its children return ContentTypeVisitor.RETURN; }