Page MenuHomePhabricator

[clangd] Add code completion of param name on /* inside function calls.
ClosedPublic

Authored by adamcz on Sep 30 2021, 6:20 AM.

Details

Summary

For example, if you have:

void foo(int bar);
foo(/*^

it should auto-complete to "bar=".

Because Sema callbacks for code completion in comments happen before we
have an AST we need to cheat in clangd by detecting completion on /*
before, moving cursor back by two characters, then running a simplified
verion of SignatureHelp to extract argument name(s) from possible
overloads.

Diff Detail

Event Timeline

adamcz created this revision.Sep 30 2021, 6:20 AM
adamcz requested review of this revision.Sep 30 2021, 6:20 AM
nridge added a subscriber: nridge.Oct 3 2021, 9:27 PM
kadircet added inline comments.Oct 5 2021, 2:11 AM
clang-tools-extra/clangd/CodeComplete.cpp
1148

change the ending comment (well, I'd actually drop it completely)

1925

i think it's subtle that we require a fullpatch here. maybe put a comment saying that "we want to see signatures coming from newly introduced includes, hence a full patch".

1972

what if the user triggers at foo(/* b^? I think we'd still want to provide bar.

I think we should detect whether we're in a comment first, we can run the Lexer for that but it'd probably be an overkill. For now I think we can just check for the string after last */, if it has a /* we're in a comment, otherwise we check for the current line only. if it has a // we're again in a comment.

Then we can switch to the codeCompleteComment mode, inside there one branch can collect completion results for possible parameter names (while filtering using the currently typed text) and we can expand with more logic later on (e.g. suggest identifiers from index?).

WDYT?

1979

i think we should propagate Opts here and then override the pieces accordingly.

adamcz updated this revision to Diff 377891.Oct 7 2021, 9:44 AM
adamcz marked an inline comment as done.

addressed review comments

clang-tools-extra/clangd/CodeComplete.cpp
1148

Oops, too much copy/paste.

We should have a clang-tidy check for that ;-)

1972

Good point about /* b^, thanks! I did not consider manually triggered code completion at all.

Detecting last */ the way you suggest seems fragile. There's macros that can expand to /* or */, there's strings, lines that are already comments (i.e. "// blah /* blah), "#if 0" sections, etc.
Running Lexer is an option, but I don't think it's necessary. Currently there's very few options when we can offer code completion:
/*^
/* ^
/*foo^
/* foo^
This can be quickly detected by scanning back through whitespace, then valid identifier characters, then whitespace again.

Since it's unclear if and when we'll be adding more code completion in comments I want to keep this simple rather than build for the future when we may indeed need a lexer.

WDYT?

(also added some tests to cover this).

1979

Hmm...why?

It seems that everything that could be relevant would be overridden here. SignatureHelp, which is very similar to this, doesn't use code complete options either. I think it may be confusing to accept an object but then override literally every single thing that matters anyway.

It's not unlikely that in the future we'll need options, but for now it seems useless. Do you have a specific option you want propagated?

thanks, mostly LG, a couple of nits :)

clang-tools-extra/clangd/CodeComplete.cpp
1120

nit: early exit

auto *Func = ...;
if(!Func)
  continue;
...
1124

nit: !PVD same for Ident

1145

nit: Why not directly have an llvm::DenseSet here? I am not sure if the order we're preserving currently is a good one anyway (depends a lot on the order of includes, i think?). If we really want to preserve some ordering, it would probably make sense to have the lexicographical one instead.

1963

let's name it more explicitly that this only checks if user is likely to be completing for function argument comment.

maybeFunctionArgumentComment ?

documentation just needs to reflect that fact now rather than explaining the being only type of comment completion supported and such.

1964

this might read easier as:

while (!Contents.empty() && isAciiIdentifierContinue(Contents.back())
  Contents = Contents.drop_back();
Contents.rtrim(); // by default this drops the chars you have in `Whitespace` already.
Offset = Contents.size();
return Contents.endswith("/*");

this has the different behaviour for /* qwer ^ but I don't think we want to complete after a word anyway (that's not what we do in the rest of the completion, space terminates the previous identifier).

As for propagating prefix, I think it's enough to check that the file content endswith whatever name we're going to suggest in the signature help collector.

nit: As for Offset, I might actually prefer returning an Optional<unsigned> while renaming the function to getFunctionArgumentCommentStart, but i am fine with this too, up to you.

1972

As discussed offline, I was mostly worried about extending this in the future, but we can think about that later. So this is good as-is.

1979

ah nvm. I confused clang::CodeCompleteOptions with clangds

adamcz updated this revision to Diff 379070.Oct 12 2021, 9:13 AM
adamcz marked 3 inline comments as done.

next round of review comments

clang-tools-extra/clangd/CodeComplete.cpp
1145

I decided to preserve the order to match what SignatureHelp is doing, but I now realize it re-orders them anyway, so good point, fixed.

Note that I used std::set, since that works with std::string and I don't think we can safely return llvm::StringRef here.

1964

First, returning Optional instead of bool + unsigned is better. Done.

Second, you're right that there's no need to return Prefix, it can be just as easily computed later. However, we still need it. I'm not sure what you're suggesting with checking if content ends with name. If it's literally Content.endswith(Name) that means you must have already typed the entire argument name.
I moved the CommentPrefix computation out of this function to make the signature nicer. It's just one line anyway. We can't easily move this to the collector, since it gets the original content, meaning we'd have to pass two offsets (original completion point and the new one) so we can extract something like "fun(/*foo^);" - we need to remove everything before /* and everything after ^. Does that explanation make sense?

Third, your version is better now. Note there is no difference in behavior on "/* qwer ^". There's even a test for that. I think I ended up with that complicated version because of the Prefix extraction, and, ironically, the "/* qwer ^" case - I just confused myself too much ;-)

Thanks

kadircet accepted this revision.Oct 19 2021, 1:42 AM

thanks, LGTM!

clang-tools-extra/clangd/CodeComplete.cpp
1145

right. you can also try llvm::StringMap which owns its keys.

1964

Thanks!

re endswith, yeah that is definitely wrong. I actually had checking a prefix of the suggestion is a suffix of the content, but even that doesn't do the trick (Comes down to what you explained after second point). We somehow need the last (possibly empty) identifier in the contents before cursor instead.

re behaviour, I missed the special if block sitting in the middle checking for trimmed.endswith("/*").

1972

Let's drop this one as you're already checking this in codeCompleteComment (+ it's going to be true almost always, so it's not like this is saving time in the general case)

This revision is now accepted and ready to land.Oct 19 2021, 1:42 AM
adamcz updated this revision to Diff 380636.Oct 19 2021, 4:00 AM
adamcz marked an inline comment as done.

final review comment (dropped Preamble check)

clang-tools-extra/clangd/CodeComplete.cpp
1979

Done. Amusingly, this changed the output of the CompletionRange test. Technically it fixed the issue that test was demonstrating, but only for that one very specific case, so I changed the test to continue demonstating the problem (difference between Sema and non-Sema behavior).

This revision was landed with ongoing or failed builds.Oct 19 2021, 4:04 AM
This revision was automatically updated to reflect the committed changes.