"Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB)));
}
+static llvm::Expected<Tweak::Selection>
+tweakSelection(const Range &Sel, const InputsAndAST &AST) {
+ auto Begin = positionToOffset(AST.Inputs.Contents, Sel.start);
+ if (!Begin)
+ return Begin.takeError();
+ auto End = positionToOffset(AST.Inputs.Contents, Sel.end);
+ if (!End)
+ return End.takeError();
+ return Tweak::Selection(AST.AST, *Begin, *End);
+}
+
void ClangdServer::enumerateTweaks(PathRef File, Range Sel,
Callback<std::vector<TweakRef>> CB) {
auto Action = [Sel](decltype(CB) CB, std::string File,
Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
-
- auto &AST = InpAST->AST;
- auto CursorLoc = sourceLocationInMainFile(
- AST.getASTContext().getSourceManager(), Sel.start);
- if (!CursorLoc)
- return CB(CursorLoc.takeError());
- Tweak::Selection Inputs = {InpAST->Inputs.Contents, InpAST->AST,
- *CursorLoc};
-
+ auto Selection = tweakSelection(Sel, *InpAST);
+ if (!Selection)
+ return CB(Selection.takeError());
std::vector<TweakRef> Res;
- for (auto &T : prepareTweaks(Inputs))
+ for (auto &T : prepareTweaks(*Selection))
Res.push_back({T->id(), T->title()});
CB(std::move(Res));
};
Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
-
- auto &AST = InpAST->AST;
- auto CursorLoc = sourceLocationInMainFile(
- AST.getASTContext().getSourceManager(), Sel.start);
- if (!CursorLoc)
- return CB(CursorLoc.takeError());
- Tweak::Selection Inputs = {InpAST->Inputs.Contents, InpAST->AST,
- *CursorLoc};
-
- auto A = prepareTweak(TweakID, Inputs);
+ auto Selection = tweakSelection(Sel, *InpAST);
+ if (!Selection)
+ return CB(Selection.takeError());
+ auto A = prepareTweak(TweakID, *Selection);
if (!A)
return CB(A.takeError());
// FIXME: run formatter on top of resulting replacements.
- return CB((*A)->apply(Inputs));
+ return CB((*A)->apply(*Selection));
};
WorkScheduler.runWithAST(
"ApplyTweak", File,
// An optimization for a common case: nodes outside macro expansions that
// don't intersect the selection may be recursively skipped.
bool canSafelySkipNode(SourceRange S) {
-<<<<<<< HEAD
auto B = SM.getDecomposedLoc(S.getBegin());
auto E = SM.getDecomposedLoc(S.getEnd());
if (B.first != SelFile || E.first != SelFile)
-=======
- auto B = SM.getDecomposedLoc(S.getBegin()),
- E = SM.getDecomposedLoc(S.getEnd());
- if (B.first != SM.getMainFileID() || E.first != SM.getMainFileID())
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
return false;
return B.second >= SelEnd || E.second < SelBeginTokenStart;
}
// LOOP_FOREVER( ++x; )
// }
// Selecting "++x" or "x" will do the right thing.
-<<<<<<< HEAD
auto B = SM.getDecomposedLoc(SM.getTopMacroCallerLoc(S.getBegin()));
auto E = SM.getDecomposedLoc(SM.getTopMacroCallerLoc(S.getEnd()));
// Otherwise, nodes in macro expansions can't be selected.
// Cheap test: is there any overlap at all between the selection and range?
// Note that E.second is the *start* of the last token, which is why we
// compare against the "rounded-down" SelBegin.
-=======
- auto B = SM.getDecomposedLoc(SM.getTopMacroCallerLoc(S.getBegin())),
- E = SM.getDecomposedLoc(SM.getTopMacroCallerLoc(S.getEnd()));
- // Otherwise, nodes in macro expansions can't be selected.
- if (B.first != SM.getMainFileID() || E.first != SM.getMainFileID())
- return SelectionTree::Unselected;
- // Cheap test: is there any overlap at all between the selection and range?
- // Note that E.second is the *start* of the last token, which is why we
- // compare against the "rounded-down" MinOffset.
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
if (B.second >= SelEnd || E.second < SelBeginTokenStart)
return SelectionTree::Unselected;
CharSourceRange R = SM.getExpansionRange(N->ASTNode.getSourceRange());
auto B = SM.getDecomposedLoc(R.getBegin());
auto E = SM.getDecomposedLoc(R.getEnd());
-<<<<<<< HEAD
if (B.first != SelFile || E.first != SelFile)
-=======
- if (B.first != SM.getMainFileID() || E.first != SM.getMainFileID())
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
continue;
assert(R.isTokenRange());
// Try to cover up to the next token, spaces between children don't count.
SourceManager &SM;
const LangOptions &LangOpts;
std::stack<Node *> Stack;
-<<<<<<< HEAD
std::deque<Node> Nodes; // Stable pointers as we add more nodes.
// Half-open selection range.
unsigned SelBegin;
// range.end + measureToken(range.end) < SelBegin (assuming range.end points
// to a token), and it saves a lex every time.
unsigned SelBeginTokenStart;
-=======
- std::deque<Node> Nodes;
- unsigned SelBegin, SelEnd, SelBeginTokenStart;
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
};
} // namespace
}
// Decide which selection emulates a "point" query in between characters.
-<<<<<<< HEAD
static std::pair<unsigned, unsigned> pointBounds(unsigned Offset, FileID FID,
ASTContext &AST) {
StringRef Buf = AST.getSourceManager().getBufferData(FID);
-=======
-static std::pair<unsigned, unsigned> pointBounds(unsigned Offset,
- ASTContext &AST) {
- StringRef Buf = AST.getSourceManager().getBufferData(
- AST.getSourceManager().getMainFileID());
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
// Edge-cases where the choice is forced.
if (Buf.size() == 0)
return {0, 0};
SelectionTree::SelectionTree(ASTContext &AST, unsigned Begin, unsigned End)
: PrintPolicy(AST.getLangOpts()) {
-<<<<<<< HEAD
// No fundamental reason the selection needs to be in the main file,
// but that's all clangd has needed so far.
FileID FID = AST.getSourceManager().getMainFileID();
SelectionTree::SelectionTree(ASTContext &AST, unsigned Offset)
: SelectionTree(AST, Offset, Offset) {}
-=======
- if (Begin == End)
- std::tie(Begin, End) = pointBounds(Begin, AST);
- PrintPolicy.TerseOutput = true;
-
- Nodes = SelectionVisitor(AST, Begin, End).take();
- Root = Nodes.empty() ? nullptr : &Nodes.front();
-}
-
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
const Node *SelectionTree::commonAncestor() const {
if (!Root)
return nullptr;
}
}
-<<<<<<< HEAD
-=======
-SelectionTree::SelectionTree(ASTContext &AST, unsigned Offset)
- : SelectionTree(AST, Offset, Offset) {}
-
->>>>>>> [clangd] Lib to compute and represent selection under cursor.
} // namespace clangd
} // namespace clang
}
} // namespace
+Tweak::Selection::Selection(ParsedAST &AST, unsigned RangeBegin,
+ unsigned RangeEnd)
+ : AST(AST), ASTSelection(AST.getASTContext(), RangeBegin, RangeEnd) {
+ auto &SM = AST.getASTContext().getSourceManager();
+ Code = SM.getBufferData(SM.getMainFileID());
+ Cursor = SM.getComposedLoc(SM.getMainFileID(), RangeBegin);
+}
+
std::vector<std::unique_ptr<Tweak>> prepareTweaks(const Tweak::Selection &S) {
validateRegistry();
#include "ClangdUnit.h"
#include "Protocol.h"
+#include "Selection.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
public:
/// Input to prepare and apply tweaks.
struct Selection {
+ Selection(ParsedAST &AST, unsigned RangeBegin, unsigned RangeEnd);
/// The text of the active document.
llvm::StringRef Code;
/// Parsed AST of the active file.
ParsedAST &AST;
/// A location of the cursor in the editor.
SourceLocation Cursor;
- // FIXME: add selection when there are checks relying on it.
+ // The AST nodes that were selected.
+ SelectionTree ASTSelection;
// FIXME: provide a way to get sources and ASTs for other files.
- // FIXME: cache some commonly required information (e.g. AST nodes under
- // cursor) to avoid redundant AST visit in every action.
};
virtual ~Tweak() = default;
/// A unique id of the action, it is always equal to the name of the class
std::string title() const override;
private:
- IfStmt *If = nullptr;
+ const IfStmt *If = nullptr;
};
REGISTER_TWEAK(SwapIfBranches);
-class FindIfUnderCursor : public RecursiveASTVisitor<FindIfUnderCursor> {
-public:
- FindIfUnderCursor(ASTContext &Ctx, SourceLocation CursorLoc, IfStmt *&Result)
- : Ctx(Ctx), CursorLoc(CursorLoc), Result(Result) {}
-
- bool VisitIfStmt(IfStmt *If) {
- // Check if the cursor is in the range of 'if (cond)'.
- // FIXME: this does not contain the closing paren, add it too.
- auto R = toHalfOpenFileRange(
- Ctx.getSourceManager(), Ctx.getLangOpts(),
- SourceRange(If->getIfLoc(), If->getCond()->getEndLoc().isValid()
- ? If->getCond()->getEndLoc()
- : If->getIfLoc()));
- if (R && halfOpenRangeTouches(Ctx.getSourceManager(), *R, CursorLoc)) {
- Result = If;
- return false;
- }
- // Check the range of 'else'.
- R = toHalfOpenFileRange(Ctx.getSourceManager(), Ctx.getLangOpts(),
- SourceRange(If->getElseLoc()));
- if (R && halfOpenRangeTouches(Ctx.getSourceManager(), *R, CursorLoc)) {
- Result = If;
+bool SwapIfBranches::prepare(const Selection &Inputs) {
+ for (const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
+ N && !If; N = N->Parent) {
+ // Stop once we hit a block, e.g. a lambda in the if condition.
+ if (dyn_cast_or_null<CompoundStmt>(N->ASTNode.get<Stmt>()))
return false;
- }
-
- return true;
+ If = dyn_cast_or_null<IfStmt>(N->ASTNode.get<Stmt>());
}
-
-private:
- ASTContext &Ctx;
- SourceLocation CursorLoc;
- IfStmt *&Result;
-};
-} // namespace
-
-bool SwapIfBranches::prepare(const Selection &Inputs) {
- auto &Ctx = Inputs.AST.getASTContext();
- FindIfUnderCursor(Ctx, Inputs.Cursor, If).TraverseAST(Ctx);
- if (!If)
- return false;
-
// avoid dealing with single-statement brances, they require careful handling
// to avoid changing semantics of the code (i.e. dangling else).
- if (!If->getThen() || !llvm::isa<CompoundStmt>(If->getThen()) ||
- !If->getElse() || !llvm::isa<CompoundStmt>(If->getElse()))
- return false;
- return true;
+ return If && dyn_cast_or_null<CompoundStmt>(If->getThen()) &&
+ dyn_cast_or_null<CompoundStmt>(If->getElse());
}
Expected<tooling::Replacements> SwapIfBranches::apply(const Selection &Inputs) {
}
std::string SwapIfBranches::title() const { return "Swap if branches"; }
+
+} // namespace
} // namespace clangd
} // namespace clang
ParsedAST AST = TU.build();
auto CheckOver = [&](Range Selection) {
- auto CursorLoc = llvm::cantFail(sourceLocationInMainFile(
- AST.getASTContext().getSourceManager(), Selection.start));
- auto T = prepareTweak(ID, Tweak::Selection{Code.code(), AST, CursorLoc});
+ unsigned Begin = cantFail(positionToOffset(Code.code(), Selection.start));
+ unsigned End = cantFail(positionToOffset(Code.code(), Selection.end));
+ auto T = prepareTweak(ID, Tweak::Selection(AST, Begin, End));
if (Available)
EXPECT_THAT_EXPECTED(T, Succeeded())
<< "code is " << markRange(Code.code(), Selection);
TU.Code = Code.code();
ParsedAST AST = TU.build();
- auto CursorLoc = llvm::cantFail(sourceLocationInMainFile(
- AST.getASTContext().getSourceManager(), SelectionRng.start));
- Tweak::Selection S = {Code.code(), AST, CursorLoc};
+ unsigned Begin = cantFail(positionToOffset(Code.code(), SelectionRng.start));
+ unsigned End = cantFail(positionToOffset(Code.code(), SelectionRng.end));
+ Tweak::Selection S(AST, Begin, End);
auto T = prepareTweak(ID, S);
if (!T)
}
)cpp";
checkTransform(ID, Input, Output);
+
+ // Available in subexpressions of the condition.
+ checkAvailable(ID, R"cpp(
+ void test() {
+ if(2 + [[2]] + 2) { return 2 + 2 + 2; } else { continue; }
+ }
+ )cpp");
+ // But not as part of the branches.
+ checkNotAvailable(ID, R"cpp(
+ void test() {
+ if(2 + 2 + 2) { return 2 + [[2]] + 2; } else { continue; }
+ }
+ )cpp");
+ // Range covers the "else" token, so available.
+ checkAvailable(ID, R"cpp(
+ void test() {
+ if(2 + 2 + 2) { return 2 + [[2 + 2; } else { continue;]] }
+ }
+ )cpp");
+ // Not available in compound statements in condition.
+ checkNotAvailable(ID, R"cpp(
+ void test() {
+ if([]{return [[true]];}()) { return 2 + 2 + 2; } else { continue; }
+ }
+ )cpp");
+ // Not available if both sides aren't braced.
+ checkNotAvailable(ID, R"cpp(
+ void test() {
+ ^if (1) return; else { return; }
+ }
+ )cpp");
+ // Only one if statement is supported!
+ checkNotAvailable(ID, R"cpp(
+ [[if(1){}else{}if(2){}else{}]]
+ )cpp");
}
} // namespace