code-server/lib/vscode/extensions/css-language-features/client/src/cssClient.ts

167 lines
6.6 KiB
TypeScript
Raw Normal View History

/*---------------------------------------------------------------------------------------------
* 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<string[]> = 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<CompletionItem[] | CompletionList> {
// 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 = <T>(obj: ProviderResult<T>): obj is Thenable<T> => obj && (<any>obj)['then'];
const r = next(document, position, context, token);
if (isThenable<CompletionItem[] | CompletionList | null | undefined>(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.');
}
});
}
}
}