Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions packages/tailwindcss-language-server/src/language/css-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -121,6 +121,7 @@ export class CssServer {
async function withDocumentAndSettings<T>(
uri: string,
callback: (result: {
original: TextDocument
document: TextDocument
settings: LanguageSettings | undefined
}) => T | Promise<T>,
Expand All @@ -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,
Expand Down
46 changes: 46 additions & 0 deletions packages/tailwindcss-language-server/tests/css/css-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] })
},
})