https://github.com/argothiel updated https://github.com/llvm/llvm-project/pull/225473
>From cc285c2f477525cc3a7d330950e3706f35bcf1df Mon Sep 17 00:00:00 2001 From: argothiel <[email protected]> Date: Tue, 22 Sep 2026 20:13:43 +0200 Subject: [PATCH 1/3] [clangd] Fix header-name completion replace ranges Handle header-name completion as a special case for replace edits. Its insert range already extends past the identifier, including delimiters such as '"' and '>'. Reuse that range for replace edits so that the insert range remains a prefix of the replace range, as required by the LSP specification. Add a unit test covering this case. Fixes llvm/llvm-project#225326 --- clang-tools-extra/clangd/CodeComplete.cpp | 9 ++-- .../clangd/unittests/CodeCompleteTests.cpp | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 80091d3a48b33f..ed5935955eafc7 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -1834,9 +1834,12 @@ class CodeCompleteFlow { } if (Opts.EnableInsertReplace) { - ReplaceRange.emplace(); - ReplaceRange->start = InsertRange.start; - ReplaceRange->end = getEndOfCodeCompletionReplace(SM); + ReplaceRange = InsertRange; + + // Header-name completion always provides a replace-like range, + // including delimiters. + if (CCContextKind != CodeCompletionContext::CCC_IncludedFile) + ReplaceRange->end = getEndOfCodeCompletionReplace(SM); } Filter = FuzzyMatcher( Recorder->CCSema->getPreprocessor().getCodeCompletionFilter()); diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 4c1cab7b11e606..6520bdf456bfb9 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -4355,6 +4355,60 @@ TEST(CompletionTest, ReplaceRangeNoCompile) { EXPECT_EQ(Results.ReplaceRange, std::nullopt); } +TEST(CompletionTest, ReplaceRangeInclude) { + clangd::CodeCompleteOptions Opts; + Opts.EnableInsertReplace = true; + + TestTU TU; + TU.AdditionalFiles["include/sub/foo.h"] = ""; + TU.AdditionalFiles["include/sub/u00e9/bar.h"] = ""; + TU.ExtraArgs = {"-Iinclude"}; + + // Replace range starts after a preceding path component and includes the + // closing angle bracket. + const char *NestedPath = R"cpp(#include <sub/[[fo^o.h>]])cpp"; + Annotations A(NestedPath); + TU.Code = A.code(); + CodeCompleteResult Results = + completions(TU, A.point(), /*IndexSymbols=*/{}, Opts); + EXPECT_EQ(Results.InsertRange, A.range()); + EXPECT_EQ(Results.ReplaceRange, A.range()); + + // Replace range starts after a preceding path component and includes the + // closing quote. + const char *WithQuote = R"cpp(#include "sub/[[fo^o.h"]])cpp"; + A = Annotations(WithQuote); + TU.Code = A.code(); + Results = completions(TU, A.point(), /*IndexSymbols=*/{}, Opts); + EXPECT_EQ(Results.InsertRange, A.range()); + EXPECT_EQ(Results.ReplaceRange, A.range()); + + // Replace range includes the path component and its trailing separator. + const char *PathComponent = R"cpp(#include "[[su^b/]]foo.h")cpp"; + A = Annotations(PathComponent); + TU.Code = A.code(); + Results = completions(TU, A.point(), /*IndexSymbols=*/{}, Opts); + EXPECT_EQ(Results.InsertRange, A.range()); + EXPECT_EQ(Results.ReplaceRange, A.range()); + + // A UCN-like escape is not interpreted in a header name. + const char *UnicodeLike = R"cpp(#include "[[su^\u00e9/]]foo.h")cpp"; + A = Annotations(UnicodeLike); + TU.Code = A.code(); + Results = completions(TU, A.point(), /*IndexSymbols=*/{}, Opts); + EXPECT_EQ(Results.InsertRange, A.range()); + EXPECT_EQ(Results.ReplaceRange, A.range()); + + // In MSVC compatibility mode, backslash is a path separator. + TU.ExtraArgs.push_back("-fms-compatibility"); + const char *UnicodeLikeMs = R"cpp(#include "[[su^\]]u00e9/bar.h")cpp"; + A = Annotations(UnicodeLikeMs); + TU.Code = A.code(); + Results = completions(TU, A.point(), /*IndexSymbols=*/{}, Opts); + EXPECT_EQ(Results.InsertRange, A.range()); + EXPECT_EQ(Results.ReplaceRange, A.range()); +} + TEST(NoCompileCompletionTest, Basic) { auto Results = completionsNoCompile(R"cpp( void func() { >From af346a78219a405b60c4279cc2be16e0c0af0f84 Mon Sep 17 00:00:00 2001 From: argothiel <[email protected]> Date: Tue, 22 Sep 2026 21:16:44 +0200 Subject: [PATCH 2/3] fixup! [clangd] Fix header-name completion replace ranges --- clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 6520bdf456bfb9..edfd29136881fd 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -4392,12 +4392,14 @@ TEST(CompletionTest, ReplaceRangeInclude) { EXPECT_EQ(Results.ReplaceRange, A.range()); // A UCN-like escape is not interpreted in a header name. + TU.ExtraArgs.push_back("-fno-ms-compatibility"); const char *UnicodeLike = R"cpp(#include "[[su^\u00e9/]]foo.h")cpp"; A = Annotations(UnicodeLike); TU.Code = A.code(); Results = completions(TU, A.point(), /*IndexSymbols=*/{}, Opts); EXPECT_EQ(Results.InsertRange, A.range()); EXPECT_EQ(Results.ReplaceRange, A.range()); + TU.ExtraArgs.pop_back(); // In MSVC compatibility mode, backslash is a path separator. TU.ExtraArgs.push_back("-fms-compatibility"); >From a03b3f63a0e34b0a229415e3e07e1cb22666eb1a Mon Sep 17 00:00:00 2001 From: argothiel <[email protected]> Date: Sat, 3 Oct 2026 16:21:53 +0200 Subject: [PATCH 3/3] fixup! [clangd] Fix header-name completion replace ranges --- clang-tools-extra/clangd/CodeComplete.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index ed5935955eafc7..2c7a317d045a94 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -1834,12 +1834,13 @@ class CodeCompleteFlow { } if (Opts.EnableInsertReplace) { - ReplaceRange = InsertRange; - // Header-name completion always provides a replace-like range, - // including delimiters. - if (CCContextKind != CodeCompletionContext::CCC_IncludedFile) - ReplaceRange->end = getEndOfCodeCompletionReplace(SM); + // including delimiters, even in the Insert mode. + const Position ReplaceRangeEnd = + CCContextKind == CodeCompletionContext::CCC_IncludedFile + ? InsertRange.end + : getEndOfCodeCompletionReplace(SM); + ReplaceRange = Range{InsertRange.start, ReplaceRangeEnd}; } Filter = FuzzyMatcher( Recorder->CCSema->getPreprocessor().getCodeCompletionFilter()); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
