Skip to content

Commit

Permalink
Surface formatting errors. Fix #937
Browse files Browse the repository at this point in the history
  • Loading branch information
octref committed Oct 22, 2018
1 parent 584f648 commit 2cf32f2
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 101 deletions.
34 changes: 34 additions & 0 deletions client/client.ts
Original file line number Diff line number Diff line change
@@ -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);
}
24 changes: 23 additions & 1 deletion client/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = {
Expand All @@ -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];
Expand Down
37 changes: 37 additions & 0 deletions client/languages.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
]
});
}
109 changes: 24 additions & 85 deletions client/vueMain.ts
Original file line number Diff line number Diff line change
@@ -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);
});
}
63 changes: 48 additions & 15 deletions server/src/services/vls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,35 +130,52 @@ 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));

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 {
Expand Down Expand Up @@ -289,6 +306,10 @@ export class VLS {
return NULL_SIGNATURE;
}

/**
* Validations
*/

private triggerValidation(textDocument: TextDocument): void {
this.cleanPendingValidation(textDocument);
this.pendingValidationRequests[textDocument.uri] = setTimeout(() => {
Expand All @@ -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);
}
Expand Down

0 comments on commit 2cf32f2

Please sign in to comment.