2021-05-03 17:00:04 +00:00
|
|
|
import './lib/env'
|
|
|
|
import {
|
|
|
|
CompletionItem,
|
|
|
|
CompletionList,
|
|
|
|
CompletionParams,
|
|
|
|
Connection,
|
|
|
|
createConnection,
|
|
|
|
DocumentColorParams,
|
|
|
|
ColorInformation,
|
|
|
|
ColorPresentation,
|
|
|
|
Hover,
|
|
|
|
InitializeParams,
|
|
|
|
InitializeResult,
|
|
|
|
TextDocumentPositionParams,
|
|
|
|
TextDocuments,
|
|
|
|
TextDocumentSyncKind,
|
|
|
|
ColorPresentationParams,
|
|
|
|
CodeActionParams,
|
|
|
|
CodeAction,
|
|
|
|
CompletionRequest,
|
|
|
|
DocumentColorRequest,
|
|
|
|
BulkRegistration,
|
|
|
|
CodeActionRequest,
|
|
|
|
BulkUnregistration,
|
|
|
|
HoverRequest,
|
2021-06-05 14:44:21 +00:00
|
|
|
DidChangeWatchedFilesNotification,
|
|
|
|
FileChangeType,
|
2021-06-14 13:11:29 +00:00
|
|
|
Disposable,
|
2022-04-25 14:06:31 +00:00
|
|
|
TextDocumentIdentifier,
|
2022-10-17 16:59:07 +00:00
|
|
|
DocumentLinkRequest,
|
|
|
|
DocumentLinkParams,
|
|
|
|
DocumentLink,
|
2021-05-03 17:00:04 +00:00
|
|
|
} from 'vscode-languageserver/node'
|
|
|
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
|
|
|
import { URI } from 'vscode-uri'
|
2021-05-06 14:18:25 +00:00
|
|
|
import { formatError, showError, SilentError } from './util/error'
|
2021-05-03 17:00:04 +00:00
|
|
|
import glob from 'fast-glob'
|
|
|
|
import normalizePath from 'normalize-path'
|
|
|
|
import * as path from 'path'
|
|
|
|
import * as os from 'os'
|
|
|
|
import * as fs from 'fs'
|
2021-11-26 15:07:15 +00:00
|
|
|
import type * as chokidar from 'chokidar'
|
2021-05-03 17:00:04 +00:00
|
|
|
import findUp from 'find-up'
|
|
|
|
import minimatch from 'minimatch'
|
|
|
|
import resolveFrom, { setPnpApi } from './util/resolveFrom'
|
2022-01-07 11:42:39 +00:00
|
|
|
import { AtRule, Container, Node, Result } from 'postcss'
|
2021-05-03 17:00:04 +00:00
|
|
|
import Module from 'module'
|
|
|
|
import Hook from './lib/hook'
|
2022-07-06 15:07:13 +00:00
|
|
|
import * as semver from 'tailwindcss-language-service/src/util/semver'
|
2021-05-03 17:00:04 +00:00
|
|
|
import dlv from 'dlv'
|
2022-07-06 15:12:54 +00:00
|
|
|
import { dset } from 'dset'
|
2021-05-03 17:00:04 +00:00
|
|
|
import pkgUp from 'pkg-up'
|
|
|
|
import stackTrace from 'stack-trace'
|
|
|
|
import extractClassNames from './lib/extractClassNames'
|
|
|
|
import { klona } from 'klona/full'
|
|
|
|
import { doHover } from 'tailwindcss-language-service/src/hoverProvider'
|
|
|
|
import {
|
|
|
|
doComplete,
|
|
|
|
resolveCompletionItem,
|
|
|
|
} from 'tailwindcss-language-service/src/completionProvider'
|
|
|
|
import {
|
|
|
|
State,
|
|
|
|
FeatureFlags,
|
|
|
|
Settings,
|
|
|
|
ClassNames,
|
2022-10-17 17:05:04 +00:00
|
|
|
Variant,
|
2021-05-03 17:00:04 +00:00
|
|
|
} from 'tailwindcss-language-service/src/util/state'
|
2022-10-18 19:35:02 +00:00
|
|
|
import { provideDiagnostics } from './lsp/diagnosticsProvider'
|
2021-05-03 17:00:04 +00:00
|
|
|
import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
|
|
|
|
import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
|
2022-10-17 16:59:07 +00:00
|
|
|
import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
|
2021-05-03 17:00:04 +00:00
|
|
|
import { debounce } from 'debounce'
|
2021-05-05 16:56:45 +00:00
|
|
|
import { getModuleDependencies } from './util/getModuleDependencies'
|
2021-06-04 14:07:32 +00:00
|
|
|
import assert from 'assert'
|
2021-05-03 17:00:04 +00:00
|
|
|
// import postcssLoadConfig from 'postcss-load-config'
|
2021-06-14 13:11:29 +00:00
|
|
|
import * as parcel from './watcher/index.js'
|
2021-10-01 13:11:45 +00:00
|
|
|
import { generateRules } from 'tailwindcss-language-service/src/util/jit'
|
|
|
|
import { getColor } from 'tailwindcss-language-service/src/util/color'
|
|
|
|
import * as culori from 'culori'
|
|
|
|
import namedColors from 'color-name'
|
2022-01-07 11:42:39 +00:00
|
|
|
import tailwindPlugins from './lib/plugins'
|
2022-09-13 16:31:09 +00:00
|
|
|
import isExcluded from './util/isExcluded'
|
2022-01-07 17:13:11 +00:00
|
|
|
import { getFileFsPath, normalizeFileNameToFsPath } from './util/uri'
|
|
|
|
import { equal } from 'tailwindcss-language-service/src/util/array'
|
2022-04-13 12:54:33 +00:00
|
|
|
import preflight from 'tailwindcss/lib/css/preflight.css'
|
2022-09-13 16:31:09 +00:00
|
|
|
import merge from 'deepmerge'
|
2022-10-18 19:35:02 +00:00
|
|
|
import { getTextWithoutComments } from 'tailwindcss-language-service/src/util/doc'
|
|
|
|
import { CONFIG_GLOB, CSS_GLOB, PACKAGE_LOCK_GLOB } from './lib/constants'
|
2022-01-07 11:42:39 +00:00
|
|
|
|
|
|
|
// @ts-ignore
|
2022-04-13 12:54:33 +00:00
|
|
|
global.__preflight = preflight
|
|
|
|
new Function(
|
|
|
|
'require',
|
|
|
|
'__dirname',
|
|
|
|
`
|
|
|
|
let oldReadFileSync = require('fs').readFileSync
|
|
|
|
require('fs').readFileSync = function (filename, ...args) {
|
|
|
|
if (filename === require('path').join(__dirname, 'css/preflight.css')) {
|
|
|
|
return global.__preflight
|
|
|
|
}
|
|
|
|
return oldReadFileSync(filename, ...args)
|
|
|
|
}
|
|
|
|
`
|
|
|
|
)(require, __dirname)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
const TRIGGER_CHARACTERS = [
|
|
|
|
// class attributes
|
|
|
|
'"',
|
|
|
|
"'",
|
|
|
|
'`',
|
|
|
|
// between class names
|
|
|
|
' ',
|
|
|
|
// @apply and emmet-style
|
|
|
|
'.',
|
|
|
|
// config/theme helper
|
2022-10-17 16:56:00 +00:00
|
|
|
'(',
|
2021-05-03 17:00:04 +00:00
|
|
|
'[',
|
|
|
|
// JIT "important" prefix
|
|
|
|
'!',
|
2021-05-18 11:22:18 +00:00
|
|
|
// JIT opacity modifiers
|
|
|
|
'/',
|
2021-05-03 17:00:04 +00:00
|
|
|
] as const
|
|
|
|
|
|
|
|
const colorNames = Object.keys(namedColors)
|
|
|
|
|
|
|
|
const connection =
|
|
|
|
process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
|
|
|
|
|
2021-10-08 16:36:41 +00:00
|
|
|
console.log = connection.console.log.bind(connection.console)
|
2021-05-06 14:18:25 +00:00
|
|
|
console.error = connection.console.error.bind(connection.console)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2021-05-06 14:18:25 +00:00
|
|
|
process.on('unhandledRejection', (e: any) => {
|
|
|
|
connection.console.error(formatError(`Unhandled exception`, e))
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
function deletePropertyPath(obj: any, path: string | string[]): void {
|
|
|
|
if (typeof path === 'string') {
|
|
|
|
path = path.split('.')
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < path.length - 1; i++) {
|
|
|
|
obj = obj[path[i]]
|
|
|
|
if (typeof obj === 'undefined') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete obj[path.pop()]
|
|
|
|
}
|
|
|
|
|
2021-05-05 16:56:45 +00:00
|
|
|
function getConfigId(configPath: string, configDependencies: string[]): string {
|
|
|
|
return JSON.stringify(
|
|
|
|
[configPath, ...configDependencies].map((file) => [file, fs.statSync(file).mtimeMs])
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-06-04 14:07:32 +00:00
|
|
|
function first<T>(...options: Array<() => T>): T {
|
|
|
|
for (let i = 0; i < options.length; i++) {
|
|
|
|
let option = options[i]
|
|
|
|
if (i === options.length - 1) {
|
|
|
|
return option()
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
return option()
|
|
|
|
} catch (_) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-01 13:11:45 +00:00
|
|
|
function firstOptional<T>(...options: Array<() => T>): T | undefined {
|
|
|
|
for (let i = 0; i < options.length; i++) {
|
|
|
|
let option = options[i]
|
|
|
|
try {
|
|
|
|
return option()
|
|
|
|
} catch (_) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
interface ProjectService {
|
2023-01-16 19:28:49 +00:00
|
|
|
projectConfig: ProjectConfig
|
2022-10-18 19:35:02 +00:00
|
|
|
enabled: () => boolean
|
|
|
|
enable: () => void
|
|
|
|
documentSelector: () => Array<DocumentSelector>
|
2021-05-03 17:00:04 +00:00
|
|
|
state: State
|
|
|
|
tryInit: () => Promise<void>
|
2022-10-18 19:35:02 +00:00
|
|
|
dispose: () => Promise<void>
|
2021-05-03 17:00:04 +00:00
|
|
|
onUpdateSettings: (settings: any) => void
|
2022-04-25 14:06:31 +00:00
|
|
|
onFileEvents: (changes: Array<{ file: string; type: FileChangeType }>) => void
|
2021-05-03 17:00:04 +00:00
|
|
|
onHover(params: TextDocumentPositionParams): Promise<Hover>
|
|
|
|
onCompletion(params: CompletionParams): Promise<CompletionList>
|
|
|
|
onCompletionResolve(item: CompletionItem): Promise<CompletionItem>
|
|
|
|
provideDiagnostics(document: TextDocument): void
|
2022-10-18 19:35:02 +00:00
|
|
|
provideDiagnosticsForce(document: TextDocument): void
|
2021-05-03 17:00:04 +00:00
|
|
|
onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]>
|
|
|
|
onColorPresentation(params: ColorPresentationParams): Promise<ColorPresentation[]>
|
|
|
|
onCodeAction(params: CodeActionParams): Promise<CodeAction[]>
|
2022-10-17 16:59:07 +00:00
|
|
|
onDocumentLinks(params: DocumentLinkParams): DocumentLink[]
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
type ProjectConfig = {
|
|
|
|
folder: string
|
|
|
|
configPath?: string
|
|
|
|
documentSelector?: Array<DocumentSelector>
|
|
|
|
isUserConfigured: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
enum DocumentSelectorPriority {
|
|
|
|
USER_CONFIGURED = 0,
|
|
|
|
CONFIG_FILE = 0,
|
|
|
|
CSS_FILE = 0,
|
|
|
|
CONTENT_FILE = 1,
|
|
|
|
CSS_DIRECTORY = 2,
|
|
|
|
CONFIG_DIRECTORY = 3,
|
2023-01-27 13:40:13 +00:00
|
|
|
PACKAGE_DIRECTORY = 4,
|
|
|
|
ROOT_DIRECTORY = 5,
|
2022-10-18 19:35:02 +00:00
|
|
|
}
|
|
|
|
type DocumentSelector = { pattern: string; priority: DocumentSelectorPriority }
|
2022-04-25 14:06:31 +00:00
|
|
|
|
2021-10-29 16:51:55 +00:00
|
|
|
function getMode(config: any): unknown {
|
|
|
|
if (typeof config.mode !== 'undefined') {
|
|
|
|
return config.mode
|
|
|
|
}
|
|
|
|
if (Array.isArray(config.presets)) {
|
|
|
|
for (let i = config.presets.length - 1; i >= 0; i--) {
|
|
|
|
let mode = getMode(config.presets[i])
|
|
|
|
if (typeof mode !== 'undefined') {
|
|
|
|
return mode
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteMode(config: any): void {
|
|
|
|
delete config.mode
|
|
|
|
if (Array.isArray(config.presets)) {
|
|
|
|
for (let preset of config.presets) {
|
|
|
|
deleteMode(preset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
const documentSettingsCache: Map<string, Settings> = new Map()
|
|
|
|
async function getConfiguration(uri?: string) {
|
|
|
|
if (documentSettingsCache.has(uri)) {
|
|
|
|
return documentSettingsCache.get(uri)
|
|
|
|
}
|
|
|
|
let [editor, tailwindCSS] = await Promise.all([
|
|
|
|
connection.workspace.getConfiguration({
|
|
|
|
section: 'editor',
|
|
|
|
scopeUri: uri,
|
|
|
|
}),
|
|
|
|
connection.workspace.getConfiguration({
|
|
|
|
section: 'tailwindCSS',
|
|
|
|
scopeUri: uri,
|
|
|
|
}),
|
|
|
|
])
|
|
|
|
editor = isObject(editor) ? editor : {}
|
|
|
|
tailwindCSS = isObject(tailwindCSS) ? tailwindCSS : {}
|
|
|
|
|
|
|
|
let config: Settings = merge<Settings>(
|
|
|
|
{
|
|
|
|
editor: { tabSize: 2 },
|
|
|
|
tailwindCSS: {
|
|
|
|
emmetCompletions: false,
|
|
|
|
classAttributes: ['class', 'className', 'ngClass'],
|
|
|
|
codeActions: true,
|
|
|
|
hovers: true,
|
|
|
|
suggestions: true,
|
|
|
|
validate: true,
|
|
|
|
colorDecorators: true,
|
|
|
|
rootFontSize: 16,
|
|
|
|
lint: {
|
|
|
|
cssConflict: 'warning',
|
|
|
|
invalidApply: 'error',
|
|
|
|
invalidScreen: 'error',
|
|
|
|
invalidVariant: 'error',
|
|
|
|
invalidConfigPath: 'error',
|
|
|
|
invalidTailwindDirective: 'error',
|
|
|
|
recommendedVariantOrder: 'warning',
|
|
|
|
},
|
|
|
|
showPixelEquivalents: true,
|
|
|
|
includeLanguages: {},
|
|
|
|
files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] },
|
|
|
|
experimental: {
|
|
|
|
classRegex: [],
|
|
|
|
configFile: null,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ editor, tailwindCSS },
|
|
|
|
{ arrayMerge: (_destinationArray, sourceArray, _options) => sourceArray }
|
|
|
|
)
|
|
|
|
documentSettingsCache.set(uri, config)
|
|
|
|
return config
|
|
|
|
}
|
|
|
|
|
|
|
|
function clearRequireCache(): void {
|
|
|
|
Object.keys(require.cache).forEach((key) => {
|
|
|
|
if (!key.endsWith('.node')) {
|
|
|
|
delete require.cache[key]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
Object.keys((Module as any)._pathCache).forEach((key) => {
|
|
|
|
delete (Module as any)._pathCache[key]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function withoutLogs<T>(getter: () => T): T {
|
|
|
|
let fns = {
|
|
|
|
log: console.log,
|
|
|
|
warn: console.warn,
|
|
|
|
error: console.error,
|
|
|
|
}
|
|
|
|
for (let key in fns) {
|
|
|
|
console[key] = () => {}
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return getter()
|
|
|
|
} finally {
|
|
|
|
for (let key in fns) {
|
|
|
|
console[key] = fns[key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function withFallback<T>(getter: () => T, fallback: T): T {
|
|
|
|
try {
|
|
|
|
return getter()
|
|
|
|
} catch (e) {
|
|
|
|
return fallback
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function dirContains(dir: string, file: string): boolean {
|
|
|
|
let relative = path.relative(dir, file)
|
|
|
|
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative)
|
|
|
|
}
|
|
|
|
|
|
|
|
function changeAffectsFile(change: string, files: string[]): boolean {
|
|
|
|
for (let file of files) {
|
|
|
|
if (change === file || dirContains(change, file)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to add parent directories to the watcher:
|
|
|
|
// https://github.com/microsoft/vscode/issues/60813
|
2023-01-27 11:24:28 +00:00
|
|
|
function getWatchPatternsForFile(file: string, root: string): string[] {
|
2022-10-18 19:35:02 +00:00
|
|
|
let tmp: string
|
|
|
|
let dir = path.dirname(file)
|
|
|
|
let patterns: string[] = [file, dir]
|
2023-01-27 11:24:28 +00:00
|
|
|
if (dir === root) {
|
|
|
|
return patterns
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
while (true) {
|
|
|
|
dir = path.dirname((tmp = dir))
|
|
|
|
if (tmp === dir) {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
patterns.push(dir)
|
2023-01-27 11:24:28 +00:00
|
|
|
if (dir === root) {
|
|
|
|
break
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return patterns
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
async function createProjectService(
|
2023-01-16 19:28:49 +00:00
|
|
|
projectKey: string,
|
2022-04-25 14:06:31 +00:00
|
|
|
projectConfig: ProjectConfig,
|
2021-05-03 17:00:04 +00:00
|
|
|
connection: Connection,
|
|
|
|
params: InitializeParams,
|
2022-04-25 14:06:31 +00:00
|
|
|
documentService: DocumentService,
|
2022-10-18 19:35:02 +00:00
|
|
|
updateCapabilities: () => void,
|
|
|
|
checkOpenDocuments: () => void,
|
|
|
|
refreshDiagnostics: () => void,
|
|
|
|
watchPatterns: (patterns: string[]) => void,
|
|
|
|
initialTailwindVersion: string
|
2021-05-03 17:00:04 +00:00
|
|
|
): Promise<ProjectService> {
|
2022-10-18 19:35:02 +00:00
|
|
|
let enabled = false
|
2022-04-25 14:06:31 +00:00
|
|
|
const folder = projectConfig.folder
|
2022-10-18 19:35:02 +00:00
|
|
|
const disposables: Array<Disposable | Promise<Disposable>> = []
|
|
|
|
let documentSelector = projectConfig.documentSelector
|
2022-01-17 13:58:13 +00:00
|
|
|
|
2023-01-27 10:30:27 +00:00
|
|
|
let itemDefaults =
|
|
|
|
params.capabilities.textDocument?.completion?.completionList?.itemDefaults ?? []
|
|
|
|
|
|
|
|
// VS Code _does_ support `itemDefaults.data` since at least 1.67.0 (this extension's min version)
|
|
|
|
// but it doesn't advertise it in its capabilities. So we manually add it here.
|
|
|
|
// See also: https://github.com/microsoft/vscode-languageserver-node/issues/1181
|
2023-03-28 10:59:47 +00:00
|
|
|
if (params.clientInfo?.name?.includes('Visual Studio Code') && !itemDefaults.includes('data')) {
|
2023-01-27 10:30:27 +00:00
|
|
|
itemDefaults.push('data')
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
let state: State = {
|
2021-05-04 10:55:32 +00:00
|
|
|
enabled: false,
|
2023-01-27 10:30:27 +00:00
|
|
|
completionItemData: {
|
|
|
|
_projectKey: projectKey,
|
|
|
|
},
|
2021-05-04 10:55:32 +00:00
|
|
|
editor: {
|
|
|
|
connection,
|
2022-01-07 17:13:11 +00:00
|
|
|
folder,
|
2022-11-09 10:01:34 +00:00
|
|
|
userLanguages: params.initializationOptions?.userLanguages
|
2021-06-04 13:34:30 +00:00
|
|
|
? params.initializationOptions.userLanguages
|
|
|
|
: {},
|
2021-05-04 10:55:32 +00:00
|
|
|
// TODO
|
|
|
|
capabilities: {
|
|
|
|
configuration: true,
|
|
|
|
diagnosticRelatedInformation: true,
|
2023-01-27 10:30:27 +00:00
|
|
|
itemDefaults,
|
2021-05-04 10:55:32 +00:00
|
|
|
},
|
2022-01-17 13:58:13 +00:00
|
|
|
getConfiguration,
|
2021-05-04 10:55:32 +00:00
|
|
|
getDocumentSymbols: (uri: string) => {
|
|
|
|
return connection.sendRequest('@/tailwindCSS/getDocumentSymbols', { uri })
|
|
|
|
},
|
2022-10-17 16:59:07 +00:00
|
|
|
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 []
|
|
|
|
}
|
|
|
|
},
|
2021-05-04 10:55:32 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
if (projectConfig.configPath) {
|
|
|
|
let deps = []
|
|
|
|
try {
|
|
|
|
deps = getModuleDependencies(projectConfig.configPath)
|
|
|
|
} catch {}
|
|
|
|
watchPatterns([
|
2023-01-27 11:24:28 +00:00
|
|
|
...getWatchPatternsForFile(projectConfig.configPath, projectConfig.folder),
|
|
|
|
...deps.flatMap((dep) => getWatchPatternsForFile(dep, projectConfig.folder)),
|
2022-10-18 19:35:02 +00:00
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
function log(...args: string[]): void {
|
|
|
|
console.log(
|
|
|
|
`[${path.relative(projectConfig.folder, projectConfig.configPath)}] ${args.join(' ')}`
|
|
|
|
)
|
|
|
|
}
|
2021-06-05 14:44:21 +00:00
|
|
|
|
|
|
|
function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void {
|
|
|
|
let needsInit = false
|
|
|
|
let needsRebuild = false
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2021-06-05 14:44:21 +00:00
|
|
|
for (let change of changes) {
|
|
|
|
let file = normalizePath(change.file)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
let isConfigFile = changeAffectsFile(file, [projectConfig.configPath])
|
2023-01-27 11:24:28 +00:00
|
|
|
let isDependency = changeAffectsFile(file, state.dependencies ?? [])
|
2022-10-18 19:35:02 +00:00
|
|
|
let isPackageFile = minimatch(file, `**/${PACKAGE_LOCK_GLOB}`, { dot: true })
|
|
|
|
|
|
|
|
if (!isConfigFile && !isDependency && !isPackageFile) continue
|
|
|
|
|
|
|
|
if (!enabled) {
|
|
|
|
if (
|
|
|
|
!projectConfig.isUserConfigured &&
|
|
|
|
projectConfig.configPath &&
|
|
|
|
(isConfigFile || isDependency)
|
|
|
|
) {
|
|
|
|
documentSelector = [
|
|
|
|
...documentSelector.filter(
|
|
|
|
({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE
|
|
|
|
),
|
|
|
|
...getContentDocumentSelectorFromConfigFile(
|
|
|
|
projectConfig.configPath,
|
|
|
|
initialTailwindVersion,
|
|
|
|
projectConfig.folder
|
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
checkOpenDocuments()
|
2021-06-14 13:11:29 +00:00
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
continue
|
2021-06-14 13:11:29 +00:00
|
|
|
}
|
|
|
|
|
2021-06-05 14:44:21 +00:00
|
|
|
if (change.type === FileChangeType.Created) {
|
2022-10-18 19:35:02 +00:00
|
|
|
log('File created:', change.file)
|
2021-06-05 14:44:21 +00:00
|
|
|
needsInit = true
|
|
|
|
break
|
|
|
|
} else if (change.type === FileChangeType.Changed) {
|
2022-10-18 19:35:02 +00:00
|
|
|
log('File changed:', change.file)
|
2021-06-14 13:11:29 +00:00
|
|
|
if (!state.enabled || isPackageFile) {
|
2021-06-05 14:44:21 +00:00
|
|
|
needsInit = true
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
needsRebuild = true
|
|
|
|
}
|
|
|
|
} else if (change.type === FileChangeType.Deleted) {
|
2022-10-18 19:35:02 +00:00
|
|
|
log('File deleted:', change.file)
|
|
|
|
if (!state.enabled || isConfigFile || isPackageFile) {
|
2021-06-05 14:44:21 +00:00
|
|
|
needsInit = true
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
needsRebuild = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsInit) {
|
2021-05-03 17:00:04 +00:00
|
|
|
tryInit()
|
2021-06-05 14:44:21 +00:00
|
|
|
} else if (needsRebuild) {
|
|
|
|
tryRebuild()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
function resetState(): void {
|
2022-10-18 19:35:02 +00:00
|
|
|
// clearAllDiagnostics(state)
|
2021-05-03 17:00:04 +00:00
|
|
|
Object.keys(state).forEach((key) => {
|
2021-06-14 13:11:29 +00:00
|
|
|
// Keep `dependencies` to ensure that they are still watched
|
|
|
|
if (key !== 'editor' && key !== 'dependencies') {
|
2021-05-04 13:32:19 +00:00
|
|
|
delete state[key]
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
})
|
|
|
|
state.enabled = false
|
2022-10-18 19:35:02 +00:00
|
|
|
refreshDiagnostics()
|
2022-04-25 14:06:31 +00:00
|
|
|
updateCapabilities()
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function tryInit() {
|
2022-10-18 19:35:02 +00:00
|
|
|
if (!enabled) {
|
|
|
|
return
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
try {
|
|
|
|
await init()
|
|
|
|
} catch (error) {
|
|
|
|
resetState()
|
|
|
|
showError(connection, error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function tryRebuild() {
|
2022-10-18 19:35:02 +00:00
|
|
|
if (!enabled) {
|
|
|
|
return
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
try {
|
|
|
|
await rebuild()
|
|
|
|
} catch (error) {
|
|
|
|
resetState()
|
|
|
|
showError(connection, error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function init() {
|
2022-10-18 19:35:02 +00:00
|
|
|
log('Initializing...')
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
clearRequireCache()
|
|
|
|
|
2022-04-25 14:06:31 +00:00
|
|
|
let configPath = projectConfig.configPath
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
if (!configPath) {
|
2021-05-04 13:49:13 +00:00
|
|
|
throw new SilentError('No config file found.')
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 11:24:28 +00:00
|
|
|
watchPatterns(getWatchPatternsForFile(configPath, projectConfig.folder))
|
2022-10-18 19:35:02 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
const pnpPath = findUp.sync(
|
|
|
|
(dir) => {
|
|
|
|
let pnpFile = path.join(dir, '.pnp.js')
|
|
|
|
if (findUp.sync.exists(pnpFile)) {
|
|
|
|
return pnpFile
|
|
|
|
}
|
|
|
|
pnpFile = path.join(dir, '.pnp.cjs')
|
|
|
|
if (findUp.sync.exists(pnpFile)) {
|
|
|
|
return pnpFile
|
|
|
|
}
|
2023-01-27 11:24:28 +00:00
|
|
|
if (dir === path.normalize(folder)) {
|
2021-05-03 17:00:04 +00:00
|
|
|
return findUp.stop
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ cwd: folder }
|
|
|
|
)
|
|
|
|
|
|
|
|
if (pnpPath) {
|
2022-04-13 12:54:33 +00:00
|
|
|
let pnpApi = require(pnpPath)
|
2021-05-03 17:00:04 +00:00
|
|
|
pnpApi.setup()
|
|
|
|
setPnpApi(pnpApi)
|
|
|
|
}
|
|
|
|
|
2021-05-05 16:56:45 +00:00
|
|
|
const configDependencies = getModuleDependencies(configPath)
|
|
|
|
const configId = getConfigId(configPath, configDependencies)
|
2021-05-03 17:00:04 +00:00
|
|
|
const configDir = path.dirname(configPath)
|
|
|
|
let tailwindcss: any
|
|
|
|
let postcss: any
|
|
|
|
let postcssSelectorParser: any
|
|
|
|
let jitModules: typeof state.modules.jit
|
|
|
|
let tailwindcssVersion: string | undefined
|
|
|
|
let postcssVersion: string | undefined
|
2022-01-07 11:42:39 +00:00
|
|
|
let pluginVersions: string | undefined
|
2021-05-03 17:00:04 +00:00
|
|
|
let browserslist: string[] | undefined
|
|
|
|
let resolveConfigFn: (config: any) => any
|
2023-03-20 11:12:02 +00:00
|
|
|
let loadConfigFn: (path: string) => any
|
2021-05-03 17:00:04 +00:00
|
|
|
let featureFlags: FeatureFlags = { future: [], experimental: [] }
|
|
|
|
let applyComplexClasses: any
|
|
|
|
|
|
|
|
try {
|
|
|
|
const tailwindcssPath = resolveFrom(configDir, 'tailwindcss')
|
|
|
|
const tailwindcssPkgPath = resolveFrom(configDir, 'tailwindcss/package.json')
|
|
|
|
const tailwindDir = path.dirname(tailwindcssPkgPath)
|
|
|
|
|
|
|
|
const postcssPath = resolveFrom(tailwindDir, 'postcss')
|
|
|
|
const postcssPkgPath = resolveFrom(tailwindDir, 'postcss/package.json')
|
|
|
|
const postcssDir = path.dirname(postcssPkgPath)
|
|
|
|
const postcssSelectorParserPath = resolveFrom(tailwindDir, 'postcss-selector-parser')
|
|
|
|
|
2022-04-13 12:54:33 +00:00
|
|
|
postcssVersion = require(postcssPkgPath).version
|
|
|
|
tailwindcssVersion = require(tailwindcssPkgPath).version
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-01-07 11:42:39 +00:00
|
|
|
pluginVersions = Object.keys(tailwindPlugins)
|
|
|
|
.map((plugin) => {
|
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
return require(resolveFrom(configDir, `${plugin}/package.json`)).version
|
2022-01-07 11:42:39 +00:00
|
|
|
} catch (_) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.join(',')
|
|
|
|
|
2021-05-04 10:55:32 +00:00
|
|
|
if (
|
|
|
|
state.enabled &&
|
|
|
|
postcssVersion === state.modules.postcss.version &&
|
|
|
|
tailwindcssVersion === state.modules.tailwindcss.version &&
|
2022-01-07 11:42:39 +00:00
|
|
|
pluginVersions === state.pluginVersions &&
|
2021-05-04 10:55:32 +00:00
|
|
|
configPath === state.configPath &&
|
2021-05-05 16:56:45 +00:00
|
|
|
configId === state.configId
|
2021-05-04 10:55:32 +00:00
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
log(`Loaded Tailwind CSS config file: ${configPath}`)
|
2021-05-09 18:42:03 +00:00
|
|
|
|
2022-04-13 12:54:33 +00:00
|
|
|
postcss = require(postcssPath)
|
|
|
|
postcssSelectorParser = require(postcssSelectorParserPath)
|
2022-10-18 19:35:02 +00:00
|
|
|
log(`Loaded postcss v${postcssVersion}: ${postcssDir}`)
|
2021-05-09 18:42:03 +00:00
|
|
|
|
2022-04-13 12:54:33 +00:00
|
|
|
tailwindcss = require(tailwindcssPath)
|
2022-10-18 19:35:02 +00:00
|
|
|
log(`Loaded tailwindcss v${tailwindcssVersion}: ${tailwindDir}`)
|
2021-05-09 18:42:03 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
resolveConfigFn = require(resolveFrom(tailwindDir, './resolveConfig.js'))
|
2021-05-03 17:00:04 +00:00
|
|
|
} catch (_) {
|
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
const resolveConfig = require(resolveFrom(tailwindDir, './lib/util/resolveConfig.js'))
|
|
|
|
const defaultConfig = require(resolveFrom(tailwindDir, './stubs/defaultConfig.stub.js'))
|
2021-05-03 17:00:04 +00:00
|
|
|
resolveConfigFn = (config) => resolveConfig([config, defaultConfig])
|
|
|
|
} catch (_) {
|
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
const resolveConfig = require(resolveFrom(
|
|
|
|
tailwindDir,
|
|
|
|
'./lib/util/mergeConfigWithDefaults.js'
|
|
|
|
))
|
|
|
|
const defaultConfig = require(resolveFrom(tailwindDir, './defaultConfig.js'))
|
2021-05-03 17:00:04 +00:00
|
|
|
resolveConfigFn = (config) => resolveConfig(config, defaultConfig())
|
|
|
|
} catch (_) {
|
|
|
|
throw Error('Failed to load resolveConfig function.')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
try {
|
|
|
|
loadConfigFn = require(resolveFrom(tailwindDir, './loadConfig.js'))
|
|
|
|
} catch {}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
if (semver.gte(tailwindcssVersion, '1.4.0') && semver.lte(tailwindcssVersion, '1.99.0')) {
|
|
|
|
const browserslistPath = resolveFrom(tailwindDir, 'browserslist')
|
|
|
|
// TODO: set path to nearest dir with package.json?
|
2022-04-13 12:54:33 +00:00
|
|
|
browserslist = require(browserslistPath)(undefined, { path: folder })
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (semver.gte(tailwindcssVersion, '1.99.0')) {
|
2021-10-01 13:11:45 +00:00
|
|
|
applyComplexClasses = firstOptional(() =>
|
2022-04-13 12:54:33 +00:00
|
|
|
require(resolveFrom(tailwindDir, './lib/lib/substituteClassApplyAtRules'))
|
2021-05-03 17:00:04 +00:00
|
|
|
)
|
2021-05-04 12:35:56 +00:00
|
|
|
} else if (semver.gte(tailwindcssVersion, '1.7.0')) {
|
2022-04-13 12:54:33 +00:00
|
|
|
applyComplexClasses = require(resolveFrom(tailwindDir, './lib/flagged/applyComplexClasses'))
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
featureFlags = require(resolveFrom(tailwindDir, './lib/featureFlags.js')).default
|
2021-05-03 17:00:04 +00:00
|
|
|
} catch (_) {}
|
|
|
|
|
2021-06-04 11:17:00 +00:00
|
|
|
// stubs
|
|
|
|
let tailwindDirectives = new Set()
|
|
|
|
let root = postcss.root()
|
|
|
|
let result = { opts: {}, messages: [] }
|
2021-06-04 14:07:32 +00:00
|
|
|
let registerDependency = () => {}
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
try {
|
2021-06-04 14:07:32 +00:00
|
|
|
let createContext = first(
|
2021-10-01 13:11:45 +00:00
|
|
|
() => {
|
2022-04-13 12:54:33 +00:00
|
|
|
let createContextFn = require(resolveFrom(
|
|
|
|
configDir,
|
|
|
|
'tailwindcss/lib/lib/setupContextUtils'
|
|
|
|
)).createContext
|
2021-10-01 13:11:45 +00:00
|
|
|
assert.strictEqual(typeof createContextFn, 'function')
|
|
|
|
return (state) => createContextFn(state.config)
|
|
|
|
},
|
2021-06-04 14:07:32 +00:00
|
|
|
() => {
|
2022-04-13 12:54:33 +00:00
|
|
|
let createContextFn = require(resolveFrom(
|
|
|
|
configDir,
|
|
|
|
'tailwindcss/lib/jit/lib/setupContextUtils'
|
|
|
|
)).createContext
|
2021-06-04 14:07:32 +00:00
|
|
|
assert.strictEqual(typeof createContextFn, 'function')
|
|
|
|
return (state) => createContextFn(state.config)
|
|
|
|
},
|
|
|
|
// TODO: the next two are canary releases only so can probably be removed
|
|
|
|
() => {
|
2022-04-13 12:54:33 +00:00
|
|
|
let setupTrackingContext = require(resolveFrom(
|
|
|
|
configDir,
|
|
|
|
'tailwindcss/lib/jit/lib/setupTrackingContext'
|
|
|
|
)).default
|
2021-06-04 14:07:32 +00:00
|
|
|
assert.strictEqual(typeof setupTrackingContext, 'function')
|
|
|
|
return (state) =>
|
|
|
|
setupTrackingContext(
|
|
|
|
state.configPath,
|
|
|
|
tailwindDirectives,
|
|
|
|
registerDependency
|
|
|
|
)(result, root)
|
|
|
|
},
|
|
|
|
() => {
|
2022-04-13 12:54:33 +00:00
|
|
|
let setupContext = require(resolveFrom(
|
|
|
|
configDir,
|
|
|
|
'tailwindcss/lib/jit/lib/setupContext'
|
|
|
|
)).default
|
2021-06-04 14:07:32 +00:00
|
|
|
assert.strictEqual(typeof setupContext, 'function')
|
|
|
|
return (state) => setupContext(state.configPath, tailwindDirectives)(result, root)
|
|
|
|
}
|
|
|
|
)
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
jitModules = {
|
|
|
|
generateRules: {
|
2021-10-01 13:11:45 +00:00
|
|
|
module: first(
|
|
|
|
() =>
|
2022-04-13 12:54:33 +00:00
|
|
|
require(resolveFrom(configDir, 'tailwindcss/lib/lib/generateRules')).generateRules,
|
2021-10-01 13:11:45 +00:00
|
|
|
() =>
|
2022-04-13 12:54:33 +00:00
|
|
|
require(resolveFrom(configDir, 'tailwindcss/lib/jit/lib/generateRules'))
|
|
|
|
.generateRules
|
2021-10-01 13:11:45 +00:00
|
|
|
),
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
2021-06-04 11:17:00 +00:00
|
|
|
createContext: {
|
|
|
|
module: createContext,
|
2021-05-06 17:01:54 +00:00
|
|
|
},
|
2021-05-10 12:41:48 +00:00
|
|
|
expandApplyAtRules: {
|
2021-10-01 13:11:45 +00:00
|
|
|
module: first(
|
|
|
|
() =>
|
2022-04-13 12:54:33 +00:00
|
|
|
require(resolveFrom(configDir, 'tailwindcss/lib/lib/expandApplyAtRules')).default,
|
2021-10-01 13:11:45 +00:00
|
|
|
() =>
|
2022-04-13 12:54:33 +00:00
|
|
|
require(resolveFrom(configDir, 'tailwindcss/lib/jit/lib/expandApplyAtRules'))
|
|
|
|
.default
|
2021-10-01 13:11:45 +00:00
|
|
|
),
|
2021-05-10 12:41:48 +00:00
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2021-05-06 16:45:41 +00:00
|
|
|
} catch (_) {
|
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
let setupContext = require(resolveFrom(configDir, 'tailwindcss/jit/lib/setupContext'))
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2021-05-06 16:45:41 +00:00
|
|
|
jitModules = {
|
|
|
|
generateRules: {
|
2022-04-13 12:54:33 +00:00
|
|
|
module: require(resolveFrom(configDir, 'tailwindcss/jit/lib/generateRules'))
|
|
|
|
.generateRules,
|
2021-05-06 16:45:41 +00:00
|
|
|
},
|
2021-06-04 11:17:00 +00:00
|
|
|
createContext: {
|
|
|
|
module: (state) => setupContext(state.configPath, tailwindDirectives)(result, root),
|
2021-05-06 17:01:54 +00:00
|
|
|
},
|
2021-05-10 12:41:48 +00:00
|
|
|
expandApplyAtRules: {
|
2022-04-13 12:54:33 +00:00
|
|
|
module: require(resolveFrom(configDir, 'tailwindcss/jit/lib/expandApplyAtRules')),
|
2021-05-10 12:41:48 +00:00
|
|
|
},
|
2021-05-06 16:45:41 +00:00
|
|
|
}
|
|
|
|
} catch (_) {}
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
} catch (error) {
|
2022-01-07 11:42:39 +00:00
|
|
|
tailwindcss = require('tailwindcss')
|
|
|
|
resolveConfigFn = require('tailwindcss/resolveConfig')
|
2023-03-29 14:43:15 +00:00
|
|
|
loadConfigFn = require('tailwindcss/loadConfig')
|
2022-01-07 11:42:39 +00:00
|
|
|
postcss = require('postcss')
|
|
|
|
tailwindcssVersion = require('tailwindcss/package.json').version
|
|
|
|
postcssVersion = require('postcss/package.json').version
|
|
|
|
postcssSelectorParser = require('postcss-selector-parser')
|
|
|
|
jitModules = {
|
|
|
|
generateRules: { module: require('tailwindcss/lib/lib/generateRules').generateRules },
|
|
|
|
createContext: {
|
|
|
|
module: (state) =>
|
|
|
|
require('tailwindcss/lib/lib/setupContextUtils').createContext(state.config),
|
|
|
|
},
|
|
|
|
expandApplyAtRules: {
|
|
|
|
module: require('tailwindcss/lib/lib/expandApplyAtRules').default,
|
|
|
|
},
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
log('Failed to load workspace modules.')
|
|
|
|
log(`Using bundled version of \`tailwindcss\`: v${tailwindcssVersion}`)
|
|
|
|
log(`Using bundled version of \`postcss\`: v${postcssVersion}`)
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
state.configPath = configPath
|
|
|
|
state.modules = {
|
|
|
|
tailwindcss: { version: tailwindcssVersion, module: tailwindcss },
|
|
|
|
postcss: { version: postcssVersion, module: postcss },
|
|
|
|
postcssSelectorParser: { module: postcssSelectorParser },
|
|
|
|
resolveConfig: { module: resolveConfigFn },
|
2023-03-20 11:12:02 +00:00
|
|
|
loadConfig: { module: loadConfigFn },
|
2021-05-03 17:00:04 +00:00
|
|
|
jit: jitModules,
|
|
|
|
}
|
|
|
|
state.browserslist = browserslist
|
|
|
|
state.featureFlags = featureFlags
|
|
|
|
state.version = tailwindcssVersion
|
2022-01-07 11:42:39 +00:00
|
|
|
state.pluginVersions = pluginVersions
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2021-06-04 11:17:00 +00:00
|
|
|
try {
|
|
|
|
state.corePlugins = Object.keys(
|
2022-04-13 12:54:33 +00:00
|
|
|
require(resolveFrom(path.dirname(state.configPath), 'tailwindcss/lib/plugins/index.js'))
|
2021-06-04 11:17:00 +00:00
|
|
|
)
|
|
|
|
} catch (_) {}
|
|
|
|
|
2021-05-04 12:35:56 +00:00
|
|
|
if (applyComplexClasses && !applyComplexClasses.default.__patched) {
|
2021-05-03 17:00:04 +00:00
|
|
|
let _applyComplexClasses = applyComplexClasses.default
|
|
|
|
applyComplexClasses.default = (config, ...args) => {
|
2021-05-10 12:41:48 +00:00
|
|
|
if (state.jit) {
|
|
|
|
return state.modules.jit.expandApplyAtRules.module(state.jitContext)
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
let configClone = klona(config)
|
|
|
|
configClone.separator = typeof state.separator === 'undefined' ? ':' : state.separator
|
|
|
|
|
|
|
|
let fn = _applyComplexClasses(configClone, ...args)
|
|
|
|
|
|
|
|
return async (css) => {
|
|
|
|
css.walkRules((rule) => {
|
|
|
|
const newSelector = rule.selector.replace(/__TWSEP__(.*?)__TWSEP__/g, '$1')
|
|
|
|
if (newSelector !== rule.selector) {
|
|
|
|
rule.before(
|
|
|
|
postcss.comment({
|
|
|
|
text: '__ORIGINAL_SELECTOR__:' + rule.selector,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
rule.selector = newSelector
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
await fn(css)
|
|
|
|
|
|
|
|
css.walkComments((comment) => {
|
|
|
|
if (comment.text.startsWith('__ORIGINAL_SELECTOR__:')) {
|
|
|
|
comment.next().selector = comment.text.replace(/^__ORIGINAL_SELECTOR__:/, '')
|
|
|
|
comment.remove()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return css
|
|
|
|
}
|
|
|
|
}
|
|
|
|
applyComplexClasses.default.__patched = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// state.postcssPlugins = { before: [], after: [] }
|
|
|
|
|
|
|
|
// try {
|
|
|
|
// let { plugins: postcssPlugins } = await postcssLoadConfig({ cwd: folder }, state.configPath)
|
|
|
|
// let tailwindIndex = postcssPlugins.findIndex((p) => {
|
|
|
|
// if (typeof p === 'function') {
|
|
|
|
// return (p as any)().postcssPlugin === 'tailwindcss'
|
|
|
|
// }
|
|
|
|
// return (p as any).postcssPlugin === 'tailwindcss'
|
|
|
|
// })
|
|
|
|
// console.log({ postcssPlugins })
|
|
|
|
// if (tailwindIndex !== -1) {
|
|
|
|
// console.log('here')
|
|
|
|
// state.postcssPlugins = {
|
|
|
|
// before: postcssPlugins.slice(0, tailwindIndex),
|
|
|
|
// after: postcssPlugins.slice(tailwindIndex + 1),
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// } catch (_error) {
|
|
|
|
// console.log(_error)
|
|
|
|
// }
|
|
|
|
|
|
|
|
await tryRebuild()
|
|
|
|
}
|
|
|
|
|
|
|
|
async function rebuild() {
|
2022-10-18 19:35:02 +00:00
|
|
|
log('Building...')
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
clearRequireCache()
|
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
const { tailwindcss, postcss, resolveConfig, loadConfig } = state.modules
|
2021-05-03 17:00:04 +00:00
|
|
|
const sepLocation = semver.gte(tailwindcss.version, '0.99.0')
|
|
|
|
? ['separator']
|
|
|
|
: ['options', 'separator']
|
2021-05-20 16:07:46 +00:00
|
|
|
let presetVariants: any[] = []
|
2021-06-04 11:17:00 +00:00
|
|
|
let originalConfig: any
|
2021-05-20 16:07:46 +00:00
|
|
|
|
2021-10-01 13:11:45 +00:00
|
|
|
let isV3 = semver.gte(tailwindcss.version, '2.99.0')
|
2023-03-20 11:12:02 +00:00
|
|
|
let hook: Hook
|
2021-10-01 13:11:45 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
if (loadConfig.module) {
|
2023-03-30 09:30:52 +00:00
|
|
|
hook = new Hook(fs.realpathSync(state.configPath))
|
|
|
|
try {
|
|
|
|
originalConfig = await loadConfig.module(state.configPath)
|
|
|
|
originalConfig = originalConfig.default ?? originalConfig
|
|
|
|
state.jit = true
|
|
|
|
} finally {
|
|
|
|
hook.unhook()
|
|
|
|
}
|
2023-03-20 11:12:02 +00:00
|
|
|
} else {
|
|
|
|
hook = new Hook(fs.realpathSync(state.configPath), (exports) => {
|
|
|
|
originalConfig = klona(exports)
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
let separator = dlv(exports, sepLocation)
|
|
|
|
if (typeof separator !== 'string') {
|
|
|
|
separator = ':'
|
|
|
|
}
|
|
|
|
dset(exports, sepLocation, `__TWSEP__${separator}__TWSEP__`)
|
|
|
|
exports[isV3 ? 'content' : 'purge'] = []
|
2021-05-20 16:07:46 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
let mode = getMode(exports)
|
|
|
|
deleteMode(exports)
|
2021-05-20 16:07:46 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
let isJit = isV3 || (state.modules.jit && mode === 'jit')
|
2021-10-01 13:11:45 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
if (isJit) {
|
|
|
|
state.jit = true
|
|
|
|
exports.variants = []
|
2021-05-20 16:07:46 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
if (Array.isArray(exports.presets)) {
|
|
|
|
for (let preset of exports.presets) {
|
|
|
|
presetVariants.push(preset.variants)
|
|
|
|
preset.variants = []
|
|
|
|
}
|
2021-05-20 16:07:46 +00:00
|
|
|
}
|
2023-03-20 11:12:02 +00:00
|
|
|
} else {
|
|
|
|
state.jit = false
|
2021-05-20 16:07:46 +00:00
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
if (state.corePlugins) {
|
|
|
|
let corePluginsConfig = {}
|
|
|
|
for (let pluginName of state.corePlugins) {
|
|
|
|
corePluginsConfig[pluginName] = true
|
|
|
|
}
|
|
|
|
exports.corePlugins = corePluginsConfig
|
2021-06-04 11:17:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
// inject JIT `matchUtilities` function
|
|
|
|
if (Array.isArray(exports.plugins)) {
|
|
|
|
exports.plugins = exports.plugins.map((plugin) => {
|
|
|
|
if (plugin.__isOptionsFunction) {
|
|
|
|
plugin = plugin()
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2023-03-20 11:12:02 +00:00
|
|
|
if (typeof plugin === 'function') {
|
|
|
|
let newPlugin = (...args) => {
|
2021-05-05 15:25:36 +00:00
|
|
|
if (!args[0].matchUtilities) {
|
|
|
|
args[0].matchUtilities = () => {}
|
|
|
|
}
|
2023-03-20 11:12:02 +00:00
|
|
|
return plugin(...args)
|
|
|
|
}
|
|
|
|
// @ts-ignore
|
|
|
|
newPlugin.__intellisense_cache_bust = Math.random()
|
|
|
|
return newPlugin
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2023-03-20 11:12:02 +00:00
|
|
|
if (plugin.handler) {
|
|
|
|
return {
|
|
|
|
...plugin,
|
|
|
|
handler: (...args) => {
|
|
|
|
if (!args[0].matchUtilities) {
|
|
|
|
args[0].matchUtilities = () => {}
|
|
|
|
}
|
|
|
|
return plugin.handler(...args)
|
|
|
|
},
|
|
|
|
__intellisense_cache_bust: Math.random(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return plugin
|
|
|
|
})
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
return exports
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
try {
|
|
|
|
require(state.configPath)
|
|
|
|
} catch (error) {
|
|
|
|
hook.unhook()
|
|
|
|
throw error
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 11:17:00 +00:00
|
|
|
if (!originalConfig) {
|
|
|
|
throw new SilentError(`Failed to load config file: ${state.configPath}`)
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
/////////////////////
|
|
|
|
if (!projectConfig.isUserConfigured) {
|
|
|
|
documentSelector = [
|
|
|
|
...documentSelector.filter(
|
|
|
|
({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE
|
|
|
|
),
|
|
|
|
...getContentDocumentSelectorFromConfigFile(
|
|
|
|
state.configPath,
|
|
|
|
tailwindcss.version,
|
|
|
|
projectConfig.folder,
|
|
|
|
originalConfig
|
|
|
|
),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
//////////////////////
|
|
|
|
|
2021-10-29 16:49:42 +00:00
|
|
|
try {
|
|
|
|
state.config = resolveConfig.module(originalConfig)
|
|
|
|
state.separator = state.config.separator
|
2023-03-27 17:56:02 +00:00
|
|
|
state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
|
|
|
|
delete state.config.blocklist
|
2021-10-29 16:49:42 +00:00
|
|
|
|
|
|
|
if (state.jit) {
|
|
|
|
state.jitContext = state.modules.jit.createContext.module(state)
|
|
|
|
state.jitContext.tailwindConfig.separator = state.config.separator
|
|
|
|
if (state.jitContext.getClassList) {
|
2023-01-27 10:33:35 +00:00
|
|
|
let classList = state.jitContext
|
|
|
|
.getClassList({ includeMetadata: true })
|
2021-12-06 16:06:30 +00:00
|
|
|
.filter((className) => className !== '*')
|
2023-01-27 10:33:35 +00:00
|
|
|
state.classListContainsMetadata = classList.some((cls) => Array.isArray(cls))
|
|
|
|
state.classList = classList.map((className) => {
|
|
|
|
if (Array.isArray(className)) {
|
|
|
|
return [
|
|
|
|
className[0],
|
|
|
|
{ color: getColor(state, className[0]), ...(className[1] ?? {}) },
|
|
|
|
]
|
|
|
|
}
|
|
|
|
return [className, { color: getColor(state, className) }]
|
|
|
|
})
|
2021-10-29 16:49:42 +00:00
|
|
|
}
|
2021-11-05 12:38:54 +00:00
|
|
|
} else {
|
|
|
|
delete state.jitContext
|
|
|
|
delete state.classList
|
2021-10-01 13:11:45 +00:00
|
|
|
}
|
2021-10-29 16:49:42 +00:00
|
|
|
} catch (error) {
|
2023-03-20 11:12:02 +00:00
|
|
|
hook?.unhook()
|
2021-10-29 16:49:42 +00:00
|
|
|
throw error
|
2021-05-10 12:41:48 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
let postcssResult: Result
|
2021-10-01 13:11:45 +00:00
|
|
|
|
|
|
|
if (state.classList) {
|
2023-03-20 11:12:02 +00:00
|
|
|
hook?.unhook()
|
2021-10-01 13:11:45 +00:00
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
postcssResult = await postcss
|
|
|
|
.module([
|
|
|
|
// ...state.postcssPlugins.before.map((x) => x()),
|
|
|
|
tailwindcss.module(state.configPath),
|
|
|
|
// ...state.postcssPlugins.after.map((x) => x()),
|
|
|
|
])
|
|
|
|
.process(
|
|
|
|
[
|
|
|
|
semver.gte(tailwindcss.version, '0.99.0') ? 'base' : 'preflight',
|
|
|
|
'components',
|
|
|
|
'utilities',
|
|
|
|
]
|
|
|
|
.map((x) => `/*__tw_intellisense_layer_${x}__*/\n@tailwind ${x};`)
|
|
|
|
.join('\n'),
|
|
|
|
{
|
|
|
|
from: undefined,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} catch (error) {
|
|
|
|
throw error
|
|
|
|
} finally {
|
2023-03-20 11:12:02 +00:00
|
|
|
hook?.unhook()
|
2021-10-01 13:11:45 +00:00
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
// if (state.dependencies) {
|
|
|
|
// chokidarWatcher?.unwatch(state.dependencies)
|
|
|
|
// }
|
2021-05-05 16:56:45 +00:00
|
|
|
state.dependencies = getModuleDependencies(state.configPath)
|
2022-10-18 19:35:02 +00:00
|
|
|
// chokidarWatcher?.add(state.dependencies)
|
2023-01-27 11:24:28 +00:00
|
|
|
watchPatterns(
|
|
|
|
(state.dependencies ?? []).flatMap((dep) =>
|
|
|
|
getWatchPatternsForFile(dep, projectConfig.folder)
|
|
|
|
)
|
|
|
|
)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2021-05-05 16:56:45 +00:00
|
|
|
state.configId = getConfigId(state.configPath, state.dependencies)
|
|
|
|
|
2021-06-04 11:17:00 +00:00
|
|
|
state.plugins = await getPlugins(originalConfig)
|
2021-10-01 13:11:45 +00:00
|
|
|
if (postcssResult) {
|
|
|
|
state.classNames = (await extractClassNames(postcssResult.root)) as ClassNames
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
state.variants = getVariants(state)
|
|
|
|
|
2021-05-17 11:38:52 +00:00
|
|
|
let screens = dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {}))
|
|
|
|
state.screens = isObject(screens) ? Object.keys(screens) : []
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
state.enabled = true
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
// updateAllDiagnostics(state)
|
|
|
|
refreshDiagnostics()
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-04-25 14:06:31 +00:00
|
|
|
updateCapabilities()
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2023-01-16 19:28:49 +00:00
|
|
|
projectConfig,
|
2022-10-18 19:35:02 +00:00
|
|
|
enabled() {
|
|
|
|
return enabled
|
|
|
|
},
|
|
|
|
enable() {
|
|
|
|
enabled = true
|
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
state,
|
2022-10-18 19:35:02 +00:00
|
|
|
documentSelector() {
|
|
|
|
return documentSelector
|
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
tryInit,
|
2022-10-18 19:35:02 +00:00
|
|
|
async dispose() {
|
|
|
|
state = { enabled: false }
|
|
|
|
for (let disposable of disposables) {
|
|
|
|
;(await disposable).dispose()
|
2021-06-14 13:11:29 +00:00
|
|
|
}
|
|
|
|
},
|
2022-01-24 12:21:15 +00:00
|
|
|
async onUpdateSettings(settings: any): Promise<void> {
|
2022-10-18 19:35:02 +00:00
|
|
|
if (state.enabled) {
|
|
|
|
refreshDiagnostics()
|
|
|
|
}
|
|
|
|
if (settings.editor.colorDecorators) {
|
|
|
|
updateCapabilities()
|
2021-05-03 17:00:04 +00:00
|
|
|
} else {
|
2022-10-18 19:35:02 +00:00
|
|
|
connection.sendNotification('@/tailwindCSS/clearColors')
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
},
|
2022-04-25 14:06:31 +00:00
|
|
|
onFileEvents,
|
2022-01-07 17:13:11 +00:00
|
|
|
async onHover(params: TextDocumentPositionParams): Promise<Hover> {
|
2022-10-18 19:35:02 +00:00
|
|
|
return withFallback(async () => {
|
|
|
|
if (!state.enabled) return null
|
|
|
|
let document = documentService.getDocument(params.textDocument.uri)
|
|
|
|
if (!document) return null
|
|
|
|
let settings = await state.editor.getConfiguration(document.uri)
|
|
|
|
if (!settings.tailwindCSS.hovers) return null
|
|
|
|
if (await isExcluded(state, document)) return null
|
|
|
|
return doHover(state, document, params.position)
|
|
|
|
}, null)
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
2022-01-07 17:13:11 +00:00
|
|
|
async onCompletion(params: CompletionParams): Promise<CompletionList> {
|
2022-10-18 19:35:02 +00:00
|
|
|
return withFallback(async () => {
|
|
|
|
if (!state.enabled) return null
|
|
|
|
let document = documentService.getDocument(params.textDocument.uri)
|
|
|
|
if (!document) return null
|
|
|
|
let settings = await state.editor.getConfiguration(document.uri)
|
|
|
|
if (!settings.tailwindCSS.suggestions) return null
|
|
|
|
if (await isExcluded(state, document)) return null
|
2023-01-27 10:30:27 +00:00
|
|
|
return doComplete(state, document, params.position, params.context)
|
2022-10-18 19:35:02 +00:00
|
|
|
}, null)
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
|
|
|
onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
|
2022-10-18 19:35:02 +00:00
|
|
|
return withFallback(() => {
|
|
|
|
if (!state.enabled) return null
|
2023-01-27 10:30:27 +00:00
|
|
|
return resolveCompletionItem(state, item)
|
2022-10-18 19:35:02 +00:00
|
|
|
}, null)
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
2022-04-20 14:04:51 +00:00
|
|
|
async onCodeAction(params: CodeActionParams): Promise<CodeAction[]> {
|
2022-10-18 19:35:02 +00:00
|
|
|
return withFallback(async () => {
|
|
|
|
if (!state.enabled) return null
|
|
|
|
let document = documentService.getDocument(params.textDocument.uri)
|
|
|
|
if (!document) return null
|
|
|
|
let settings = await state.editor.getConfiguration(document.uri)
|
|
|
|
if (!settings.tailwindCSS.codeActions) return null
|
2023-03-27 17:39:56 +00:00
|
|
|
return doCodeActions(state, params, document)
|
2022-10-18 19:35:02 +00:00
|
|
|
}, null)
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
2022-10-17 16:59:07 +00:00
|
|
|
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()
|
|
|
|
)
|
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
provideDiagnostics: debounce((document: TextDocument) => {
|
|
|
|
if (!state.enabled) return
|
|
|
|
provideDiagnostics(state, document)
|
|
|
|
}, 500),
|
2022-10-18 19:35:02 +00:00
|
|
|
provideDiagnosticsForce: (document: TextDocument) => {
|
|
|
|
if (!state.enabled) return
|
|
|
|
provideDiagnostics(state, document)
|
|
|
|
},
|
2021-05-03 17:00:04 +00:00
|
|
|
async onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]> {
|
2022-10-18 19:35:02 +00:00
|
|
|
return withFallback(async () => {
|
|
|
|
if (!state.enabled) return []
|
|
|
|
let document = documentService.getDocument(params.textDocument.uri)
|
|
|
|
if (!document) return []
|
|
|
|
if (await isExcluded(state, document)) return null
|
|
|
|
return getDocumentColors(state, document)
|
|
|
|
}, null)
|
2021-05-03 17:00:04 +00:00
|
|
|
},
|
|
|
|
async onColorPresentation(params: ColorPresentationParams): Promise<ColorPresentation[]> {
|
|
|
|
let document = documentService.getDocument(params.textDocument.uri)
|
2022-10-18 19:35:02 +00:00
|
|
|
if (!document) return []
|
2021-05-03 17:00:04 +00:00
|
|
|
let className = document.getText(params.range)
|
|
|
|
let match = className.match(
|
|
|
|
new RegExp(`-\\[(${colorNames.join('|')}|(?:(?:#|rgba?\\(|hsla?\\())[^\\]]+)\\]$`, 'i')
|
|
|
|
)
|
|
|
|
// let match = className.match(/-\[((?:#|rgba?\(|hsla?\()[^\]]+)\]$/i)
|
|
|
|
if (match === null) return []
|
|
|
|
|
|
|
|
let currentColor = match[1]
|
|
|
|
|
|
|
|
let isNamedColor = colorNames.includes(currentColor)
|
2021-10-01 13:11:45 +00:00
|
|
|
|
|
|
|
let color: culori.RgbColor = {
|
|
|
|
mode: 'rgb',
|
2021-05-03 17:00:04 +00:00
|
|
|
r: params.color.red,
|
|
|
|
g: params.color.green,
|
|
|
|
b: params.color.blue,
|
2021-10-01 13:11:45 +00:00
|
|
|
alpha: params.color.alpha,
|
|
|
|
}
|
|
|
|
|
|
|
|
let hexValue = culori.formatHex8(color)
|
|
|
|
|
|
|
|
if (!isNamedColor && (currentColor.length === 4 || currentColor.length === 5)) {
|
|
|
|
let [, ...chars] =
|
|
|
|
hexValue.match(/^#([a-f\d])\1([a-f\d])\2([a-f\d])\3(?:([a-f\d])\4)?$/i) ?? []
|
|
|
|
if (chars.length) {
|
|
|
|
hexValue = `#${chars.filter(Boolean).join('')}`
|
|
|
|
}
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
if (hexValue.length === 5) {
|
|
|
|
hexValue = hexValue.replace(/f$/, '')
|
|
|
|
} else if (hexValue.length === 9) {
|
|
|
|
hexValue = hexValue.replace(/ff$/, '')
|
|
|
|
}
|
|
|
|
|
|
|
|
let prefix = className.substr(0, match.index)
|
|
|
|
|
|
|
|
return [
|
|
|
|
hexValue,
|
2021-10-01 13:11:45 +00:00
|
|
|
culori.formatRgb(color).replace(/ /g, ''),
|
|
|
|
culori
|
|
|
|
.formatHsl(color)
|
|
|
|
.replace(/ /g, '')
|
|
|
|
// round numbers
|
|
|
|
.replace(/\d+\.\d+(%?)/g, (value, suffix) => `${Math.round(parseFloat(value))}${suffix}`),
|
2021-05-03 17:00:04 +00:00
|
|
|
].map((value) => ({ label: `${prefix}-[${value}]` }))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function isObject(value: unknown): boolean {
|
|
|
|
return Object.prototype.toString.call(value) === '[object Object]'
|
|
|
|
}
|
|
|
|
|
|
|
|
type SimplePlugin = (api: any) => {}
|
|
|
|
type WrappedPlugin = { handler: (api: any) => {} }
|
|
|
|
type Plugin = SimplePlugin | WrappedPlugin
|
|
|
|
|
|
|
|
function runPlugin(
|
|
|
|
plugin: Plugin,
|
|
|
|
state: State,
|
|
|
|
apiOverrides: Record<string, Function> = {}
|
|
|
|
): void {
|
|
|
|
let config = state.config
|
|
|
|
let postcss = state.modules.postcss.module
|
|
|
|
let browserslist = state.browserslist
|
|
|
|
|
|
|
|
const browserslistTarget = browserslist && browserslist.includes('ie 11') ? 'ie11' : 'relaxed'
|
|
|
|
const pluginFn = typeof plugin === 'function' ? plugin : plugin.handler
|
|
|
|
|
|
|
|
try {
|
|
|
|
pluginFn({
|
|
|
|
addUtilities: () => {},
|
|
|
|
addComponents: () => {},
|
|
|
|
addBase: () => {},
|
2021-05-05 15:22:25 +00:00
|
|
|
matchUtilities: () => {},
|
2021-05-03 17:00:04 +00:00
|
|
|
addVariant: () => {},
|
|
|
|
e: (x) => x,
|
|
|
|
prefix: (x) => x,
|
|
|
|
theme: (path, defaultValue) => dlv(config, `theme.${path}`, defaultValue),
|
|
|
|
variants: () => [],
|
|
|
|
config: (path, defaultValue) => dlv(config, path, defaultValue),
|
|
|
|
corePlugins: (path) => {
|
|
|
|
if (Array.isArray(config.corePlugins)) {
|
|
|
|
return config.corePlugins.includes(path)
|
|
|
|
}
|
|
|
|
return dlv(config, `corePlugins.${path}`, true)
|
|
|
|
},
|
|
|
|
target: (path) => {
|
|
|
|
if (typeof config.target === 'string') {
|
|
|
|
return config.target === 'browserslist' ? browserslistTarget : config.target
|
|
|
|
}
|
|
|
|
const [defaultTarget, targetOverrides] = dlv(config, 'target')
|
|
|
|
const target = dlv(targetOverrides, path, defaultTarget)
|
|
|
|
return target === 'browserslist' ? browserslistTarget : target
|
|
|
|
},
|
|
|
|
postcss,
|
|
|
|
...apiOverrides,
|
|
|
|
})
|
|
|
|
} catch (_) {}
|
|
|
|
}
|
|
|
|
|
2021-11-05 16:13:22 +00:00
|
|
|
function isAtRule(node: Node): node is AtRule {
|
|
|
|
return node.type === 'atrule'
|
|
|
|
}
|
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
function getVariants(state: State): Array<Variant> {
|
|
|
|
if (state.jitContext?.getVariants) {
|
|
|
|
return state.jitContext.getVariants()
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
if (state.jit) {
|
|
|
|
let result: Array<Variant> = []
|
2021-06-04 11:17:00 +00:00
|
|
|
// [name, [sort, fn]]
|
|
|
|
// [name, [[sort, fn]]]
|
|
|
|
Array.from(state.jitContext.variantMap as Map<string, [any, any]>).forEach(
|
|
|
|
([variantName, variantFnOrFns]) => {
|
2022-10-17 17:05:04 +00:00
|
|
|
result.push({
|
|
|
|
name: variantName,
|
|
|
|
values: [],
|
|
|
|
isArbitrary: false,
|
|
|
|
hasDash: true,
|
|
|
|
selectors: () => {
|
|
|
|
function escape(className: string): string {
|
|
|
|
let node = state.modules.postcssSelectorParser.module.className()
|
|
|
|
node.value = className
|
|
|
|
return dlv(node, 'raws.value', node.value)
|
|
|
|
}
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
|
|
|
|
([_sort, fn]) => fn
|
|
|
|
)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
let placeholder = '__variant_placeholder__'
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
let root = state.modules.postcss.module.root({
|
|
|
|
nodes: [
|
|
|
|
state.modules.postcss.module.rule({
|
|
|
|
selector: `.${escape(placeholder)}`,
|
|
|
|
nodes: [],
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
let classNameParser = state.modules.postcssSelectorParser.module((selectors) => {
|
|
|
|
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
function getClassNameFromSelector(selector) {
|
|
|
|
return classNameParser.transformSync(selector)
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
function modifySelectors(modifierFunction) {
|
|
|
|
root.each((rule) => {
|
|
|
|
if (rule.type !== 'rule') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rule.selectors = rule.selectors.map((selector) => {
|
|
|
|
return modifierFunction({
|
|
|
|
get className() {
|
|
|
|
return getClassNameFromSelector(selector)
|
|
|
|
},
|
|
|
|
selector,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
|
|
|
|
let definitions = []
|
|
|
|
|
|
|
|
for (let fn of fns) {
|
|
|
|
let definition: string
|
|
|
|
let container = root.clone()
|
2022-10-18 19:35:02 +00:00
|
|
|
let returnValue = withoutLogs(() =>
|
|
|
|
fn({
|
|
|
|
container,
|
|
|
|
separator: state.separator,
|
|
|
|
modifySelectors,
|
|
|
|
format: (def: string) => {
|
|
|
|
definition = def.replace(/:merge\(([^)]+)\)/g, '$1')
|
|
|
|
},
|
|
|
|
wrap: (rule: Container) => {
|
|
|
|
if (isAtRule(rule)) {
|
|
|
|
definition = `@${rule.name} ${rule.params}`
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
if (!definition) {
|
|
|
|
definition = returnValue
|
2021-11-05 16:13:22 +00:00
|
|
|
}
|
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
if (definition) {
|
|
|
|
definitions.push(definition)
|
|
|
|
continue
|
|
|
|
}
|
2021-11-05 16:13:22 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
container.walkDecls((decl) => {
|
|
|
|
decl.remove()
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
definition = container
|
|
|
|
.toString()
|
|
|
|
.replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
|
|
|
|
.replace(/(?<!\\)[{}]/g, '')
|
|
|
|
.replace(/\s*\n\s*/g, ' ')
|
|
|
|
.trim()
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
if (!definition.includes(placeholder)) {
|
|
|
|
definitions.push(definition)
|
|
|
|
}
|
|
|
|
}
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
return definitions
|
|
|
|
},
|
|
|
|
})
|
2021-06-04 11:17:00 +00:00
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
)
|
2021-06-04 11:17:00 +00:00
|
|
|
|
|
|
|
return result
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let config = state.config
|
|
|
|
let tailwindcssVersion = state.modules.tailwindcss.version
|
|
|
|
|
|
|
|
let variants = ['responsive', 'hover']
|
|
|
|
semver.gte(tailwindcssVersion, '0.3.0') && variants.push('focus', 'group-hover')
|
|
|
|
semver.gte(tailwindcssVersion, '0.5.0') && variants.push('active')
|
|
|
|
semver.gte(tailwindcssVersion, '0.7.0') && variants.push('focus-within')
|
|
|
|
semver.gte(tailwindcssVersion, '1.0.0-beta.1') && variants.push('default')
|
|
|
|
semver.gte(tailwindcssVersion, '1.1.0') &&
|
|
|
|
variants.push('first', 'last', 'odd', 'even', 'disabled', 'visited')
|
|
|
|
semver.gte(tailwindcssVersion, '1.3.0') && variants.push('group-focus')
|
|
|
|
semver.gte(tailwindcssVersion, '1.5.0') && variants.push('focus-visible', 'checked')
|
|
|
|
semver.gte(tailwindcssVersion, '1.6.0') && variants.push('motion-safe', 'motion-reduce')
|
|
|
|
semver.gte(tailwindcssVersion, '2.0.0-alpha.1') && variants.push('dark')
|
|
|
|
|
|
|
|
let plugins = Array.isArray(config.plugins) ? config.plugins : []
|
|
|
|
|
|
|
|
plugins.forEach((plugin) => {
|
|
|
|
runPlugin(plugin, state, {
|
|
|
|
addVariant: (name) => {
|
|
|
|
variants.push(name)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-10-17 17:05:04 +00:00
|
|
|
return variants.map((variant) => ({
|
|
|
|
name: variant,
|
|
|
|
values: [],
|
|
|
|
isArbitrary: false,
|
|
|
|
hasDash: true,
|
|
|
|
selectors: () => [],
|
|
|
|
}))
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getPlugins(config: any) {
|
|
|
|
let plugins = config.plugins
|
|
|
|
|
|
|
|
if (!Array.isArray(plugins)) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all(
|
|
|
|
plugins.map(async (plugin) => {
|
|
|
|
let pluginConfig = plugin.config
|
|
|
|
if (!isObject(pluginConfig)) {
|
|
|
|
pluginConfig = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
let contributes = {
|
|
|
|
theme: isObject(pluginConfig.theme) ? Object.keys(pluginConfig.theme) : [],
|
|
|
|
variants: isObject(pluginConfig.variants) ? Object.keys(pluginConfig.variants) : [],
|
|
|
|
}
|
|
|
|
|
|
|
|
const fn = plugin.handler || plugin
|
|
|
|
const fnName =
|
|
|
|
typeof fn.name === 'string' && fn.name !== 'handler' && fn.name !== '' ? fn.name : null
|
|
|
|
|
|
|
|
try {
|
|
|
|
fn()
|
|
|
|
} catch (e) {
|
|
|
|
const trace = stackTrace.parse(e)
|
|
|
|
if (trace.length === 0) {
|
|
|
|
return {
|
|
|
|
name: fnName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const file = trace[0].fileName
|
|
|
|
const dir = path.dirname(file)
|
|
|
|
let pkgPath = pkgUp.sync({ cwd: dir })
|
|
|
|
if (!pkgPath) {
|
|
|
|
return {
|
|
|
|
name: fnName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let pkg: any
|
|
|
|
try {
|
2022-04-13 12:54:33 +00:00
|
|
|
pkg = require(pkg)
|
2021-05-03 17:00:04 +00:00
|
|
|
} catch (_) {
|
|
|
|
return {
|
|
|
|
name: fnName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pkg.name && path.resolve(dir, pkg.main || 'index.js') === file) {
|
|
|
|
return {
|
|
|
|
name: pkg.name,
|
|
|
|
homepage: pkg.homepage,
|
|
|
|
contributes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
name: fnName,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
// try {
|
|
|
|
// return reimport(resolveFrom(tailwindDir, './lib/corePlugins.js'))
|
|
|
|
// return require('./lib/corePlugins.js', tailwindBase).default({
|
|
|
|
// corePlugins: resolvedConfig.corePlugins,
|
|
|
|
// })
|
|
|
|
// } catch (_) {
|
|
|
|
// return []
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
async function getConfigFileFromCssFile(cssFile: string): Promise<string | null> {
|
|
|
|
let css = getTextWithoutComments(await fs.promises.readFile(cssFile, 'utf8'), 'css')
|
|
|
|
let match = css.match(/@config\s*(?<config>'[^']+'|"[^"]+")/)
|
|
|
|
if (!match) {
|
|
|
|
return null
|
|
|
|
}
|
2023-01-27 11:24:28 +00:00
|
|
|
return normalizePath(path.resolve(path.dirname(cssFile), match.groups.config.slice(1, -1)))
|
2022-10-18 19:35:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getPackageRoot(cwd: string, rootDir: string) {
|
|
|
|
try {
|
|
|
|
let pkgJsonPath = findUp.sync(
|
|
|
|
(dir) => {
|
|
|
|
let pkgJson = path.join(dir, 'package.json')
|
|
|
|
if (findUp.sync.exists(pkgJson)) {
|
|
|
|
return pkgJson
|
|
|
|
}
|
2023-01-27 11:24:28 +00:00
|
|
|
if (dir === path.normalize(rootDir)) {
|
2022-10-18 19:35:02 +00:00
|
|
|
return findUp.stop
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ cwd }
|
|
|
|
)
|
|
|
|
return pkgJsonPath ? path.dirname(pkgJsonPath) : rootDir
|
|
|
|
} catch {
|
|
|
|
return rootDir
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getContentDocumentSelectorFromConfigFile(
|
|
|
|
configPath: string,
|
|
|
|
tailwindVersion: string,
|
|
|
|
rootDir: string,
|
|
|
|
actualConfig?: any
|
|
|
|
): DocumentSelector[] {
|
|
|
|
let config = actualConfig ?? require(configPath)
|
|
|
|
let contentConfig: unknown = config.content?.files ?? config.content
|
|
|
|
let content = Array.isArray(contentConfig) ? contentConfig : []
|
|
|
|
let relativeEnabled = semver.gte(tailwindVersion, '3.2.0')
|
|
|
|
? config.future?.relativeContentPathsByDefault || config.content?.relative
|
|
|
|
: false
|
|
|
|
let contentBase: string
|
|
|
|
if (relativeEnabled) {
|
|
|
|
contentBase = path.dirname(configPath)
|
|
|
|
} else {
|
|
|
|
contentBase = getPackageRoot(path.dirname(configPath), rootDir)
|
|
|
|
}
|
|
|
|
return content
|
|
|
|
.filter((item): item is string => typeof item === 'string')
|
|
|
|
.map((item) =>
|
|
|
|
item.startsWith('!')
|
|
|
|
? `!${path.resolve(contentBase, item.slice(1))}`
|
|
|
|
: path.resolve(contentBase, item)
|
|
|
|
)
|
|
|
|
.map((item) => ({
|
|
|
|
pattern: normalizePath(item),
|
|
|
|
priority: DocumentSelectorPriority.CONTENT_FILE,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
class TW {
|
|
|
|
private initialized = false
|
2022-10-18 19:35:02 +00:00
|
|
|
private lspHandlersAdded = false
|
2021-05-03 17:00:04 +00:00
|
|
|
private workspaces: Map<string, { name: string; workspaceFsPath: string }>
|
|
|
|
private projects: Map<string, ProjectService>
|
2023-01-16 19:28:49 +00:00
|
|
|
private projectCounter: number
|
2021-05-03 17:00:04 +00:00
|
|
|
private documentService: DocumentService
|
|
|
|
public initializeParams: InitializeParams
|
2022-04-25 14:06:31 +00:00
|
|
|
private registrations: Promise<BulkUnregistration>
|
2022-10-18 19:35:02 +00:00
|
|
|
private disposables: Disposable[] = []
|
2022-10-20 16:48:54 +00:00
|
|
|
private watchPatterns: (patterns: string[]) => void = () => {}
|
2022-10-18 19:35:02 +00:00
|
|
|
private watched: string[] = []
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
constructor(private connection: Connection) {
|
|
|
|
this.documentService = new DocumentService(this.connection)
|
|
|
|
this.workspaces = new Map()
|
|
|
|
this.projects = new Map()
|
2023-01-16 19:28:49 +00:00
|
|
|
this.projectCounter = 0
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async init(): Promise<void> {
|
|
|
|
if (this.initialized) return
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
clearRequireCache()
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
this.initialized = true
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2023-03-10 11:09:36 +00:00
|
|
|
let base: string
|
|
|
|
if (this.initializeParams.rootUri) {
|
|
|
|
base = URI.parse(this.initializeParams.rootUri).fsPath
|
|
|
|
} else if (this.initializeParams.rootPath) {
|
|
|
|
base = normalizeFileNameToFsPath(this.initializeParams.rootPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!base) {
|
2021-05-03 17:00:04 +00:00
|
|
|
console.error('No workspace folders found, not initializing.')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-10 11:09:36 +00:00
|
|
|
base = normalizePath(base)
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
let workspaceFolders: Array<ProjectConfig> = []
|
|
|
|
let globalSettings = await getConfiguration()
|
|
|
|
let ignore = globalSettings.tailwindCSS.files.exclude
|
|
|
|
let configFileOrFiles = globalSettings.tailwindCSS.experimental.configFile
|
2022-04-25 14:06:31 +00:00
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
let cssFileConfigMap: Map<string, string> = new Map()
|
|
|
|
let configTailwindVersionMap: Map<string, string> = new Map()
|
2022-04-25 14:06:31 +00:00
|
|
|
|
2022-10-20 17:41:01 +00:00
|
|
|
// base directory to resolve relative `experimental.configFile` paths against
|
|
|
|
let userDefinedConfigBase = this.initializeParams.initializationOptions?.workspaceFile
|
|
|
|
? path.dirname(this.initializeParams.initializationOptions.workspaceFile)
|
|
|
|
: base
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
if (configFileOrFiles) {
|
2022-04-25 14:06:31 +00:00
|
|
|
if (
|
|
|
|
typeof configFileOrFiles !== 'string' &&
|
|
|
|
(!isObject(configFileOrFiles) ||
|
|
|
|
!Object.entries(configFileOrFiles).every(([key, value]) => {
|
|
|
|
if (typeof key !== 'string') return false
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
return value.every((item) => typeof item === 'string')
|
|
|
|
}
|
|
|
|
return typeof value === 'string'
|
|
|
|
}))
|
|
|
|
) {
|
|
|
|
console.error('Invalid `experimental.configFile` configuration, not initializing.')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let configFiles =
|
2023-01-05 14:28:04 +00:00
|
|
|
typeof configFileOrFiles === 'string'
|
|
|
|
? { [configFileOrFiles]: path.resolve(base, '**') }
|
|
|
|
: configFileOrFiles
|
2022-04-25 14:06:31 +00:00
|
|
|
|
|
|
|
workspaceFolders = Object.entries(configFiles).map(
|
|
|
|
([relativeConfigPath, relativeDocumentSelectorOrSelectors]) => {
|
|
|
|
return {
|
|
|
|
folder: base,
|
2023-01-27 11:24:28 +00:00
|
|
|
configPath: normalizePath(path.resolve(userDefinedConfigBase, relativeConfigPath)),
|
2022-10-18 19:35:02 +00:00
|
|
|
documentSelector: [].concat(relativeDocumentSelectorOrSelectors).map((selector) => ({
|
|
|
|
priority: DocumentSelectorPriority.USER_CONFIGURED,
|
2023-01-27 10:35:37 +00:00
|
|
|
pattern: normalizePath(path.resolve(userDefinedConfigBase, selector)),
|
2022-10-18 19:35:02 +00:00
|
|
|
})),
|
|
|
|
isUserConfigured: true,
|
2022-04-25 14:06:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2022-10-18 19:35:02 +00:00
|
|
|
} else {
|
|
|
|
let projects: Record<string, Array<DocumentSelector>> = {}
|
|
|
|
|
|
|
|
let files = await glob([`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`], {
|
|
|
|
cwd: base,
|
|
|
|
ignore: (await getConfiguration()).tailwindCSS.files.exclude,
|
|
|
|
onlyFiles: true,
|
|
|
|
absolute: true,
|
|
|
|
suppressErrors: true,
|
|
|
|
dot: true,
|
|
|
|
concurrency: Math.max(os.cpus().length, 1),
|
|
|
|
})
|
|
|
|
|
|
|
|
for (let filename of files) {
|
|
|
|
let normalizedFilename = normalizePath(filename)
|
|
|
|
let isCssFile = minimatch(normalizedFilename, `**/${CSS_GLOB}`, { dot: true })
|
|
|
|
let configPath = isCssFile ? await getConfigFileFromCssFile(filename) : filename
|
|
|
|
if (!configPath) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
let twVersion = require('tailwindcss/package.json').version
|
|
|
|
let isDefaultVersion = true
|
|
|
|
try {
|
|
|
|
let v = require(resolveFrom(path.dirname(configPath), 'tailwindcss/package.json')).version
|
|
|
|
if (typeof v === 'string') {
|
|
|
|
twVersion = v
|
|
|
|
isDefaultVersion = false
|
|
|
|
}
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
if (isCssFile && (!semver.gte(twVersion, '3.2.0') || isDefaultVersion)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-03-20 11:12:02 +00:00
|
|
|
if (
|
|
|
|
(configPath.endsWith('.ts') || configPath.endsWith('.mjs')) &&
|
|
|
|
!semver.gte(twVersion, '3.3.0')
|
|
|
|
) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
configTailwindVersionMap.set(configPath, twVersion)
|
|
|
|
|
|
|
|
let contentSelector: Array<DocumentSelector> = []
|
|
|
|
try {
|
|
|
|
contentSelector = getContentDocumentSelectorFromConfigFile(configPath, twVersion, base)
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
let documentSelector: DocumentSelector[] = [
|
|
|
|
{
|
|
|
|
pattern: normalizePath(filename),
|
|
|
|
priority: isCssFile
|
|
|
|
? DocumentSelectorPriority.CSS_FILE
|
|
|
|
: DocumentSelectorPriority.CONFIG_FILE,
|
|
|
|
},
|
|
|
|
...(isCssFile
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
pattern: normalizePath(configPath),
|
|
|
|
priority: DocumentSelectorPriority.CONFIG_FILE,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
: []),
|
|
|
|
...contentSelector,
|
|
|
|
{
|
|
|
|
pattern: normalizePath(path.join(path.dirname(filename), '**')),
|
|
|
|
priority: isCssFile
|
|
|
|
? DocumentSelectorPriority.CSS_DIRECTORY
|
|
|
|
: DocumentSelectorPriority.CONFIG_DIRECTORY,
|
|
|
|
},
|
|
|
|
...(isCssFile
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
pattern: normalizePath(path.join(path.dirname(configPath), '**')),
|
|
|
|
priority: DocumentSelectorPriority.CONFIG_DIRECTORY,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
: []),
|
|
|
|
{
|
|
|
|
pattern: normalizePath(path.join(getPackageRoot(path.dirname(configPath), base), '**')),
|
2023-01-27 13:40:13 +00:00
|
|
|
priority: DocumentSelectorPriority.PACKAGE_DIRECTORY,
|
2022-10-18 19:35:02 +00:00
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
projects[configPath] = [...(projects[configPath] ?? []), ...documentSelector]
|
|
|
|
|
|
|
|
if (isCssFile) {
|
|
|
|
cssFileConfigMap.set(normalizedFilename, configPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-27 13:40:13 +00:00
|
|
|
let projectKeys = Object.keys(projects)
|
|
|
|
let projectCount = projectKeys.length
|
|
|
|
|
|
|
|
if (projectCount > 0) {
|
|
|
|
if (projectCount === 1) {
|
|
|
|
projects[projectKeys[0]].push({
|
|
|
|
pattern: normalizePath(path.join(base, '**')),
|
|
|
|
priority: DocumentSelectorPriority.ROOT_DIRECTORY,
|
|
|
|
})
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
workspaceFolders = Object.entries(projects).map(([configPath, documentSelector]) => {
|
|
|
|
return {
|
|
|
|
folder: base,
|
|
|
|
configPath,
|
|
|
|
isUserConfigured: false,
|
|
|
|
documentSelector: documentSelector
|
|
|
|
.sort((a, z) => a.priority - z.priority)
|
|
|
|
.filter(
|
|
|
|
({ pattern }, index, documentSelectors) =>
|
|
|
|
documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index
|
|
|
|
),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2022-04-25 14:06:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
console.log(`[Global] Creating projects: ${JSON.stringify(workspaceFolders)}`)
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
const onDidChangeWatchedFiles = async (
|
|
|
|
changes: Array<{ file: string; type: FileChangeType }>
|
|
|
|
): Promise<void> => {
|
|
|
|
let needsRestart = false
|
|
|
|
|
|
|
|
changeLoop: for (let change of changes) {
|
|
|
|
let normalizedFilename = normalizePath(change.file)
|
|
|
|
|
|
|
|
for (let ignorePattern of ignore) {
|
|
|
|
if (minimatch(normalizedFilename, ignorePattern, { dot: true })) {
|
|
|
|
continue changeLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let isPackageFile = minimatch(normalizedFilename, `**/${PACKAGE_LOCK_GLOB}`, { dot: true })
|
|
|
|
if (isPackageFile) {
|
2023-01-16 19:28:49 +00:00
|
|
|
for (let [, project] of this.projects) {
|
2022-10-18 19:35:02 +00:00
|
|
|
let twVersion = require('tailwindcss/package.json').version
|
|
|
|
try {
|
|
|
|
let v = require(resolveFrom(
|
2023-01-16 19:28:49 +00:00
|
|
|
path.dirname(project.projectConfig.configPath),
|
2022-10-18 19:35:02 +00:00
|
|
|
'tailwindcss/package.json'
|
|
|
|
)).version
|
|
|
|
if (typeof v === 'string') {
|
|
|
|
twVersion = v
|
|
|
|
}
|
|
|
|
} catch {}
|
2023-01-16 19:28:49 +00:00
|
|
|
if (configTailwindVersionMap.get(project.projectConfig.configPath) !== twVersion) {
|
2022-10-18 19:35:02 +00:00
|
|
|
needsRestart = true
|
|
|
|
break changeLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let isCssFile = minimatch(normalizedFilename, `**/${CSS_GLOB}`, {
|
|
|
|
dot: true,
|
|
|
|
})
|
|
|
|
if (isCssFile) {
|
|
|
|
let configPath = await getConfigFileFromCssFile(change.file)
|
|
|
|
if (
|
|
|
|
cssFileConfigMap.has(normalizedFilename) &&
|
|
|
|
cssFileConfigMap.get(normalizedFilename) !== configPath
|
|
|
|
) {
|
|
|
|
needsRestart = true
|
|
|
|
break
|
|
|
|
} else if (!cssFileConfigMap.has(normalizedFilename) && configPath) {
|
|
|
|
needsRestart = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let isConfigFile = minimatch(normalizedFilename, `**/${CONFIG_GLOB}`, {
|
|
|
|
dot: true,
|
|
|
|
})
|
|
|
|
if (isConfigFile && change.type === FileChangeType.Created) {
|
|
|
|
needsRestart = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2023-01-16 19:28:49 +00:00
|
|
|
for (let [, project] of this.projects) {
|
2022-10-18 19:35:02 +00:00
|
|
|
if (
|
|
|
|
change.type === FileChangeType.Deleted &&
|
2023-01-16 19:28:49 +00:00
|
|
|
changeAffectsFile(normalizedFilename, [project.projectConfig.configPath])
|
2022-10-18 19:35:02 +00:00
|
|
|
) {
|
|
|
|
needsRestart = true
|
|
|
|
break changeLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsRestart) {
|
|
|
|
this.restart()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let [, project] of this.projects) {
|
|
|
|
project.onFileEvents(changes)
|
|
|
|
}
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-04-25 14:06:31 +00:00
|
|
|
if (this.initializeParams.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) {
|
2022-10-18 19:35:02 +00:00
|
|
|
this.disposables.push(
|
|
|
|
this.connection.onDidChangeWatchedFiles(async ({ changes }) => {
|
|
|
|
let normalizedChanges = changes
|
|
|
|
.map(({ uri, type }) => ({
|
2022-04-25 14:06:31 +00:00
|
|
|
file: URI.parse(uri).fsPath,
|
|
|
|
type,
|
|
|
|
}))
|
2022-10-18 19:35:02 +00:00
|
|
|
.filter(
|
|
|
|
(change, changeIndex, changes) =>
|
|
|
|
changes.findIndex((c) => c.file === change.file && c.type === change.type) ===
|
|
|
|
changeIndex
|
|
|
|
)
|
|
|
|
|
|
|
|
await onDidChangeWatchedFiles(normalizedChanges)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
let disposable = await this.connection.client.register(
|
|
|
|
DidChangeWatchedFilesNotification.type,
|
|
|
|
{
|
|
|
|
watchers: [
|
|
|
|
{ globPattern: `**/${CONFIG_GLOB}` },
|
|
|
|
{ globPattern: `**/${PACKAGE_LOCK_GLOB}` },
|
|
|
|
{ globPattern: `**/${CSS_GLOB}` },
|
|
|
|
],
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
this.disposables.push(disposable)
|
|
|
|
|
|
|
|
this.watchPatterns = (patterns) => {
|
|
|
|
let newPatterns = this.filterNewWatchPatterns(patterns)
|
|
|
|
if (newPatterns.length) {
|
|
|
|
console.log(`[Global] Adding watch patterns: ${newPatterns.join(', ')}`)
|
|
|
|
this.connection.client
|
|
|
|
.register(DidChangeWatchedFilesNotification.type, {
|
|
|
|
watchers: newPatterns.map((pattern) => ({ globPattern: pattern })),
|
|
|
|
})
|
|
|
|
.then((disposable) => {
|
|
|
|
this.disposables.push(disposable)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (parcel.getBinding()) {
|
|
|
|
let typeMap = {
|
|
|
|
create: FileChangeType.Created,
|
|
|
|
update: FileChangeType.Changed,
|
|
|
|
delete: FileChangeType.Deleted,
|
|
|
|
}
|
|
|
|
|
|
|
|
let subscription = await parcel.subscribe(
|
|
|
|
base,
|
|
|
|
(err, events) => {
|
|
|
|
onDidChangeWatchedFiles(
|
|
|
|
events.map((event) => ({ file: event.path, type: typeMap[event.type] }))
|
2022-04-25 14:06:31 +00:00
|
|
|
)
|
2022-10-18 19:35:02 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
ignore: ignore.map((ignorePattern) =>
|
|
|
|
path.resolve(base, ignorePattern.replace(/^[*/]+/, '').replace(/[*/]+$/, ''))
|
|
|
|
),
|
2022-04-25 14:06:31 +00:00
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
this.disposables.push({
|
|
|
|
dispose() {
|
|
|
|
subscription.unsubscribe()
|
|
|
|
},
|
2022-04-25 14:06:31 +00:00
|
|
|
})
|
2022-10-18 19:35:02 +00:00
|
|
|
} else {
|
|
|
|
let watch: typeof chokidar.watch = require('chokidar').watch
|
|
|
|
let chokidarWatcher = watch(
|
|
|
|
[`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`],
|
|
|
|
{
|
|
|
|
cwd: base,
|
|
|
|
ignorePermissionErrors: true,
|
|
|
|
ignoreInitial: true,
|
|
|
|
ignored: ignore,
|
|
|
|
awaitWriteFinish: {
|
|
|
|
stabilityThreshold: 100,
|
|
|
|
pollInterval: 20,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
await new Promise<void>((resolve) => {
|
|
|
|
chokidarWatcher.on('ready', () => resolve())
|
|
|
|
})
|
|
|
|
|
|
|
|
chokidarWatcher
|
|
|
|
.on('add', (file) =>
|
|
|
|
onDidChangeWatchedFiles([
|
|
|
|
{ file: path.resolve(base, file), type: FileChangeType.Created },
|
|
|
|
])
|
|
|
|
)
|
|
|
|
.on('change', (file) =>
|
|
|
|
onDidChangeWatchedFiles([
|
|
|
|
{ file: path.resolve(base, file), type: FileChangeType.Changed },
|
|
|
|
])
|
|
|
|
)
|
|
|
|
.on('unlink', (file) =>
|
|
|
|
onDidChangeWatchedFiles([
|
|
|
|
{ file: path.resolve(base, file), type: FileChangeType.Deleted },
|
|
|
|
])
|
|
|
|
)
|
|
|
|
|
|
|
|
this.disposables.push({
|
|
|
|
dispose() {
|
|
|
|
chokidarWatcher.close()
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
this.watchPatterns = (patterns) => {
|
|
|
|
let newPatterns = this.filterNewWatchPatterns(patterns)
|
|
|
|
if (newPatterns.length) {
|
|
|
|
console.log(`[Global] Adding watch patterns: ${newPatterns.join(', ')}`)
|
|
|
|
chokidarWatcher.add(newPatterns)
|
|
|
|
}
|
|
|
|
}
|
2022-04-25 14:06:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
await Promise.all(
|
|
|
|
workspaceFolders.map((projectConfig) =>
|
|
|
|
this.addProject(
|
|
|
|
projectConfig,
|
|
|
|
this.initializeParams,
|
|
|
|
this.watchPatterns,
|
|
|
|
configTailwindVersionMap.get(projectConfig.configPath)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
// init projects for documents that are _already_ open
|
|
|
|
for (let document of this.documentService.getAllDocuments()) {
|
|
|
|
let project = this.getProject(document)
|
|
|
|
if (project && !project.enabled()) {
|
|
|
|
project.enable()
|
|
|
|
await project.tryInit()
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
this.setupLSPHandlers()
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
this.disposables.push(
|
|
|
|
this.connection.onDidChangeConfiguration(async ({ settings }) => {
|
|
|
|
let previousExclude = globalSettings.tailwindCSS.files.exclude
|
|
|
|
|
|
|
|
documentSettingsCache.clear()
|
|
|
|
globalSettings = await getConfiguration()
|
|
|
|
|
|
|
|
if (!equal(previousExclude, globalSettings.tailwindCSS.files.exclude)) {
|
|
|
|
this.restart()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let [, project] of this.projects) {
|
|
|
|
project.onUpdateSettings(settings)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.disposables.push(
|
|
|
|
this.connection.onShutdown(() => {
|
|
|
|
this.dispose()
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.disposables.push(
|
|
|
|
this.documentService.onDidChangeContent((change) => {
|
|
|
|
this.getProject(change.document)?.provideDiagnostics(change.document)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.disposables.push(
|
|
|
|
this.documentService.onDidOpen((event) => {
|
|
|
|
let project = this.getProject(event.document)
|
|
|
|
if (project && !project.enabled()) {
|
|
|
|
project.enable()
|
|
|
|
project.tryInit()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
private filterNewWatchPatterns(patterns: string[]) {
|
|
|
|
let newWatchPatterns = patterns.filter((pattern) => !this.watched.includes(pattern))
|
|
|
|
this.watched.push(...newWatchPatterns)
|
|
|
|
return newWatchPatterns
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 19:35:02 +00:00
|
|
|
private async addProject(
|
|
|
|
projectConfig: ProjectConfig,
|
|
|
|
params: InitializeParams,
|
|
|
|
watchPatterns: (patterns: string[]) => void,
|
|
|
|
tailwindVersion: string
|
|
|
|
): Promise<void> {
|
2023-01-16 19:28:49 +00:00
|
|
|
let key = String(this.projectCounter++)
|
|
|
|
const project = await createProjectService(
|
|
|
|
key,
|
|
|
|
projectConfig,
|
|
|
|
this.connection,
|
|
|
|
params,
|
|
|
|
this.documentService,
|
|
|
|
() => this.updateCapabilities(),
|
|
|
|
() => {
|
|
|
|
for (let document of this.documentService.getAllDocuments()) {
|
|
|
|
let project = this.getProject(document)
|
|
|
|
if (project && !project.enabled()) {
|
|
|
|
project.enable()
|
|
|
|
project.tryInit()
|
|
|
|
break
|
2022-10-18 19:35:02 +00:00
|
|
|
}
|
2023-01-16 19:28:49 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
() => this.refreshDiagnostics(),
|
|
|
|
(patterns: string[]) => watchPatterns(patterns),
|
|
|
|
tailwindVersion
|
|
|
|
)
|
|
|
|
this.projects.set(key, project)
|
2022-10-18 19:35:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private refreshDiagnostics() {
|
|
|
|
for (let doc of this.documentService.getAllDocuments()) {
|
|
|
|
let project = this.getProject(doc)
|
|
|
|
if (project) {
|
|
|
|
project.provideDiagnosticsForce(doc)
|
|
|
|
} else {
|
|
|
|
this.connection.sendDiagnostics({ uri: doc.uri, diagnostics: [] })
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private setupLSPHandlers() {
|
2022-10-18 19:35:02 +00:00
|
|
|
if (this.lspHandlersAdded) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.lspHandlersAdded = true
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
this.connection.onHover(this.onHover.bind(this))
|
|
|
|
this.connection.onCompletion(this.onCompletion.bind(this))
|
|
|
|
this.connection.onCompletionResolve(this.onCompletionResolve.bind(this))
|
|
|
|
this.connection.onDocumentColor(this.onDocumentColor.bind(this))
|
|
|
|
this.connection.onColorPresentation(this.onColorPresentation.bind(this))
|
|
|
|
this.connection.onCodeAction(this.onCodeAction.bind(this))
|
2022-10-17 16:59:07 +00:00
|
|
|
this.connection.onDocumentLinks(this.onDocumentLinks.bind(this))
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2022-04-25 14:06:31 +00:00
|
|
|
private updateCapabilities() {
|
|
|
|
if (this.registrations) {
|
|
|
|
this.registrations.then((r) => r.dispose())
|
|
|
|
}
|
|
|
|
|
|
|
|
let projects = Array.from(this.projects.values())
|
|
|
|
|
|
|
|
let capabilities = BulkRegistration.create()
|
|
|
|
|
|
|
|
capabilities.add(HoverRequest.type, { documentSelector: null })
|
|
|
|
capabilities.add(DocumentColorRequest.type, { documentSelector: null })
|
|
|
|
capabilities.add(CodeActionRequest.type, { documentSelector: null })
|
2022-10-17 16:59:07 +00:00
|
|
|
capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
|
2022-04-25 14:06:31 +00:00
|
|
|
|
|
|
|
capabilities.add(CompletionRequest.type, {
|
|
|
|
documentSelector: null,
|
|
|
|
resolveProvider: true,
|
|
|
|
triggerCharacters: [
|
|
|
|
...TRIGGER_CHARACTERS,
|
2022-10-13 08:23:19 +00:00
|
|
|
...projects
|
|
|
|
.map((project) => project.state.separator)
|
|
|
|
.filter((sep) => typeof sep === 'string')
|
|
|
|
.map((sep) => sep.slice(-1)),
|
2022-04-25 14:06:31 +00:00
|
|
|
].filter(Boolean),
|
|
|
|
})
|
|
|
|
|
|
|
|
this.registrations = this.connection.client.register(capabilities)
|
|
|
|
}
|
|
|
|
|
|
|
|
private getProject(document: TextDocumentIdentifier): ProjectService {
|
|
|
|
let fallbackProject: ProjectService
|
2022-10-18 19:35:02 +00:00
|
|
|
let matchedProject: ProjectService
|
|
|
|
let matchedPriority: number = Infinity
|
|
|
|
|
2023-01-16 19:28:49 +00:00
|
|
|
for (let [, project] of this.projects) {
|
|
|
|
if (project.projectConfig.configPath) {
|
2022-10-18 19:35:02 +00:00
|
|
|
let documentSelector = project
|
|
|
|
.documentSelector()
|
|
|
|
.concat()
|
|
|
|
// move all the negated patterns to the front
|
|
|
|
.sort((a, z) => {
|
|
|
|
if (a.pattern.startsWith('!') && !z.pattern.startsWith('!')) {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if (!a.pattern.startsWith('!') && z.pattern.startsWith('!')) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
2023-01-05 14:56:39 +00:00
|
|
|
for (let selector of documentSelector) {
|
2022-10-18 19:35:02 +00:00
|
|
|
let fsPath = URI.parse(document.uri).fsPath
|
2023-01-05 14:56:39 +00:00
|
|
|
let pattern = selector.pattern.replace(/[\[\]{}]/g, (m) => `\\${m}`)
|
2022-10-18 19:35:02 +00:00
|
|
|
if (pattern.startsWith('!') && minimatch(fsPath, pattern.slice(1), { dot: true })) {
|
|
|
|
break
|
|
|
|
}
|
2023-01-05 14:56:39 +00:00
|
|
|
if (minimatch(fsPath, pattern, { dot: true }) && selector.priority < matchedPriority) {
|
2022-10-18 19:35:02 +00:00
|
|
|
matchedProject = project
|
2023-01-05 14:56:39 +00:00
|
|
|
matchedPriority = selector.priority
|
2022-04-25 14:06:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!fallbackProject) {
|
|
|
|
fallbackProject = project
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
|
|
|
|
if (matchedProject) {
|
|
|
|
return matchedProject
|
|
|
|
}
|
|
|
|
|
2022-04-25 14:06:31 +00:00
|
|
|
return fallbackProject
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
async onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]> {
|
2022-04-25 14:06:31 +00:00
|
|
|
return this.getProject(params.textDocument)?.onDocumentColor(params) ?? []
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async onColorPresentation(params: ColorPresentationParams): Promise<ColorPresentation[]> {
|
2022-04-25 14:06:31 +00:00
|
|
|
return this.getProject(params.textDocument)?.onColorPresentation(params) ?? []
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async onHover(params: TextDocumentPositionParams): Promise<Hover> {
|
2022-04-25 14:06:31 +00:00
|
|
|
return this.getProject(params.textDocument)?.onHover(params) ?? null
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async onCompletion(params: CompletionParams): Promise<CompletionList> {
|
2022-04-25 14:06:31 +00:00
|
|
|
return this.getProject(params.textDocument)?.onCompletion(params) ?? null
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
|
2023-01-27 10:30:27 +00:00
|
|
|
return this.projects.get(item.data?._projectKey)?.onCompletionResolve(item) ?? null
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
onCodeAction(params: CodeActionParams): Promise<CodeAction[]> {
|
2022-04-25 14:06:31 +00:00
|
|
|
return this.getProject(params.textDocument)?.onCodeAction(params) ?? null
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-17 16:59:07 +00:00
|
|
|
onDocumentLinks(params: DocumentLinkParams): DocumentLink[] {
|
|
|
|
return this.getProject(params.textDocument)?.onDocumentLinks(params) ?? null
|
|
|
|
}
|
|
|
|
|
2021-05-03 17:00:04 +00:00
|
|
|
listen() {
|
|
|
|
this.connection.listen()
|
|
|
|
}
|
|
|
|
|
|
|
|
dispose(): void {
|
2021-06-14 13:11:29 +00:00
|
|
|
for (let [, project] of this.projects) {
|
|
|
|
project.dispose()
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
this.projects = new Map()
|
|
|
|
|
|
|
|
this.refreshDiagnostics()
|
|
|
|
|
|
|
|
if (this.registrations) {
|
|
|
|
this.registrations.then((r) => r.dispose())
|
|
|
|
this.registrations = undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
this.disposables.forEach((d) => d.dispose())
|
|
|
|
this.disposables.length = 0
|
|
|
|
|
|
|
|
this.watched.length = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
restart(): void {
|
|
|
|
console.log('----------\nRESTARTING\n----------')
|
|
|
|
this.dispose()
|
|
|
|
this.initialized = false
|
|
|
|
this.init()
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class DocumentService {
|
|
|
|
public documents: TextDocuments<TextDocument>
|
|
|
|
|
|
|
|
constructor(conn: Connection) {
|
|
|
|
this.documents = new TextDocuments(TextDocument)
|
|
|
|
this.documents.listen(conn)
|
|
|
|
}
|
|
|
|
|
|
|
|
getDocument(uri: string) {
|
|
|
|
return this.documents.get(uri)
|
|
|
|
}
|
|
|
|
|
|
|
|
getAllDocuments() {
|
|
|
|
return this.documents.all()
|
|
|
|
}
|
|
|
|
|
|
|
|
get onDidChangeContent() {
|
|
|
|
return this.documents.onDidChangeContent
|
|
|
|
}
|
|
|
|
get onDidClose() {
|
|
|
|
return this.documents.onDidClose
|
|
|
|
}
|
2022-10-18 19:35:02 +00:00
|
|
|
get onDidOpen() {
|
|
|
|
return this.documents.onDidOpen
|
|
|
|
}
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function supportsDynamicRegistration(connection: Connection, params: InitializeParams): boolean {
|
|
|
|
return (
|
|
|
|
connection.onInitialized &&
|
2022-09-12 09:13:02 +00:00
|
|
|
params.capabilities.textDocument.hover?.dynamicRegistration &&
|
|
|
|
params.capabilities.textDocument.colorProvider?.dynamicRegistration &&
|
|
|
|
params.capabilities.textDocument.codeAction?.dynamicRegistration &&
|
2022-10-17 16:59:07 +00:00
|
|
|
params.capabilities.textDocument.completion?.dynamicRegistration &&
|
|
|
|
params.capabilities.textDocument.documentLink?.dynamicRegistration
|
2021-05-03 17:00:04 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const tw = new TW(connection)
|
|
|
|
|
2021-06-04 11:17:00 +00:00
|
|
|
connection.onInitialize(async (params: InitializeParams): Promise<InitializeResult> => {
|
|
|
|
tw.initializeParams = params
|
2021-05-03 17:00:04 +00:00
|
|
|
|
2021-06-04 11:17:00 +00:00
|
|
|
if (supportsDynamicRegistration(connection, params)) {
|
2021-05-03 17:00:04 +00:00
|
|
|
return {
|
|
|
|
capabilities: {
|
|
|
|
textDocumentSync: TextDocumentSyncKind.Full,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2021-06-04 11:17:00 +00:00
|
|
|
|
2022-12-13 17:05:12 +00:00
|
|
|
await tw.init()
|
2021-06-04 11:17:00 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
capabilities: {
|
|
|
|
textDocumentSync: TextDocumentSyncKind.Full,
|
|
|
|
hoverProvider: true,
|
|
|
|
colorProvider: true,
|
|
|
|
codeActionProvider: true,
|
2022-10-17 16:59:07 +00:00
|
|
|
documentLinkProvider: {},
|
2021-06-04 11:17:00 +00:00
|
|
|
completionProvider: {
|
|
|
|
resolveProvider: true,
|
|
|
|
triggerCharacters: [...TRIGGER_CHARACTERS, ':'],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
})
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
connection.onInitialized(async () => {
|
|
|
|
await tw.init()
|
|
|
|
})
|
|
|
|
|
|
|
|
tw.listen()
|