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,190 @@ 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+ std::string TuURI = URI::createFile (Sel.AST ->tuPath ()).toString ();
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 = TuURI == CandidateSymbol->Definition .FileURI ;
632+ if (SameFile && !CandidateSameFile)
633+ return ;
634+ if (!SameFile && CandidateSameFile) {
635+ if (Candidate->isTemplateDecl ())
636+ return ;
637+ SameFile = true ;
585638 }
586- return error (
587- " failed to determine insertion location: no end of class found" );
639+ Anchor = *CandidateSymbol;
640+ };
641+
642+ // Try to find adjacent function declarations.
643+ // Determine the closest one by alternatingly going "up" and "down"
644+ // from our function in increasing steps.
645+ const DeclContext *ParentContext = Source->getParent ();
646+ const auto SourceIt = llvm::find_if (
647+ ParentContext->decls (), [this ](const Decl *D) { return D == Source; });
648+ if (SourceIt == ParentContext->decls_end ())
649+ return {};
650+ const int Preceding = std::distance (ParentContext->decls_begin (), SourceIt);
651+ const int Following =
652+ std::distance (SourceIt, ParentContext->decls_end ()) - 1 ;
653+ for (int Offset = 1 ; Offset <= Preceding || Offset <= Following; ++Offset) {
654+ if (Offset <= Preceding)
655+ CheckCandidate (
656+ *std::next (ParentContext->decls_begin (), Preceding - Offset));
657+ if (Anchor)
658+ return std::make_pair (*Anchor, RelativeInsertPos::After);
659+ if (Offset <= Following)
660+ CheckCandidate (*std::next (SourceIt, Offset));
661+ if (Anchor)
662+ return std::make_pair (*Anchor, RelativeInsertPos::Before);
588663 }
664+ return {};
665+ }
589666
590- auto Region = getEligiblePoints (
591- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
667+ // We don't know the actual start or end of the definition, only the position
668+ // of the name. Therefore, we heuristically try to locate the last token
669+ // before or in this function, respectively. Adapt as required by user code.
670+ llvm::Expected<Position> getInsertionPointFromExistingDefinition (
671+ const llvm::MemoryBuffer &Buffer, const Symbol &S,
672+ RelativeInsertPos RelInsertPos, ParsedAST *AST) {
673+ auto LspPos = indexToLSPLocation (S.Definition , AST->tuPath ());
674+ if (!LspPos)
675+ return LspPos.takeError ();
676+ auto StartOffset =
677+ positionToOffset (Buffer.getBuffer (), LspPos->range .start );
678+ if (!StartOffset)
679+ return LspPos.takeError ();
680+ SourceLocation InsertionLoc;
681+ SourceManager &SM = AST->getSourceManager ();
682+ FileID F = Buffer.getBufferIdentifier () == AST->tuPath ()
683+ ? SM.getMainFileID ()
684+ : SM.createFileID (Buffer);
685+
686+ auto InsertBefore = [&] {
687+ // Go backwards until we encounter one of the following:
688+ // - An opening brace (of a namespace).
689+ // - A closing brace (of a function definition).
690+ // - A semicolon (of a declaration).
691+ // If no such token was found, then the first token in the file starts the
692+ // definition.
693+ auto Tokens = syntax::tokenize (syntax::FileRange (F, 0 , *StartOffset), SM,
694+ AST->getLangOpts ());
695+ if (Tokens.empty ())
696+ return ;
697+ for (auto I = std::rbegin (Tokens);
698+ InsertionLoc.isInvalid () && I != std::rend (Tokens); ++I) {
699+ switch (I->kind ()) {
700+ case tok::l_brace:
701+ case tok::r_brace:
702+ case tok::semi:
703+ if (I != std::rbegin (Tokens))
704+ InsertionLoc = std::prev (I)->location ();
705+ else
706+ InsertionLoc = I->endLocation ();
707+ break ;
708+ default :
709+ break ;
710+ }
711+ }
712+ if (InsertionLoc.isInvalid ())
713+ InsertionLoc = Tokens.front ().location ();
714+ };
592715
593- assert (!Region.EligiblePoints .empty ());
594- auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
595- if (!Offset)
596- return Offset.takeError ();
716+ if (RelInsertPos == RelativeInsertPos::Before) {
717+ InsertBefore ();
718+ } else {
719+ // Skip over one top-level pair of parentheses (for the parameter list)
720+ // and one pair of curly braces (for the code block).
721+ // If that fails, insert before the function instead.
722+ auto Tokens = syntax::tokenize (
723+ syntax::FileRange (F, *StartOffset, Buffer.getBuffer ().size ()), SM,
724+ AST->getLangOpts ());
725+ bool SkippedParams = false ;
726+ int OpenParens = 0 ;
727+ int OpenBraces = 0 ;
728+ std::optional<syntax::Token> Tok;
729+ for (const auto &T : Tokens) {
730+ tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
731+ tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
732+ int &Count = SkippedParams ? OpenBraces : OpenParens;
733+ if (T.kind () == StartKind) {
734+ ++Count;
735+ } else if (T.kind () == EndKind) {
736+ if (--Count == 0 ) {
737+ if (SkippedParams) {
738+ Tok = T;
739+ break ;
740+ }
741+ SkippedParams = true ;
742+ } else if (Count < 0 ) {
743+ break ;
744+ }
745+ }
746+ }
747+ if (Tok)
748+ InsertionLoc = Tok->endLocation ();
749+ else
750+ InsertBefore ();
751+ }
597752
598- auto TargetContext =
599- findContextForNS (Region.EnclosingNamespace , Source->getDeclContext ());
600- if (!TargetContext)
601- return error (" define outline: couldn't find a context for target" );
753+ return InsertionLoc.isValid () ? sourceLocToPosition (SM, InsertionLoc)
754+ : Position ();
755+ }
602756
603- return InsertionPoint{*TargetContext, *Offset};
757+ // Returns the most natural insertion point in this file.
758+ // This is a fallback for when we failed to find an existing definition to
759+ // place the new one next to. It only considers namespace proximity.
760+ llvm::Expected<InsertionPoint> getInsertionPointInMainFile (ParsedAST *AST) {
761+ // If the definition goes to the same file and there is a namespace,
762+ // we should (and, in the case of anonymous namespaces, need to)
763+ // put the definition into the original namespace block.
764+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
765+ if (!Klass)
766+ return error (" moving to same file not supported for free functions" );
767+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
768+ const auto &TokBuf = AST->getTokens ();
769+ auto Tokens = TokBuf.expandedTokens ();
770+ auto It = llvm::lower_bound (
771+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
772+ return Tok.location () < EndLoc;
773+ });
774+ while (It != Tokens.end ()) {
775+ if (It->kind () != tok::semi) {
776+ ++It;
777+ continue ;
778+ }
779+ unsigned Offset =
780+ AST->getSourceManager ().getDecomposedLoc (It->endLocation ()).second ;
781+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
782+ }
783+ return error (
784+ " failed to determine insertion location: no end of class found" );
604785 }
605786
606787private:
0 commit comments