- name
- poly-context
- description
- Implement PolyContext feature gating, environment detection, or language substitution.
# PolyContext
`PolyContext` answers "what coding environment am I in right now." It is a **standalone platform
mechanism** — [poly-symbols](../poly-symbols/SKILL.md) is its biggest consumer (gating which
`PolySymbolQueryScopeContributor`/`QueryConfigurator` registrations apply), but `PolyContext` itself
has no dependency on PolySymbols or on web frameworks being involved at all. Its defining advantage
is that it is **heavily optimized for performance** (cached, cheap, `RequiresReadLock`-only), which
makes it the right tool for *any* "is feature X relevant here" check that would otherwise get
reinvented per-feature as an ad-hoc cached provider — not just "which JS framework is active."
Confirmed non-web examples already in this repo: `PolyContext.PKG_MANAGER_RUBY_GEMS`/
`PKG_MANAGER_SYMFONY_BUNDLES` constants live right next to `PKG_MANAGER_NODE_PACKAGES` in
`PolyContext.kt`, and `ruby/tests/.../gem/GemContextTest.kt` queries a `"timecop-context"` kind
(is the `timecop` Ruby gem present?) with no PolySymbols query executor or web framework in sight.
For any given context **`kind`** (e.g. `"framework"`), at most one **`name`** is active at a
location, or `null` if none applies.
```kotlin
PolyContext.get("framework", psiElement) // "vue" | "angular" | "astro" | null
```
## Two location overloads
```kotlin
PolyContext.get(kind: PolyContextKind, location: PsiElement): PolyContextName?
PolyContext.get(kind: PolyContextKind, location: VirtualFile, project: Project): PolyContextName?
```
| Location | Cost | Capability |
|---|---|---|
| `PsiElement` | normal | can inspect the PSI tree (e.g. check imports) — use for most coding-assistance logic |
| `VirtualFile` + `Project` | cheap, indexing-safe | no PSI access; results may legitimately differ from the `PsiElement` answer for the same file once parsed |
**The `VirtualFile`+`Project` overload is what makes `PolyContext` usable during language
substitution** — i.e. *before* any PSI exists for the file, when the platform is still deciding
which `Language` to parse it as. Concrete chain in this repo:
`WebFrameworkHtmlLanguageSubstitutor.getLanguage(file: VirtualFile, project: Project)` (a
`LanguageSubstitutor`, `plugins/JavaScriptLanguage/web-platform/.../WebFrameworkHtmlLanguageSubstitutor.kt:15`)
→ `WebFramework.forContext(file, project)` → `PolySymbolFramework.inLocation(file, project)`
(`community/platform/polySymbols/src/com/intellij/polySymbols/framework/PolySymbolFramework.kt:52-53`)
→ `PolyContext.get(KIND_FRAMEWORK, file, project)`. The result picks which HTML dialect
(Vue/Angular/plain) the file's `Language` is substituted to, entirely before a PSI tree is built.
Canonical PolySymbols use case: web framework detection. Only one of Vue/Angular/React/Astro can be
the active `framework` at a location, and each plugin's `PolySymbolQueryScopeContributor`/
`QueryConfigurator` gates its registrations with `.inContext { it.framework == MyFramework.ID }`
(see [poly-symbols/references/query-model.md](../poly-symbols/references/query-model.md)) — but
this is one consumer among many, not the reason `PolyContext` exists.
## `PolyContextProvider` — direct/dynamic detection
Implement `isEnabled()` (or `isForbidden()`) and register at EP `com.intellij.polySymbols.context`.
Results are called very often — **cache them** (`CachedValuesManager`).
```kotlin
PolyContext.get("stimulus-context", psiElement) == "true"
```
**Blocking**: `isForbidden()` with name `"any"` suppresses an entire context kind — e.g. a Python
templating (Blade-style) blocker disables the whole `framework` kind so it never conflicts with
Vue/Angular detection.
Real examples in this repo/docs: `StimulusContextProvider` (checks for a JS import, caches the
result), `PyTemplatesWebContextBlocker` (forbids `framework` when Python templating is detected).
For a full custom-detector example, see `VueFileContextProvider`
(`contrib/vuejs/vuejs-backend/src/org/jetbrains/vuejs/context/VueFileContextProvider.kt`) — returns
true unconditionally for `.vue` files, and for any HTML-compatible file whose `<script src="...">`
matches a known Vue CDN/filename pattern (a cached-value scan, not a per-call check). Angular's
equivalent is `AngularCliContextProvider` (`contrib/Angular/.../org/angular2/cli/`), registered at
`polySymbols.context kind="framework" name="angular"`.
## Context rules — the declarative, faster alternative
Many providers redundantly re-check the same thing (is package X a dependency?). Context **rules**
are declarative and let the platform compute proximity once instead of running N ad-hoc providers.
### Via Web Types
Under a top-level `contexts-config` property in a Web Types JSON file (see
[poly-symbols/references/web-types.md](../poly-symbols/references/web-types.md)):
`kind` → `name` → `enable-when`/`disable-when`. (Pre-2024.2 files use a deprecated layout where
`name` is top-level and `kind` a sibling property — don't use that shape for new files.)
`enable-when` rule kinds: `file-extensions`, `file-name-patterns`, `ide-libraries`,
`project-tool-executables`, `node-packages`, `ruby-gems`.
`disable-when` is more limited: `file-extensions`, `file-name-patterns` only.
**Proximity scoring** (lower = closer/stronger match; the winning `name` for a `kind` is whichever
rule has the lowest total):
- `file-extensions` / `file-name-patterns` → `0.0` (perfect match)
- `ide-libraries` / `ruby-gems` → `Double.MAX_VALUE` (project/module-level match)
- `node-packages` → computed from `package.json` location + dependency type: same directory as
base `0.0`, `+1.0` per parent directory walked up, plus importance modifiers —
`peerDependencies +0.1`, `bundledDependencies +0.2`, `dependencies +0.3`,
`optionalDependencies +0.4`, `devDependencies +0.5`, indirect (`node_modules`) `+0.6`.
Web Types shipping a context rule are registered at EP `com.intellij.polySymbols.webTypes`
(currently Node Package Manager only, requires the JS plugin) with two naming strategies: name the
file after the real npm package if the rule should trigger on that package's presence, or use an
arbitrary name + `enableByDefault="true"` if it should always apply.
### Via code — `PolyContextRulesProvider`
For rules that can't be expressed as static file-based data, implement a `PolySymbolQueryConfigurator`
and override `getContextRulesProviders(project, dir): List<PolyContextRulesProvider>` at EP
`com.intellij.polySymbols.queryConfigurator`. **Rule output must be stable** — a change triggers
project rescanning and cache invalidation.
## `.ws-context` — user override file
Since 2024.1.2, users can force context values with a `.ws-context` JSON file:
- `<context kind>` → `<context name>` — direct top-level assignment (implicitly applies to `/**`).
- `<GLOB path>` → nested context-details object — GLOB supports only `*` in the final segment;
`**` matches nested directories.
When multiple patterns match a file, resolution priority is: (1) most path segments excluding `**`,
(2) prefer patterns with an actual file-name match (not ending in `**`/`/`), (3) first-defined wins.
## `PolyContextSourceProximityProvider`
For integrating a new package manager or a language with a global-library concept, register at EP
`com.intellij.polySymbols.contextSourceProximityProvider` and implement `calculateProximity()`,
computing proximity per `sourceNames` entry matching a `sourceKind` (e.g. a dependency type). Return
a `Result` with `modificationTrackers` — keep the tracker count minimal since it feeds **every**
`PolyContext.get()` call.
## Related
- **[poly-symbols](../poly-symbols/SKILL.md)** — PolyContext's biggest consumer, not something it depends on.
- **[poly-symbols/references/web-types.md](../poly-symbols/references/web-types.md)** — `contexts-config` file format.
- Official docs: [Poly Symbols Context](https://plugins.jetbrains.com/docs/intellij/polysymbols-context.html)
(the only official doc page for this API — despite the URL/page name, treat it as documenting a
general-purpose context mechanism, not something PolySymbols-exclusive).
View on GitHub