diff --git a/package.json b/package.json index 485e36e..1e49537 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,12 @@ "default": true, "description": "Enable Peeking from HTML tags in addition to classnames and IDs. React components are ignored, but it's a good idea to disable this feature when using Angular" }, + "cssPeek.peekVariables": { + "scope": "window", + "type": "boolean", + "default": true, + "description": "Include preprocessor variable definitions (e.g. SCSS `$foo`, LESS `@foo`) in peek results. Disable to hide variables from go-to and peek definition." + }, "cssPeek.peekFromLanguages": { "scope": "window", "type": "array", diff --git a/server/src/core/findDefinition.ts b/server/src/core/findDefinition.ts index 539c1ab..c112da8 100644 --- a/server/src/core/findDefinition.ts +++ b/server/src/core/findDefinition.ts @@ -1,8 +1,5 @@ import * as path from "path"; -import { - Location, - SymbolInformation, -} from "vscode-languageserver/node"; +import { Location, SymbolInformation } from "vscode-languageserver/node"; import { TextDocument } from "vscode-languageserver-textdocument"; import { getCSSLanguageService, @@ -57,8 +54,10 @@ function resolveSymbolName(symbols: SymbolInformation[], i: number): string { export function findSymbols( selector: Selector, - stylesheetMap: StylesheetMap + stylesheetMap: StylesheetMap, + options: { peekVariables?: boolean } = {} ): SymbolInformation[] { + const { peekVariables = true } = options; const foundSymbols: SymbolInformation[] = []; // Construct RegExp of selector to test against the symbols @@ -113,6 +112,9 @@ export function findSymbols( console.log(`Searching through them all for /${selection}/`); symbols.forEach((symbol, i) => { + if (!peekVariables && symbol.kind === SymbolKind.Variable) { + return; + } const name = resolveSymbolName(symbols, i); // console.log( @@ -144,7 +146,10 @@ export function findSymbols( export function findDefinition( selector: Selector, - stylesheetMap: StylesheetMap + stylesheetMap: StylesheetMap, + options: { peekVariables?: boolean } = {} ): Location[] { - return findSymbols(selector, stylesheetMap).map(({ location }) => location); + return findSymbols(selector, stylesheetMap, options).map( + ({ location }) => location + ); } diff --git a/server/src/server.ts b/server/src/server.ts index 933a961..8d02eaa 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -127,6 +127,7 @@ connection.onInitialize((params) => { /* Sync Configuration Settings */ interface Settings { supportTags: boolean; + peekVariables: boolean; peekFromLanguages: string[]; peekToExclude: string[]; } @@ -147,6 +148,7 @@ connection.onInitialized(() => { // The global settings, used when the `workspace/configuration` request is not supported by the client. const defaultSettings: Settings = { supportTags: true, + peekVariables: true, peekFromLanguages: ["html"], peekToExclude: ["**/node_modules/**", "**/bower_components/**"], }; @@ -225,11 +227,13 @@ connection.onDefinition( return null; } - return findDefinition(selector, styleSheets); + return findDefinition(selector, styleSheets, { + peekVariables: settings.peekVariables, + }); } ); -connection.onWorkspaceSymbol(({ query }) => { +connection.onWorkspaceSymbol(async ({ query }) => { if (query.length < 2) return []; const selectors: Selector[] = [ { @@ -246,8 +250,19 @@ connection.onWorkspaceSymbol(({ query }) => { }, ]; + const settings = hasConfigurationCapability + ? ((await connection.workspace.getConfiguration({ + section: "cssPeek", + })) as Settings) || defaultSettings + : globalSettings; + return selectors.reduce( - (p, selector) => [...p, ...findSymbols(selector, styleSheets)], + (p, selector) => [ + ...p, + ...findSymbols(selector, styleSheets, { + peekVariables: settings.peekVariables, + }), + ], [] ); }); diff --git a/tests/src/findDefinition.test.ts b/tests/src/findDefinition.test.ts index e52d85a..9d83c31 100644 --- a/tests/src/findDefinition.test.ts +++ b/tests/src/findDefinition.test.ts @@ -1,8 +1,15 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { TextDocument as ServerTextDocument } from "vscode-languageserver"; +import { + SymbolInformation, + SymbolKind, + TextDocument as ServerTextDocument, +} from "vscode-languageserver"; -import { findDefinition } from "../../server/out/core/findDefinition"; +import { + findDefinition, + findSymbols, +} from "../../server/out/core/findDefinition"; import { create } from "../../server/out/logger"; import type { StylesheetMap, Selector } from "../../server/src/types"; @@ -74,4 +81,54 @@ suite("findDefinition", () => { const lines = defs.map((d) => d.range.start.line); assert.deepStrictEqual(lines, [0, 16, 16, 19]); }); + + test("peekVariables toggle filters Variable-kind symbols", () => { + // Pre-populate the symbol cache with a Variable symbol whose name would + // otherwise match the selector. Isolates the filter from the language + // service's name resolution (preprocessor variables like `$foo` don't + // normally match HTML class/id/tag regexes). + const uri = "file:///__test_variables.scss"; + const doc = ServerTextDocument.create(uri, "scss", 1, ""); + const variableSymbol: SymbolInformation = { + name: ".test", + kind: SymbolKind.Variable, + location: { + uri, + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 0 }, + }, + }, + }; + const buildMap = (): StylesheetMap => ({ + [uri]: { document: doc, symbols: [variableSymbol] }, + }); + const selector: Selector = { attribute: "class", value: "test" }; + + assert.strictEqual( + findSymbols(selector, buildMap(), { peekVariables: true }).length, + 1, + "variable should be included when peekVariables=true" + ); + assert.strictEqual( + findSymbols(selector, buildMap()).length, + 1, + "variable should be included when option is omitted (default true)" + ); + assert.strictEqual( + findSymbols(selector, buildMap(), { peekVariables: false }).length, + 0, + "variable should be filtered when peekVariables=false" + ); + }); + + test("peekVariables=false does not filter non-variable symbols", async () => { + const localMap = await loadStylesheets(["stylesheet.css"]); + const selector: Selector = { attribute: "class", value: "test" }; + const defs = findDefinition(selector, localMap, { peekVariables: false }); + assert.ok( + defs.length > 0, + "class selectors should still resolve when peekVariables=false" + ); + }); });