================
@@ -548,59 +601,193 @@ class DefineOutline : public Tweak {
return std::move(*Effect);
}
- // Returns the most natural insertion point for \p QualifiedName in \p
- // Contents. This currently cares about only the namespace proximity, but in
- // feature it should also try to follow ordering of declarations. For
example,
- // if decls come in order `foo, bar, baz` then this function should return
- // some point between foo and baz for inserting bar.
- // FIXME: The selection can be made smarter by looking at the definition
- // locations for adjacent decls to Source. Unfortunately pseudo parsing in
- // getEligibleRegions only knows about namespace begin/end events so we
- // can't match function start/end positions yet.
- llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
- const Selection &Sel) {
- // If the definition goes to the same file and there is a namespace,
- // we should (and, in the case of anonymous namespaces, need to)
- // put the definition into the original namespace block.
- if (SameFile) {
- auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext();
- if (!Klass)
- return error("moving to same file not supported for free functions");
- const SourceLocation EndLoc = Klass->getBraceRange().getEnd();
- const auto &TokBuf = Sel.AST->getTokens();
- auto Tokens = TokBuf.expandedTokens();
- auto It = llvm::lower_bound(
- Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
- return Tok.location() < EndLoc;
- });
- while (It != Tokens.end()) {
- if (It->kind() != tok::semi) {
- ++It;
- continue;
+ enum class RelativeInsertPos { Before, After };
+ std::optional<std::tuple<SymbolLocation, Path, RelativeInsertPos>>
+ getDefinitionOfAdjacentDecl(const Selection &Sel) {
+ if (!Sel.Index)
+ return {};
+ std::optional<std::pair<SymbolLocation, Path>> Anchor;
+ std::string TuURI = URI::createFile(Sel.AST->tuPath()).toString();
+ auto CheckCandidate = [&](Decl *Candidate) {
+ assert(Candidate != Source);
+ if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
+ !Func || Func->isThisDeclarationADefinition()) {
+ return;
+ }
+ std::optional<std::pair<SymbolLocation, Path>> CandidateLoc;
+ Sel.Index->lookup({{getSymbolID(Candidate)}}, [&](const Symbol &S) {
+ if (S.Definition) {
+ CandidateLoc = std::make_pair(S.Definition,
+ StringRef(S.Definition.FileURI).str());
}
- unsigned Offset = Sel.AST->getSourceManager()
- .getDecomposedLoc(It->endLocation())
- .second;
- return InsertionPoint{Klass->getEnclosingNamespaceContext(), Offset};
+ });
+ if (!CandidateLoc)
+ return;
+
+ // If our definition is constrained to the same file, ignore
+ // definitions that are not located there.
+ // If our definition is not constrained to the same file, but
+ // our anchor definition is in the same file, then we also put our
+ // definition there, because that appears to be the user preference.
+ // Exception: If the existing definition is a template, then the
+ // location is likely due to technical necessity rather than preference,
+ // so ignore that definition.
+ bool CandidateSameFile = TuURI == CandidateLoc->second;
+ if (SameFile && !CandidateSameFile)
+ return;
+ if (!SameFile && CandidateSameFile) {
+ if (Candidate->isTemplateDecl())
+ return;
+ SameFile = true;
}
- return error(
- "failed to determine insertion location: no end of class found");
+ Anchor = *CandidateLoc;
+ };
+
+ // Try to find adjacent function declarations.
+ // Determine the closest one by alternatingly going "up" and "down"
+ // from our function in increasing steps.
+ const DeclContext *ParentContext = Source->getParent();
+ const auto SourceIt = llvm::find_if(
+ ParentContext->decls(), [this](const Decl *D) { return D == Source; });
+ if (SourceIt == ParentContext->decls_end())
+ return {};
+ const int Preceding = std::distance(ParentContext->decls_begin(),
SourceIt);
+ const int Following =
+ std::distance(SourceIt, ParentContext->decls_end()) - 1;
+ for (int Offset = 1; Offset <= Preceding || Offset <= Following; ++Offset)
{
+ if (Offset <= Preceding)
+ CheckCandidate(
+ *std::next(ParentContext->decls_begin(), Preceding - Offset));
+ if (Anchor)
+ return std::make_tuple(Anchor->first, Anchor->second,
+ RelativeInsertPos::After);
+ if (Offset <= Following)
+ CheckCandidate(*std::next(SourceIt, Offset));
+ if (Anchor)
+ return std::make_tuple(Anchor->first, Anchor->second,
+ RelativeInsertPos::Before);
}
+ return {};
+ }
- auto Region = getEligiblePoints(
- Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
+ // We don't know the actual start or end of the definition, only the position
+ // of the name. Therefore, we heuristically try to locate the last token
+ // before or in this function, respectively. Adapt as required by user code.
+ llvm::Expected<Position> getInsertionPointFromExistingDefinition(
+ const llvm::MemoryBuffer &Buffer, const SymbolLocation &Loc,
+ RelativeInsertPos RelInsertPos, ParsedAST *AST) {
+ auto LspPos = indexToLSPLocation(Loc, AST->tuPath());
+ if (!LspPos)
+ return LspPos.takeError();
+ auto StartOffset =
+ positionToOffset(Buffer.getBuffer(), LspPos->range.start);
+ if (!StartOffset)
+ return LspPos.takeError();
+ SourceLocation InsertionLoc;
+ SourceManager &SM = AST->getSourceManager();
+ FileID F = Buffer.getBufferIdentifier() == AST->tuPath()
+ ? SM.getMainFileID()
+ : SM.createFileID(Buffer);
+
+ auto InsertBefore = [&] {
+ // Go backwards until we encounter one of the following:
+ // - An opening brace (of a namespace).
+ // - A closing brace (of a function definition).
+ // - A semicolon (of a declaration).
+ // If no such token was found, then the first token in the file starts
the
+ // definition.
+ auto Tokens = syntax::tokenize(syntax::FileRange(F, 0, *StartOffset), SM,
+ AST->getLangOpts());
+ if (Tokens.empty())
+ return;
+ for (auto I = std::rbegin(Tokens);
+ InsertionLoc.isInvalid() && I != std::rend(Tokens); ++I) {
+ switch (I->kind()) {
+ case tok::l_brace:
+ case tok::r_brace:
+ case tok::semi:
+ if (I != std::rbegin(Tokens))
+ InsertionLoc = std::prev(I)->location();
+ else
+ InsertionLoc = I->endLocation();
+ break;
+ default:
+ break;
+ }
+ }
+ if (InsertionLoc.isInvalid())
+ InsertionLoc = Tokens.front().location();
+ };
- assert(!Region.EligiblePoints.empty());
- auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
- if (!Offset)
- return Offset.takeError();
+ if (RelInsertPos == RelativeInsertPos::Before) {
+ InsertBefore();
+ } else {
+ // Skip over one top-level pair of parentheses (for the parameter list)
+ // and one pair of curly braces (for the code block).
+ // If that fails, insert before the function instead.
+ auto Tokens = syntax::tokenize(
+ syntax::FileRange(F, *StartOffset, Buffer.getBuffer().size()), SM,
+ AST->getLangOpts());
+ bool SkippedParams = false;
+ int OpenParens = 0;
+ int OpenBraces = 0;
+ std::optional<syntax::Token> Tok;
+ for (const auto &T : Tokens) {
+ tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
+ tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
+ int &Count = SkippedParams ? OpenBraces : OpenParens;
+ if (T.kind() == StartKind) {
+ ++Count;
+ } else if (T.kind() == EndKind) {
+ if (--Count == 0) {
+ if (SkippedParams) {
+ Tok = T;
+ break;
+ }
+ SkippedParams = true;
+ } else if (Count < 0) {
+ break;
+ }
+ }
+ }
+ if (Tok)
+ InsertionLoc = Tok->endLocation();
+ else
+ InsertBefore();
+ }
- auto TargetContext =
- findContextForNS(Region.EnclosingNamespace, Source->getDeclContext());
- if (!TargetContext)
- return error("define outline: couldn't find a context for target");
+ return InsertionLoc.isValid() ? sourceLocToPosition(SM, InsertionLoc)
+ : Position();
----------------
ckandeler wrote:
Probably the latter was intended, but I'm not convinced anymore. Better use the
existing fallback then.
https://github.com/llvm/llvm-project/pull/128164
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits