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,18 @@ 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+ auto URI = URI::resolve (Anchor->first .Definition .FileURI );
498+ if (!URI)
499+ return URI.takeError ();
500+ CCFile = *URI;
501+ } else {
502+ CCFile = SameFile ? Sel.AST ->tuPath ().str ()
503+ : getSourceFile (Sel.AST ->tuPath (), Sel);
504+ }
494505 if (!CCFile)
495506 return error (" Couldn't find a suitable implementation file." );
496507 assert (Sel.FS && " FS Must be set in apply" );
@@ -499,21 +510,62 @@ class DefineOutline : public Tweak {
499510 // doesn't exist?
500511 if (!Buffer)
501512 return llvm::errorCodeToError (Buffer.getError ());
513+
514+ std::optional<Position> InsertionPos;
515+ if (Anchor) {
516+ if (auto P = getInsertionPointFromExistingDefinition (
517+ **Buffer, Anchor->first , Anchor->second , Sel.AST )) {
518+ InsertionPos = *P;
519+ }
520+ }
521+
502522 auto Contents = Buffer->get ()->getBuffer ();
503- auto InsertionPoint = getInsertionPoint (Contents, Sel);
504- if (!InsertionPoint)
505- return InsertionPoint.takeError ();
523+
524+ std::size_t Offset = -1 ;
525+ const DeclContext *EnclosingNamespace = nullptr ;
526+ std::string EnclosingNamespaceName;
527+
528+ if (InsertionPos) {
529+ EnclosingNamespaceName = getNamespaceAtPosition (Contents, *InsertionPos,
530+ Sel.AST ->getLangOpts ());
531+ } else if (SameFile) {
532+ auto P = getInsertionPointInMainFile (Sel.AST );
533+ if (!P)
534+ return P.takeError ();
535+ Offset = P->Offset ;
536+ EnclosingNamespace = P->EnclosingNamespace ;
537+ } else {
538+ auto Region = getEligiblePoints (
539+ Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
540+ assert (!Region.EligiblePoints .empty ());
541+ EnclosingNamespaceName = Region.EnclosingNamespace ;
542+ InsertionPos = Region.EligiblePoints .back ();
543+ }
544+
545+ if (InsertionPos) {
546+ auto O = positionToOffset (Contents, *InsertionPos);
547+ if (!O)
548+ return O.takeError ();
549+ Offset = *O;
550+ auto TargetContext =
551+ findContextForNS (EnclosingNamespaceName, Source->getDeclContext ());
552+ if (!TargetContext)
553+ return error (" define outline: couldn't find a context for target" );
554+ EnclosingNamespace = *TargetContext;
555+ }
556+
557+ assert (Offset >= 0 );
558+ assert (EnclosingNamespace);
506559
507560 auto FuncDef = getFunctionSourceCode (
508- Source, InsertionPoint-> EnclosingNamespace , Sel.AST ->getTokens (),
561+ Source, EnclosingNamespace, Sel.AST ->getTokens (),
509562 Sel.AST ->getHeuristicResolver (),
510563 SameFile && isHeaderFile (Sel.AST ->tuPath (), Sel.AST ->getLangOpts ()));
511564 if (!FuncDef)
512565 return FuncDef.takeError ();
513566
514567 SourceManagerForFile SMFF (*CCFile, Contents);
515- const tooling::Replacement InsertFunctionDef (
516- *CCFile, InsertionPoint->Offset , 0 , *FuncDef);
568+ const tooling::Replacement InsertFunctionDef (*CCFile, Offset, 0 , *FuncDef);
517569 auto Effect = Effect::mainFileEdit (
518570 SMFF.get (), tooling::Replacements (InsertFunctionDef));
519571 if (!Effect)
@@ -548,59 +600,190 @@ class DefineOutline : public Tweak {
548600 return std::move (*Effect);
549601 }
550602
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 ;
603+ enum class RelativeInsertPos { Before, After };
604+ std::optional<std::pair<Symbol, RelativeInsertPos>>
605+ getDefinitionOfAdjacentDecl (const Selection &Sel) {
606+ if (!Sel.Index )
607+ return {};
608+ std::optional<Symbol> Anchor;
609+ std::string TuURI = URI::createFile (Sel.AST ->tuPath ()).toString ();
610+ auto CheckCandidate = [&](Decl *Candidate) {
611+ assert (Candidate != Source);
612+ if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
613+ !Func || Func->isThisDeclarationADefinition ()) {
614+ return ;
615+ }
616+ std::optional<Symbol> CandidateSymbol;
617+ Sel.Index ->lookup ({{getSymbolID (Candidate)}}, [&](const Symbol &S) {
618+ if (S.Definition ) {
619+ CandidateSymbol = S;
580620 }
581- unsigned Offset = Sel.AST ->getSourceManager ()
582- .getDecomposedLoc (It->endLocation ())
583- .second ;
584- return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
621+ });
622+ if (!CandidateSymbol)
623+ return ;
624+
625+ // If our definition is constrained to the same file, ignore
626+ // definitions that are not located there.
627+ // If our definition is not constrained to the same file, but
628+ // our anchor definition is in the same file, then we also put our
629+ // definition there, because that appears to be the user preference.
630+ // Exception: If the existing definition is a template, then the
631+ // location is likely due to technical necessity rather than preference,
632+ // so ignore that definition.
633+ bool CandidateSameFile = TuURI == CandidateSymbol->Definition .FileURI ;
634+ if (SameFile && !CandidateSameFile)
635+ return ;
636+ if (!SameFile && CandidateSameFile) {
637+ if (Candidate->isTemplateDecl ())
638+ return ;
639+ SameFile = true ;
585640 }
586- return error (
587- " failed to determine insertion location: no end of class found" );
641+ Anchor = *CandidateSymbol;
642+ };
643+
644+ // Try to find adjacent function declarations.
645+ // Determine the closest one by alternatingly going "up" and "down"
646+ // from our function in increasing steps.
647+ const DeclContext *ParentContext = Source->getParent ();
648+ const auto SourceIt = llvm::find_if (
649+ ParentContext->decls (), [this ](const Decl *D) { return D == Source; });
650+ if (SourceIt == ParentContext->decls_end ())
651+ return {};
652+ const int Preceding = std::distance (ParentContext->decls_begin (), SourceIt);
653+ const int Following =
654+ std::distance (SourceIt, ParentContext->decls_end ()) - 1 ;
655+ for (int Offset = 1 ; Offset <= Preceding || Offset <= Following; ++Offset) {
656+ if (Offset <= Preceding)
657+ CheckCandidate (
658+ *std::next (ParentContext->decls_begin (), Preceding - Offset));
659+ if (Anchor)
660+ return std::make_pair (*Anchor, RelativeInsertPos::After);
661+ if (Offset <= Following)
662+ CheckCandidate (*std::next (SourceIt, Offset));
663+ if (Anchor)
664+ return std::make_pair (*Anchor, RelativeInsertPos::Before);
588665 }
666+ return {};
667+ }
589668
590- auto Region = getEligiblePoints (
591- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
669+ // We don't know the actual start or end of the definition, only the position
670+ // of the name. Therefore, we heuristically try to locate the last token
671+ // before or in this function, respectively. Adapt as required by user code.
672+ llvm::Expected<Position> getInsertionPointFromExistingDefinition (
673+ const llvm::MemoryBuffer &Buffer, const Symbol &S,
674+ RelativeInsertPos RelInsertPos, ParsedAST *AST) {
675+ auto LspPos = indexToLSPLocation (S.Definition , AST->tuPath ());
676+ if (!LspPos)
677+ return LspPos.takeError ();
678+ auto StartOffset =
679+ positionToOffset (Buffer.getBuffer (), LspPos->range .start );
680+ if (!StartOffset)
681+ return LspPos.takeError ();
682+ SourceLocation InsertionLoc;
683+ SourceManager &SM = AST->getSourceManager ();
684+ FileID F = Buffer.getBufferIdentifier () == AST->tuPath ()
685+ ? SM.getMainFileID ()
686+ : SM.createFileID (Buffer);
687+
688+ auto InsertBefore = [&] {
689+ // Go backwards until we encounter one of the following:
690+ // - An opening brace (of a namespace).
691+ // - A closing brace (of a function definition).
692+ // - A semicolon (of a declaration).
693+ // If no such token was found, then the first token in the file starts the
694+ // definition.
695+ auto Tokens = syntax::tokenize (syntax::FileRange (F, 0 , *StartOffset), SM,
696+ AST->getLangOpts ());
697+ if (Tokens.empty ())
698+ return ;
699+ for (auto I = std::rbegin (Tokens);
700+ InsertionLoc.isInvalid () && I != std::rend (Tokens); ++I) {
701+ switch (I->kind ()) {
702+ case tok::l_brace:
703+ case tok::r_brace:
704+ case tok::semi:
705+ if (I != std::rbegin (Tokens))
706+ InsertionLoc = std::prev (I)->location ();
707+ else
708+ InsertionLoc = I->endLocation ();
709+ break ;
710+ default :
711+ break ;
712+ }
713+ }
714+ if (InsertionLoc.isInvalid ())
715+ InsertionLoc = Tokens.front ().location ();
716+ };
592717
593- assert (!Region.EligiblePoints .empty ());
594- auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
595- if (!Offset)
596- return Offset.takeError ();
718+ if (RelInsertPos == RelativeInsertPos::Before) {
719+ InsertBefore ();
720+ } else {
721+ // Skip over one top-level pair of parentheses (for the parameter list)
722+ // and one pair of curly braces (for the code block).
723+ // If that fails, insert before the function instead.
724+ auto Tokens = syntax::tokenize (
725+ syntax::FileRange (F, *StartOffset, Buffer.getBuffer ().size ()), SM,
726+ AST->getLangOpts ());
727+ bool SkippedParams = false ;
728+ int OpenParens = 0 ;
729+ int OpenBraces = 0 ;
730+ std::optional<syntax::Token> Tok;
731+ for (const auto &T : Tokens) {
732+ tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
733+ tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
734+ int &Count = SkippedParams ? OpenBraces : OpenParens;
735+ if (T.kind () == StartKind) {
736+ ++Count;
737+ } else if (T.kind () == EndKind) {
738+ if (--Count == 0 ) {
739+ if (SkippedParams) {
740+ Tok = T;
741+ break ;
742+ }
743+ SkippedParams = true ;
744+ } else if (Count < 0 ) {
745+ break ;
746+ }
747+ }
748+ }
749+ if (Tok)
750+ InsertionLoc = Tok->endLocation ();
751+ else
752+ InsertBefore ();
753+ }
597754
598- auto TargetContext =
599- findContextForNS (Region.EnclosingNamespace , Source->getDeclContext ());
600- if (!TargetContext)
601- return error (" define outline: couldn't find a context for target" );
755+ return InsertionLoc.isValid () ? sourceLocToPosition (SM, InsertionLoc)
756+ : Position ();
757+ }
602758
603- return InsertionPoint{*TargetContext, *Offset};
759+ // Returns the most natural insertion point in this file.
760+ // This is a fallback for when we failed to find an existing definition to
761+ // place the new one next to. It only considers namespace proximity.
762+ llvm::Expected<InsertionPoint> getInsertionPointInMainFile (ParsedAST *AST) {
763+ // If the definition goes to the same file and there is a namespace,
764+ // we should (and, in the case of anonymous namespaces, need to)
765+ // put the definition into the original namespace block.
766+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
767+ if (!Klass)
768+ return error (" moving to same file not supported for free functions" );
769+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
770+ const auto &TokBuf = AST->getTokens ();
771+ auto Tokens = TokBuf.expandedTokens ();
772+ auto It = llvm::lower_bound (
773+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
774+ return Tok.location () < EndLoc;
775+ });
776+ while (It != Tokens.end ()) {
777+ if (It->kind () != tok::semi) {
778+ ++It;
779+ continue ;
780+ }
781+ unsigned Offset =
782+ AST->getSourceManager ().getDecomposedLoc (It->endLocation ()).second ;
783+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
784+ }
785+ return error (
786+ " failed to determine insertion location: no end of class found" );
604787 }
605788
606789private:
0 commit comments