Skip to content

fix: preserve list-element indentation in onTypeFormatting - #1327

Open
psy-q wants to merge 2 commits into
redhat-developer:mainfrom
psy-q:fix/list-indent-ontype
Open

fix: preserve list-element indentation in onTypeFormatting#1327
psy-q wants to merge 2 commits into
redhat-developer:mainfrom
psy-q:fix/list-indent-ontype

Conversation

@psy-q

@psy-q psy-q commented Aug 29, 2026

Copy link
Copy Markdown

This PR fixes wrong indentation of list items when pressing enter in an existing list, having onTypeFormatting enabled.

The onTypeFormatting handler currently returns an unconditional "- " at params.position, dropping the sequence indentation and placing the dash at column 0 (invalid YAML).

It also ignores whitespace the client had already auto-inserted on the line, putting 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 or eglot sends the request with the line already carrying client-side indentation, so the edit must merge with it rather than overwrite it.

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.

What does this PR do?

Changes the behavior of onTypeFormatting to insert list items in the correct location and align the server with LSP spec.

When pressing Enter after a block-sequence item, the server's onTypeFormatting handler returns an edit that drops the sequence indentation, producing an invalid dash at column 0. Example (cursor at end of the last line, hit Enter):

What issues does this PR fix or reference?

Fixes wrong indentation:

test:
  - hello
  - world
-      <- wrong, server returns "- " at column 0 (invalid YAML)

Expected: the new item is aligned with the existing - (dash at the same column as the preceding item):

test:
  - hello
  - world
  -    <- correct

LSP spec

The handler is invoked because this server advertises the capability:

Server Capability -- documentOnTypeFormattingProvider (DocumentOnTypeFormattingOptions)

firstTriggerCharacter: string;   // "\n" in our case

-- LSP 3.17, Document On Type Formatting Request

Advertising the capability is an explicit invitation for clients to delegate newline formatting to the server, so it is expected that a spec-compliant client will call the server on Enter.

DocumentOnTypeFormattingParams.position is explicitly not guaranteed to be where the dash should land:

interface DocumentOnTypeFormattingParams {
    /**
     * The position around which the on type formatting should happen.
     * This is not necessarily the exact position where the character denoted
     * by the property `ch` got typed.
     */
    position: Position;
    ...
    /**
     * ... That is not necessarily the last character that got inserted into
     * the document since the client could auto insert characters as well
     * (e.g. like automatic brace completion).
     */
    ch: string;
}

-- LSP 3.17, DocumentOnTypeFormattingParams (emphasis added)

The fix therefore computes the target indentation + '- ' from the preceding item and inserts only the missing text after whatever whitespace already exists on the line, rather than assuming position = insertion point.

No issues in this project, but I reported the symptoms of this (perhaps erroneously) on Emacs' lsp-mode: emacs-lsp/lsp-mode#5112

Is it tested? How?

Tested via a live Emacs client, connecting to 1.24.0 and this patched server to observe the correct/wrong behavior.

The behavior occurs both with lsp-mode and with eglot.

Also automated tests are included.

LLM note

I am just a meat proxy/flesh goblin but I put Opencode's Big Pickle through its paces and tried to double and triple-verify that this is indeed the correct behavior according to spec. However, I don't know a thing about LSP server implementations so there could be a whale-sized problem here I'm not seeing.

What I know is that emacs lsp-mode behaves properly with this patched server and VS Code is unaffected because it never seems to make any use of this functionality.

DCO note

As far as I know, the legal standing of LLM code and its licensing has never been decided in any court of law, so it's also unclear if this contribution can adhere to the DCO.

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 <opencode-agent[bot]@users.noreply.github.com>
@psy-q
psy-q requested a review from datho7561 as a code owner August 29, 2026 10:42
@datho7561

Copy link
Copy Markdown
Contributor

I can't replicate this is in VS Code, and it seems that Helix doesn't support format on type. I couldn't seem to get lsp-mode working in Emacs to test this out (I'm trying out Spacemacs to do this).

@psy-q

psy-q commented Aug 31, 2026

Copy link
Copy Markdown
Author

It might be easier to reproduce with eglot, just use a minimal .emacs init file:

;;; -*- lexical-binding: t; -*-
(package-install 'yaml-mode)
;; (setq eglot-ignored-server-capabilities '(:documentOnTypeFormattingProvider))

Then start with e.g. emacs -q -l .emacs.minimal --init-directory /tmp/emacs

It should install the yaml-mode package on first start. When you open a YAML file, yaml-mode should activate automatically. You'll have to manually start eglot though: M-x eglot RET, so alt-x, "eglot", enter. Or option-x on Macs. I'm assuming that you were trying Spacemacs because you don't usually work with Emacs; thus the extra stuff here. But let me know if I can provide more repro steps.

The option (setq eglot-ignored-server-capabilities '(:documentOnTypeFormattingProvider)) will disable onTypeFormatting and if you restart eglot after evaluating it (or quit and restart Emacs with that option in the config file), you'll see that the indentation works as expected.

PS: I couldn't reproduce it in VS Code either, but I logged the server interactions that VS Code makes and it never even used onTypeFormatting. So I presume they switch that feature off there for YAML just like I did by hand in Emacs? The extension I tested it with is redhat.vscode-yaml and the logs are attached to the lsp-mode issue.

PPS: Spacemacs will probably have added dozens of packages that might already turn this feature off. The blank configuration above uses an unconfigured Emacs with just eglot on board. Eglot should react appropriately and set onTypeFormatting after yaml-language-server advertises support for it. This should be the quickest way to validate the problem.

PPPS: I can also reproduce it in Neovim. It's wrong in a different way there, yaml-language-server will insert an extra - but after the cursor. To reproduce in Neovim:

vim.lsp.enable('yamlls')
vim.lsp.on_type_formatting.enable()

Here's a short demo of the problem in Emacs with an otherwise blank configuration:

Screencast_20260831_192914.webm

@datho7561

Copy link
Copy Markdown
Contributor

Then start with e.g. emacs -q -l .emacs.minimal --init-directory /tmp/emacs

Thanks! I was able to replicate the failure that way. I'll test this out now.

@datho7561

datho7561 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This doesn't seem to fix it? This is what I have after this change:

2026-09-01.11-28-28.mp4

(this is the same behaviour as before this change)

@psy-q

psy-q commented Sep 1, 2026

Copy link
Copy Markdown
Author

Oh, yours look like it behaves in a different way now, instead of inserting - at column 0 it keeps it at the correct column but the cursor is in the wrong place. I thought I had it working in my last test here (even in Neovim), I'll have to redo the tests as well.

I will see if I can find the cause and if not I'll proxy this back to the LLM (sorry).

@psy-q
psy-q force-pushed the fix/list-indent-ontype branch from ebcc7ed to 0efc870 Compare September 1, 2026 19:30
@psy-q

psy-q commented Sep 1, 2026

Copy link
Copy Markdown
Author

I made another attempt that should fix that behavior by replacing the whole line. This now works for me reliably using eglot and lsp-mode.

However, I also observe that other editors (e.g. Kate, Neovim) simply switch off onTypeFormatting completely. The same thing you saw in VS Code. Maybe the behavior of this feature is so unpredictable that editors just don't use it?

I'm willing to believe the LLM that the feature is supposed to be used when it's advertised, and that it works as it should now, but I don't know enough about this to judge for sure. So if this is too flaky to merge, I can close it and just use my patched server or disable onTypeFormatting like the other editors.

@datho7561

Copy link
Copy Markdown
Contributor

It's still not working for me. I double checked that emacs+eglot was actually picking up my local copy of yaml-language-server by changing the inserted content to - meow, and it inserted that content when I tried it again. Are you sure you got it working locally?

I'm willing to believe that the onTypeFormatting is hitting an edge case that's underspecified in the LSP spec: when text is inserted at the current location of the cursor, where should the cursor end up?. It seems like eglot doesn't move the cursor to the right of the inserted content, whereas VS Code does. In fact, here is the upstream discussion in eglot: joaotavora/eglot#960 .

@psy-q

psy-q commented Sep 2, 2026

Copy link
Copy Markdown
Author

Thanks for testing again, I can't explain why it doesn't work, I've now cloned the whole thing on a second machine and even a different OS (macOS in this case) and it seems to work for me:

Screen.Recording.2026-09-02.at.07.38.37.mov

But with the whole cursor placement differences (that other editors also suffer from) I think there might be too much inexplicable magic involved here to chase this any further. I'd be happy to drop this and just switch off on-type formatting, and sorry for taking up your time.

Unless someone with more knowledge volunteers to push it further, of course :) I'm leaving my fork open in case it's useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants