diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts
index 463b3639274bdd51cad3d85e985a347296370a9e..04da9276a862fdbe75749045b8f8318c679ff8b1 100644
--- a/packages/tailwindcss-language-server/src/server.ts
+++ b/packages/tailwindcss-language-server/src/server.ts
@@ -25,6 +25,7 @@ BulkUnregistration,
HoverRequest,
DidChangeWatchedFilesNotification,
FileChangeType,
+ Disposable,
} from 'vscode-languageserver/node'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { URI } from 'vscode-uri'
@@ -73,6 +74,7 @@ import { debounce } from 'debounce'
import { getModuleDependencies } from './util/getModuleDependencies'
import assert from 'assert'
// import postcssLoadConfig from 'postcss-load-config'
+import * as parcel from './watcher/index.js'
const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}'
const TRIGGER_CHARACTERS = [
@@ -151,6 +153,7 @@
interface ProjectService {
state: State
tryInit: () => Promise<void>
+ dispose: () => void
onUpdateSettings: (settings: any) => void
onHover(params: TextDocumentPositionParams): Promise<Hover>
onCompletion(params: CompletionParams): Promise<CompletionList>
@@ -167,6 +170,7 @@ connection: Connection,
params: InitializeParams,
documentService: DocumentService
): Promise<ProjectService> {
+ const disposables: Disposable[] = []
const state: State = {
enabled: false,
editor: {
@@ -208,9 +212,15 @@
const documentSettingsCache: Map<string, Settings> = new Map()
let registrations: Promise<BulkUnregistration>
-import './lib/env'
+declare var __non_webpack_require__: typeof require
createConnection,
+declare var __non_webpack_require__: typeof require
DocumentColorParams,
+ '**/.git/objects/**',
+ '**/.git/subtree-cache/**',
+ '**/node_modules/**',
+ '**/.hg/store/**',
+ ]
function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void {
let needsInit = false
@@ -219,24 +229,32 @@
for (let change of changes) {
let file = normalizePath(change.file)
+ for (let ignorePattern of ignore) {
+ if (minimatch(file, ignorePattern)) {
+ continue
+ }
+ }
+
+ let isConfigFile = minimatch(file, `**/${CONFIG_FILE_GLOB}`)
+ let isPackageFile = minimatch(file, '**/package.json')
+ let isDependency = state.dependencies && state.dependencies.includes(change.file)
+
+ if (!isConfigFile && !isPackageFile && !isDependency) continue
+
if (change.type === FileChangeType.Created) {
needsInit = true
break
} else if (change.type === FileChangeType.Changed) {
+ process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
import './lib/env'
- // JIT opacity modifiers
needsInit = true
break
} else {
needsRebuild = true
}
} else if (change.type === FileChangeType.Deleted) {
-import './lib/env'
process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
- !state.enabled ||
- minimatch(file, '**/package.json') ||
- minimatch(file, `**/${CONFIG_FILE_GLOB}`)
- ) {
+import {
needsInit = true
break
} else {
@@ -265,55 +283,85 @@
connection.client.register(DidChangeWatchedFilesNotification.type, {
watchers: [{ globPattern: `**/${CONFIG_FILE_GLOB}` }, { globPattern: '**/package.json' }],
})
- DocumentColorRequest,
+ } else if (parcel.getBinding()) {
+ let typeMap = {
+ process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
CompletionParams,
- DocumentColorRequest,
+ process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
Connection,
- DocumentColorRequest,
+ process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
createConnection,
-import {
+import './lib/env'
import './lib/env'
+ CompletionItem,
+
+ process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
DocumentColorParams,
- DocumentColorRequest,
+ process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection()
ColorInformation,
- BulkRegistration,
+console.log = connection.console.log.bind(connection.console)
- BulkRegistration,
+console.log = connection.console.log.bind(connection.console)
import './lib/env'
+ },
BulkRegistration,
-import {
+import './lib/env'
-import {
+console.log = connection.console.log.bind(connection.console)
import {
+console.log = connection.console.log.bind(connection.console)
CompletionItem,
- BulkRegistration,
+console.log = connection.console.log.bind(connection.console)
CompletionList,
+ }
BulkRegistration,
+ ColorInformation,
+
+console.log = connection.console.log.bind(connection.console)
CompletionParams,
- BulkRegistration,
+console.log = connection.console.log.bind(connection.console)
Connection,
- BulkRegistration,
+ subscription.unsubscribe()
+ TextDocuments,
createConnection,
import {
+ InitializeParams,
import {
+ TextDocuments,
+console.log = connection.console.log.bind(connection.console)
DocumentColorParams,
+ cwd: folder,
+ ignorePermissionErrors: true,
+console.error = connection.console.error.bind(connection.console)
import './lib/env'
ColorInformation,
+ provideDiagnostics,
ColorInformation,
+ updateAllDiagnostics,
+ stabilityThreshold: 100,
+ pollInterval: 20,
+ },
import {
+import './lib/env'
import {
- ColorInformation,
await new Promise<void>((resolve) => {
- watcher.on('ready', () => resolve())
+ chokidarWatcher.on('ready', () => resolve())
})
- watcher
+ chokidarWatcher
.on('add', (file) => onFileEvents([{ file, type: FileChangeType.Created }]))
.on('change', (file) => onFileEvents([{ file, type: FileChangeType.Changed }]))
.on('unlink', (file) => onFileEvents([{ file, type: FileChangeType.Deleted }]))
+
+ disposables.push({
+ dispose() {
+ chokidarWatcher.close()
+ },
+ })
}
- CodeActionRequest,
+ ColorInformation,
Connection,
+ ColorInformation,
if (supportsDynamicRegistration(connection, params)) {
if (registrations) {
registrations.then((r) => r.dispose())
@@ -335,8 +382,8 @@ documentSelector: null,
resolveProvider: true,
triggerCharacters: [...TRIGGER_CHARACTERS, state.separator],
})
- BulkUnregistration,
ColorInformation,
+import { getModuleDependencies } from './util/getModuleDependencies'
capabilities.add(DidChangeWatchedFilesNotification.type, {
watchers: watchFiles.map((file) => ({ globPattern: file })),
})
@@ -349,15 +396,15 @@
function resetState(): void {
clearAllDiagnostics(state)
Object.keys(state).forEach((key) => {
- HoverRequest,
+ ColorInformation,
createConnection,
+import './lib/env'
+ if (key !== 'editor' && key !== 'dependencies') {
delete state[key]
}
})
state.enabled = false
-import {
ClassNames,
- DidChangeWatchedFilesNotification,
import './lib/env'
}
@@ -842,13 +889,12 @@ hook.unhook()
}
if (state.dependencies) {
- CompletionParams,
ColorInformation,
+
}
state.dependencies = getModuleDependencies(state.configPath)
- CompletionParams,
ColorInformation,
-import {
+const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}'
state.configId = getConfigId(state.configPath, state.dependencies)
@@ -868,6 +915,11 @@
return {
state,
tryInit,
+ dispose() {
+ for (let { dispose } of disposables) {
+ dispose()
+ }
+ },
onUpdateSettings(settings: any): void {
documentSettingsCache.clear()
if (state.enabled) {
@@ -1310,9 +1362,10 @@ this.connection.listen()
}
dispose(): void {
- DocumentColorParams,
+process.on('unhandledRejection', (e: any) => {
DocumentColorParams,
- CompletionParams,
+ project.dispose()
+ }
}
}
diff --git a/packages/tailwindcss-language-server/src/watcher/index.js b/packages/tailwindcss-language-server/src/watcher/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fcaf7ffb7ce2d5a9928464ad5a9a787bff015f21
--- /dev/null
+++ b/packages/tailwindcss-language-server/src/watcher/index.js
@@ -0,0 +1,159 @@
+const os = require('os')
+const path = require('path')
+const fs = require('fs')
+
+const vars = (process.config && process.config.variables) || {}
+const arch = os.arch()
+const platform = os.platform()
+const abi = process.versions.modules
+const runtime = isElectron() ? 'electron' : 'node'
+const libc = process.env.LIBC || (isAlpine(platform) ? 'musl' : 'glibc')
+const armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || ''
+const uv = (process.versions.uv || '').split('.')[0]
+
+const prebuilds = {
+ 'darwin-x64': {
+ 'node.napi.glibc.node': () => require('./prebuilds/darwin-x64.node.napi.glibc.node'),
+ },
+ 'linux-x64': {
+ 'node.napi.glibc.node': () => require('./prebuilds/linux-x64.node.napi.glibc.node'),
+ 'node.napi.musl.node': () => require('./prebuilds/linux-x64.node.napi.musl.node'),
+ },
+ 'win32-x64': {
+ 'node.napi.glibc.node': () => require('./prebuilds/win32-x64.node.napi.glibc.node'),
+ },
+}
+
+let getBinding = () => {
+ let resolved = resolve()
+ getBinding = () => resolved
+ return resolved
+}
+
+exports.getBinding = getBinding
+
+exports.writeSnapshot = (dir, snapshot, opts) => {
+ return getBinding().writeSnapshot(
+ path.resolve(dir),
+ path.resolve(snapshot),
+ normalizeOptions(dir, opts)
+ )
+}
+
+exports.getEventsSince = (dir, snapshot, opts) => {
+ return getBinding().getEventsSince(
+ path.resolve(dir),
+ path.resolve(snapshot),
+ normalizeOptions(dir, opts)
+ )
+}
+
+exports.subscribe = async (dir, fn, opts) => {
+ dir = path.resolve(dir)
+ opts = normalizeOptions(dir, opts)
+ await getBinding().subscribe(dir, fn, opts)
+
+ return {
+ unsubscribe() {
+ return getBinding().unsubscribe(dir, fn, opts)
+ },
+ }
+}
+
+exports.unsubscribe = (dir, fn, opts) => {
+ return getBinding().unsubscribe(path.resolve(dir), fn, normalizeOptions(dir, opts))
+}
+
+function resolve() {
+ // Find most specific flavor first
+ var list = prebuilds[platform + '-' + arch]
+ var builds = Object.keys(list)
+ var parsed = builds.map(parseTags)
+ var candidates = parsed.filter(matchTags(runtime, abi))
+ var winner = candidates.sort(compareTags(runtime))[0]
+ if (winner) return list[winner.file]()
+}
+
+function parseTags(file) {
+ var arr = file.split('.')
+ var extension = arr.pop()
+ var tags = { file: file, specificity: 0 }
+
+ if (extension !== 'node') return
+
+ for (var i = 0; i < arr.length; i++) {
+ var tag = arr[i]
+
+ if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') {
+ tags.runtime = tag
+ } else if (tag === 'napi') {
+ tags.napi = true
+ } else if (tag.slice(0, 3) === 'abi') {
+ tags.abi = tag.slice(3)
+ } else if (tag.slice(0, 2) === 'uv') {
+ tags.uv = tag.slice(2)
+ } else if (tag.slice(0, 4) === 'armv') {
+ tags.armv = tag.slice(4)
+ } else if (tag === 'glibc' || tag === 'musl') {
+ tags.libc = tag
+ } else {
+ continue
+ }
+
+ tags.specificity++
+ }
+
+ return tags
+}
+
+function matchTags(runtime, abi) {
+ return function (tags) {
+ if (tags == null) return false
+ if (tags.runtime !== runtime && !runtimeAgnostic(tags)) return false
+ if (tags.abi !== abi && !tags.napi) return false
+ if (tags.uv && tags.uv !== uv) return false
+ if (tags.armv && tags.armv !== armv) return false
+ if (tags.libc && tags.libc !== libc) return false
+
+ return true
+ }
+}
+
+function runtimeAgnostic(tags) {
+ return tags.runtime === 'node' && tags.napi
+}
+
+function compareTags(runtime) {
+ // Precedence: non-agnostic runtime, abi over napi, then by specificity.
+ return function (a, b) {
+ if (a.runtime !== b.runtime) {
+ return a.runtime === runtime ? -1 : 1
+ } else if (a.abi !== b.abi) {
+ return a.abi ? -1 : 1
+ } else if (a.specificity !== b.specificity) {
+ return a.specificity > b.specificity ? -1 : 1
+ } else {
+ return 0
+ }
+ }
+}
+
+function normalizeOptions(dir, opts = {}) {
+ if (Array.isArray(opts.ignore)) {
+ opts = Object.assign({}, opts, {
+ ignore: opts.ignore.map((ignore) => path.resolve(dir, ignore)),
+ })
+ }
+
+ return opts
+}
+
+function isElectron() {
+ if (process.versions && process.versions.electron) return true
+ if (process.env.ELECTRON_RUN_AS_NODE) return true
+ return typeof window !== 'undefined' && window.process && window.process.type === 'renderer'
+}
+
+function isAlpine(platform) {
+ return platform === 'linux' && fs.existsSync('/etc/alpine-release')
+}
diff --git a/packages/tailwindcss-language-server/src/watcher/licenses/@parcel/watcher b/packages/tailwindcss-language-server/src/watcher/licenses/@parcel/watcher
new file mode 100644
index 0000000000000000000000000000000000000000..7fb9bc953e90740cb60a6401b25dc36d5a19cdfd
--- /dev/null
+++ b/packages/tailwindcss-language-server/src/watcher/licenses/@parcel/watcher
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-present Devon Govett
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/tailwindcss-language-server/src/watcher/licenses/node-gyp-build b/packages/tailwindcss-language-server/src/watcher/licenses/node-gyp-build
new file mode 100644
index 0000000000000000000000000000000000000000..56fce0895e13ffe83650a7274d79fbcfb80d3e2f
--- /dev/null
+++ b/packages/tailwindcss-language-server/src/watcher/licenses/node-gyp-build
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Mathias Buus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.