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,18 @@ 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+ std::get<0 >(*Anchor).FileURI = std::get<1 >(*Anchor).c_str ();
498+ auto URI = URI::resolve (std::get<0 >(*Anchor).FileURI );
499+ if (!URI)
500+ return URI.takeError ();
501+ CCFile = *URI;
502+ } else {
503+ CCFile = SameFile ? Sel.AST ->tuPath ().str ()
504+ : getSourceFile (Sel.AST ->tuPath (), Sel);
505+ }
494506 if (!CCFile)
495507 return error (" Couldn't find a suitable implementation file." );
496508 assert (Sel.FS && " FS Must be set in apply" );
@@ -499,21 +511,62 @@ class DefineOutline : public Tweak {
499511 // doesn't exist?
500512 if (!Buffer)
501513 return llvm::errorCodeToError (Buffer.getError ());
514+
515+ std::optional<Position> InsertionPos;
516+ if (Anchor) {
517+ if (auto P = getInsertionPointFromExistingDefinition (
518+ **Buffer, std::get<0 >(*Anchor), std::get<2 >(*Anchor), Sel.AST )) {
519+ InsertionPos = *P;
520+ }
521+ }
522+
502523 auto Contents = Buffer->get ()->getBuffer ();
503- auto InsertionPoint = getInsertionPoint (Contents, Sel);
504- if (!InsertionPoint)
505- return InsertionPoint.takeError ();
524+
525+ std::size_t Offset = -1 ;
526+ const DeclContext *EnclosingNamespace = nullptr ;
527+ std::string EnclosingNamespaceName;
528+
529+ if (InsertionPos) {
530+ EnclosingNamespaceName = getNamespaceAtPosition (Contents, *InsertionPos,
531+ Sel.AST ->getLangOpts ());
532+ } else if (SameFile) {
533+ auto P = getInsertionPointInMainFile (Sel.AST );
534+ if (!P)
535+ return P.takeError ();
536+ Offset = P->Offset ;
537+ EnclosingNamespace = P->EnclosingNamespace ;
538+ } else {
539+ auto Region = getEligiblePoints (
540+ Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
541+ assert (!Region.EligiblePoints .empty ());
542+ EnclosingNamespaceName = Region.EnclosingNamespace ;
543+ InsertionPos = Region.EligiblePoints .back ();
544+ }
545+
546+ if (InsertionPos) {
547+ auto O = positionToOffset (Contents, *InsertionPos);
548+ if (!O)
549+ return O.takeError ();
550+ Offset = *O;
551+ auto TargetContext =
552+ findContextForNS (EnclosingNamespaceName, Source->getDeclContext ());
553+ if (!TargetContext)
554+ return error (" define outline: couldn't find a context for target" );
555+ EnclosingNamespace = *TargetContext;
556+ }
557+
558+ assert (Offset >= 0 );
559+ assert (EnclosingNamespace);
506560
507561 auto FuncDef = getFunctionSourceCode (
508- Source, InsertionPoint-> EnclosingNamespace , Sel.AST ->getTokens (),
562+ Source, EnclosingNamespace, Sel.AST ->getTokens (),
509563 Sel.AST ->getHeuristicResolver (),
510564 SameFile && isHeaderFile (Sel.AST ->tuPath (), Sel.AST ->getLangOpts ()));
511565 if (!FuncDef)
512566 return FuncDef.takeError ();
513567
514568 SourceManagerForFile SMFF (*CCFile, Contents);
515- const tooling::Replacement InsertFunctionDef (
516- *CCFile, InsertionPoint->Offset , 0 , *FuncDef);
569+ const tooling::Replacement InsertFunctionDef (*CCFile, Offset, 0 , *FuncDef);
517570 auto Effect = Effect::mainFileEdit (
518571 SMFF.get (), tooling::Replacements (InsertFunctionDef));
519572 if (!Effect)
@@ -548,59 +601,193 @@ class DefineOutline : public Tweak {
548601 return std::move (*Effect);
549602 }
550603
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 ;
604+ enum class RelativeInsertPos { Before, After };
605+ std::optional<std::tuple<SymbolLocation, Path, RelativeInsertPos>>
606+ getDefinitionOfAdjacentDecl (const Selection &Sel) {
607+ if (!Sel.Index )
608+ return {};
609+ std::optional<std::pair<SymbolLocation, Path>> Anchor;
610+ std::string TuURI = URI::createFile (Sel.AST ->tuPath ()).toString ();
611+ auto CheckCandidate = [&](Decl *Candidate) {
612+ assert (Candidate != Source);
613+ if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
614+ !Func || Func->isThisDeclarationADefinition ()) {
615+ return ;
616+ }
617+ std::optional<std::pair<SymbolLocation, Path>> CandidateLoc;
618+ Sel.Index ->lookup ({{getSymbolID (Candidate)}}, [&](const Symbol &S) {
619+ if (S.Definition ) {
620+ CandidateLoc = std::make_pair (S.Definition ,
621+ StringRef (S.Definition .FileURI ).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->second ;
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