Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add support for @config
  • Loading branch information
bradlc committed Oct 17, 2022
commit bf57dd14bca3bd90c114fa32d88a5fee6db7ec8a
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ async function validateTextDocument(textDocument: TextDocument): Promise<void> {
.filter((diagnostic) => {
if (
diagnostic.code === 'unknownAtRules' &&
/Unknown at rule @(tailwind|apply)/.test(diagnostic.message)
/Unknown at rule @(tailwind|apply|config)/.test(diagnostic.message)
) {
return false
}
Expand Down
44 changes: 43 additions & 1 deletion packages/tailwindcss-language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
FileChangeType,
Disposable,
TextDocumentIdentifier,
DocumentLinkRequest,
DocumentLinkParams,
DocumentLink,
} from 'vscode-languageserver/node'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { URI } from 'vscode-uri'
Expand Down Expand Up @@ -68,6 +71,7 @@ import {
} from './lsp/diagnosticsProvider'
import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
import { debounce } from 'debounce'
import { getModuleDependencies } from './util/getModuleDependencies'
import assert from 'assert'
Expand Down Expand Up @@ -188,6 +192,7 @@ interface ProjectService {
onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]>
onColorPresentation(params: ColorPresentationParams): Promise<ColorPresentation[]>
onCodeAction(params: CodeActionParams): Promise<CodeAction[]>
onDocumentLinks(params: DocumentLinkParams): DocumentLink[]
}

type ProjectConfig = { folder: string; configPath?: string; documentSelector?: string[] }
Expand Down Expand Up @@ -299,6 +304,27 @@ async function createProjectService(
getDocumentSymbols: (uri: string) => {
return connection.sendRequest('@/tailwindCSS/getDocumentSymbols', { uri })
},
async readDirectory(document, directory) {
try {
directory = path.resolve(path.dirname(getFileFsPath(document.uri)), directory)
let dirents = await fs.promises.readdir(directory, { withFileTypes: true })
let result: Array<[string, { isDirectory: boolean }] | null> = await Promise.all(
dirents.map(async (dirent) => {
let isDirectory = dirent.isDirectory()
return (await isExcluded(
state,
document,
path.join(directory, dirent.name, isDirectory ? '/' : '')
))
? null
: [dirent.name, { isDirectory }]
})
)
return result.filter((item) => item !== null)
} catch {
return []
}
},
},
}

