mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
add css formatter. fixes #19166
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
* 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 { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList, FormattingOptions, workspace } from 'vscode';
|
||||
import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, CommonLanguageClient, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getCustomDataSource } from './customData';
|
||||
import { RequestService, serveFileSystemRequests } from './requests';
|
||||
@@ -22,12 +22,30 @@ export interface Runtime {
|
||||
fs?: RequestService;
|
||||
}
|
||||
|
||||
interface FormatterRegistration {
|
||||
readonly languageId: string;
|
||||
readonly settingId: string;
|
||||
provider: Disposable | undefined;
|
||||
}
|
||||
|
||||
interface CSSFormatSettings {
|
||||
selectorSeparatorNewline?: boolean;
|
||||
newlineBetweenRules?: boolean;
|
||||
spaceAroundSelectorSeparator?: boolean;
|
||||
}
|
||||
|
||||
const cssFormatSettingKeys: (keyof CSSFormatSettings)[] = ['selectorSeparatorNewline', 'newlineBetweenRules', 'spaceAroundSelectorSeparator'];
|
||||
|
||||
export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) {
|
||||
|
||||
const customDataSource = getCustomDataSource(context.subscriptions);
|
||||
|
||||
let documentSelector = ['css', 'scss', 'less'];
|
||||
|
||||
const formatterRegistrations: FormatterRegistration[] = documentSelector.map(languageId => ({
|
||||
languageId, settingId: `${languageId}.format.enable`, provider: undefined
|
||||
}));
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
documentSelector,
|
||||
@@ -35,7 +53,9 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
||||
configurationSection: ['css', 'scss', 'less']
|
||||
},
|
||||
initializationOptions: {
|
||||
handledSchemas: ['file']
|
||||
handledSchemas: ['file'],
|
||||
provideFormatter: false, // tell the server to not provide formatting capability
|
||||
customCapabilities: { rangeFormatting: { editLimit: 10000 } }
|
||||
},
|
||||
middleware: {
|
||||
provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult<CompletionItem[] | CompletionList> {
|
||||
@@ -84,6 +104,13 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
||||
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
|
||||
});
|
||||
|
||||
// manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652.
|
||||
for (const registration of formatterRegistrations) {
|
||||
updateFormatterRegistration(registration);
|
||||
context.subscriptions.push({ dispose: () => registration.provider?.dispose() });
|
||||
context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration)));
|
||||
}
|
||||
|
||||
serveFileSystemRequests(client, runtime);
|
||||
});
|
||||
|
||||
@@ -143,4 +170,47 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateFormatterRegistration(registration: FormatterRegistration) {
|
||||
const formatEnabled = workspace.getConfiguration().get(registration.settingId);
|
||||
if (!formatEnabled && registration.provider) {
|
||||
registration.provider.dispose();
|
||||
registration.provider = undefined;
|
||||
} else if (formatEnabled && !registration.provider) {
|
||||
registration.provider = languages.registerDocumentRangeFormattingEditProvider(registration.languageId, {
|
||||
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
|
||||
const filesConfig = workspace.getConfiguration('files', document);
|
||||
|
||||
const fileFormattingOptions = {
|
||||
trimTrailingWhitespace: filesConfig.get<boolean>('trimTrailingWhitespace'),
|
||||
trimFinalNewlines: filesConfig.get<boolean>('trimFinalNewlines'),
|
||||
insertFinalNewline: filesConfig.get<boolean>('insertFinalNewline'),
|
||||
};
|
||||
const params: DocumentRangeFormattingParams = {
|
||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
|
||||
range: client.code2ProtocolConverter.asRange(range),
|
||||
options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions)
|
||||
};
|
||||
// add the css formatter options from the settings
|
||||
const formatterSettings = workspace.getConfiguration(registration.languageId, document).get<CSSFormatSettings>('format');
|
||||
if (formatterSettings) {
|
||||
for (const key of cssFormatSettingKeys) {
|
||||
const val = formatterSettings[key];
|
||||
if (val !== undefined) {
|
||||
params.options[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(params.options));
|
||||
return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
|
||||
client.protocol2CodeConverter.asTextEdits,
|
||||
(error) => {
|
||||
client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +307,30 @@
|
||||
],
|
||||
"default": "off",
|
||||
"description": "%css.trace.server.desc%"
|
||||
},
|
||||
"css.format.enable": {
|
||||
"type": "boolean",
|
||||
"scope": "window",
|
||||
"default": true,
|
||||
"description": "%css.format.enable.desc%"
|
||||
},
|
||||
"css.format.selectorSeparatorNewline": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "%css.format.selectorSeparatorNewline.desc%"
|
||||
},
|
||||
"css.format.newlineBetweenRules": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "%css.format.newlineBetweenRules.desc%"
|
||||
},
|
||||
"css.format.spaceAroundSelectorSeparator": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"markdownDescription": "%css.format.spaceAroundSelectorSeparator.desc%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -563,6 +587,30 @@
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "%scss.lint.unknownAtRules.desc%"
|
||||
},
|
||||
"scss.format.enable": {
|
||||
"type": "boolean",
|
||||
"scope": "window",
|
||||
"default": true,
|
||||
"description": "%scss.format.enable.desc%"
|
||||
},
|
||||
"scss.format.selectorSeparatorNewline": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "%scss.format.selectorSeparatorNewline.desc%"
|
||||
},
|
||||
"scss.format.newlineBetweenRules": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "%scss.format.newlineBetweenRules.desc%"
|
||||
},
|
||||
"scss.format.spaceAroundSelectorSeparator": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"markdownDescription": "%scss.format.spaceAroundSelectorSeparator.desc%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -820,6 +868,30 @@
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "%less.lint.unknownAtRules.desc%"
|
||||
},
|
||||
"less.format.enable": {
|
||||
"type": "boolean",
|
||||
"scope": "window",
|
||||
"default": true,
|
||||
"description": "%less.format.enable.desc%"
|
||||
},
|
||||
"less.format.selectorSeparatorNewline": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "%less.format.selectorSeparatorNewline.desc%"
|
||||
},
|
||||
"less.format.newlineBetweenRules": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "%less.format.newlineBetweenRules.desc%"
|
||||
},
|
||||
"less.format.spaceAroundSelectorSeparator": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"markdownDescription": "%less.format.spaceAroundSelectorSeparator.desc%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
"css.validate.desc": "Enables or disables all validations.",
|
||||
"css.hover.documentation": "Show tag and attribute documentation in CSS hovers.",
|
||||
"css.hover.references": "Show references to MDN in CSS hovers.",
|
||||
"css.format.enable.desc": "Enable/disable default LESS formatter.",
|
||||
"css.format.selectorSeparatorNewline.desc": "Separate selectors with newline or not",
|
||||
"css.format.newlineBetweenRules.desc": "Add a new line after every css rule",
|
||||
"css.format.spaceAroundSelectorSeparator.desc": "Ensure space around selector separators",
|
||||
"less.title": "LESS",
|
||||
"less.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.",
|
||||
"less.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.",
|
||||
@@ -57,6 +61,10 @@
|
||||
"less.validate.desc": "Enables or disables all validations.",
|
||||
"less.hover.documentation": "Show tag and attribute documentation in LESS hovers.",
|
||||
"less.hover.references": "Show references to MDN in LESS hovers.",
|
||||
"less.format.enable.desc": "Enable/disable default LESS formatter.",
|
||||
"less.format.selectorSeparatorNewline.desc": "Separate selectors with newline or not",
|
||||
"less.format.newlineBetweenRules.desc": "Add a new line after every css rule",
|
||||
"less.format.spaceAroundSelectorSeparator.desc": "Ensure space around selector separators",
|
||||
"scss.title": "SCSS (Sass)",
|
||||
"scss.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.",
|
||||
"scss.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.",
|
||||
@@ -84,6 +92,10 @@
|
||||
"scss.validate.desc": "Enables or disables all validations.",
|
||||
"scss.hover.documentation": "Show tag and attribute documentation in SCSS hovers.",
|
||||
"scss.hover.references": "Show references to MDN in SCSS hovers.",
|
||||
"scss.format.enable.desc": "Enable/disable default LESS formatter.",
|
||||
"scss.format.selectorSeparatorNewline.desc": "Separate selectors with newline or not",
|
||||
"scss.format.newlineBetweenRules.desc": "Add a new line after every css rule",
|
||||
"scss.format.spaceAroundSelectorSeparator.desc": "Ensure space around selector separators",
|
||||
"css.colorDecorators.enable.deprecationMessage": "The setting `css.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.",
|
||||
"scss.colorDecorators.enable.deprecationMessage": "The setting `scss.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.",
|
||||
"less.colorDecorators.enable.deprecationMessage": "The setting `less.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`."
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"main": "./out/node/cssServerMain",
|
||||
"browser": "./dist/browser/cssServerMain",
|
||||
"dependencies": {
|
||||
"vscode-css-languageservice": "^5.1.13",
|
||||
"vscode-css-languageservice": "^5.2.0",
|
||||
"vscode-languageserver": "^7.0.0",
|
||||
"vscode-uri": "^3.0.3"
|
||||
},
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable
|
||||
Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit
|
||||
} from 'vscode-languageserver';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice';
|
||||
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position, CSSFormatConfiguration } from 'vscode-css-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import { formatError, runSafeAsync } from './utils/runner';
|
||||
import { getDocumentContext } from './utils/documentContext';
|
||||
@@ -52,6 +52,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
let scopedSettingsSupport = false;
|
||||
let foldingRangeLimit = Number.MAX_VALUE;
|
||||
let workspaceFolders: WorkspaceFolder[];
|
||||
let formatterMaxNumberOfEdits = Number.MAX_VALUE;
|
||||
|
||||
let dataProvidersReady: Promise<any> = Promise.resolve();
|
||||
|
||||
@@ -87,6 +88,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
|
||||
scopedSettingsSupport = !!getClientCapability('workspace.configuration', false);
|
||||
foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
|
||||
formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE;
|
||||
|
||||
languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities });
|
||||
languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities });
|
||||
@@ -107,7 +109,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
renameProvider: true,
|
||||
colorProvider: {},
|
||||
foldingRangeProvider: true,
|
||||
selectionRangeProvider: true
|
||||
selectionRangeProvider: true,
|
||||
documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true,
|
||||
documentFormattingProvider: params.initializationOptions?.provideFormatter === true,
|
||||
};
|
||||
return { capabilities };
|
||||
});
|
||||
@@ -367,6 +371,28 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
}, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
async function onFormat(textDocument: TextDocumentIdentifier, range: Range | undefined, options: FormattingOptions): Promise<TextEdit[]> {
|
||||
const document = documents.get(textDocument.uri);
|
||||
if (document) {
|
||||
console.log(JSON.stringify(options));
|
||||
const edits = getLanguageService(document).format(document, range ?? getFullRange(document), options as CSSFormatConfiguration);
|
||||
if (edits.length > formatterMaxNumberOfEdits) {
|
||||
const newText = TextDocument.applyEdits(document, edits);
|
||||
return [TextEdit.replace(getFullRange(document), newText)];
|
||||
}
|
||||
return edits;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
connection.onDocumentRangeFormatting((formatParams, token) => {
|
||||
return runSafeAsync(runtime, () => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentFormatting((formatParams, token) => {
|
||||
return runSafeAsync(runtime, () => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onNotification(CustomDataChangedNotification.type, updateDataProviders);
|
||||
|
||||
// Listen on the connection
|
||||
@@ -374,4 +400,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
|
||||
}
|
||||
|
||||
function getFullRange(document: TextDocument): Range {
|
||||
return Range.create(Position.create(0, 0), document.positionAt(document.getText().length));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
|
||||
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
|
||||
|
||||
vscode-css-languageservice@^5.1.13:
|
||||
version "5.1.13"
|
||||
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.13.tgz#debc7c8368223b211a734cb7eb7789c586d3e2d9"
|
||||
integrity sha512-FA0foqMzMmEoO0WJP+MjoD4dRERhKS+Ag+yBrtmWQDmw2OuZ1R/5FkvI/XdTkCpHmTD9VMczugpHRejQyTXCNQ==
|
||||
vscode-css-languageservice@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.2.0.tgz#84fa95f8314c742080c09f623bf9f0727621eebf"
|
||||
integrity sha512-FR5yDEfzbXJtYmZYrA7JWFcRSLHsJw3nv55XAmx7qdwRpFj9yy0ulKfN/NUUdiZW2jZU2fD/+Y4VJYPdafHDag==
|
||||
dependencies:
|
||||
vscode-languageserver-textdocument "^1.0.1"
|
||||
vscode-languageserver-textdocument "^1.0.4"
|
||||
vscode-languageserver-types "^3.16.0"
|
||||
vscode-nls "^5.0.0"
|
||||
vscode-uri "^3.0.2"
|
||||
vscode-uri "^3.0.3"
|
||||
|
||||
vscode-jsonrpc@6.0.0:
|
||||
version "6.0.0"
|
||||
@@ -35,10 +35,10 @@ vscode-languageserver-protocol@3.16.0:
|
||||
vscode-jsonrpc "6.0.0"
|
||||
vscode-languageserver-types "3.16.0"
|
||||
|
||||
vscode-languageserver-textdocument@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f"
|
||||
integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==
|
||||
vscode-languageserver-textdocument@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157"
|
||||
integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==
|
||||
|
||||
vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0:
|
||||
version "3.16.0"
|
||||
@@ -57,11 +57,6 @@ vscode-nls@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
|
||||
integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==
|
||||
|
||||
vscode-uri@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.2.tgz#ecfd1d066cb8ef4c3a208decdbab9a8c23d055d0"
|
||||
integrity sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA==
|
||||
|
||||
vscode-uri@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84"
|
||||
|
||||
Reference in New Issue
Block a user