77// ===----------------------------------------------------------------------===//
88
99#include " AST.h"
10+ #include " FindSymbols.h"
1011#include " FindTarget.h"
1112#include " HeaderSourceSwitch.h"
1213#include " ParsedAST.h"
3435#include < cstddef>
3536#include < optional>
3637#include < string>
38+ #include < tuple>
3739
3840namespace clang {
3941namespace clangd {
@@ -489,8 +491,17 @@ class DefineOutline : public Tweak {
489491
490492 Expected<Effect> apply (const Selection &Sel) override {
491493 const SourceManager &SM = Sel.AST ->getSourceManager ();
492- auto CCFile = SameFile ? Sel.AST ->tuPath ().str ()
493- : getSourceFile (Sel.AST ->tuPath (), Sel);
494+ std::optional<Path> CCFile;
495+ auto Anchor = getDefinitionOfAdjacentDecl (Sel);
496+ if (Anchor) {
497+ auto URI = URI::resolve (std::get<0 >(*Anchor).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, std::get<0 >(*Anchor), std::get<2 >(*Anchor), 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,194 @@ 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::tuple<SymbolLocation, Path, RelativeInsertPos>>
605+ getDefinitionOfAdjacentDecl (const Selection &Sel) {
606+ if (!Sel.Index )
607+ return {};
608+ std::optional<std::pair<SymbolLocation, Path>> 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<std::pair<SymbolLocation, Path>> CandidateLoc;
617+ Sel.Index ->lookup ({{getSymbolID (Candidate)}}, [&](const Symbol &S) {
618+ if (S.Definition ) {
619+ CandidateLoc = std::make_pair (S.Definition ,
620+ StringRef (S.Definition .FileURI ).str ());
621+ CandidateLoc->first .FileURI = CandidateLoc->second .c_str ();
580622 }
581- unsigned Offset = Sel.AST ->getSourceManager ()
582- .getDecomposedLoc (It->endLocation ())
583- .second ;
584- return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
623+ });
624+ if (!CandidateLoc)
625+ return ;
626+
627+ // If our definition is constrained to the same file, ignore
628+ // definitions that are not located there.
629+ // If our definition is not constrained to the same file, but
630+ // our anchor definition is in the same file, then we also put our
631+ // definition there, because that appears to be the user preference.
632+ // Exception: If the existing definition is a template, then the
633+ // location is likely due to technical necessity rather than preference,
634+ // so ignore that definition.
635+ bool CandidateSameFile = TuURI == CandidateLoc->first .FileURI ;
636+ if (SameFile && !CandidateSameFile)
637+ return ;
638+ if (!SameFile && CandidateSameFile) {
639+ if (Candidate->isTemplateDecl ())
640+ return ;
641+ SameFile = true ;
585642 }
586- return error (
587- " failed to determine insertion location: no end of class found" );
643+ Anchor = *CandidateLoc;
644+ };
645+
646+ // Try to find adjacent function declarations.
647+ // Determine the closest one by alternatingly going "up" and "down"
648+ // from our function in increasing steps.
649+ const DeclContext *ParentContext = Source->getParent ();
650+ const auto SourceIt = llvm::find_if (
651+ ParentContext->decls (), [this ](const Decl *D) { return D == Source; });
652+ if (SourceIt == ParentContext->decls_end ())
653+ return {};
654+ const int Preceding = std::distance (ParentContext->decls_begin (), SourceIt);
655+ const int Following =
656+ std::distance (SourceIt, ParentContext->decls_end ()) - 1 ;
657+ for (int Offset = 1 ; Offset <= Preceding || Offset <= Following; ++Offset) {
658+ if (Offset <= Preceding)
659+ CheckCandidate (
660+ *std::next (ParentContext->decls_begin (), Preceding - Offset));
661+ if (Anchor)
662+ return std::make_tuple (Anchor->first , Anchor->second ,
663+ RelativeInsertPos::After);
664+ if (Offset <= Following)
665+ CheckCandidate (*std::next (SourceIt, Offset));
666+ if (Anchor)
667+ return std::make_tuple (Anchor->first , Anchor->second ,
668+ RelativeInsertPos::Before);
588669 }
670+ return {};
671+ }
589672
590- auto Region = getEligiblePoints (
591- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
673+ // We don't know the actual start or end of the definition, only the position
674+ // of the name. Therefore, we heuristically try to locate the last token
675+ // before or in this function, respectively. Adapt as required by user code.
676+ llvm::Expected<Position> getInsertionPointFromExistingDefinition (
677+ const llvm::MemoryBuffer &Buffer, const SymbolLocation &Loc,
678+ RelativeInsertPos RelInsertPos, ParsedAST *AST) {
679+ auto LspPos = indexToLSPLocation (Loc, AST->tuPath ());
680+ if (!LspPos)
681+ return LspPos.takeError ();
682+ auto StartOffset =
683+ positionToOffset (Buffer.getBuffer (), LspPos->range .start );
684+ if (!StartOffset)
685+ return LspPos.takeError ();
686+ SourceLocation InsertionLoc;
687+ SourceManager &SM = AST->getSourceManager ();
688+ FileID F = Buffer.getBufferIdentifier () == AST->tuPath ()
689+ ? SM.getMainFileID ()
690+ : SM.createFileID (Buffer);
691+
692+ auto InsertBefore = [&] {
693+ // Go backwards until we encounter one of the following:
694+ // - An opening brace (of a namespace).
695+ // - A closing brace (of a function definition).
696+ // - A semicolon (of a declaration).
697+ // If no such token was found, then the first token in the file starts the
698+ // definition.
699+ auto Tokens = syntax::tokenize (syntax::FileRange (F, 0 , *StartOffset), SM,
700+ AST->getLangOpts ());
701+ if (Tokens.empty ())
702+ return ;
703+ for (auto I = std::rbegin (Tokens);
704+ InsertionLoc.isInvalid () && I != std::rend (Tokens); ++I) {
705+ switch (I->kind ()) {
706+ case tok::l_brace:
707+ case tok::r_brace:
708+ case tok::semi:
709+ if (I != std::rbegin (Tokens))
710+ InsertionLoc = std::prev (I)->location ();
711+ else
712+ InsertionLoc = I->endLocation ();
713+ break ;
714+ default :
715+ break ;
716+ }
717+ }
718+ if (InsertionLoc.isInvalid ())
719+ InsertionLoc = Tokens.front ().location ();
720+ };
592721
593- assert (!Region.EligiblePoints .empty ());
594- auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
595- if (!Offset)
596- return Offset.takeError ();
722+ if (RelInsertPos == RelativeInsertPos::Before) {
723+ InsertBefore ();
724+ } else {
725+ // Skip over one top-level pair of parentheses (for the parameter list)
726+ // and one pair of curly braces (for the code block).
727+ // If that fails, insert before the function instead.
728+ auto Tokens = syntax::tokenize (
729+ syntax::FileRange (F, *StartOffset, Buffer.getBuffer ().size ()), SM,
730+ AST->getLangOpts ());
731+ bool SkippedParams = false ;
732+ int OpenParens = 0 ;
733+ int OpenBraces = 0 ;
734+ std::optional<syntax::Token> Tok;
735+ for (const auto &T : Tokens) {
736+ tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
737+ tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
738+ int &Count = SkippedParams ? OpenBraces : OpenParens;
739+ if (T.kind () == StartKind) {
740+ ++Count;
741+ } else if (T.kind () == EndKind) {
742+ if (--Count == 0 ) {
743+ if (SkippedParams) {
744+ Tok = T;
745+ break ;
746+ }
747+ SkippedParams = true ;
748+ } else if (Count < 0 ) {
749+ break ;
750+ }
751+ }
752+ }
753+ if (Tok)
754+ InsertionLoc = Tok->endLocation ();
755+ else
756+ InsertBefore ();
757+ }
597758
598- auto TargetContext =
599- findContextForNS (Region.EnclosingNamespace , Source->getDeclContext ());
600- if (!TargetContext)
601- return error (" define outline: couldn't find a context for target" );
759+ return InsertionLoc.isValid () ? sourceLocToPosition (SM, InsertionLoc)
760+ : Position ();
761+ }
602762
603- return InsertionPoint{*TargetContext, *Offset};
763+ // Returns the most natural insertion point in this file.
764+ // This is a fallback for when we failed to find an existing definition to
765+ // place the new one next to. It only considers namespace proximity.
766+ llvm::Expected<InsertionPoint> getInsertionPointInMainFile (ParsedAST *AST) {
767+ // If the definition goes to the same file and there is a namespace,
768+ // we should (and, in the case of anonymous namespaces, need to)
769+ // put the definition into the original namespace block.
770+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
771+ if (!Klass)
772+ return error (" moving to same file not supported for free functions" );
773+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
774+ const auto &TokBuf = AST->getTokens ();
775+ auto Tokens = TokBuf.expandedTokens ();
776+ auto It = llvm::lower_bound (
777+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
778+ return Tok.location () < EndLoc;
779+ });
780+ while (It != Tokens.end ()) {
781+ if (It->kind () != tok::semi) {
782+ ++It;
783+ continue ;
784+ }
785+ unsigned Offset =
786+ AST->getSourceManager ().getDecomposedLoc (It->endLocation ()).second ;
787+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
788+ }
789+ return error (
790+ " failed to determine insertion location: no end of class found" );
604791 }
605792
606793private:
0 commit comments