Expand Down Expand Up @@ -1028,6 +1054,14 @@ async function createProjectService(
if (!settings.tailwindCSS.codeActions) return null
return doCodeActions(state, params)
},
onDocumentLinks(params: DocumentLinkParams): DocumentLink[] {
if (!state.enabled) return null
let document = documentService.getDocument(params.textDocument.uri)
if (!document) return null
return getDocumentLinks(state, document, (linkPath) =>
URI.file(path.resolve(path.dirname(URI.parse(document.uri).fsPath), linkPath)).toString()
)
},
provideDiagnostics: debounce((document: TextDocument) => {
if (!state.enabled) return
provideDiagnostics(state, document)
Expand Down Expand Up @@ -1485,6 +1519,7 @@ class TW {
this.connection.onDocumentColor(this.onDocumentColor.bind(this))
this.connection.onColorPresentation(this.onColorPresentation.bind(this))
this.connection.onCodeAction(this.onCodeAction.bind(this))
this.connection.onDocumentLinks(this.onDocumentLinks.bind(this))
}

private updateCapabilities() {
Expand All @@ -1499,6 +1534,7 @@ class TW {
capabilities.add(HoverRequest.type, { documentSelector: null })
capabilities.add(DocumentColorRequest.type, { documentSelector: null })
capabilities.add(CodeActionRequest.type, { documentSelector: null })
capabilities.add(DocumentLinkRequest.type, { documentSelector: null })

capabilities.add(CompletionRequest.type, {
documentSelector: null,
Expand Down Expand Up @@ -1564,6 +1600,10 @@ class TW {
return this.getProject(params.textDocument)?.onCodeAction(params) ?? null
}

onDocumentLinks(params: DocumentLinkParams): DocumentLink[] {
return this.getProject(params.textDocument)?.onDocumentLinks(params) ?? null
}

listen() {
this.connection.listen()
}
Expand Down Expand Up @@ -1605,7 +1645,8 @@ function supportsDynamicRegistration(connection: Connection, params: InitializeP
params.capabilities.textDocument.hover?.dynamicRegistration &&
params.capabilities.textDocument.colorProvider?.dynamicRegistration &&
params.capabilities.textDocument.codeAction?.dynamicRegistration &&
params.capabilities.textDocument.completion?.dynamicRegistration
params.capabilities.textDocument.completion?.dynamicRegistration &&
params.capabilities.textDocument.documentLink?.dynamicRegistration
)
}

Expand All @@ -1630,6 +1671,7 @@ connection.onInitialize(async (params: InitializeParams): Promise<InitializeResu
hoverProvider: true,
colorProvider: true,
codeActionProvider: true,
documentLinkProvider: {},
completionProvider: {
resolveProvider: true,
triggerCharacters: [...TRIGGER_CHARACTERS, ':'],
Expand Down
7 changes: 5 additions & 2 deletions packages/tailwindcss-language-server/src/util/isExcluded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { State } from 'tailwindcss-language-service/src/util/state'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { getFileFsPath } from './uri'

export default async function isExcluded(state: State, document: TextDocument): Promise<boolean> {
export default async function isExcluded(
state: State,
document: TextDocument,
file: string = getFileFsPath(document.uri)
): Promise<boolean> {
let settings = await state.editor.getConfiguration(document.uri)
let file = getFileFsPath(document.uri)

for (let pattern of settings.tailwindCSS.files.exclude) {
if (minimatch(file, path.join(state.editor.folder, pattern))) {
Expand Down
61 changes: 61 additions & 0 deletions packages/tailwindcss-language-service/src/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,20 @@ function provideCssDirectiveCompletions(
},
},
]),
...(semver.gte(state.version, '3.2.0')
? [
{
label: '@config',
documentation: {
kind: 'markdown' as typeof MarkupKind.Markdown,
value: `[Tailwind CSS Documentation](${docsUrl(
state.version,
'functions-and-directives/#config'
)})`,
},
},
]
: []),
]

return {
Expand All @@ -1016,6 +1030,52 @@ function provideCssDirectiveCompletions(
}
}

async function provideConfigDirectiveCompletions(
state: State,
document: TextDocument,
position: Position
): Promise<CompletionList> {
if (!isCssContext(state, document, position)) {
return null
}

if (!semver.gte(state.version, '3.2.0')) {
return null
}

let text = document.getText({ start: { line: position.line, character: 0 }, end: position })
let match = text.match(/@config\s*(?<partial>'[^']*|"[^"]*)$/)
if (!match) {
return null
}
let partial = match.groups.partial.slice(1) // remove quote
let valueBeforeLastSlash = partial.substring(0, partial.lastIndexOf('/'))
let valueAfterLastSlash = partial.substring(partial.lastIndexOf('/') + 1)

return {
isIncomplete: false,
items: (await state.editor.readDirectory(document, valueBeforeLastSlash || '.'))
.filter(([name, type]) => type.isDirectory || /\.c?js$/.test(name))
.map(([name, type]) => ({
label: type.isDirectory ? name + '/' : name,
kind: type.isDirectory ? 19 : 17,
textEdit: {
newText: type.isDirectory ? name + '/' : name,
range: {
start: {
line: position.line,
character: position.character - valueAfterLastSlash.length,
},
end: position,
},
},
command: type.isDirectory
? { command: 'editor.action.triggerSuggest', title: '' }
: undefined,
})),
}
}

async function provideEmmetCompletions(
state: State,
document: TextDocument,
Expand Down Expand Up @@ -1104,6 +1164,7 @@ export async function doComplete(
provideVariantsDirectiveCompletions(state, document, position) ||
provideTailwindDirectiveCompletions(state, document, position) ||
provideLayerDirectiveCompletions(state, document, position) ||
(await provideConfigDirectiveCompletions(state, document, position)) ||
(await provideCustomClassNameCompletions(state, document, position))

if (result) return result
Expand Down
57 changes: 57 additions & 0 deletions packages/tailwindcss-language-service/src/documentLinksProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { State } from './util/state'
import type { DocumentLink, Range, TextDocument } from 'vscode-languageserver'
import { isCssDoc } from './util/css'
import { getLanguageBoundaries } from './util/getLanguageBoundaries'
import { findAll, indexToPosition } from './util/find'
import { getTextWithoutComments } from './util/doc'
import { absoluteRange } from './util/absoluteRange'
import * as semver from './util/semver'

export function getDocumentLinks(
state: State,
document: TextDocument,
resolveTarget: (linkPath: string) => string
): DocumentLink[] {
return getConfigDirectiveLinks(state, document, resolveTarget)
}

function getConfigDirectiveLinks(
state: State,
document: TextDocument,
resolveTarget: (linkPath: string) => string
): DocumentLink[] {
if (!semver.gte(state.version, '3.2.0')) {
return []
}

let links: DocumentLink[] = []
let ranges: Range[] = []

if (isCssDoc(state, document)) {
ranges.push(undefined)
} else {
let boundaries = getLanguageBoundaries(state, document)
if (!boundaries) return []
ranges.push(...boundaries.filter((b) => b.type === 'css').map(({ range }) => range))
}

for (let range of ranges) {
let text = getTextWithoutComments(document, 'css', range)
let matches = findAll(/@config\s*(?<path>'[^']+'|"[^"]+")/g, text)

for (let match of matches) {
links.push({
target: resolveTarget(match.groups.path.slice(1, -1)),
range: absoluteRange(
{
start: indexToPosition(text, match.index + match[0].length - match.groups.path.length),
end: indexToPosition(text, match.index + match[0].length),
},
range
),
})
}
}

return links
}
4 changes: 4 additions & 0 deletions packages/tailwindcss-language-service/src/util/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export type EditorState = {
}
getConfiguration: (uri?: string) => Promise<Settings>
getDocumentSymbols: (uri: string) => Promise<SymbolInformation[]>
readDirectory: (
document: TextDocument,
directory: string
) => Promise<Array<[name: string, type: { isDirectory: boolean }]>>
}

type DiagnosticSeveritySetting = 'ignore' | 'warning' | 'error'
Expand Down