From 2cf32f2b6f345a215e184a7ec9b028419ec803ee Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 22 Oct 2018 08:27:17 -0700 Subject: [PATCH] Surface formatting errors. Fix #937 --- client/client.ts | 34 ++++++++++++ client/grammar.ts | 24 +++++++- client/languages.ts | 37 +++++++++++++ client/vueMain.ts | 109 ++++++++----------------------------- server/src/services/vls.ts | 63 ++++++++++++++++----- 5 files changed, 166 insertions(+), 101 deletions(-) create mode 100644 client/client.ts create mode 100644 client/languages.ts diff --git a/client/client.ts b/client/client.ts new file mode 100644 index 0000000000..0e1c7bd456 --- /dev/null +++ b/client/client.ts @@ -0,0 +1,34 @@ +import * as vscode from 'vscode'; +import { + LanguageClient, + RevealOutputChannelOn, + ServerOptions, + TransportKind, + LanguageClientOptions +} from 'vscode-languageclient'; + +export function initializeLanguageClient(serverModule: string): LanguageClient { + const debugOptions = { execArgv: ['--nolazy', '--inspect=6005'] }; + + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + }; + + const documentSelector = ['vue']; + const config = vscode.workspace.getConfiguration(); + + const clientOptions: LanguageClientOptions = { + documentSelector, + synchronize: { + configurationSection: ['vetur', 'emmet', 'html', 'javascript', 'typescript', 'prettier', 'stylusSupremacy'], + fileEvents: vscode.workspace.createFileSystemWatcher('{**/*.js,**/*.ts}', true, false, true) + }, + initializationOptions: { + config + }, + revealOutputChannelOn: RevealOutputChannelOn.Never + }; + + return new LanguageClient('vetur', 'Vue Language Server', serverOptions, clientOptions); +} diff --git a/client/grammar.ts b/client/grammar.ts index eff167704d..94981fbeff 100644 --- a/client/grammar.ts +++ b/client/grammar.ts @@ -3,6 +3,8 @@ */ import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; // Available grammar scopes const SCOPES: { [lang: string]: string } = { @@ -24,7 +26,27 @@ const SCOPES: { [lang: string]: string } = { php: 'source.php' }; -export function getGeneratedGrammar(grammarPath: string, customBlocks: { [k: string]: string }): string { +export function generateGrammarCommandHandler(extensionPath: string) { + const customBlocks: { [k: string]: string } = + vscode.workspace.getConfiguration().get('vetur.grammar.customBlocks') || {}; + + return () => { + try { + const generatedGrammar = getGeneratedGrammar( + path.resolve(extensionPath, 'syntaxes/vue.json'), + customBlocks + ); + fs.writeFileSync(path.resolve(extensionPath, 'syntaxes/vue-generated.json'), generatedGrammar, 'utf-8'); + vscode.window.showInformationMessage('Successfully generated vue grammar. Reload VS Code to enable it.'); + } catch (e) { + vscode.window.showErrorMessage( + 'Failed to generate vue grammar. `vetur.grammar.customBlocks` contain invalid language values' + ); + } + }; +} + +function getGeneratedGrammar(grammarPath: string, customBlocks: { [k: string]: string }): string { const grammar = JSON.parse(fs.readFileSync(grammarPath, 'utf-8')); for (const tag in customBlocks) { const lang = customBlocks[tag]; diff --git a/client/languages.ts b/client/languages.ts new file mode 100644 index 0000000000..9281b8f1e8 --- /dev/null +++ b/client/languages.ts @@ -0,0 +1,37 @@ +import { languages, IndentAction } from 'vscode'; + +const EMPTY_ELEMENTS: string[] = [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'menuitem', + 'meta', + 'param', + 'source', + 'track', + 'wbr' +]; + +export function registerLanguageConfigurations() { + languages.setLanguageConfiguration('vue-html', { + wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, + onEnterRules: [ + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i, + action: { indentAction: IndentAction.IndentOutdent } + }, + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + action: { indentAction: IndentAction.Indent } + } + ] + }); +} diff --git a/client/vueMain.ts b/client/vueMain.ts index 38eff63295..9b36f4a863 100644 --- a/client/vueMain.ts +++ b/client/vueMain.ts @@ -1,102 +1,41 @@ -import * as fs from 'fs'; import * as path from 'path'; - import * as vscode from 'vscode'; -import { languages, workspace, ExtensionContext, IndentAction } from 'vscode'; -import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind, - RevealOutputChannelOn -} from 'vscode-languageclient'; -import { getGeneratedGrammar } from './grammar'; - -const EMPTY_ELEMENTS: string[] = [ - 'area', - 'base', - 'br', - 'col', - 'embed', - 'hr', - 'img', - 'input', - 'keygen', - 'link', - 'menuitem', - 'meta', - 'param', - 'source', - 'track', - 'wbr' -]; +import { LanguageClient } from 'vscode-languageclient'; +import { generateGrammarCommandHandler } from './grammar'; +import { registerLanguageConfigurations } from './languages'; +import { initializeLanguageClient } from './client'; -export function activate(context: ExtensionContext) { +export function activate(context: vscode.ExtensionContext) { /** * Custom Block Grammar generation command */ context.subscriptions.push( - vscode.commands.registerCommand('vetur.generateGrammar', () => { - const customBlocks: { [k: string]: string } = - workspace.getConfiguration().get('vetur.grammar.customBlocks') || {}; - try { - const generatedGrammar = getGeneratedGrammar( - path.resolve(context.extensionPath, 'syntaxes/vue.json'), - customBlocks - ); - fs.writeFileSync(path.resolve(context.extensionPath, 'syntaxes/vue-generated.json'), generatedGrammar, 'utf-8'); - vscode.window.showInformationMessage('Successfully generated vue grammar. Reload VS Code to enable it.'); - } catch (e) { - vscode.window.showErrorMessage( - 'Failed to generate vue grammar. `vetur.grammar.customBlocks` contain invalid language values' - ); - } - }) + vscode.commands.registerCommand('vetur.generateGrammar', generateGrammarCommandHandler(context.extensionPath)) ); + registerLanguageConfigurations(); + /** * Vue Language Server Initialization */ - const serverModule = context.asAbsolutePath(path.join('server', 'dist', 'vueServerMain.js')); - const debugOptions = { execArgv: ['--nolazy', '--inspect=6005'] }; - - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } - }; - - const documentSelector = ['vue']; - const config = workspace.getConfiguration(); - const clientOptions: LanguageClientOptions = { - documentSelector, - synchronize: { - configurationSection: ['vetur', 'emmet', 'html', 'javascript', 'typescript', 'prettier', 'stylusSupremacy'], - fileEvents: vscode.workspace.createFileSystemWatcher('{**/*.js,**/*.ts}', true, false, true) - }, - initializationOptions: { - config - }, - revealOutputChannelOn: RevealOutputChannelOn.Never - }; - - const client = new LanguageClient('vetur', 'Vue Language Server', serverOptions, clientOptions); - const disposable = client.start(); - context.subscriptions.push(disposable); + const serverModule = context.asAbsolutePath(path.join('server', 'dist', 'vueServerMain.js')); + const client = initializeLanguageClient(serverModule); + context.subscriptions.push(client.start()); - languages.setLanguageConfiguration('vue-html', { - wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, - onEnterRules: [ - { - beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), - afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i, - action: { indentAction: IndentAction.IndentOutdent } - }, - { - beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), - action: { indentAction: IndentAction.Indent } - } - ] + client.onReady().then(() => { + registerCustomClientNotificationHandlers(client); }); } +function registerCustomClientNotificationHandlers(client: LanguageClient) { + client.onNotification('$/displayInfo', (msg: string) => { + vscode.window.showInformationMessage(msg); + }); + client.onNotification('$/displayWarning', (msg: string) => { + vscode.window.showWarningMessage(msg); + }); + client.onNotification('$/displayError', (msg: string) => { + vscode.window.showErrorMessage(msg); + }); +} diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index c4084f8bdc..a82f7c9198 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -130,6 +130,24 @@ export class VLS { }); } + /** + * Custom Notifications + */ + + displayInfoMessage(msg: string): void { + this.lspConnection.sendNotification('$/displayInfo', msg); + } + displayWarningMessage(msg: string): void { + this.lspConnection.sendNotification('$/displayWarning', msg); + } + displayErrorMessage(msg: string): void { + this.lspConnection.sendNotification('$/displayError', msg); + } + + /** + * Language Features + */ + onDocumentFormatting({ textDocument, options }: DocumentFormattingParams): TextEdit[] { const doc = this.documentService.getDocument(textDocument.uri)!; const fullDocRange = Range.create(Position.create(0, 0), doc.positionAt(doc.getText().length)); @@ -137,28 +155,27 @@ export class VLS { const modeRanges = this.languageModes.getModesInRange(doc, fullDocRange); const allEdits: TextEdit[] = []; + const errMessages: string[] = []; + modeRanges.forEach(range => { if (range.mode && range.mode.format) { - const edits = range.mode.format(doc, range, options); - for (const edit of edits) { - allEdits.push(edit); + try { + const edits = range.mode.format(doc, range, options); + for (const edit of edits) { + allEdits.push(edit); + } + } catch (err) { + errMessages.push(err.toString()); } } }); - return allEdits; - } - - doValidate(doc: TextDocument): Diagnostic[] { - const diagnostics: Diagnostic[] = []; - if (doc.languageId === 'vue') { - this.languageModes.getAllModesInDocument(doc).forEach(mode => { - if (mode.doValidation && this.validation[mode.getId()]) { - pushAll(diagnostics, mode.doValidation(doc)); - } - }); + if (errMessages.length !== 0) { + this.displayErrorMessage('Formatting failed: "' + errMessages.join('\n') + '"'); + return []; } - return diagnostics; + + return allEdits; } onCompletion({ textDocument, position }: TextDocumentPositionParams): CompletionList { @@ -289,6 +306,10 @@ export class VLS { return NULL_SIGNATURE; } + /** + * Validations + */ + private triggerValidation(textDocument: TextDocument): void { this.cleanPendingValidation(textDocument); this.pendingValidationRequests[textDocument.uri] = setTimeout(() => { @@ -310,6 +331,18 @@ export class VLS { this.lspConnection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } + doValidate(doc: TextDocument): Diagnostic[] { + const diagnostics: Diagnostic[] = []; + if (doc.languageId === 'vue') { + this.languageModes.getAllModesInDocument(doc).forEach(mode => { + if (mode.doValidation && this.validation[mode.getId()]) { + pushAll(diagnostics, mode.doValidation(doc)); + } + }); + } + return diagnostics; + } + removeDocument(doc: TextDocument): void { this.languageModes.onDocumentRemoved(doc); }