diff --git a/client/client.ts b/client/client.ts index 671dc8eef9..1e12fece73 100644 --- a/client/client.ts +++ b/client/client.ts @@ -9,10 +9,22 @@ import { import { resolve } from 'path'; import { existsSync } from 'fs'; -export function initializeLanguageClient(vlsModulePath: string, globalSnippetDir: string): LanguageClient { +export function initializeLanguageClient( + vlsModulePath: string, + globalSnippetDir: string, + workspaceFolder?: vscode.WorkspaceFolder +): LanguageClient { const debugOptions = { execArgv: ['--nolazy', '--inspect=6005'] }; - const documentSelector = ['vue']; + const documentSelector = workspaceFolder + ? [ + { + language: 'vue', + scheme: 'file', + pattern: `${workspaceFolder.uri.fsPath}/**/*` + } + ] + : ['vue']; const config = vscode.workspace.getConfiguration(); let serverPath; @@ -56,7 +68,8 @@ export function initializeLanguageClient(vlsModulePath: string, globalSnippetDir config, globalSnippetDir }, - revealOutputChannelOn: RevealOutputChannelOn.Never + revealOutputChannelOn: RevealOutputChannelOn.Never, + workspaceFolder }; return new LanguageClient('vetur', 'Vue Language Server', serverOptions, clientOptions); diff --git a/client/vueMain.ts b/client/vueMain.ts index 3cfc33fb3a..bd3ec49308 100644 --- a/client/vueMain.ts +++ b/client/vueMain.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { LanguageClient, WorkspaceEdit } from 'vscode-languageclient'; +import { LanguageClient, WorkspaceEdit, WorkspaceFolder } from 'vscode-languageclient'; import { generateGrammarCommandHandler } from './commands/generateGrammarCommand'; import { registerLanguageConfigurations } from './languages'; import { initializeLanguageClient } from './client'; @@ -12,6 +12,11 @@ import { import { getGlobalSnippetDir } from './userSnippetDir'; import { generateOpenUserScaffoldSnippetFolderCommand } from './commands/openUserScaffoldSnippetFolderCommand'; +/** + * Map of workspace folder URI -> LanguageClient + */ +const clients = new Map(); + export async function activate(context: vscode.ExtensionContext) { const isInsiders = vscode.env.appName.includes('Insiders'); const globalSnippetDir = getGlobalSnippetDir(isInsiders); @@ -28,6 +33,15 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('vetur.generateGrammar', generateGrammarCommandHandler(context.extensionPath)) ); + registerLanguageConfigurations(); + + /** + * Vue Language Server path + * To be able to work with multiple workspaces, we initialize one language service per workspace folder + */ + + const serverModule = context.asAbsolutePath(join('server', 'dist', 'vueServerMain.js')); + /** * Open custom snippet folder */ @@ -44,7 +58,6 @@ export async function activate(context: vscode.ExtensionContext) { * Vue Language Server Initialization */ - const serverModule = context.asAbsolutePath(join('server', 'dist', 'vueServerMain.js')); const client = initializeLanguageClient(serverModule, globalSnippetDir); context.subscriptions.push(client.start()); @@ -65,6 +78,43 @@ export async function activate(context: vscode.ExtensionContext) { }, () => promise ); + + function onDidOpenTextDocument(document: vscode.TextDocument) { + const client = initializeClientForTextDocument(context, serverModule, globalSnippetDir, document); + // TODO handle commands properly + if (!client || clients.size > 1) { + return; + } + context.subscriptions.push( + vscode.commands.registerCommand('vetur.applyWorkspaceEdits', (args: WorkspaceEdit) => { + const edit = client.protocol2CodeConverter.asWorkspaceEdit(args)!; + vscode.workspace.applyEdit(edit); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('vetur.chooseTypeScriptRefactoring', (args: any) => { + client.sendRequest('requestCodeActionEdits', args).then(edits => { + if (edits) { + vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edits)!); + } + }); + }) + ); + } + + vscode.workspace.onDidOpenTextDocument(onDidOpenTextDocument); + vscode.workspace.textDocuments.forEach(onDidOpenTextDocument); + + vscode.workspace.onDidChangeWorkspaceFolders(event => { + for (const folder of event.removed) { + const client = clients.get(folder.uri.toString()); + if (client) { + clients.delete(folder.uri.toString()); + client.stop(); + } + } + }); } function registerCustomClientNotificationHandlers(client: LanguageClient) { @@ -87,3 +137,35 @@ function registerCustomLSPCommands(context: vscode.ExtensionContext, client: Lan vscode.commands.registerCommand('vetur.showCorrespondingVirtualFile', generateShowVirtualFileCommand(client)) ); } + +function initializeClientForTextDocument( + context: vscode.ExtensionContext, + serverModule: string, + globalSnippetDir: string, + document: vscode.TextDocument +): LanguageClient | undefined { + const folder = vscode.workspace.getWorkspaceFolder(document.uri); + + if (!folder || document.languageId !== 'vue') { + return; + } + + if (!clients.has(folder.uri.toString())) { + const client = initializeLanguageClient(serverModule, globalSnippetDir, folder); + + client + .onReady() + .then(() => { + registerCustomClientNotificationHandlers(client); + registerCustomLSPCommands(context, client); + }) + .catch(e => { + console.log(`Client initialization failed for workspace: ${folder.uri.toString()}`); + }); + + context.subscriptions.push(client.start()); + clients.set(folder.uri.toString(), client); + + return client; + } +} diff --git a/server/src/vueServerMain.ts b/server/src/vueServerMain.ts index 4d72df6f2d..c96467cb91 100644 --- a/server/src/vueServerMain.ts +++ b/server/src/vueServerMain.ts @@ -11,7 +11,7 @@ connection.onInitialize( async (params: InitializeParams): Promise => { await vls.init(params); - console.log('Vetur initialized'); + console.log(`Vetur initialized for workspace folder: ${params.rootPath}`); return { capabilities: vls.capabilities