diff --git a/clang-tools-extra/clangd/Diagnostics.cpp b/clang-tools-extra/clangd/Diagnostics.cpp --- a/clang-tools-extra/clangd/Diagnostics.cpp +++ b/clang-tools-extra/clangd/Diagnostics.cpp @@ -691,6 +691,13 @@ llvm::StringRef Remove = Lexer::getSourceText(FixIt.RemoveRange, SM, *LangOpts, &Invalid); llvm::StringRef Insert = FixIt.CodeToInsert; + if (FixIt.InsertFromRange.isValid()) { + assert(FixIt.CodeToInsert.empty() && + "Cannot insert text when range is specified"); + if (!Invalid) + Insert = Lexer::getSourceText(FixIt.InsertFromRange, SM, *LangOpts, + &Invalid); + } if (!Invalid) { llvm::raw_svector_ostream M(Message); if (!Remove.empty() && !Insert.empty()) { diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -548,7 +548,15 @@ TextEdit Result; Result.range = halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L)); - Result.newText = FixIt.CodeToInsert; + + if (FixIt.InsertFromRange.isValid()) { + assert(FixIt.CodeToInsert.empty() && + "Cannot insert text when range is specified"); + // FIXME: Propagate errors from getSourceText + Result.newText = Lexer::getSourceText(FixIt.InsertFromRange, M, L).str(); + } else { + Result.newText = FixIt.CodeToInsert; + } return Result; } diff --git a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp --- a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp +++ b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp @@ -802,6 +802,61 @@ EXPECT_FALSE(isKeyword("override", LangOpts)); } +Expected sourceRangeInMainFile(SourceManager &SM, Range R) { + if (auto Beg = sourceLocationInMainFile(SM, R.start)) { + if (auto End = sourceLocationInMainFile(SM, R.end)) { + return SourceRange(*Beg, *End); + } else { + return End.takeError(); + } + } else { + return Beg.takeError(); + } +} + +TEST(SourceCodeTests, FixItToTextEdit) { + Annotations Code(R"( + int Var = $V1[[$V1^Value1]] + $V2[[$V2^Value2]]; + )"); + SourceManagerForFile SMFF("File", Code.code()); + SourceManager &SM = SMFF.get(); + LangOptions LangOpts; + Expected V1Char = sourceRangeInMainFile(SM, Code.range("V1")); + Expected V2Char = sourceRangeInMainFile(SM, Code.range("V2")); + auto V1Tok = sourceLocationInMainFile(SM, Code.point("V1")); + auto V2Tok = sourceLocationInMainFile(SM, Code.point("V2")); + ASSERT_TRUE(V1Char && V1Char->isValid()); + ASSERT_TRUE(V2Char && V2Char->isValid()); + ASSERT_TRUE(V1Tok && V1Tok->isValid()); + ASSERT_TRUE(V2Tok && V2Tok->isValid()); + auto RemoveCharRange = CharSourceRange::getCharRange(*V1Char); + auto RemoveTokRange = CharSourceRange::getTokenRange(*V1Tok, *V1Tok); + { + FixItHint Hint; + Hint.RemoveRange = RemoveCharRange; + Hint.InsertFromRange = CharSourceRange::getCharRange(*V2Char); + TextEdit E = toTextEdit(Hint, SM, LangOpts); + EXPECT_EQ(E.range, Code.range("V1")); + EXPECT_EQ(E.newText, "Value2"); + } + { + FixItHint Hint; + Hint.RemoveRange = RemoveCharRange; + Hint.InsertFromRange = CharSourceRange::getTokenRange(*V2Tok); + TextEdit E = toTextEdit(Hint, SM, LangOpts); + EXPECT_EQ(E.range, Code.range("V1")); + EXPECT_EQ(E.newText, "Value2"); + } + { + FixItHint Hint; + Hint.RemoveRange = RemoveTokRange; + Hint.CodeToInsert = "Value2"; + TextEdit E = toTextEdit(Hint, SM, LangOpts); + EXPECT_EQ(E.range, Code.range("V1")); + EXPECT_EQ(E.newText, "Value2"); + } +} + } // namespace } // namespace clangd } // namespace clang