diff --git a/extensions/vscode-vue-language-features/package.json b/extensions/vscode-vue-language-features/package.json index 88b46138f..8f5407ade 100644 --- a/extensions/vscode-vue-language-features/package.json +++ b/extensions/vscode-vue-language-features/package.json @@ -370,6 +370,55 @@ "default": false, "description": "Auto-complete Ref value with `.value`." }, + "volar.initialIndent": { + "type": "object", + "description": "Whether to have initial indent.", + "default": { + "html": true + }, + "properties": { + "html": { + "type": "boolean", + "default": true + }, + "pug": { + "type": "boolean", + "default": false + }, + "typescript": { + "type": "boolean", + "default": false + }, + "javascript": { + "type": "boolean", + "default": false + }, + "typescriptreact": { + "type": "boolean", + "default": false + }, + "javascriptreact": { + "type": "boolean", + "default": false + }, + "css": { + "type": "boolean", + "default": false + }, + "scss": { + "type": "boolean", + "default": false + }, + "json": { + "type": "boolean", + "default": false + }, + "jsonc": { + "type": "boolean", + "default": false + } + } + }, "volar.takeOverMode.enabled": { "type": [ "boolean", diff --git a/packages/shared/src/node.ts b/packages/shared/src/node.ts index 153abaa03..e26f5cf9d 100644 --- a/packages/shared/src/node.ts +++ b/packages/shared/src/node.ts @@ -1,3 +1,6 @@ export * from './browser'; export * from './ts_node'; export * from './http'; + +// fix build +export * as _0 from 'vscode-languageserver-textdocument'; diff --git a/packages/typescript-language-service/src/services/formatting.ts b/packages/typescript-language-service/src/services/formatting.ts index 1ee4fcf93..b2e7f5427 100644 --- a/packages/typescript-language-service/src/services/formatting.ts +++ b/packages/typescript-language-service/src/services/formatting.ts @@ -17,6 +17,9 @@ export function register( const fileName = shared.getPathOfUri(document.uri); const tsOptions = await settings.getFormatOptions?.(document.uri, options) ?? options; + if (typeof(tsOptions.indentSize) === "boolean" || typeof(tsOptions.indentSize) === "string") { + tsOptions.indentSize = undefined; + } let scriptEdits: ReturnType | undefined; try { diff --git a/packages/vue-language-core/src/plugins/vue-sfc-customblocks.ts b/packages/vue-language-core/src/plugins/vue-sfc-customblocks.ts index 2cfca21d6..d87574fb3 100644 --- a/packages/vue-language-core/src/plugins/vue-sfc-customblocks.ts +++ b/packages/vue-language-core/src/plugins/vue-sfc-customblocks.ts @@ -1,5 +1,12 @@ import { VueLanguagePlugin } from '../sourceFile'; +const presetInitialIndentBrackets: Record = { + json: ['{', '}'], + jsonc: ['{', '}'], + html: [''], + markdown: [''], +}; + const plugin: VueLanguagePlugin = () => { return { @@ -22,7 +29,9 @@ const plugin: VueLanguagePlugin = () => { embeddedFile.capabilities = { diagnostics: true, foldingRanges: true, - formatting: true, + formatting: { + initialIndentBracket: presetInitialIndentBrackets[customBlock.lang], + }, documentSymbol: true, codeActions: true, inlayHints: true, diff --git a/packages/vue-language-core/src/plugins/vue-sfc-scripts.ts b/packages/vue-language-core/src/plugins/vue-sfc-scripts.ts index 9990f2e8e..e309840b4 100644 --- a/packages/vue-language-core/src/plugins/vue-sfc-scripts.ts +++ b/packages/vue-language-core/src/plugins/vue-sfc-scripts.ts @@ -23,7 +23,9 @@ const plugin: VueLanguagePlugin = () => { embeddedFile.capabilities = { diagnostics: false, foldingRanges: true, - formatting: true, + formatting: { + initialIndentBracket: ['{', '}'], + }, documentSymbol: true, codeActions: false, inlayHints: false, diff --git a/packages/vue-language-core/src/plugins/vue-sfc-styles.ts b/packages/vue-language-core/src/plugins/vue-sfc-styles.ts index 178bd259a..6e3d6d1fa 100644 --- a/packages/vue-language-core/src/plugins/vue-sfc-styles.ts +++ b/packages/vue-language-core/src/plugins/vue-sfc-styles.ts @@ -1,5 +1,10 @@ import { VueLanguagePlugin } from '../sourceFile'; +const presetInitialIndentBrackets: Record = { + css: ['{', '}'], + scss: ['{', '}'], +}; + const plugin: VueLanguagePlugin = () => { return { @@ -22,7 +27,9 @@ const plugin: VueLanguagePlugin = () => { embeddedFile.capabilities = { diagnostics: true, foldingRanges: true, - formatting: true, + formatting: { + initialIndentBracket: presetInitialIndentBrackets[style.lang], + }, documentSymbol: true, codeActions: true, inlayHints: true, @@ -48,5 +55,5 @@ const plugin: VueLanguagePlugin = () => { } }, }; -} +}; export = plugin; diff --git a/packages/vue-language-core/src/plugins/vue-sfc-template.ts b/packages/vue-language-core/src/plugins/vue-sfc-template.ts index 458a9c4f7..a346acf24 100644 --- a/packages/vue-language-core/src/plugins/vue-sfc-template.ts +++ b/packages/vue-language-core/src/plugins/vue-sfc-template.ts @@ -1,5 +1,10 @@ import { VueLanguagePlugin } from '../sourceFile'; +const presetInitialIndentBrackets: Record = { + html: [''], + pug: ['div', '\n'], +}; + const plugin: VueLanguagePlugin = () => { return { @@ -17,7 +22,9 @@ const plugin: VueLanguagePlugin = () => { embeddedFile.capabilities = { diagnostics: true, foldingRanges: true, - formatting: true, + formatting: { + initialIndentBracket: presetInitialIndentBrackets[sfc.template.lang], + }, documentSymbol: true, codeActions: true, inlayHints: true, diff --git a/packages/vue-language-core/src/sourceFile.ts b/packages/vue-language-core/src/sourceFile.ts index b7ae694c5..e3e9cb914 100644 --- a/packages/vue-language-core/src/sourceFile.ts +++ b/packages/vue-language-core/src/sourceFile.ts @@ -74,7 +74,9 @@ export interface EmbeddedFile { capabilities: { diagnostics: boolean, foldingRanges: boolean, - formatting: boolean, + formatting: boolean | { + initialIndentBracket?: [string, string], + }, documentSymbol: boolean, codeActions: boolean, inlayHints: boolean, diff --git a/packages/vue-language-service/src/documentFeatures/format.ts b/packages/vue-language-service/src/documentFeatures/format.ts index e5ffe9768..43acbd00d 100644 --- a/packages/vue-language-service/src/documentFeatures/format.ts +++ b/packages/vue-language-service/src/documentFeatures/format.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode-languageserver-protocol'; import type { EmbeddedStructure } from '@volar/vue-language-core'; import type { DocumentServiceRuntimeContext } from '../types'; import { EmbeddedDocumentSourceMap, VueDocument } from '../vueDocuments'; +import { useConfigurationHost } from '@volar/vue-language-service-types'; export function register(context: DocumentServiceRuntimeContext) { @@ -24,8 +25,8 @@ export function register(context: DocumentServiceRuntimeContext) { const originalDocument = document; const rootEdits = onTypeParams - ? await tryFormatOnType(document, onTypeParams.position, onTypeParams.ch) - : await tryFormat(document, range); + ? await tryFormat(document, onTypeParams.position, undefined, onTypeParams.ch) + : await tryFormat(document, range, undefined); const vueDocument = context.getVueDocument(document); if (!vueDocument) @@ -37,6 +38,8 @@ export function register(context: DocumentServiceRuntimeContext) { let level = 0; + const initialIndentLanguageId = await useConfigurationHost()?.getConfiguration>('volar.initialIndent') ?? { html: true }; + while (true) { tryUpdateVueDocument(); @@ -57,6 +60,9 @@ export function register(context: DocumentServiceRuntimeContext) { continue; const sourceMap = vueDocument.sourceMapsMap.get(embedded.self); + const initialIndentBracket = typeof embedded.self.file.capabilities.formatting === 'object' && initialIndentLanguageId[sourceMap.mappedDocument.languageId] + ? embedded.self.file.capabilities.formatting.initialIndentBracket + : undefined; let _edits: vscode.TextEdit[] | undefined; @@ -65,7 +71,12 @@ export function register(context: DocumentServiceRuntimeContext) { const embeddedPosition = sourceMap.getMappedRange(onTypeParams.position)?.[0].start; if (embeddedPosition) { - _edits = await tryFormatOnType(sourceMap.mappedDocument, embeddedPosition, onTypeParams.ch); + _edits = await tryFormat( + sourceMap.mappedDocument, + embeddedPosition, + initialIndentBracket, + onTypeParams.ch, + ); } } @@ -103,7 +114,11 @@ export function register(context: DocumentServiceRuntimeContext) { sourceMapEmbeddedDocumentUri: sourceMap.mappedDocument.uri, }; - _edits = await tryFormat(sourceMap.mappedDocument, embeddedRange); + _edits = await tryFormat( + sourceMap.mappedDocument, + embeddedRange, + initialIndentBracket, + ); } } @@ -182,21 +197,41 @@ export function register(context: DocumentServiceRuntimeContext) { } } - async function tryFormat(document: TextDocument, range: vscode.Range) { + async function tryFormat(document: TextDocument, range: vscode.Range | vscode.Position, initialIndentBracket: [string, string] | undefined, ch?: string) { - const plugins = context.getFormatPlugins(); + const plugins = context.getPlugins(); - context.updateTsLs(document); + let formatDocument = document; + let formatRange = range; - for (const plugin of plugins) { + if (initialIndentBracket) { + formatDocument = TextDocument.create( + document.uri, + document.languageId, + document.version, + initialIndentBracket[0] + document.getText() + initialIndentBracket[1], + ); + formatRange = { + start: formatDocument.positionAt(0), + end: formatDocument.positionAt(formatDocument.getText().length), + }; + } - if (!plugin.format) - continue; + context.updateTsLs(formatDocument); + + for (const plugin of plugins) { let edits: vscode.TextEdit[] | null | undefined; try { - edits = await plugin.format(document, range, options); + if (vscode.Position.is(formatRange)) { + if (ch !== undefined) { + edits = await plugin.formatOnType?.(formatDocument, formatRange, ch, options); + } + } + else { + edits = await plugin.format?.(formatDocument, formatRange, options); + } } catch (err) { console.error(err); @@ -205,34 +240,25 @@ export function register(context: DocumentServiceRuntimeContext) { if (!edits) continue; - return edits; - } - } - - async function tryFormatOnType(document: TextDocument, position: vscode.Position, ch: string) { - - const plugins = context.getFormatPlugins(); + if (!edits.length) + return edits; - context.updateTsLs(document); + let newText = TextDocument.applyEdits(formatDocument, edits); - for (const plugin of plugins) { - - if (!plugin.formatOnType) - continue; - - let edits: vscode.TextEdit[] | null | undefined; - - try { - edits = await plugin.formatOnType(document, position, ch, options); + if (initialIndentBracket) { + newText = newText.substring( + newText.indexOf(initialIndentBracket[0]) + initialIndentBracket[0].length, + newText.lastIndexOf(initialIndentBracket[1]), + ); } - catch (err) { - console.error(err); - } - - if (!edits) - continue; - return edits; + return [{ + range: { + start: document.positionAt(0), + end: document.positionAt(document.getText().length), + }, + newText, + }]; } } diff --git a/packages/vue-language-service/src/documentService.ts b/packages/vue-language-service/src/documentService.ts index f5327bfde..8fa78a791 100644 --- a/packages/vue-language-service/src/documentService.ts +++ b/packages/vue-language-service/src/documentService.ts @@ -70,17 +70,7 @@ export function getDocumentService( ts, getVueDocument: doc => vueDocuments.get(doc), }); - - // formatter plugins const pugFormatPlugin = usePugFormatPlugin(); - const formatPlugns = [ - ...customPlugins, - cssPlugin, - htmlPlugin, - pugFormatPlugin, - jsonPlugin, - tsPlugin, - ].map(patchHtmlFormat); const context: DocumentServiceRuntimeContext = { typescript: ts, @@ -91,15 +81,13 @@ export function getDocumentService( vuePlugin, htmlPlugin, pugPlugin, + pugFormatPlugin, cssPlugin, jsonPlugin, tsPlugin, autoWrapParenthesesPlugin, ]; }, - getFormatPlugins() { - return formatPlugns; - }, updateTsLs(document) { if (isTsDocument(document)) { tsLs = getSingleFileTypeScriptService(context.typescript, ts2, document, tsSettings); @@ -143,45 +131,3 @@ export function getDocumentService( return vueDoc; } } - -function patchHtmlFormat(htmlPlugin: T) { - - const originalFormat = htmlPlugin.format; - - if (originalFormat) { - - htmlPlugin.format = async (document, range, options) => { - - if (document.languageId === 'html') { - - const prefixes = ''; - - const patchDocument = TextDocument.create(document.uri, document.languageId, document.version, prefixes + document.getText() + suffixes); - const result = await originalFormat?.(patchDocument, { - start: patchDocument.positionAt(0), - end: patchDocument.positionAt(patchDocument.getText().length), - }, options); - - if (!result?.length) - return result; - - let newText = TextDocument.applyEdits(patchDocument, result); - newText = newText.trim(); - newText = newText.substring(prefixes.length, newText.length - suffixes.length); - - return [{ - newText, - range: { - start: document.positionAt(0), - end: document.positionAt(document.getText().length), - } - }]; - } - - return originalFormat?.(document, range, options); - }; - } - - return htmlPlugin; -} diff --git a/packages/vue-language-service/src/plugins/html.ts b/packages/vue-language-service/src/plugins/html.ts index 16494e511..1a374cc55 100644 --- a/packages/vue-language-service/src/plugins/html.ts +++ b/packages/vue-language-service/src/plugins/html.ts @@ -108,10 +108,20 @@ export default function (options: { return; } - return htmlLs.format(document, range, { + const edits = htmlLs.format(document, range, { ...options_2, ...options, }); + + const newText = TextDocument.applyEdits(document, edits); + + return [{ + newText: '\n' + newText.trim() + '\n', + range: { + start: document.positionAt(0), + end: document.positionAt(document.getText().length), + }, + }] }); }, diff --git a/packages/vue-language-service/src/tsConfigs.ts b/packages/vue-language-service/src/tsConfigs.ts index 552d950fd..0afe63b48 100644 --- a/packages/vue-language-service/src/tsConfigs.ts +++ b/packages/vue-language-service/src/tsConfigs.ts @@ -14,7 +14,7 @@ export function getTsSettings(configurationHost: ConfigurationHost | undefined) export async function getFormatOptions( configurationHost: ConfigurationHost | undefined, uri: string, - options?: vscode.FormattingOptions + options?: vscode.FormattingOptions & ts.FormatCodeSettings, ): Promise { let config = await configurationHost?.getConfiguration(isTypeScriptDocument(uri) ? 'typescript.format' : 'javascript.format', uri); @@ -22,8 +22,7 @@ export async function getFormatOptions( config = config ?? {}; return { - tabSize: options?.tabSize, - indentSize: options?.tabSize, + ...options, convertTabsToSpaces: options?.insertSpaces, // We can use \n here since the editor normalizes later on to its line endings. newLineCharacter: '\n', diff --git a/packages/vue-language-service/src/types.ts b/packages/vue-language-service/src/types.ts index 72e9d1942..7f0134435 100644 --- a/packages/vue-language-service/src/types.ts +++ b/packages/vue-language-service/src/types.ts @@ -8,7 +8,6 @@ export interface DocumentServiceRuntimeContext { typescript: typeof import('typescript/lib/tsserverlibrary'); getVueDocument(document: TextDocument): VueDocument | undefined; getPlugins(): EmbeddedLanguageServicePlugin[]; - getFormatPlugins(): EmbeddedLanguageServicePlugin[]; updateTsLs(document: TextDocument): void; };