tailwind-ctp-intellisense/packages/tailwindcss-intellisense/src/extension.ts

212 lines
6.7 KiB
TypeScript
Raw Normal View History

2020-04-11 21:20:45 +00:00
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as path from 'path'
import {
workspace as Workspace,
window as Window,
ExtensionContext,
TextDocument,
OutputChannel,
WorkspaceFolder,
Uri,
ConfigurationScope,
2020-04-11 21:20:45 +00:00
} from 'vscode'
import {
LanguageClient,
LanguageClientOptions,
TransportKind,
} from 'vscode-languageclient'
2020-04-23 18:54:01 +00:00
import { registerConfigErrorHandler } from './lib/registerConfigErrorHandler'
import { DEFAULT_LANGUAGES } from './lib/languages'
import isObject from './util/isObject'
import { dedupe, equal } from './util/array'
2020-05-09 16:24:56 +00:00
import { createEmitter } from './lib/emitter'
import { onMessage } from './lsp/notifications'
2020-08-12 17:45:36 +00:00
import { registerColorDecorator } from './lib/registerColorDecorator'
const CLIENT_ID = 'tailwindcss-intellisense'
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
2020-04-11 21:20:45 +00:00
let clients: Map<string, LanguageClient> = new Map()
let languages: Map<string, string[]> = new Map()
2020-04-11 21:20:45 +00:00
let _sortedWorkspaceFolders: string[] | undefined
function sortedWorkspaceFolders(): string[] {
if (_sortedWorkspaceFolders === void 0) {
_sortedWorkspaceFolders = Workspace.workspaceFolders
? Workspace.workspaceFolders
.map((folder) => {
let result = folder.uri.toString()
if (result.charAt(result.length - 1) !== '/') {
result = result + '/'
}
return result
})
.sort((a, b) => {
return a.length - b.length
})
: []
}
return _sortedWorkspaceFolders
}
Workspace.onDidChangeWorkspaceFolders(
() => (_sortedWorkspaceFolders = undefined)
)
function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
let sorted = sortedWorkspaceFolders()
for (let element of sorted) {
let uri = folder.uri.toString()
if (uri.charAt(uri.length - 1) !== '/') {
uri = uri + '/'
}
if (uri.startsWith(element)) {
return Workspace.getWorkspaceFolder(Uri.parse(element))!
}
}
return folder
}
function getUserLanguages(folder?: WorkspaceFolder): Record<string, string> {
const langs = Workspace.getConfiguration('tailwindCSS', folder)
.includeLanguages
return isObject(langs) ? langs : {}
}
2020-04-11 21:20:45 +00:00
export function activate(context: ExtensionContext) {
let module = context.asAbsolutePath(path.join('dist', 'server', 'index.js'))
let outputChannel: OutputChannel = Window.createOutputChannel(CLIENT_ID)
2020-04-11 21:20:45 +00:00
// TODO: check if the actual language MAPPING changed
// not just the language IDs
// e.g. "plaintext" already exists but you change it from "html" to "css"
Workspace.onDidChangeConfiguration((event) => {
clients.forEach((client, key) => {
const folder = Workspace.getWorkspaceFolder(Uri.parse(key))
if (event.affectsConfiguration('tailwindCSS', folder)) {
const userLanguages = getUserLanguages(folder)
if (userLanguages) {
const userLanguageIds = Object.keys(userLanguages)
const newLanguages = dedupe([
...DEFAULT_LANGUAGES,
...userLanguageIds,
])
if (!equal(newLanguages, languages.get(folder.uri.toString()))) {
languages.set(folder.uri.toString(), newLanguages)
if (client) {
clients.delete(folder.uri.toString())
client.stop()
bootWorkspaceClient(folder)
}
}
}
}
})
})
function bootWorkspaceClient(folder: WorkspaceFolder) {
if (clients.has(folder.uri.toString())) {
2020-04-11 21:20:45 +00:00
return
}
// placeholder so we don't boot another server before this one is ready
clients.set(folder.uri.toString(), null)
let debugOptions = {
execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`],
}
let serverOptions = {
run: { module, transport: TransportKind.ipc },
debug: {
module,
transport: TransportKind.ipc,
options: debugOptions,
},
}
let clientOptions: LanguageClientOptions = {
documentSelector: languages
.get(folder.uri.toString())
.map((language) => ({
scheme: 'file',
2020-04-11 21:20:45 +00:00
language,
pattern: `${folder.uri.fsPath}/**/*`,
2020-04-11 21:20:45 +00:00
})),
diagnosticCollectionName: CLIENT_ID,
workspaceFolder: folder,
outputChannel: outputChannel,
middleware: {},
initializationOptions: {
userLanguages: getUserLanguages(folder),
},
}
let client = new LanguageClient(
CLIENT_ID,
CLIENT_NAME,
serverOptions,
clientOptions
)
client.onReady().then(() => {
2020-05-09 16:24:56 +00:00
let emitter = createEmitter(client)
registerConfigErrorHandler(emitter)
2020-08-12 17:45:36 +00:00
registerColorDecorator(client, context, emitter)
onMessage(client, 'getConfiguration', async (scope) => {
return Workspace.getConfiguration('tailwindCSS', scope)
})
})
client.start()
clients.set(folder.uri.toString(), client)
}
function didOpenTextDocument(document: TextDocument): void {
// We are only interested in language mode text
if (document.uri.scheme !== 'file') {
2020-04-11 21:20:45 +00:00
return
}
let uri = document.uri
2020-04-11 21:20:45 +00:00
let folder = Workspace.getWorkspaceFolder(uri)
// Files outside a folder can't be handled. This might depend on the language.
// Single file languages like JSON might handle files outside the workspace folders.
if (!folder) {
return
}
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder)
if (!languages.has(folder.uri.toString())) {
languages.set(
folder.uri.toString(),
dedupe([...DEFAULT_LANGUAGES, ...Object.keys(getUserLanguages())])
2020-04-11 21:20:45 +00:00
)
}
bootWorkspaceClient(folder)
2020-04-11 21:20:45 +00:00
}
Workspace.onDidOpenTextDocument(didOpenTextDocument)
Workspace.textDocuments.forEach(didOpenTextDocument)
Workspace.onDidChangeWorkspaceFolders((event) => {
for (let folder of event.removed) {
let client = clients.get(folder.uri.toString())
if (client) {
clients.delete(folder.uri.toString())
client.stop()
}
}
})
}
export function deactivate(): Thenable<void> {
let promises: Thenable<void>[] = []
for (let client of clients.values()) {
promises.push(client.stop())
}
return Promise.all(promises).then(() => undefined)
}