Skip to content

Speed up project selector matching #1381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 22, 2025
Merged

Speed up project selector matching #1381

merged 4 commits into from
May 22, 2025

Conversation

thecrypticace
Copy link
Contributor

@thecrypticace thecrypticace commented May 22, 2025

When a request comes in to the language server for a document we have to determine what project that document belongs to. This is because your workspace can have many tailwind config files and potentially across different Tailwind CSS versions.

The process for this happens in two stages:

  1. Initialization computes a list of selectors for a project. These selectors contain glob patterns that match a filepath to one or more projects. In addition to the glob pattern is a priority which is a "lowest match wins" scale. So things like user configured patterns in settings are the most important, then your config files, then content from the content array in v3 or detected sources in v4, approximate path matches based on folder, etc…
  2. When we get a request for hovers, completions, etc… we check every project against the list of selectors to see if it matches and at what priority. This involves compiling glob patterns.

The lowest priority match wins. If multiple projects match a document at the same priority then first match wins. If a project contains a selector that discards a particular document then that project is removed from consideration before continuing.

Now for the problem, we were re-compiling globs over and over and over again. Normally, for a small project this isn't an issue but automatic content detection means that a large number of paths can be returned. And if you have lots of projects? Welp… this adds up. What's more is that when VSCode needed to compute document symbols requests were made to our language server (… i don't know why actually) which then incurred a perf hit caused by these globs being repeatedly recompiled… and since this is effectively a single threaded operation it delays any waiting promises.

So this PR does two things:

  • It moves the sorting that was happening for every request for every project to happen during project initialization. If selectors are computed or recomputed they are sorted then. We do this sorting to move the negative matchers to the front so we can disqualify files from matching a project quicker.
  • We cache the compiled matchers. This is especially important if you have several v4 projects and many of them list the same paths.

In a realworld project this lowered the time to show suggestions from emmet from 4 SECONDS (omg) to about 15-20ms on an M3 Max machine.

aside: There was also sometimes a delay in time even getting completion requests due to the single-threaded nature of JS. That could also end up being multiple seconds. So in reality the time could be from range from 4–8s depending on when you made the request.

@thecrypticace thecrypticace marked this pull request as ready for review May 22, 2025 21:16
}

export function createPathMatcher(): PathMatcher {
let matchers = new DefaultMap<string, picomatch.Matcher>((pattern) => {
Copy link
Member

@RobinMalfait RobinMalfait May 22, 2025

Choose a reason for hiding this comment

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

DefaultMap coming in clutch and saving our butts once again

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FOR REAL

Copy link
Member

@RobinMalfait RobinMalfait left a comment

Choose a reason for hiding this comment

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

Makes sene!

@thecrypticace thecrypticace merged commit 9c2a9d5 into main May 22, 2025
12 checks passed
@thecrypticace thecrypticace deleted the fix/selector-perf branch May 22, 2025 21:52
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.

2 participants