add includeLanguages setting and remove default language client

master
Brad Cornes 2020-05-03 18:11:45 +01:00
parent 6af6797f2c
commit 3836cbf2a5
10 changed files with 176 additions and 98 deletions

View File

@ -55,6 +55,10 @@
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"description": "" "description": ""
},
"tailwindCSS.includeLanguages": {
"type": "object",
"default": {}
} }
} }
} }

View File

@ -18,10 +18,15 @@ import {
TransportKind, TransportKind,
} from 'vscode-languageclient' } from 'vscode-languageclient'
import { registerConfigErrorHandler } from './lib/registerConfigErrorHandler' import { registerConfigErrorHandler } from './lib/registerConfigErrorHandler'
import { LANGUAGES } from './lib/languages' import { DEFAULT_LANGUAGES } from './lib/languages'
import isObject from './util/isObject'
import { dedupe, equal } from './util/array'
const CLIENT_ID = 'tailwindcss-intellisense'
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
let defaultClient: LanguageClient
let clients: Map<string, LanguageClient> = new Map() let clients: Map<string, LanguageClient> = new Map()
let languages: Map<string, string[]> = new Map()
let _sortedWorkspaceFolders: string[] | undefined let _sortedWorkspaceFolders: string[] | undefined
function sortedWorkspaceFolders(): string[] { function sortedWorkspaceFolders(): string[] {
@ -60,79 +65,83 @@ function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
return folder return folder
} }
function getUserLanguages(folder?: WorkspaceFolder): Record<string, string> {
const langs = Workspace.getConfiguration('tailwindCSS', folder)
.includeLanguages
return isObject(langs) ? langs : {}
}
export function activate(context: ExtensionContext) { export function activate(context: ExtensionContext) {
let module = context.asAbsolutePath( let module = context.asAbsolutePath(path.join('dist', 'server', 'index.js'))
path.join('dist', 'server', 'index.js') let outputChannel: OutputChannel = Window.createOutputChannel(CLIENT_ID)
)
let outputChannel: OutputChannel = Window.createOutputChannel(
'lsp-multi-server-example'
)
function didOpenTextDocument(document: TextDocument): void { // TODO: check if the actual language MAPPING changed
// We are only interested in language mode text // not just the language IDs
if ( // e.g. "plaintext" already exists but you change it from "html" to "css"
LANGUAGES.indexOf(document.languageId) === -1 || Workspace.onDidChangeConfiguration((event) => {
(document.uri.scheme !== 'file' && document.uri.scheme !== 'untitled') clients.forEach((client, key) => {
) { const folder = Workspace.getWorkspaceFolder(Uri.parse(key))
if (event.affectsConfiguration('tailwindCSS', folder)) {
const userLanguages = getUserLanguages(folder)
if (userLanguages) {
const userLanguageIds = Object.keys(userLanguages)
const newLanguages = dedupe([
...DEFAULT_LANGUAGES,
...userLanguageIds,
])
if (!equal(newLanguages, languages.get(folder.uri.toString()))) {
languages.set(folder.uri.toString(), newLanguages)
if (client) {
clients.delete(folder.uri.toString())
client.stop()
bootWorkspaceClient(folder)
}
}
}
}
})
})
function bootWorkspaceClient(folder: WorkspaceFolder) {
if (clients.has(folder.uri.toString())) {
return return
} }
let uri = document.uri // placeholder so we don't boot another server before this one is ready
// Untitled files go to a default client. clients.set(folder.uri.toString(), null)
if (uri.scheme === 'untitled' && !defaultClient) {
let debugOptions = { execArgv: ['--nolazy', '--inspect=6010'] }
let serverOptions = {
run: { module, transport: TransportKind.ipc },
debug: { module, transport: TransportKind.ipc, options: debugOptions },
}
let clientOptions: LanguageClientOptions = {
documentSelector: LANGUAGES.map((language) => ({
scheme: 'untitled',
language,
})),
diagnosticCollectionName: 'lsp-multi-server-example',
outputChannel: outputChannel,
}
defaultClient = new LanguageClient(
'lsp-multi-server-example',
'LSP Multi Server Example',
serverOptions,
clientOptions
)
defaultClient.start()
return
}
let folder = Workspace.getWorkspaceFolder(uri)
// Files outside a folder can't be handled. This might depend on the language.
// Single file languages like JSON might handle files outside the workspace folders.
if (!folder) {
return
}
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder)
if (!clients.has(folder.uri.toString())) {
let debugOptions = { let debugOptions = {
execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`], execArgv: ['--nolazy', `--inspect=${6011 + clients.size}`],
} }
let serverOptions = { let serverOptions = {
run: { module, transport: TransportKind.ipc }, run: { module, transport: TransportKind.ipc },
debug: { module, transport: TransportKind.ipc, options: debugOptions }, debug: {
module,
transport: TransportKind.ipc,
options: debugOptions,
},
} }
let clientOptions: LanguageClientOptions = { let clientOptions: LanguageClientOptions = {
documentSelector: LANGUAGES.map((language) => ({ documentSelector: languages
.get(folder.uri.toString())
.map((language) => ({
scheme: 'file', scheme: 'file',
language, language,
pattern: `${folder.uri.fsPath}/**/*`, pattern: `${folder.uri.fsPath}/**/*`,
})), })),
diagnosticCollectionName: 'lsp-multi-server-example', diagnosticCollectionName: CLIENT_ID,
workspaceFolder: folder, workspaceFolder: folder,
outputChannel: outputChannel, outputChannel: outputChannel,
middleware: {}, middleware: {},
initializationOptions: {
userLanguages: getUserLanguages(folder),
},
} }
let client = new LanguageClient( let client = new LanguageClient(
'lsp-multi-server-example', CLIENT_ID,
'LSP Multi Server Example', CLIENT_NAME,
serverOptions, serverOptions,
clientOptions clientOptions
) )
@ -144,6 +153,31 @@ export function activate(context: ExtensionContext) {
client.start() client.start()
clients.set(folder.uri.toString(), client) clients.set(folder.uri.toString(), client)
} }
function didOpenTextDocument(document: TextDocument): void {
// We are only interested in language mode text
if (document.uri.scheme !== 'file') {
return
}
let uri = document.uri
let folder = Workspace.getWorkspaceFolder(uri)
// Files outside a folder can't be handled. This might depend on the language.
// Single file languages like JSON might handle files outside the workspace folders.
if (!folder) {
return
}
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder)
if (!languages.has(folder.uri.toString())) {
languages.set(
folder.uri.toString(),
dedupe([...DEFAULT_LANGUAGES, ...Object.keys(getUserLanguages())])
)
}
bootWorkspaceClient(folder)
} }
Workspace.onDidOpenTextDocument(didOpenTextDocument) Workspace.onDidOpenTextDocument(didOpenTextDocument)
@ -161,9 +195,6 @@ export function activate(context: ExtensionContext) {
export function deactivate(): Thenable<void> { export function deactivate(): Thenable<void> {
let promises: Thenable<void>[] = [] let promises: Thenable<void>[] = []
if (defaultClient) {
promises.push(defaultClient.stop())
}
for (let client of clients.values()) { for (let client of clients.values()) {
promises.push(client.stop()) promises.push(client.stop())
} }

View File

@ -1,4 +1,4 @@
export const LANGUAGES = [ export const DEFAULT_LANGUAGES = [
// html // html
'aspnetcorerazor', 'aspnetcorerazor',
'blade', 'blade',

View File

@ -191,13 +191,13 @@ function provideClassNameCompletions(
let doc = state.editor.documents.get(params.textDocument.uri) let doc = state.editor.documents.get(params.textDocument.uri)
if ( if (
isHtmlContext(doc, params.position) || isHtmlContext(state, doc, params.position) ||
isJsContext(doc, params.position) isJsContext(state, doc, params.position)
) { ) {
return provideClassAttributeCompletions(state, params) return provideClassAttributeCompletions(state, params)
} }
if (isCssContext(doc, params.position)) { if (isCssContext(state, doc, params.position)) {
return provideAtApplyCompletions(state, params) return provideAtApplyCompletions(state, params)
} }
@ -210,7 +210,7 @@ function provideCssHelperCompletions(
): CompletionList { ): CompletionList {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) { if (!isCssContext(state, doc, position)) {
return null return null
} }
@ -318,7 +318,7 @@ function provideTailwindDirectiveCompletions(
): CompletionList { ): CompletionList {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) { if (!isCssContext(state, doc, position)) {
return null return null
} }
@ -409,7 +409,7 @@ function provideVariantsDirectiveCompletions(
): CompletionList { ): CompletionList {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) { if (!isCssContext(state, doc, position)) {
return null return null
} }
@ -457,7 +457,7 @@ function provideScreenDirectiveCompletions(
): CompletionList { ): CompletionList {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) { if (!isCssContext(state, doc, position)) {
return null return null
} }
@ -505,7 +505,7 @@ function provideCssDirectiveCompletions(
): CompletionList { ): CompletionList {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) { if (!isCssContext(state, doc, position)) {
return null return null
} }
@ -600,9 +600,9 @@ async function provideEmmetCompletions(
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
const syntax = isHtmlContext(doc, position) const syntax = isHtmlContext(state, doc, position)
? 'html' ? 'html'
: isJsContext(doc, position) : isJsContext(state, doc, position)
? 'jsx' ? 'jsx'
: null : null

View File

@ -27,7 +27,7 @@ function provideCssHelperHover(
): Hover { ): Hover {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) return null if (!isCssContext(state, doc, position)) return null
const line = doc.getText({ const line = doc.getText({
start: { line: position.line, character: 0 }, start: { line: position.line, character: 0 },
@ -81,7 +81,11 @@ function provideClassAttributeHover(
): Hover { ): Hover {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isHtmlContext(doc, position) && !isJsContext(doc, position)) return null if (
!isHtmlContext(state, doc, position) &&
!isJsContext(state, doc, position)
)
return null
let hovered = getClassNameAtPosition(doc, position) let hovered = getClassNameAtPosition(doc, position)
if (!hovered) return null if (!hovered) return null
@ -111,7 +115,7 @@ function provideAtApplyHover(
): Hover { ): Hover {
let doc = state.editor.documents.get(textDocument.uri) let doc = state.editor.documents.get(textDocument.uri)
if (!isCssContext(doc, position)) return null if (!isCssContext(state, doc, position)) return null
const classNames = findClassNamesInRange(doc, { const classNames = findClassNamesInRange(doc, {
start: { line: Math.max(position.line - 10, 0), character: 0 }, start: { line: Math.max(position.line - 10, 0), character: 0 },

View File

@ -32,7 +32,10 @@ let connection = createConnection(ProposedFeatures.all)
let documents = new TextDocuments() let documents = new TextDocuments()
let workspaceFolder: string | null let workspaceFolder: string | null
const defaultSettings: Settings = { emmetCompletions: false } const defaultSettings: Settings = {
emmetCompletions: false,
includeLanguages: {},
}
let globalSettings: Settings = defaultSettings let globalSettings: Settings = defaultSettings
let documentSettings: Map<string, Settings> = new Map() let documentSettings: Map<string, Settings> = new Map()
@ -53,6 +56,11 @@ connection.onInitialize(
documents, documents,
documentSettings, documentSettings,
globalSettings, globalSettings,
userLanguages:
params.initializationOptions &&
params.initializationOptions.userLanguages
? params.initializationOptions.userLanguages
: {},
capabilities: { capabilities: {
configuration: configuration:
capabilities.workspace && !!capabilities.workspace.configuration, capabilities.workspace && !!capabilities.workspace.configuration,

View File

@ -1,5 +1,6 @@
import { TextDocument, Position } from 'vscode-languageserver' import { TextDocument, Position } from 'vscode-languageserver'
import { isInsideTag, isVueDoc, isSvelteDoc } from './html' import { isInsideTag, isVueDoc, isSvelteDoc } from './html'
import { State } from './state'
export const CSS_LANGUAGES = [ export const CSS_LANGUAGES = [
'css', 'css',
@ -10,12 +11,20 @@ export const CSS_LANGUAGES = [
'stylus', 'stylus',
] ]
function isCssDoc(doc: TextDocument): boolean { function isCssDoc(state: State, doc: TextDocument): boolean {
return CSS_LANGUAGES.indexOf(doc.languageId) !== -1 const userCssLanguages = Object.keys(
state.editor.userLanguages
).filter((lang) => CSS_LANGUAGES.includes(state.editor.userLanguages[lang]))
return [...CSS_LANGUAGES, ...userCssLanguages].indexOf(doc.languageId) !== -1
} }
export function isCssContext(doc: TextDocument, position: Position): boolean { export function isCssContext(
if (isCssDoc(doc)) { state: State,
doc: TextDocument,
position: Position
): boolean {
if (isCssDoc(state, doc)) {
return true return true
} }

View File

@ -1,4 +1,5 @@
import { TextDocument, Position } from 'vscode-languageserver' import { TextDocument, Position } from 'vscode-languageserver'
import { State } from './state'
export const HTML_LANGUAGES = [ export const HTML_LANGUAGES = [
'aspnetcorerazor', 'aspnetcorerazor',
@ -27,8 +28,14 @@ export const HTML_LANGUAGES = [
'twig', 'twig',
] ]
export function isHtmlDoc(doc: TextDocument): boolean { export function isHtmlDoc(state: State, doc: TextDocument): boolean {
return HTML_LANGUAGES.indexOf(doc.languageId) !== -1 const userHtmlLanguages = Object.keys(
state.editor.userLanguages
).filter((lang) => HTML_LANGUAGES.includes(state.editor.userLanguages[lang]))
return (
[...HTML_LANGUAGES, ...userHtmlLanguages].indexOf(doc.languageId) !== -1
)
} }
export function isVueDoc(doc: TextDocument): boolean { export function isVueDoc(doc: TextDocument): boolean {
@ -39,13 +46,17 @@ export function isSvelteDoc(doc: TextDocument): boolean {
return doc.languageId === 'svelte' return doc.languageId === 'svelte'
} }
export function isHtmlContext(doc: TextDocument, position: Position): boolean { export function isHtmlContext(
state: State,
doc: TextDocument,
position: Position
): boolean {
let str = doc.getText({ let str = doc.getText({
start: { line: 0, character: 0 }, start: { line: 0, character: 0 },
end: position, end: position,
}) })
if (isHtmlDoc(doc) && !isInsideTag(str, ['script', 'style'])) { if (isHtmlDoc(state, doc) && !isInsideTag(str, ['script', 'style'])) {
return true return true
} }

View File

@ -1,5 +1,6 @@
import { TextDocument, Position } from 'vscode-languageserver' import { TextDocument, Position } from 'vscode-languageserver'
import { isHtmlDoc, isInsideTag, isVueDoc, isSvelteDoc } from './html' import { isHtmlDoc, isInsideTag, isVueDoc, isSvelteDoc } from './html'
import { State } from './state'
export const JS_LANGUAGES = [ export const JS_LANGUAGES = [
'javascript', 'javascript',
@ -8,12 +9,20 @@ export const JS_LANGUAGES = [
'typescriptreact', 'typescriptreact',
] ]
export function isJsDoc(doc: TextDocument): boolean { export function isJsDoc(state: State, doc: TextDocument): boolean {
return JS_LANGUAGES.indexOf(doc.languageId) !== -1 const userJsLanguages = Object.keys(
state.editor.userLanguages
).filter((lang) => JS_LANGUAGES.includes(state.editor.userLanguages[lang]))
return [...JS_LANGUAGES, ...userJsLanguages].indexOf(doc.languageId) !== -1
} }
export function isJsContext(doc: TextDocument, position: Position): boolean { export function isJsContext(
if (isJsDoc(doc)) { state: State,
doc: TextDocument,
position: Position
): boolean {
if (isJsDoc(state, doc)) {
return true return true
} }
@ -22,7 +31,7 @@ export function isJsContext(doc: TextDocument, position: Position): boolean {
end: position, end: position,
}) })
if (isHtmlDoc(doc) && isInsideTag(str, ['script'])) { if (isHtmlDoc(state, doc) && isInsideTag(str, ['script'])) {
return true return true
} }

View File

@ -18,6 +18,7 @@ export type EditorState = {
documents: TextDocuments documents: TextDocuments
documentSettings: Map<string, Settings> documentSettings: Map<string, Settings>
globalSettings: Settings globalSettings: Settings
userLanguages: Record<string, string>
capabilities: { capabilities: {
configuration: boolean configuration: boolean
} }
@ -25,6 +26,7 @@ export type EditorState = {
export type Settings = { export type Settings = {
emmetCompletions: boolean emmetCompletions: boolean
includeLanguages: Record<string, string>
} }
export type State = null | { export type State = null | {