tailwind-ctp-intellisense/packages/tailwindcss-intellisense/src/lsp/server.ts

234 lines
6.7 KiB
TypeScript

/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
createConnection,
TextDocuments,
ProposedFeatures,
TextDocumentSyncKind,
CompletionItem,
InitializeParams,
InitializeResult,
CompletionParams,
CompletionList,
Hover,
TextDocumentPositionParams,
DidChangeConfigurationNotification,
CodeActionParams,
CodeAction,
} from 'vscode-languageserver'
import getTailwindState from '../class-names/index'
import { State, Settings, EditorState } from 'tailwindcss-language-service'
import {
resolveCompletionItem,
doComplete,
doHover,
doCodeActions,
} from 'tailwindcss-language-service'
import { URI } from 'vscode-uri'
import {
provideDiagnostics,
updateAllDiagnostics,
clearAllDiagnostics,
} from './providers/diagnostics/diagnosticsProvider'
import { createEmitter } from '../lib/emitter'
import { registerDocumentColorProvider } from './providers/documentColorProvider'
import { TextDocument } from 'vscode-languageserver-textdocument'
let connection = createConnection(ProposedFeatures.all)
const state: State = { enabled: false, emitter: createEmitter(connection) }
let documents = new TextDocuments(TextDocument)
let workspaceFolder: string | null
const defaultSettings: Settings = {
tabSize: 2,
emmetCompletions: false,
includeLanguages: {},
experimental: {
classRegex: [],
},
showPixelEquivalents: true,
rootFontSize: 16,
validate: true,
lint: {
cssConflict: 'warning',
invalidApply: 'error',
invalidScreen: 'error',
invalidVariant: 'error',
invalidConfigPath: 'error',
invalidTailwindDirective: 'error',
},
}
let globalSettings: Settings = defaultSettings
let documentSettings: Map<string, Settings> = new Map()
documents.onDidClose((event) => {
documentSettings.delete(event.document.uri)
})
documents.onDidChangeContent((change) => {
if (!state.enabled) return
provideDiagnostics(state, change.document)
})
documents.listen(connection)
connection.onInitialize(
async (params: InitializeParams): Promise<InitializeResult> => {
const capabilities = params.capabilities
state.editor = {
connection,
documents,
documentSettings,
globalSettings,
userLanguages:
params.initializationOptions &&
params.initializationOptions.userLanguages
? params.initializationOptions.userLanguages
: {},
capabilities: {
configuration:
capabilities.workspace && !!capabilities.workspace.configuration,
diagnosticRelatedInformation:
capabilities.textDocument &&
capabilities.textDocument.publishDiagnostics &&
capabilities.textDocument.publishDiagnostics.relatedInformation,
},
}
const tailwindState = await getTailwindState(
params.rootPath || URI.parse(params.rootUri).path,
{
// @ts-ignore
onChange: (newState: State): void => {
if (newState && !newState.error) {
Object.assign(state, newState, { enabled: true })
connection.sendNotification('tailwindcss/configUpdated', [
state.configPath,
state.config,
state.plugins,
])
updateAllDiagnostics(state)
} else {
state.enabled = false
if (newState && newState.error) {
const payload: {
message: string
file?: string
line?: number
} = { message: newState.error.message }
const lines = newState.error.stack.toString().split('\n')
const match = /^(?<file>.*?):(?<line>[0-9]+)$/.exec(lines[0])
if (match) {
payload.file = match.groups.file
payload.line = parseInt(match.groups.line, 10)
}
connection.sendNotification('tailwindcss/configError', [payload])
}
clearAllDiagnostics(state)
// TODO
// connection.sendNotification('tailwindcss/configUpdated', [null])
}
},
}
)
if (tailwindState) {
Object.assign(state, tailwindState, { enabled: true })
} else {
state.enabled = false
}
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Full,
completionProvider: {
resolveProvider: true,
triggerCharacters: [
// class attributes
'"',
"'",
'`',
// between class names
' ',
// @apply and emmet-style
'.',
// config/theme helper
'[',
// TODO: restart server if separater changes?
typeof state.separator === 'undefined' ? ':' : state.separator,
],
},
hoverProvider: true,
codeActionProvider: true,
},
}
}
)
connection.onInitialized &&
connection.onInitialized(async () => {
if (state.editor.capabilities.configuration) {
connection.client.register(
DidChangeConfigurationNotification.type,
undefined
)
}
connection.sendNotification('tailwindcss/configUpdated', [
state.configPath,
state.config,
state.plugins,
])
registerDocumentColorProvider(state)
})
connection.onDidChangeConfiguration((change) => {
if (state.editor.capabilities.configuration) {
// Reset all cached document settings
state.editor.documentSettings.clear()
} else {
state.editor.globalSettings = <Settings>(
(change.settings.tailwindCSS || defaultSettings)
)
}
updateAllDiagnostics(state)
})
connection.onCompletion(
(params: CompletionParams): Promise<CompletionList> => {
if (!state.enabled) return null
let document = state.editor.documents.get(params.textDocument.uri)
if (!document) return null
return doComplete(state, document, params.position)
}
)
connection.onCompletionResolve(
(item: CompletionItem): Promise<CompletionItem> => {
if (!state.enabled) return null
return resolveCompletionItem(state, item)
}
)
connection.onHover(
(params: TextDocumentPositionParams): Promise<Hover> => {
if (!state.enabled) return null
let document = state.editor.documents.get(params.textDocument.uri)
if (!document) return null
return doHover(state, document, params.position)
}
)
connection.onCodeAction(
(params: CodeActionParams): Promise<CodeAction[]> => {
if (!state.enabled) return null
return doCodeActions(state, params)
}
)
connection.listen()