/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList } from 'vscode'; import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, CommonLanguageClient } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; import { getCustomDataSource } from './customData'; import { RequestService, serveFileSystemRequests } from './requests'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('css/customDataChanged'); } const localize = nls.loadMessageBundle(); export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string; } }; fs?: RequestService; } export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { const customDataSource = getCustomDataSource(context.subscriptions); let documentSelector = ['css', 'scss', 'less']; // Options to control the language client let clientOptions: LanguageClientOptions = { documentSelector, synchronize: { configurationSection: ['css', 'scss', 'less'] }, initializationOptions: { handledSchemas: ['file'] }, middleware: { provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { // testing the replace / insert mode function updateRanges(item: CompletionItem) { const range = item.range; if (range instanceof Range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { item.range = { inserting: new Range(range.start, position), replacing: range }; } } function updateLabel(item: CompletionItem) { if (item.kind === CompletionItemKind.Color) { item.label2 = { name: item.label, type: (item.documentation as string) }; } } // testing the new completion function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { if (r) { (Array.isArray(r) ? r : r.items).forEach(updateRanges); (Array.isArray(r) ? r : r.items).forEach(updateLabel); } return r; } const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; const r = next(document, position, context, token); if (isThenable(r)) { return r.then(updateProposals); } return updateProposals(r); } } }; // Create the language client and start the client. let client = newLanguageClient('css', localize('cssserver.name', 'CSS Language Server'), clientOptions); client.registerProposedFeatures(); client.onReady().then(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); }); serveFileSystemRequests(client, runtime); }); let disposable = client.start(); // Push the disposable to the context's subscriptions so that the // client can be deactivated on extension deactivation context.subscriptions.push(disposable); let indentationRules = { increaseIndentPattern: /(^.*\{[^}]*$)/, decreaseIndentPattern: /^\s*\}/ }; languages.setLanguageConfiguration('css', { wordPattern: /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g, indentationRules: indentationRules }); languages.setLanguageConfiguration('less', { wordPattern: /(#?-?\d*\.\d\w*%?)|(::?[\w-]+(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g, indentationRules: indentationRules }); languages.setLanguageConfiguration('scss', { wordPattern: /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@$#.!])?[\w-?]+%?|[@#!$.])/g, indentationRules: indentationRules }); client.onReady().then(() => { context.subscriptions.push(initCompletionProvider()); }); function initCompletionProvider(): Disposable { const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/; return languages.registerCompletionItemProvider(documentSelector, { provideCompletionItems(doc: TextDocument, pos: Position) { let lineUntilPos = doc.getText(new Range(new Position(pos.line, 0), pos)); let match = lineUntilPos.match(regionCompletionRegExpr); if (match) { let range = new Range(new Position(pos.line, match[1].length), pos); let beginProposal = new CompletionItem('#region', CompletionItemKind.Snippet); beginProposal.range = range; TextEdit.replace(range, '/* #region */'); beginProposal.insertText = new SnippetString('/* #region $1*/'); beginProposal.documentation = localize('folding.start', 'Folding Region Start'); beginProposal.filterText = match[2]; beginProposal.sortText = 'za'; let endProposal = new CompletionItem('#endregion', CompletionItemKind.Snippet); endProposal.range = range; endProposal.insertText = '/* #endregion */'; endProposal.documentation = localize('folding.end', 'Folding Region End'); endProposal.sortText = 'zb'; endProposal.filterText = match[2]; return [beginProposal, endProposal]; } return null; } }); } commands.registerCommand('_css.applyCodeAction', applyCodeAction); function applyCodeAction(uri: string, documentVersion: number, edits: TextEdit[]) { let textEditor = window.activeTextEditor; if (textEditor && textEditor.document.uri.toString() === uri) { if (textEditor.document.version !== documentVersion) { window.showInformationMessage(`CSS fix is outdated and can't be applied to the document.`); } textEditor.edit(mutator => { for (let edit of edits) { mutator.replace(client.protocol2CodeConverter.asRange(edit.range), edit.newText); } }).then(success => { if (!success) { window.showErrorMessage('Failed to apply CSS fix to the document. Please consider opening an issue with steps to reproduce.'); } }); } } }