2021-05-03 17:00:04 +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,
|
2022-04-13 13:05:41 +00:00
|
|
|
languages as Languages,
|
2021-05-03 17:00:04 +00:00
|
|
|
ExtensionContext,
|
|
|
|
TextDocument,
|
|
|
|
OutputChannel,
|
|
|
|
WorkspaceFolder,
|
|
|
|
Uri,
|
|
|
|
commands,
|
|
|
|
SymbolInformation,
|
|
|
|
Position,
|
|
|
|
Range,
|
|
|
|
TextEditorDecorationType,
|
2021-05-04 09:59:34 +00:00
|
|
|
RelativePattern,
|
2021-05-04 11:40:50 +00:00
|
|
|
ConfigurationScope,
|
2022-01-17 14:07:55 +00:00
|
|
|
WorkspaceConfiguration,
|
2022-04-13 13:05:41 +00:00
|
|
|
CompletionItem,
|
|
|
|
CompletionItemKind,
|
|
|
|
CompletionList,
|
|
|
|
ProviderResult,
|
|
|
|
SnippetString,
|
|
|
|
TextEdit,
|
2021-05-03 17:00:04 +00:00
|
|
|
} from 'vscode'
|
2021-05-04 11:57:10 +00:00
|
|
|
import {
|
|
|
|
LanguageClient,
|
|
|
|
LanguageClientOptions,
|
2021-05-04 12:26:55 +00:00
|
|
|
ServerOptions,
|
2021-05-04 11:57:10 +00:00
|
|
|
TransportKind,
|
|
|
|
State as LanguageClientState,
|
2021-05-04 12:28:08 +00:00
|
|
|
RevealOutputChannelOn,
|
2022-04-13 13:05:41 +00:00
|
|
|
Disposable,
|
2021-05-04 11:57:10 +00:00
|
|
|
} from 'vscode-languageclient/node'
|
2021-06-01 11:37:15 +00:00
|
|
|
import { languages as defaultLanguages } from 'tailwindcss-language-service/src/util/languages'
|
|
|
|
import isObject from 'tailwindcss-language-service/src/util/isObject'
|
2021-05-03 17:00:04 +00:00
|
|
|
import { dedupe, equal } from 'tailwindcss-language-service/src/util/array'
|
2021-10-01 13:11:45 +00:00
|
|
|
import namedColors from 'color-name'
|
2022-01-07 17:13:11 +00:00
|
|
|
import minimatch from 'minimatch'
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
const colorNames = Object.keys(namedColors)
|
|
|
|
|
|
|
|
const CLIENT_ID = 'tailwindcss-intellisense'
|
|
|
|
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
|
|
|
|
|
2021-05-06 17:20:25 +00:00
|
|
|
const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}'
|
2021-05-04 09:59:34 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
let clients: Map<string, LanguageClient> = new Map()
|
|
|
|
let languages: Map<string, string[]> = new Map()
|
2021-05-04 09:59:34 +00:00
|
|
|
let searchedFolders: Set<string> = new Set()
|
2021-05-03 17:00:04 +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
|
|
|
|
}
|
|
|
|
|
|
|
|
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 : {}
|
|
|
|
}
|
|
|
|
|
2022-01-17 14:07:55 +00:00
|
|
|
function getGlobalExcludePatterns(scope: ConfigurationScope): string[] {
|
|
|
|
return Object.entries(Workspace.getConfiguration('files', scope).get('exclude'))
|
|
|
|
.filter(([, value]) => value === true)
|
2022-01-07 17:13:11 +00:00
|
|
|
.map(([key]) => key)
|
2022-01-24 12:21:15 +00:00
|
|
|
.filter(Boolean)
|
2022-01-17 14:07:55 +00:00
|
|
|
}
|
2022-01-07 17:13:11 +00:00
|
|
|
|
2022-01-17 14:07:55 +00:00
|
|
|
function getExcludePatterns(scope: ConfigurationScope): string[] {
|
2022-01-07 17:13:11 +00:00
|
|
|
return [
|
2022-01-17 14:07:55 +00:00
|
|
|
...getGlobalExcludePatterns(scope),
|
2022-01-24 12:21:15 +00:00
|
|
|
...(<string[]>Workspace.getConfiguration('tailwindCSS', scope).get('files.exclude')).filter(
|
|
|
|
Boolean
|
|
|
|
),
|
2022-01-07 17:13:11 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
function isExcluded(file: string, folder: WorkspaceFolder): boolean {
|
|
|
|
let exclude = getExcludePatterns(folder)
|
|
|
|
|
|
|
|
for (let pattern of exclude) {
|
|
|
|
if (minimatch(file, path.join(folder.uri.fsPath, pattern))) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-01-24 12:21:15 +00:00
|
|
|
function mergeExcludes(settings: WorkspaceConfiguration, scope: ConfigurationScope): any {
|
2022-01-07 17:13:11 +00:00
|
|
|
return {
|
|
|
|
...settings,
|
|
|
|
files: {
|
|
|
|
...settings.files,
|
2022-01-24 12:21:15 +00:00
|
|
|
exclude: getExcludePatterns(scope),
|
2022-01-07 17:13:11 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-18 09:51:22 +00:00
|
|
|
export async function activate(context: ExtensionContext) {
|
2022-04-13 12:54:33 +00:00
|
|
|
let module = context.asAbsolutePath(path.join('dist', 'server.js'))
|
|
|
|
let prod = path.join('dist', 'tailwindServer.js')
|
2021-05-18 09:51:22 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
await Workspace.fs.stat(Uri.joinPath(context.extensionUri, prod))
|
|
|
|
module = context.asAbsolutePath(prod)
|
|
|
|
} catch (_) {}
|
|
|
|
|
2021-05-04 14:05:52 +00:00
|
|
|
let outputChannel: OutputChannel
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
context.subscriptions.push(
|
|
|
|
commands.registerCommand('tailwindCSS.showOutput', () => {
|
2021-05-04 14:05:52 +00:00
|
|
|
if (outputChannel) {
|
|
|
|
outputChannel.show()
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
})
|
|
|
|
)
|
|
|
|
|
2021-05-04 09:59:34 +00:00
|
|
|
let watcher = Workspace.createFileSystemWatcher(`**/${CONFIG_FILE_GLOB}`, false, true, true)
|
|
|
|
|
|
|
|
watcher.onDidCreate((uri) => {
|
|
|
|
let folder = Workspace.getWorkspaceFolder(uri)
|
|
|
|
if (!folder) {
|
|
|
|
return
|
|
|
|
}
|
2022-01-07 17:13:11 +00:00
|
|
|
if (!isExcluded(uri.fsPath, folder)) {
|
|
|
|
folder = getOuterMostWorkspaceFolder(folder)
|
|
|
|
bootWorkspaceClient(folder)
|
|
|
|
}
|
2021-05-04 09:59:34 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
context.subscriptions.push(watcher)
|
|
|
|
|
2021-05-03 17:00:04 +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"
|
2021-05-04 15:27:05 +00:00
|
|
|
context.subscriptions.push(
|
|
|
|
Workspace.onDidChangeConfiguration((event) => {
|
2022-04-25 14:06:31 +00:00
|
|
|
;[...clients].forEach(([key, client]) => {
|
2021-05-04 15:27:05 +00:00
|
|
|
const folder = Workspace.getWorkspaceFolder(Uri.parse(key))
|
2022-04-25 14:06:31 +00:00
|
|
|
let reboot = false
|
2021-05-04 15:27:05 +00:00
|
|
|
|
2022-04-25 14:06:31 +00:00
|
|
|
if (event.affectsConfiguration('tailwindCSS.includeLanguages', folder)) {
|
2021-05-04 15:27:05 +00:00
|
|
|
const userLanguages = getUserLanguages(folder)
|
|
|
|
if (userLanguages) {
|
|
|
|
const userLanguageIds = Object.keys(userLanguages)
|
2021-06-01 11:37:15 +00:00
|
|
|
const newLanguages = dedupe([...defaultLanguages, ...userLanguageIds])
|
2021-05-04 15:27:05 +00:00
|
|
|
if (!equal(newLanguages, languages.get(folder.uri.toString()))) {
|
|
|
|
languages.set(folder.uri.toString(), newLanguages)
|
2022-04-25 14:06:31 +00:00
|
|
|
reboot = true
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-25 14:06:31 +00:00
|
|
|
|
|
|
|
if (event.affectsConfiguration('tailwindCSS.experimental.configFile', folder)) {
|
|
|
|
reboot = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reboot && client) {
|
|
|
|
clients.delete(folder.uri.toString())
|
|
|
|
client.stop()
|
|
|
|
bootWorkspaceClient(folder)
|
|
|
|
}
|
2021-05-04 15:27:05 +00:00
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
})
|
2021-05-04 15:27:05 +00:00
|
|
|
)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-04-13 13:05:41 +00:00
|
|
|
let cssServerBooted = false
|
|
|
|
async function bootCssServer() {
|
|
|
|
if (cssServerBooted) return
|
|
|
|
cssServerBooted = true
|
|
|
|
|
|
|
|
let module = context.asAbsolutePath(path.join('dist', 'cssServer.js'))
|
|
|
|
let prod = path.join('dist', 'tailwindModeServer.js')
|
|
|
|
|
|
|
|
try {
|
|
|
|
await Workspace.fs.stat(Uri.joinPath(context.extensionUri, prod))
|
|
|
|
module = context.asAbsolutePath(prod)
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
let client = new LanguageClient(
|
|
|
|
'tailwindcss-intellisense-css',
|
|
|
|
'Tailwind CSS',
|
|
|
|
{
|
|
|
|
run: {
|
|
|
|
module,
|
|
|
|
transport: TransportKind.ipc,
|
|
|
|
},
|
|
|
|
debug: {
|
|
|
|
module,
|
|
|
|
transport: TransportKind.ipc,
|
|
|
|
options: {
|
|
|
|
execArgv: ['--nolazy', '--inspect=6051'],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
documentSelector: [{ language: 'tailwindcss' }],
|
|
|
|
outputChannelName: 'Tailwind CSS Language Mode',
|
|
|
|
synchronize: { configurationSection: ['css'] },
|
|
|
|
middleware: {
|
|
|
|
provideCompletionItem(document, position, context, token, next) {
|
|
|
|
function updateRanges(item: CompletionItem) {
|
|
|
|
const range = item.range
|
|
|
|
if (
|
|
|
|
range instanceof Range &&
|
|
|
|
range.end.isAfter(position) &&
|
|
|
|
range.start.isBeforeOrEqual(position)
|
|
|
|
) {
|
|
|
|
item.range = { inserting: new Range(range.start, position), replacing: range }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateLabel(item: CompletionItem) {
|
|
|
|
if (item.kind === CompletionItemKind.Color) {
|
|
|
|
item.label = {
|
|
|
|
label: item.label as string,
|
|
|
|
description: item.documentation as string,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateProposals(
|
|
|
|
r: CompletionItem[] | CompletionList | null | undefined
|
|
|
|
): CompletionItem[] | CompletionList | null | undefined {
|
|
|
|
if (r) {
|
|
|
|
;(Array.isArray(r) ? r : r.items).forEach(updateRanges)
|
|
|
|
;(Array.isArray(r) ? r : r.items).forEach(updateLabel)
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
const isThenable = <T>(obj: ProviderResult<T>): obj is Thenable<T> =>
|
|
|
|
obj && (<any>obj)['then']
|
|
|
|
|
|
|
|
const r = next(document, position, context, token)
|
|
|
|
if (isThenable<CompletionItem[] | CompletionList | null | undefined>(r)) {
|
|
|
|
return r.then(updateProposals)
|
|
|
|
}
|
|
|
|
return updateProposals(r)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-09-07 15:45:22 +00:00
|
|
|
await client.start()
|
|
|
|
context.subscriptions.push(initCompletionProvider())
|
2022-04-13 13:05:41 +00:00
|
|
|
|
|
|
|
function initCompletionProvider(): Disposable {
|
|
|
|
const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/
|
|
|
|
|
|
|
|
return Languages.registerCompletionItemProvider(['tailwindcss'], {
|
|
|
|
provideCompletionItems(doc: TextDocument, pos: Position) {
|
|
|
|
let lineUntilPos = doc.getText(new Range(new Position(pos.line, 0), pos))
|
|
|
|
let match = lineUntilPos.match(regionCompletionRegExpr)
|
|
|
|
if (match) {
|
|
|
|
let range = new Range(new Position(pos.line, match[1].length), pos)
|
|
|
|
let beginProposal = new CompletionItem('#region', CompletionItemKind.Snippet)
|
|
|
|
beginProposal.range = range
|
|
|
|
TextEdit.replace(range, '/* #region */')
|
|
|
|
beginProposal.insertText = new SnippetString('/* #region $1*/')
|
|
|
|
beginProposal.documentation = 'Folding Region Start'
|
|
|
|
beginProposal.filterText = match[2]
|
|
|
|
beginProposal.sortText = 'za'
|
|
|
|
let endProposal = new CompletionItem('#endregion', CompletionItemKind.Snippet)
|
|
|
|
endProposal.range = range
|
|
|
|
endProposal.insertText = '/* #endregion */'
|
|
|
|
endProposal.documentation = 'Folding Region End'
|
|
|
|
endProposal.sortText = 'zb'
|
|
|
|
endProposal.filterText = match[2]
|
|
|
|
return [beginProposal, endProposal]
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
function bootWorkspaceClient(folder: WorkspaceFolder) {
|
|
|
|
if (clients.has(folder.uri.toString())) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-04 11:57:10 +00:00
|
|
|
let colorDecorationType: TextEditorDecorationType
|
|
|
|
function clearColors(): void {
|
|
|
|
if (colorDecorationType) {
|
|
|
|
colorDecorationType.dispose()
|
|
|
|
colorDecorationType = undefined
|
|
|
|
}
|
|
|
|
}
|
2021-05-04 15:27:05 +00:00
|
|
|
context.subscriptions.push({
|
|
|
|
dispose() {
|
|
|
|
if (colorDecorationType) {
|
|
|
|
colorDecorationType.dispose()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2021-05-04 11:57:10 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
// placeholder so we don't boot another server before this one is ready
|
|
|
|
clients.set(folder.uri.toString(), null)
|
|
|
|
|
2021-05-04 09:59:34 +00:00
|
|
|
if (!languages.has(folder.uri.toString())) {
|
|
|
|
languages.set(
|
|
|
|
folder.uri.toString(),
|
2021-06-01 11:37:15 +00:00
|
|
|
dedupe([...defaultLanguages, ...Object.keys(getUserLanguages(folder))])
|
2021-05-04 09:59:34 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-05-04 14:05:52 +00:00
|
|
|
if (!outputChannel) {
|
|
|
|
outputChannel = Window.createOutputChannel(CLIENT_NAME)
|
2021-05-04 15:27:05 +00:00
|
|
|
context.subscriptions.push(outputChannel)
|
2021-05-04 14:05:52 +00:00
|
|
|
commands.executeCommand('setContext', 'tailwindCSS.hasOutputChannel', true)
|
|
|
|
}
|
|
|
|
|
2021-06-04 13:00:00 +00:00
|
|
|
let configuration = {
|
|
|
|
editor: Workspace.getConfiguration('editor', folder),
|
2022-01-07 17:13:11 +00:00
|
|
|
tailwindCSS: mergeExcludes(Workspace.getConfiguration('tailwindCSS', folder), folder),
|
2021-06-04 13:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let inspectPort = configuration.tailwindCSS.get('inspectPort')
|
|
|
|
|
2021-05-04 12:26:55 +00:00
|
|
|
let serverOptions: ServerOptions = {
|
2021-06-04 13:00:00 +00:00
|
|
|
run: {
|
|
|
|
module,
|
|
|
|
transport: TransportKind.ipc,
|
|
|
|
options: { execArgv: inspectPort === null ? [] : [`--inspect=${inspectPort}`] },
|
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
debug: {
|
|
|
|
module,
|
|
|
|
transport: TransportKind.ipc,
|
2021-05-04 12:26:55 +00:00
|
|
|
options: {
|
|
|
|
execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`],
|
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
|
|
|
}
|
2021-06-04 13:00:00 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
let clientOptions: LanguageClientOptions = {
|
|
|
|
documentSelector: languages.get(folder.uri.toString()).map((language) => ({
|
|
|
|
scheme: 'file',
|
|
|
|
language,
|
2022-01-17 16:55:31 +00:00
|
|
|
pattern: `${folder.uri.fsPath.replace(/[\[\]\{\}]/g, '?')}/**/*`,
|
2021-05-03 17:00:04 +00:00
|
|
|
})),
|
|
|
|
diagnosticCollectionName: CLIENT_ID,
|
|
|
|
workspaceFolder: folder,
|
|
|
|
outputChannel: outputChannel,
|
2021-05-04 12:28:08 +00:00
|
|
|
revealOutputChannelOn: RevealOutputChannelOn.Never,
|
2021-05-03 17:00:04 +00:00
|
|
|
middleware: {
|
|
|
|
async resolveCompletionItem(item, token, next) {
|
|
|
|
let result = await next(item, token)
|
|
|
|
let selections = Window.activeTextEditor.selections
|
|
|
|
if (selections.length > 1 && result.additionalTextEdits?.length > 0) {
|
|
|
|
let length =
|
|
|
|
selections[0].start.character - result.additionalTextEdits[0].range.start.character
|
|
|
|
let prefixLength =
|
|
|
|
result.additionalTextEdits[0].range.end.character -
|
|
|
|
result.additionalTextEdits[0].range.start.character
|
|
|
|
|
|
|
|
let ranges = selections.map((selection) => {
|
|
|
|
return new Range(
|
|
|
|
new Position(selection.start.line, selection.start.character - length),
|
|
|
|
new Position(
|
|
|
|
selection.start.line,
|
|
|
|
selection.start.character - length + prefixLength
|
|
|
|
)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
if (
|
|
|
|
ranges
|
|
|
|
.map((range) => Window.activeTextEditor.document.getText(range))
|
|
|
|
.every((text, _index, arr) => arr.indexOf(text) === 0)
|
|
|
|
) {
|
|
|
|
// all the same
|
|
|
|
result.additionalTextEdits = ranges.map((range) => {
|
|
|
|
return { range, newText: result.additionalTextEdits[0].newText }
|
|
|
|
})
|
|
|
|
} else {
|
2022-04-13 13:05:41 +00:00
|
|
|
result.insertText =
|
|
|
|
typeof result.label === 'string' ? result.label : result.label.label
|
2021-05-03 17:00:04 +00:00
|
|
|
result.additionalTextEdits = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
},
|
|
|
|
async provideDocumentColors(document, token, next) {
|
|
|
|
let colors = await next(document, token)
|
|
|
|
let editableColors = colors.filter((color) => {
|
|
|
|
let text =
|
|
|
|
Workspace.textDocuments.find((doc) => doc === document)?.getText(color.range) ?? ''
|
|
|
|
return new RegExp(
|
|
|
|
`-\\[(${colorNames.join('|')}|((?:#|rgba?\\(|hsla?\\())[^\\]]+)\\]$`
|
|
|
|
).test(text)
|
|
|
|
})
|
|
|
|
let nonEditableColors = colors.filter((color) => !editableColors.includes(color))
|
|
|
|
|
|
|
|
if (!colorDecorationType) {
|
|
|
|
colorDecorationType = Window.createTextEditorDecorationType({
|
|
|
|
before: {
|
|
|
|
width: '0.8em',
|
|
|
|
height: '0.8em',
|
|
|
|
contentText: ' ',
|
|
|
|
border: '0.1em solid',
|
|
|
|
margin: '0.1em 0.2em 0',
|
|
|
|
},
|
|
|
|
dark: {
|
|
|
|
before: {
|
|
|
|
borderColor: '#eeeeee',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
light: {
|
|
|
|
before: {
|
|
|
|
borderColor: '#000000',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
Window.visibleTextEditors
|
|
|
|
.find((editor) => editor.document === document)
|
|
|
|
?.setDecorations(
|
|
|
|
colorDecorationType,
|
|
|
|
nonEditableColors.map(({ range, color }) => ({
|
|
|
|
range,
|
|
|
|
renderOptions: {
|
|
|
|
before: {
|
|
|
|
backgroundColor: `rgba(${color.red * 255}, ${color.green * 255}, ${
|
|
|
|
color.blue * 255
|
|
|
|
}, ${color.alpha})`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
)
|
|
|
|
|
|
|
|
return editableColors
|
|
|
|
},
|
|
|
|
workspace: {
|
2021-05-04 11:40:50 +00:00
|
|
|
configuration: (params) => {
|
|
|
|
return params.items.map(({ section, scopeUri }) => {
|
|
|
|
let scope: ConfigurationScope = folder
|
|
|
|
if (scopeUri) {
|
|
|
|
let doc = Workspace.textDocuments.find((doc) => doc.uri.toString() === scopeUri)
|
|
|
|
if (doc) {
|
|
|
|
scope = {
|
|
|
|
languageId: doc.languageId,
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2021-05-04 11:40:50 +00:00
|
|
|
}
|
2022-01-07 17:13:11 +00:00
|
|
|
let settings = Workspace.getConfiguration(section, scope)
|
|
|
|
|
|
|
|
if (section === 'tailwindCSS') {
|
|
|
|
return mergeExcludes(settings, scope)
|
|
|
|
}
|
|
|
|
|
|
|
|
return settings
|
2021-05-04 11:40:50 +00:00
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
initializationOptions: {
|
|
|
|
userLanguages: getUserLanguages(folder),
|
|
|
|
},
|
|
|
|
synchronize: {
|
2022-01-17 14:08:15 +00:00
|
|
|
configurationSection: ['files', 'editor', 'tailwindCSS'],
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
|
|
|
}
|
2021-05-04 09:59:34 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
let client = new LanguageClient(CLIENT_ID, CLIENT_NAME, serverOptions, clientOptions)
|
|
|
|
|
2022-09-07 15:45:22 +00:00
|
|
|
client.onNotification('@/tailwindCSS/error', async ({ message }) => {
|
|
|
|
let action = await Window.showErrorMessage(message, 'Go to output')
|
|
|
|
if (action === 'Go to output') {
|
|
|
|
commands.executeCommand('tailwindCSS.showOutput')
|
|
|
|
}
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-09-07 15:45:22 +00:00
|
|
|
client.onNotification('@/tailwindCSS/clearColors', () => clearColors())
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-09-07 15:45:22 +00:00
|
|
|
client.onRequest('@/tailwindCSS/getDocumentSymbols', async ({ uri }) => {
|
|
|
|
return commands.executeCommand<SymbolInformation[]>(
|
|
|
|
'vscode.executeDocumentSymbolProvider',
|
|
|
|
Uri.parse(uri)
|
|
|
|
)
|
2021-05-03 17:00:04 +00:00
|
|
|
})
|
|
|
|
|
2021-05-04 11:57:10 +00:00
|
|
|
client.onDidChangeState(({ newState }) => {
|
|
|
|
if (newState === LanguageClientState.Stopped) {
|
|
|
|
clearColors()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
client.start()
|
|
|
|
clients.set(folder.uri.toString(), client)
|
|
|
|
}
|
|
|
|
|
2021-05-04 09:59:34 +00:00
|
|
|
async function didOpenTextDocument(document: TextDocument): Promise<void> {
|
2022-04-13 13:05:41 +00:00
|
|
|
if (document.languageId === 'tailwindcss') {
|
|
|
|
bootCssServer()
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
// We are only interested in language mode text
|
|
|
|
if (document.uri.scheme !== 'file') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let uri = document.uri
|
|
|
|
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)
|
|
|
|
|
2021-05-04 09:59:34 +00:00
|
|
|
if (searchedFolders.has(folder.uri.toString())) return
|
|
|
|
|
|
|
|
searchedFolders.add(folder.uri.toString())
|
|
|
|
|
|
|
|
let [configFile] = await Workspace.findFiles(
|
|
|
|
new RelativePattern(folder, `**/${CONFIG_FILE_GLOB}`),
|
2022-01-07 17:13:11 +00:00
|
|
|
`{${getExcludePatterns(folder).join(',')}}`,
|
2021-05-04 09:59:34 +00:00
|
|
|
1
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!configFile) {
|
|
|
|
return
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bootWorkspaceClient(folder)
|
|
|
|
}
|
|
|
|
|
2021-05-04 15:27:05 +00:00
|
|
|
context.subscriptions.push(Workspace.onDidOpenTextDocument(didOpenTextDocument))
|
2021-05-03 17:00:04 +00:00
|
|
|
Workspace.textDocuments.forEach(didOpenTextDocument)
|
2021-05-04 15:27:05 +00:00
|
|
|
context.subscriptions.push(
|
|
|
|
Workspace.onDidChangeWorkspaceFolders((event) => {
|
|
|
|
_sortedWorkspaceFolders = undefined
|
|
|
|
|
|
|
|
for (let folder of event.removed) {
|
|
|
|
let client = clients.get(folder.uri.toString())
|
|
|
|
if (client) {
|
|
|
|
searchedFolders.delete(folder.uri.toString())
|
|
|
|
clients.delete(folder.uri.toString())
|
|
|
|
client.stop()
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2021-05-04 15:27:05 +00:00
|
|
|
})
|
|
|
|
)
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|