From 1144bca438b9436168892e9dda2fa95b213d8e34 Mon Sep 17 00:00:00 2001 From: Psy-Q Date: Sat, 29 Aug 2026 12:25:37 +0200 Subject: [PATCH 1/2] fix: preserve list-element indentation in onTypeFormatting When pressing Enter after a block-sequence item, the onTypeFormatting handler returned an unconditional "- " at params.position, dropping the sequence indentation and placing the dash at column 0 (invalid YAML). It also ignored whitespace the client had already auto-inserted on the line, stamping the dash *before* those existing spaces. This is the LSP-spec-correct behavior to expect: the server advertises documentOnTypeFormattingProvider with firstTriggerCharacter "\n", and DocumentOnTypeFormattingParams explicitly notes that position is "not necessarily the exact position where the character denoted by the property ch got typed" and that a client "could auto insert characters as well" (e.g. automatic indentation/brace completion). A compliant client like lsp-mode sends the request with the line already carrying client-side indentation, so the edit must merge with it rather than overwrite it. Rewrite the block-sequence branch to derive the sequence indentation from the preceding line and insert only the missing text after whatever whitespace already exists on the line: - empty line -> insert "indentation + '- '" (dash aligned with the preceding item's dash) - already-correct line -> no-op - line shorter than expected -> insert the missing suffix after existing spaces (handles client-side auto-indent, e.g. lsp-mode) - line longer than expected -> delete the excess Verified live in Emacs with lsp-mode: the stock 1.24.0 server produced a dash at column 0, while the fixed server leaves the line correctly indented with the dash in place. Co-authored-by: OpenCode Agent --- .../services/yamlOnTypeFormatting.ts | 27 +++++++++++++++++-- test/yamlOnTypeFormatting.test.ts | 16 +++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/languageservice/services/yamlOnTypeFormatting.ts b/src/languageservice/services/yamlOnTypeFormatting.ts index 27f80dcda..d2edda9c8 100644 --- a/src/languageservice/services/yamlOnTypeFormatting.ts +++ b/src/languageservice/services/yamlOnTypeFormatting.ts @@ -46,8 +46,31 @@ export function doDocumentOnTypeFormatting( return [TextEdit.insert(position, ' '.repeat(params.options.tabSize))]; } - if (previousLine.includes(' - ') && !previousLine.includes(': ')) { - return [TextEdit.insert(position, '- ')]; + if (previousLine.trimStart().startsWith('-') && !previousLine.includes(': ')) { + const indentation = previousLine.slice(0, previousLine.length - previousLine.trimStart().length); + const expectedText = indentation + '- '; + const currentLine = tb.getLineContent(position.line).replace('\r', '').replace('\n', ''); + if (currentLine.trim().length !== 0) { + // non-space content, do nothing + return; + } else if (currentLine.length === expectedText.length) { + // already right; do nothing + return; + } else if (currentLine.length < expectedText.length) { + // insert after whatever indentation is already on the line, so a leading + // dash never lands before existing spaces (e.g. client-side auto-indent) + const insertPosition = Position.create(position.line, currentLine.length); + return [TextEdit.insert(insertPosition, expectedText.slice(currentLine.length))]; + } else { + return [ + TextEdit.del( + Range.create( + Position.create(position.line, 0), + Position.create(position.line, currentLine.length - expectedText.length) + ) + ), + ]; + } } if (previousLine.includes(' - ') && previousLine.includes(': ')) { diff --git a/test/yamlOnTypeFormatting.test.ts b/test/yamlOnTypeFormatting.test.ts index e42ea779e..2cf335c4d 100644 --- a/test/yamlOnTypeFormatting.test.ts +++ b/test/yamlOnTypeFormatting.test.ts @@ -40,6 +40,22 @@ describe('YAML On Type Formatter', () => { expect(result[0]).to.eqls(TextEdit.insert(pos, '- ')); }); + it('should preserve list element indentation after newline', () => { + const doc = setupTextDocument('test:\n - hello\n - world\n'); + const pos = Position.create(3, 0); + const params = createParams(pos); + const result = doDocumentOnTypeFormatting(doc, params); + expect(result[0]).to.eql(TextEdit.insert(pos, ' - ')); + }); + + it('should align list element dash after existing client-side indentation', () => { + const doc = setupTextDocument('test:\n - hello\n - world\n '); + const pos = Position.create(3, 0); + const params = createParams(pos); + const result = doDocumentOnTypeFormatting(doc, params); + expect(result[0]).to.eql(TextEdit.insert(Position.create(3, 2), '- ')); + }); + it('should add indentation for mapping in array', () => { const doc = setupTextDocument('some:\n - arr:\n '); const pos = Position.create(2, 2); From 0efc870817ead79c8deb2b7556645eb0d7452a6e Mon Sep 17 00:00:00 2001 From: Psy-Q Date: Tue, 1 Sep 2026 21:29:50 +0200 Subject: [PATCH 2/2] Replace entire line --- .../services/yamlOnTypeFormatting.ts | 32 ++++++++++--------- test/yamlOnTypeFormatting.test.ts | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/languageservice/services/yamlOnTypeFormatting.ts b/src/languageservice/services/yamlOnTypeFormatting.ts index d2edda9c8..cedb48b75 100644 --- a/src/languageservice/services/yamlOnTypeFormatting.ts +++ b/src/languageservice/services/yamlOnTypeFormatting.ts @@ -53,24 +53,26 @@ export function doDocumentOnTypeFormatting( if (currentLine.trim().length !== 0) { // non-space content, do nothing return; - } else if (currentLine.length === expectedText.length) { + } + if (currentLine === expectedText) { // already right; do nothing return; - } else if (currentLine.length < expectedText.length) { - // insert after whatever indentation is already on the line, so a leading - // dash never lands before existing spaces (e.g. client-side auto-indent) - const insertPosition = Position.create(position.line, currentLine.length); - return [TextEdit.insert(insertPosition, expectedText.slice(currentLine.length))]; - } else { - return [ - TextEdit.del( - Range.create( - Position.create(position.line, 0), - Position.create(position.line, currentLine.length - expectedText.length) - ) - ), - ]; } + if (position.character >= indentation.length) { + // The client already auto-indented the line and placed the cursor at + // (or past) the indentation; just append the dash. + return [TextEdit.insert(Position.create(position.line, currentLine.length), '- ')]; + } + // The client sent a position before the existing whitespace + // (e.g. lsp-mode, eglot send column 0 even though the line already has + // auto-indented spaces). Replace the whole line content so the + // result is always correct. + return [ + TextEdit.replace( + Range.create(Position.create(position.line, 0), Position.create(position.line, currentLine.length)), + expectedText + ), + ]; } if (previousLine.includes(' - ') && previousLine.includes(': ')) { diff --git a/test/yamlOnTypeFormatting.test.ts b/test/yamlOnTypeFormatting.test.ts index 2cf335c4d..3c56365a9 100644 --- a/test/yamlOnTypeFormatting.test.ts +++ b/test/yamlOnTypeFormatting.test.ts @@ -53,7 +53,7 @@ describe('YAML On Type Formatter', () => { const pos = Position.create(3, 0); const params = createParams(pos); const result = doDocumentOnTypeFormatting(doc, params); - expect(result[0]).to.eql(TextEdit.insert(Position.create(3, 2), '- ')); + expect(result[0]).to.eql(TextEdit.replace(Range.create(Position.create(3, 0), Position.create(3, 2)), ' - ')); }); it('should add indentation for mapping in array', () => {