77// ===----------------------------------------------------------------------===//
88
99#include " AST.h"
10+ #include " FindSymbols.h"
1011#include " FindTarget.h"
1112#include " HeaderSourceSwitch.h"
1213#include " ParsedAST.h"
@@ -489,8 +490,16 @@ class DefineOutline : public Tweak {
489490
490491 Expected<Effect> apply (const Selection &Sel) override {
491492 const SourceManager &SM = Sel.AST ->getSourceManager ();
492- auto CCFile = SameFile ? Sel.AST ->tuPath ().str ()
493- : getSourceFile (Sel.AST ->tuPath (), Sel);
493+ std::optional<Path> CCFile;
494+ std::optional<std::pair<Symbol, RelativeInsertPos>> Anchor =
495+ getDefinitionOfAdjacentDecl (Sel);
496+ if (Anchor) {
497+ CCFile = Anchor->first .Definition .FileURI ;
498+ CCFile->erase (0 , 7 ); // "file://"
499+ } else {
500+ CCFile = SameFile ? Sel.AST ->tuPath ().str ()
501+ : getSourceFile (Sel.AST ->tuPath (), Sel);
502+ }
494503 if (!CCFile)
495504 return error (" Couldn't find a suitable implementation file." );
496505 assert (Sel.FS && " FS Must be set in apply" );
@@ -499,21 +508,62 @@ class DefineOutline : public Tweak {
499508 // doesn't exist?
500509 if (!Buffer)
501510 return llvm::errorCodeToError (Buffer.getError ());
511+
512+ std::optional<Position> InsertionPos;
513+ if (Anchor) {
514+ if (auto P = getInsertionPointFromExistingDefinition (
515+ **Buffer, Anchor->first , Anchor->second , Sel.AST )) {
516+ InsertionPos = *P;
517+ }
518+ }
519+
502520 auto Contents = Buffer->get ()->getBuffer ();
503- auto InsertionPoint = getInsertionPoint (Contents, Sel);
504- if (!InsertionPoint)
505- return InsertionPoint.takeError ();
521+
522+ std::size_t Offset = -1 ;
523+ const DeclContext *EnclosingNamespace = nullptr ;
524+ std::string EnclosingNamespaceName;
525+
526+ if (InsertionPos) {
527+ EnclosingNamespaceName = getNamespaceAtPosition (Contents, *InsertionPos,
528+ Sel.AST ->getLangOpts ());
529+ } else if (SameFile) {
530+ auto P = getInsertionPointInMainFile (Sel.AST );
531+ if (!P)
532+ return P.takeError ();
533+ Offset = P->Offset ;
534+ EnclosingNamespace = P->EnclosingNamespace ;
535+ } else {
536+ auto Region = getEligiblePoints (
537+ Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
538+ assert (!Region.EligiblePoints .empty ());
539+ EnclosingNamespaceName = Region.EnclosingNamespace ;
540+ InsertionPos = Region.EligiblePoints .back ();
541+ }
542+
543+ if (InsertionPos) {
544+ auto O = positionToOffset (Contents, *InsertionPos);
545+ if (!O)
546+ return O.takeError ();
547+ Offset = *O;
548+ auto TargetContext =
549+ findContextForNS (EnclosingNamespaceName, Source->getDeclContext ());
550+ if (!TargetContext)
551+ return error (" define outline: couldn't find a context for target" );
552+ EnclosingNamespace = *TargetContext;
553+ }
554+
555+ assert (Offset >= 0 );
556+ assert (EnclosingNamespace);
506557
507558 auto FuncDef = getFunctionSourceCode (
508- Source, InsertionPoint-> EnclosingNamespace , Sel.AST ->getTokens (),
559+ Source, EnclosingNamespace, Sel.AST ->getTokens (),
509560 Sel.AST ->getHeuristicResolver (),
510561 SameFile && isHeaderFile (Sel.AST ->tuPath (), Sel.AST ->getLangOpts ()));
511562 if (!FuncDef)
512563 return FuncDef.takeError ();
513564
514565 SourceManagerForFile SMFF (*CCFile, Contents);
515- const tooling::Replacement InsertFunctionDef (
516- *CCFile, InsertionPoint->Offset , 0 , *FuncDef);
566+ const tooling::Replacement InsertFunctionDef (*CCFile, Offset, 0 , *FuncDef);
517567 auto Effect = Effect::mainFileEdit (
518568 SMFF.get (), tooling::Replacements (InsertFunctionDef));
519569 if (!Effect)
@@ -548,59 +598,191 @@ class DefineOutline : public Tweak {
548598 return std::move (*Effect);
549599 }
550600
551- // Returns the most natural insertion point for \p QualifiedName in \p
552- // Contents. This currently cares about only the namespace proximity, but in
553- // feature it should also try to follow ordering of declarations. For example,
554- // if decls come in order `foo, bar, baz` then this function should return
555- // some point between foo and baz for inserting bar.
556- // FIXME: The selection can be made smarter by looking at the definition
557- // locations for adjacent decls to Source. Unfortunately pseudo parsing in
558- // getEligibleRegions only knows about namespace begin/end events so we
559- // can't match function start/end positions yet.
560- llvm::Expected<InsertionPoint> getInsertionPoint (llvm::StringRef Contents,
561- const Selection &Sel) {
562- // If the definition goes to the same file and there is a namespace,
563- // we should (and, in the case of anonymous namespaces, need to)
564- // put the definition into the original namespace block.
565- if (SameFile) {
566- auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
567- if (!Klass)
568- return error (" moving to same file not supported for free functions" );
569- const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
570- const auto &TokBuf = Sel.AST ->getTokens ();
571- auto Tokens = TokBuf.expandedTokens ();
572- auto It = llvm::lower_bound (
573- Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
574- return Tok.location () < EndLoc;
575- });
576- while (It != Tokens.end ()) {
577- if (It->kind () != tok::semi) {
578- ++It;
579- continue ;
601+ enum class RelativeInsertPos { Before, After };
602+ std::optional<std::pair<Symbol, RelativeInsertPos>>
603+ getDefinitionOfAdjacentDecl (const Selection &Sel) {
604+ if (!Sel.Index )
605+ return {};
606+ std::optional<Symbol> Anchor;
607+ Path PrefixedTuPath = " file://" + Sel.AST ->tuPath ().str ();
608+ auto CheckCandidate = [&](Decl *Candidate) {
609+ assert (Candidate != Source);
610+ if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
611+ !Func || Func->isThisDeclarationADefinition ()) {
612+ return ;
613+ }
614+ std::optional<Symbol> CandidateSymbol;
615+ Sel.Index ->lookup ({{getSymbolID (Candidate)}}, [&](const Symbol &S) {
616+ if (S.Definition ) {
617+ CandidateSymbol = S;
580618 }
581- unsigned Offset = Sel.AST ->getSourceManager ()
582- .getDecomposedLoc (It->endLocation ())
583- .second ;
584- return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
619+ });
620+ if (!CandidateSymbol)
621+ return ;
622+
623+ // If our definition is constrained to the same file, ignore
624+ // definitions that are not located there.
625+ // If our definition is not constrained to the same file, but
626+ // our anchor definition is in the same file, then we also put our
627+ // definition there, because that appears to be the user preference.
628+ // Exception: If the existing definition is a template, then the
629+ // location is likely due to technical necessity rather than preference,
630+ // so ignore that definition.
631+ bool CandidateSameFile =
632+ PrefixedTuPath == CandidateSymbol->Definition .FileURI ;
633+ if (SameFile && !CandidateSameFile)
634+ return ;
635+ if (!SameFile && CandidateSameFile) {
636+ if (Candidate->isTemplateDecl ())
637+ return ;
638+ SameFile = true ;
585639 }
586- return error (
587- " failed to determine insertion location: no end of class found" );
640+ Anchor = *CandidateSymbol;
641+ };
642+
643+ // Try to find adjacent function declarations.
644+ // Determine the closest one by alternatingly going "up" and "down"
645+ // from our function in increasing steps.
646+ const DeclContext *ParentContext = Source->getParent ();
647+ const auto SourceIt = llvm::find_if (
648+ ParentContext->decls (), [this ](const Decl *D) { return D == Source; });
649+ if (SourceIt == ParentContext->decls_end ())
650+ return {};
651+ const int Preceding = std::distance (ParentContext->decls_begin (), SourceIt);
652+ const int Following =
653+ std::distance (SourceIt, ParentContext->decls_end ()) - 1 ;
654+ for (int Offset = 1 ; Offset <= Preceding || Offset <= Following; ++Offset) {
655+ if (Offset <= Preceding)
656+ CheckCandidate (
657+ *std::next (ParentContext->decls_begin (), Preceding - Offset));
658+ if (Anchor)
659+ return std::make_pair (*Anchor, RelativeInsertPos::After);
660+ if (Offset <= Following)
661+ CheckCandidate (*std::next (SourceIt, Offset));
662+ if (Anchor)
663+ return std::make_pair (*Anchor, RelativeInsertPos::Before);
588664 }
665+ return {};
666+ }
589667
590- auto Region = getEligiblePoints (
591- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
668+ // We don't know the actual start or end of the definition, only the position
669+ // of the name. Therefore, we heuristically try to locate the last token
670+ // before or in this function, respectively. Adapt as required by user code.
671+ llvm::Expected<Position> getInsertionPointFromExistingDefinition (
672+ const llvm::MemoryBuffer &Buffer, const Symbol &S,
673+ RelativeInsertPos RelInsertPos, ParsedAST *AST) {
674+ auto LspPos = indexToLSPLocation (S.Definition , AST->tuPath ());
675+ if (!LspPos)
676+ return LspPos.takeError ();
677+ auto StartOffset =
678+ positionToOffset (Buffer.getBuffer (), LspPos->range .start );
679+ if (!StartOffset)
680+ return LspPos.takeError ();
681+ SourceLocation InsertionLoc;
682+ SourceManager &SM = AST->getSourceManager ();
683+ FileID F = Buffer.getBufferIdentifier () == AST->tuPath ()
684+ ? SM.getMainFileID ()
685+ : SM.createFileID (Buffer);
686+
687+ auto InsertBefore = [&] {
688+ // Go backwards until we encounter one of the following:
689+ // - An opening brace (of a namespace).
690+ // - A closing brace (of a function definition).
691+ // - A semicolon (of a declaration).
692+ // If no such token was found, then the first token in the file starts the
693+ // definition.
694+ auto Tokens = syntax::tokenize (syntax::FileRange (F, 0 , *StartOffset), SM,
695+ AST->getLangOpts ());
696+ if (Tokens.empty ())
697+ return ;
698+ for (auto I = std::rbegin (Tokens);
699+ InsertionLoc.isInvalid () && I != std::rend (Tokens); ++I) {
700+ switch (I->kind ()) {
701+ case tok::l_brace:
702+ case tok::r_brace:
703+ case tok::semi:
704+ if (I != std::rbegin (Tokens))
705+ InsertionLoc = std::prev (I)->location ();
706+ else
707+ InsertionLoc = I->endLocation ();
708+ break ;
709+ default :
710+ break ;
711+ }
712+ }
713+ if (InsertionLoc.isInvalid ())
714+ InsertionLoc = Tokens.front ().location ();
715+ };
592716
593- assert (!Region.EligiblePoints .empty ());
594- auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
595- if (!Offset)
596- return Offset.takeError ();
717+ if (RelInsertPos == RelativeInsertPos::Before) {
718+ InsertBefore ();
719+ } else {
720+ // Skip over one top-level pair of parentheses (for the parameter list)
721+ // and one pair of curly braces (for the code block).
722+ // If that fails, insert before the function instead.
723+ auto Tokens = syntax::tokenize (
724+ syntax::FileRange (F, *StartOffset, Buffer.getBuffer ().size ()), SM,
725+ AST->getLangOpts ());
726+ bool SkippedParams = false ;
727+ int OpenParens = 0 ;
728+ int OpenBraces = 0 ;
729+ std::optional<syntax::Token> Tok;
730+ for (const auto &T : Tokens) {
731+ tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
732+ tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
733+ int &Count = SkippedParams ? OpenBraces : OpenParens;
734+ if (T.kind () == StartKind) {
735+ ++Count;
736+ } else if (T.kind () == EndKind) {
737+ if (--Count == 0 ) {
738+ if (SkippedParams) {
739+ Tok = T;
740+ break ;
741+ }
742+ SkippedParams = true ;
743+ } else if (Count < 0 ) {
744+ break ;
745+ }
746+ }
747+ }
748+ if (Tok)
749+ InsertionLoc = Tok->endLocation ();
750+ else
751+ InsertBefore ();
752+ }
597753
598- auto TargetContext =
599- findContextForNS (Region.EnclosingNamespace , Source->getDeclContext ());
600- if (!TargetContext)
601- return error (" define outline: couldn't find a context for target" );
754+ return InsertionLoc.isValid () ? sourceLocToPosition (SM, InsertionLoc)
755+ : Position ();
756+ }
602757
603- return InsertionPoint{*TargetContext, *Offset};
758+ // Returns the most natural insertion point in this file.
759+ // This is a fallback for when we failed to find an existing definition to
760+ // place the new one next to. It only considers namespace proximity.
761+ llvm::Expected<InsertionPoint> getInsertionPointInMainFile (ParsedAST *AST) {
762+ // If the definition goes to the same file and there is a namespace,
763+ // we should (and, in the case of anonymous namespaces, need to)
764+ // put the definition into the original namespace block.
765+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
766+ if (!Klass)
767+ return error (" moving to same file not supported for free functions" );
768+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
769+ const auto &TokBuf = AST->getTokens ();
770+ auto Tokens = TokBuf.expandedTokens ();
771+ auto It = llvm::lower_bound (
772+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
773+ return Tok.location () < EndLoc;
774+ });
775+ while (It != Tokens.end ()) {
776+ if (It->kind () != tok::semi) {
777+ ++It;
778+ continue ;
779+ }
780+ unsigned Offset =
781+ AST->getSourceManager ().getDecomposedLoc (It->endLocation ()).second ;
782+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
783+ }
784+ return error (
785+ " failed to determine insertion location: no end of class found" );
604786 }
605787
606788private:
0 commit comments