[llvm-branch-commits] [clang-tools-extra] release/23.x: [clangd] Fix header-name completion replace ranges (#225473) (PR #228722)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Oct 3 08:11:55 PDT 2026
https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/228722
Backport da43ad37dcc98427fe95039b75ca037d9995d3e8
Requested by: @argothiel
>From e5830eb866634d1e671cc887943f76a7dd63638e Mon Sep 17 00:00:00 2001
From: argothiel <13932353+argothiel at users.noreply.github.com>
Date: Sat, 3 Oct 2026 17:00:19 +0200
Subject: [PATCH] [clangd] Fix header-name completion replace ranges (#225473)
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
(cherry picked from commit da43ad37dcc98427fe95039b75ca037d9995d3e8)
---
clang-tools-extra/clangd/CodeComplete.cpp | 10 +++-
.../clangd/unittests/CodeCompleteTests.cpp | 56 +++++++++++++++++++
2 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 5f70dfe0dfd71aa..1ed97ed4fe4432c 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1833,9 +1833,13 @@ class CodeCompleteFlow {
}
if (Opts.EnableInsertReplace) {
- ReplaceRange.emplace();
- ReplaceRange->start = InsertRange.start;
- ReplaceRange->end = getEndOfCodeCompletionReplace(SM);
+ // Header-name completion always provides a replace-like range,
+ // 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());
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 5fad022cfe57b66..f0d423d1c9bba74 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -4327,6 +4327,62 @@ 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.
+ 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");
+ 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() {
More information about the llvm-branch-commits
mailing list