diff --git a/src/languageserver/handlers/languageHandlers.ts b/src/languageserver/handlers/languageHandlers.ts index 1d4ed68bd..3cf7104d0 100644 --- a/src/languageserver/handlers/languageHandlers.ts +++ b/src/languageserver/handlers/languageHandlers.ts @@ -35,7 +35,7 @@ import type { WorkspaceEdit, } from 'vscode-languageserver-types'; import { isKubernetesAssociatedDocument } from '../../languageservice/parser/isKubernetes'; -import type { LanguageService } from '../../languageservice/yamlLanguageService'; +import type { CustomFormatterOptions, LanguageService } from '../../languageservice/yamlLanguageService'; import type { SettingsState } from '../../yamlSettings'; import type { ValidationHandler } from './validationHandlers'; import { ResultLimitReachedNotification } from '../../requestTypes'; @@ -128,14 +128,14 @@ export class LanguageHandlers { return []; } - const formatEnabled = await this.resolveFormatterState(document); - if (!formatEnabled) { + const formatterSettings = await this.resolveFormatterSettings(document); + if (formatterSettings.enable === false) { return []; } const customFormatterSettings = { tabWidth: formatParams.options.tabSize, - ...this.yamlSettings.yamlFormatterSettings, + ...formatterSettings, }; return this.languageService.doFormat(document, customFormatterSettings); @@ -317,18 +317,31 @@ export class LanguageHandlers { }; } - private async resolveFormatterState(document: TextDocument): Promise { - const fallback = this.yamlSettings.yamlFormatterSettings.enable !== false; + private async resolveFormatterSettings(document: TextDocument): Promise { + const fallback = this.yamlSettings.yamlFormatterSettings; if (this.yamlSettings.hasConfigurationCapability && this.connection.workspace?.getConfiguration) { try { - const scopedLanguageSettings = await this.connection.workspace.getConfiguration({ - section: `[${document.languageId}]`, - scopeUri: document.uri, - }); + const [scopedFormatterSettings, scopedLanguageSettings] = await this.connection.workspace.getConfiguration([ + { + section: 'yaml.format', + scopeUri: document.uri, + }, + { + section: `[${document.languageId}]`, + scopeUri: document.uri, + }, + ]); + + const resolvedSettings = { + ...fallback, + ...(scopedFormatterSettings ?? {}), + }; if (typeof scopedLanguageSettings?.['yaml.format.enable'] === 'boolean') { - return scopedLanguageSettings['yaml.format.enable']; + resolvedSettings.enable = scopedLanguageSettings['yaml.format.enable']; } + + return resolvedSettings; } catch { // ignore and fall back to global setting } diff --git a/test/formatter.test.ts b/test/formatter.test.ts index 545be44b3..e3bb67f4c 100644 --- a/test/formatter.test.ts +++ b/test/formatter.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; import * as sinon from 'sinon'; +import { TextDocument } from 'vscode-languageserver-textdocument'; import type { FormattingOptions, TextEdit } from 'vscode-languageserver-types'; import type { CustomFormatterOptions } from '../src'; import type { LanguageHandlers } from '../src/languageserver/handlers/languageHandlers'; @@ -15,7 +16,9 @@ import { setupLanguageService, setupTextDocument } from './utils/testHelper'; type LanguageHandlerWithConnection = { connection: { workspace: { - getConfiguration: (item?: { section?: string }) => Promise; + getConfiguration: ( + item?: { section?: string; scopeUri?: string } | Array<{ section?: string; scopeUri?: string }> + ) => Promise; }; }; }; @@ -67,7 +70,7 @@ describe('Formatter Tests', () => { yamlSettings.documents = new TextDocumentTestManager(); (yamlSettings.documents as TextDocumentTestManager).set(testTextDocument); const connection = (languageHandler as unknown as LanguageHandlerWithConnection).connection; - sandbox.stub(connection.workspace, 'getConfiguration').resolves({ 'yaml.format.enable': false }); + sandbox.stub(connection.workspace, 'getConfiguration').resolves([{}, { 'yaml.format.enable': false }]); yamlSettings.hasConfigurationCapability = true; const edits = await languageHandler.formatterHandler({ options: { tabSize: 2, insertSpaces: true }, @@ -76,6 +79,39 @@ describe('Formatter Tests', () => { assert.equal(edits.length, 0); }); + it('Formatting uses resource-scoped settings for each document', async () => { + const content = 'root:\n - child: "text"\n'; + const singleQuoteDocument = TextDocument.create('file:///workspace/single/test.yaml', 'yaml', 0, content); + const doubleQuoteDocument = TextDocument.create('file:///workspace/double/test.yaml', 'yaml', 0, content); + yamlSettings.documents = new TextDocumentTestManager(); + (yamlSettings.documents as TextDocumentTestManager).set(singleQuoteDocument); + (yamlSettings.documents as TextDocumentTestManager).set(doubleQuoteDocument); + yamlSettings.yamlFormatterSettings = { singleQuote: false, enable: true }; + yamlSettings.hasConfigurationCapability = true; + + const connection = (languageHandler as unknown as LanguageHandlerWithConnection).connection; + const getConfiguration = sandbox.stub(connection.workspace, 'getConfiguration').callsFake(async (items) => { + assert.ok(Array.isArray(items)); + assert.equal(items[0].section, 'yaml.format'); + assert.equal(items[1].section, '[yaml]'); + assert.equal(items[0].scopeUri, items[1].scopeUri); + return [{ singleQuote: items[0].scopeUri?.includes('/single/') }, {}]; + }); + + const singleQuoteEdits = await languageHandler.formatterHandler({ + options: { tabSize: 2, insertSpaces: true }, + textDocument: singleQuoteDocument, + }); + const doubleQuoteEdits = await languageHandler.formatterHandler({ + options: { tabSize: 2, insertSpaces: true }, + textDocument: doubleQuoteDocument, + }); + + assert.equal(singleQuoteEdits[0].newText, "root:\n - child: 'text'\n"); + assert.equal(doubleQuoteEdits.length, 0); + assert.equal(getConfiguration.callCount, 2); + }); + it('Formatting works with custom tags', async () => { const content = 'cwd: !Test test'; const edits = await parseSetup(content);