import * as path from 'node:path' import * as cp from 'node:child_process' import * as rpc from 'vscode-jsonrpc' import { beforeAll } from 'vitest' async function init(fixture) { let settings = {} let docSettings = new Map() let childProcess = cp.fork('./bin/tailwindcss-language-server', { silent: true }) const capabilities = { textDocument: { codeAction: { dynamicRegistration: true }, codeLens: { dynamicRegistration: true }, colorProvider: { dynamicRegistration: true }, completion: { completionItem: { commitCharactersSupport: true, documentationFormat: ['markdown', 'plaintext'], snippetSupport: true, }, completionItemKind: { valueSet: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ], }, contextSupport: true, dynamicRegistration: true, }, definition: { dynamicRegistration: true }, documentHighlight: { dynamicRegistration: true }, documentLink: { dynamicRegistration: true }, documentSymbol: { dynamicRegistration: true, symbolKind: { valueSet: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, ], }, }, formatting: { dynamicRegistration: true }, hover: { contentFormat: ['markdown', 'plaintext'], dynamicRegistration: true, }, implementation: { dynamicRegistration: true }, onTypeFormatting: { dynamicRegistration: true }, publishDiagnostics: { relatedInformation: true }, rangeFormatting: { dynamicRegistration: true }, references: { dynamicRegistration: true }, rename: { dynamicRegistration: true }, signatureHelp: { dynamicRegistration: true, signatureInformation: { documentationFormat: ['markdown', 'plaintext'] }, }, synchronization: { didSave: true, dynamicRegistration: true, willSave: true, willSaveWaitUntil: true, }, typeDefinition: { dynamicRegistration: true }, }, workspace: { applyEdit: true, configuration: true, didChangeConfiguration: { dynamicRegistration: true }, didChangeWatchedFiles: { dynamicRegistration: true }, executeCommand: { dynamicRegistration: true }, symbol: { dynamicRegistration: true, symbolKind: { valueSet: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, ], }, }, workspaceEdit: { documentChanges: true }, workspaceFolders: true, }, } let connection = rpc.createMessageConnection( new rpc.StreamMessageReader(childProcess.stdout), new rpc.StreamMessageWriter(childProcess.stdin) ) connection.listen() await connection.sendRequest(new rpc.RequestType('initialize'), { processId: -1, // rootPath: '.', rootUri: `file://${path.resolve('./tests/fixtures/', fixture)}`, capabilities, trace: 'off', workspaceFolders: [], initializationOptions: { testMode: true, }, }) await connection.sendNotification(new rpc.NotificationType('initialized')) connection.onRequest(new rpc.RequestType('workspace/configuration'), (params) => { return params.items.map((item) => { if (docSettings.has(item.scopeUri)) { return docSettings.get(item.scopeUri)[item.section] ?? {} } return settings[item.section] ?? {} }) }) let initPromise = new Promise((resolve) => { connection.onRequest(new rpc.RequestType('client/registerCapability'), ({ registrations }) => { if (registrations.findIndex((r) => r.method === 'textDocument/completion') > -1) { resolve() } return null }) }) let counter = 0 return { connection, sendRequest(type, params) { return connection.sendRequest(new rpc.RequestType(type), params) }, onNotification(type, callback) { return connection.onNotification(new rpc.RequestType(type), callback) }, async openDocument({ text, lang = 'html', dir = '', settings = {} }) { let uri = `file://${path.resolve('./tests/fixtures', fixture, dir, `file-${counter++}`)}` docSettings.set(uri, settings) await connection.sendNotification(new rpc.NotificationType('textDocument/didOpen'), { textDocument: { uri, languageId: lang, version: 1, text, }, }) await initPromise return { uri, async updateSettings(settings) { docSettings.set(uri, settings) await connection.sendNotification( new rpc.NotificationType('workspace/didChangeConfiguration') ) }, } }, async updateSettings(newSettings) { settings = newSettings await connection.sendNotification( new rpc.NotificationType('workspace/didChangeConfiguration') ) }, async updateFile(file, text) { let uri = `file://${path.resolve('./tests/fixtures', fixture, file)}` await connection.sendNotification(new rpc.NotificationType('textDocument/didChange'), { textDocument: { uri, version: counter++ }, contentChanges: [{ text }], }) }, } } export function withFixture(fixture, callback) { let c = {} beforeAll(async () => { // Using the connection object as the prototype lets us access the connection // without defining getters for all the methods and also lets us add helpers // to the connection object without having to resort to using a Proxy Object.setPrototypeOf(c, await init(fixture)) return () => c.connection.end() }) callback(c) } // let counter = 0 // export async function openDocument(connection, fixture, languageId, text) { // let uri = `file://${path.resolve('./tests/fixtures', fixture, `file-${counter++}`)}` // await connection.sendNotification(new rpc.NotificationType('textDocument/didOpen'), { // textDocument: { // uri, // languageId, // version: 1, // text, // }, // }) // await initPromise // return uri // } // export async function updateSettings(connection, newSettings) { // settings = newSettings // await connection.sendNotification(new rpc.NotificationType('workspace/didChangeConfiguration')) // }