textDocument/onTypeFormatting and live indentation.

Hi,

I would like to implement clang-format aware newline indentation in Clangd. It looks like Clangd only supports ‘}’ trigger character for the ‘onTypeFormatting’ request. Would it make sense to extend it to support the newline character, and to insert appropriate indentation instead of reformatting the range in that case?

Cheers,
Alex

The proposal looks promising, but why only reindent? Here are two useful type-as-you-go heuristics that I’ve seen (’^’ indicates cursor position before and after adding a newline):

  1. Reformatting braces.
    before:
    if (foo) {^}

after (note two newlines were added, so strictly speaking it’s not just a reindent):
if (foo) {
^
}

with a different brace formatting style:
if (foo)
{
^
}

  1. Auto-typing comment markers.

before:
/// Comment line 1.^
/// Comment line 2.
int foo();

after:
/// Comment line 1.
/// ^
/// Comment line 2.

With a different comment type. Before:
/** Comment line 1. ^

  • Comment line 2.
    */

after:

/** Comment line 1.

  • ^
  • Comment line 2.
    */

We’ve had some discussion in the past about the right way for clang-format to support live-formatting cases.

IIRC clang-format doesn’t have any current logic specific to this, instead having “format these ranges”. That mode works well for formatting only changed lines and similar cases, but it may or may not be the right model for format-as-you-type.

In particular I don’t think this model will ever give you useful autoindent, because if you were checking in the code instead of continuing to type, you’d want the leading spaces gone.
So we’d probably need to either:

  • add a new “as-you-type” mode or entrypoint in clang-format’s core, and carefully decide what new features it needs and how they should interact with clang-format’s existing model, or
  • hack something together outside of clang-format’s core (possibly reusing its parser)

My sense is that the first is likely to give better results, e.g. in the reformatting braces cases the cursor/indentation and braces may interact.
Either way it might make sense to provide the result as a clang-format API, rather than building the logic inside clangd directly.

Thanks for the detailed responses!
I definitely agree that Clangd should provide more than just indentation. Your examples perfectly illustrate what kind of live formatting should be done.

We’ve had some discussion in the past about the right way for clang-format to support live-formatting cases.

IIRC clang-format doesn’t have any current logic specific to this, instead having “format these ranges”. That mode works well for formatting only changed lines and similar cases, but it may or may not be the right model for format-as-you-type.

Right. After some experiments I started leaning towards it being not the right model. Ideally we’d have something better, just like you describe below.

In particular I don’t think this model will ever give you useful autoindent, because if you were checking in the code instead of continuing to type, you’d want the leading spaces gone.
So we’d probably need to either:

  • add a new “as-you-type” mode or entrypoint in clang-format’s core, and carefully decide what new features it needs and how they should interact with clang-format’s existing model, or
  • hack something together outside of clang-format’s core (possibly reusing its parser)

One other hand-wavy solution that I considered: insert a fake cursor token into the token stream which will be have the effect of reindenting the line correctly, e.g.

  1. Reformatting braces.
    before:
    if (foo) {^}

after;
if (foo) {
^ // clang-format will treat the cursor as a special token, and thus won’t format the ‘if’ as if its body was empty.
}

My sense is that the first is likely to give better results, e.g. in the reformatting braces cases the cursor/indentation and braces may interact.

Either way it might make sense to provide the result as a clang-format API, rather than building the logic inside clangd directly.

Agreed.

Better live formatting is definitely something that we need down the line, but I’m not planning to work on it as of yet. I’ll keep this on my list of potential things to do in Clangd for now, but I won’t mind if someone else picks it up. I’ll update the list if I’ll have more ideas/thoughts/proposals for this problem.

Cheers,
Alex