/* -------------------------------------------------------------------------------------------- * 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, } from 'vscode-languageserver' import getTailwindState from '../class-names/index' import { State, Settings, EditorState } from './util/state' import { provideCompletions, resolveCompletionItem, } from './providers/completionProvider' import { provideHover } from './providers/hoverProvider' import { URI } from 'vscode-uri' import { getDocumentSettings } from './util/getDocumentSettings' import { provideDiagnostics, updateAllDiagnostics, clearAllDiagnostics, } from './providers/diagnosticsProvider' import { createEmitter } from '../lib/emitter' let connection = createConnection(ProposedFeatures.all) let state: State = { enabled: false, emitter: createEmitter(connection) } let documents = new TextDocuments() let workspaceFolder: string | null const defaultSettings: Settings = { emmetCompletions: false, includeLanguages: {}, validate: true, lint: { utilityConflicts: 'warning', unsupportedApply: 'error', unknownScreen: 'error', unknownVariant: 'error', invalidHelperKey: 'error', unsupportedTailwindDirective: 'error', }, } let globalSettings: Settings = defaultSettings let documentSettings: Map = new Map() documents.onDidOpen((event) => { getDocumentSettings(state, event.document) }) documents.onDidClose((event) => { documentSettings.delete(event.document.uri) }) documents.onDidChangeContent((change) => { provideDiagnostics(state, change.document) }) documents.listen(connection) connection.onInitialize( async (params: InitializeParams): Promise => { const capabilities = params.capabilities const editorState: EditorState = { 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) { state = { ...newState, enabled: true, emitter: state.emitter, editor: editorState, } connection.sendNotification('tailwindcss/configUpdated', [ state.configPath, state.config, state.plugins, ]) updateAllDiagnostics(state) } else { state = { enabled: false, emitter: state.emitter, editor: editorState, } 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 = /^(?.*?):(?[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) { state = { enabled: true, emitter: state.emitter, editor: editorState, ...tailwindState, } } else { state = { enabled: false, emitter: state.emitter, editor: editorState } } return { capabilities: { // textDocumentSync: { // openClose: true, // change: TextDocumentSyncKind.None // }, textDocumentSync: documents.syncKind, 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, }, } } ) 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, ]) }) connection.onDidChangeConfiguration((change) => { if (state.editor.capabilities.configuration) { // Reset all cached document settings state.editor.documentSettings.clear() } else { state.editor.globalSettings = ( (change.settings.tailwindCSS || defaultSettings) ) } updateAllDiagnostics(state) }) connection.onCompletion( (params: CompletionParams): Promise => { if (!state.enabled) return null return provideCompletions(state, params) } ) connection.onCompletionResolve( (item: CompletionItem): CompletionItem => { if (!state.enabled) return null return resolveCompletionItem(state, item) } ) connection.onHover( (params: TextDocumentPositionParams): Hover => { if (!state.enabled) return null return provideHover(state, params) } ) connection.listen()