diff --git a/packages/tailwindcss-language-server/src/language/css-server.ts b/packages/tailwindcss-language-server/src/language/css-server.ts index a43910b22..73e967fcc 100644 --- a/packages/tailwindcss-language-server/src/language/css-server.ts +++ b/packages/tailwindcss-language-server/src/language/css-server.ts @@ -13,7 +13,7 @@ import { CompletionItemKind, Connection, } from 'vscode-languageserver/node' -import { TextDocument } from 'vscode-languageserver-textdocument' +import { Position, TextDocument } from 'vscode-languageserver-textdocument' import { Utils, URI } from 'vscode-uri' import { getLanguageModelCache } from './languageModelCache' import { Stylesheet } from 'vscode-css-languageservice' @@ -121,6 +121,7 @@ export class CssServer { async function withDocumentAndSettings( uri: string, callback: (result: { + original: TextDocument document: TextDocument settings: LanguageSettings | undefined }) => T | Promise, @@ -130,13 +131,64 @@ export class CssServer { return null } return await callback({ + original: document, document: createVirtualCssDocument(document), settings: await getDocumentSettings(document), }) } + function isInImportDirective(doc: TextDocument, pos: Position) { + let text = doc.getText({ + start: { line: pos.line, character: 0 }, + end: pos, + }) + + // Scan backwards to see if we're inside an `@import` directive + let foundImport = false + let foundDirective = false + + for (let i = text.length - 1; i >= 0; i--) { + let char = text[i] + if (char === '\n') break + + if (char === '(' && !foundDirective) { + if (text.startsWith(' source(', i - 7)) { + foundDirective = true + } + + // + else if (text.startsWith(' theme(', i - 6)) { + foundDirective = true + } + + // + else if (text.startsWith(' prefix(', i - 7)) { + foundDirective = true + } + } + + // + else if (char === '@' && !foundImport) { + if (text.startsWith('@import ', i)) { + foundImport = true + } + } + } + + return foundImport && foundDirective + } + connection.onCompletion(async ({ textDocument, position }, _token) => - withDocumentAndSettings(textDocument.uri, async ({ document, settings }) => { + withDocumentAndSettings(textDocument.uri, async ({ original, document, settings }) => { + // If we're inside source(…), prefix(…), or theme(…), don't show + // completions from the CSS language server + if (isInImportDirective(original, position)) { + return { + isIncomplete: false, + items: [], + } + } + let result = await cssLanguageService.doComplete2( document, position, diff --git a/packages/tailwindcss-language-server/tests/css/css-server.test.ts b/packages/tailwindcss-language-server/tests/css/css-server.test.ts index e8970e08a..388e94af7 100644 --- a/packages/tailwindcss-language-server/tests/css/css-server.test.ts +++ b/packages/tailwindcss-language-server/tests/css/css-server.test.ts @@ -689,3 +689,49 @@ defineTest({ expect(await doc.diagnostics()).toEqual([]) }, }) + +defineTest({ + name: 'completions are hidden inside @import source(…)/theme(…)/prefix(…) functions', + prepare: async ({ root }) => ({ + client: await createClient({ + server: 'css', + root, + }), + }), + handle: async ({ client }) => { + let doc = await client.open({ + lang: 'tailwindcss', + name: 'file-1.css', + text: css` + @import './file.css' source(none); + @import './file.css' theme(inline); + @import './file.css' prefix(tw); + @import './file.css' source(none) theme(inline) prefix(tw); + `, + }) + + // @import './file.css' source(none) + // ^ + // @import './file.css' theme(inline); + // ^ + // @import './file.css' prefix(tw); + // ^ + let completionsA = await doc.completions({ line: 0, character: 29 }) + let completionsB = await doc.completions({ line: 1, character: 28 }) + let completionsC = await doc.completions({ line: 2, character: 29 }) + + expect(completionsA).toEqual({ isIncomplete: false, items: [] }) + expect(completionsB).toEqual({ isIncomplete: false, items: [] }) + expect(completionsC).toEqual({ isIncomplete: false, items: [] }) + + // @import './file.css' source(none) theme(inline) prefix(tw); + // ^ ^ ^ + let completionsD = await doc.completions({ line: 3, character: 29 }) + let completionsE = await doc.completions({ line: 3, character: 41 }) + let completionsF = await doc.completions({ line: 3, character: 56 }) + + expect(completionsD).toEqual({ isIncomplete: false, items: [] }) + expect(completionsE).toEqual({ isIncomplete: false, items: [] }) + expect(completionsF).toEqual({ isIncomplete: false, items: [] }) + }, +}) diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 31a8ca66a..824781252 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -2,7 +2,7 @@ ## Prerelease -- Nothing yet! +- Hide completions from CSS language server inside `@import "…" source(…)` ([#1091](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1091)) # 0.14.12