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 {
@@ -362,6 +364,12 @@ struct InsertionPoint {
362364 size_t Offset;
363365};
364366
367+ enum class RelativeInsertPos { Before, After };
368+ struct InsertionAnchor {
369+ Location Loc;
370+ RelativeInsertPos RelInsertPos = RelativeInsertPos::Before;
371+ };
372+
365373// Returns the range that should be deleted from declaration, which always
366374// contains function body. In addition to that it might contain constructor
367375// initializers.
@@ -489,8 +497,14 @@ class DefineOutline : public Tweak {
489497
490498 Expected<Effect> apply (const Selection &Sel) override {
491499 const SourceManager &SM = Sel.AST ->getSourceManager ();
492- auto CCFile = SameFile ? Sel.AST ->tuPath ().str ()
493- : getSourceFile (Sel.AST ->tuPath (), Sel);
500+ std::optional<Path> CCFile;
501+ auto Anchor = getDefinitionOfAdjacentDecl (Sel);
502+ if (Anchor) {
503+ CCFile = Anchor->Loc .uri .file ();
504+ } else {
505+ CCFile = SameFile ? Sel.AST ->tuPath ().str ()
506+ : getSourceFile (Sel.AST ->tuPath (), Sel);
507+ }
494508 if (!CCFile)
495509 return error (" Couldn't find a suitable implementation file." );
496510 assert (Sel.FS && " FS Must be set in apply" );
@@ -499,21 +513,62 @@ class DefineOutline : public Tweak {
499513 // doesn't exist?
500514 if (!Buffer)
501515 return llvm::errorCodeToError (Buffer.getError ());
516+
502517 auto Contents = Buffer->get ()->getBuffer ();
503- auto InsertionPoint = getInsertionPoint (Contents, Sel);
504- if (!InsertionPoint)
505- return InsertionPoint.takeError ();
518+ SourceManagerForFile SMFF (*CCFile, Contents);
519+
520+ std::optional<Position> InsertionPos;
521+ if (Anchor) {
522+ if (auto P = getInsertionPointFromExistingDefinition (
523+ SMFF, **Buffer, Anchor->Loc , Anchor->RelInsertPos , Sel.AST )) {
524+ InsertionPos = *P;
525+ }
526+ }
527+
528+ std::optional<std::size_t > Offset;
529+ const DeclContext *EnclosingNamespace = nullptr ;
530+ std::string EnclosingNamespaceName;
531+
532+ if (InsertionPos) {
533+ EnclosingNamespaceName = getNamespaceAtPosition (Contents, *InsertionPos,
534+ Sel.AST ->getLangOpts ());
535+ } else if (SameFile) {
536+ auto P = getInsertionPointInMainFile (Sel.AST );
537+ if (!P)
538+ return P.takeError ();
539+ Offset = P->Offset ;
540+ EnclosingNamespace = P->EnclosingNamespace ;
541+ } else {
542+ auto Region = getEligiblePoints (
543+ Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
544+ assert (!Region.EligiblePoints .empty ());
545+ EnclosingNamespaceName = Region.EnclosingNamespace ;
546+ InsertionPos = Region.EligiblePoints .back ();
547+ }
548+
549+ if (InsertionPos) {
550+ auto O = positionToOffset (Contents, *InsertionPos);
551+ if (!O)
552+ return O.takeError ();
553+ Offset = *O;
554+ auto TargetContext =
555+ findContextForNS (EnclosingNamespaceName, Source->getDeclContext ());
556+ if (!TargetContext)
557+ return error (" define outline: couldn't find a context for target" );
558+ EnclosingNamespace = *TargetContext;
559+ }
560+
561+ assert (Offset);
562+ assert (EnclosingNamespace);
506563
507564 auto FuncDef = getFunctionSourceCode (
508- Source, InsertionPoint-> EnclosingNamespace , Sel.AST ->getTokens (),
565+ Source, EnclosingNamespace, Sel.AST ->getTokens (),
509566 Sel.AST ->getHeuristicResolver (),
510567 SameFile && isHeaderFile (Sel.AST ->tuPath (), Sel.AST ->getLangOpts ()));
511568 if (!FuncDef)
512569 return FuncDef.takeError ();
513570
514- SourceManagerForFile SMFF (*CCFile, Contents);
515- const tooling::Replacement InsertFunctionDef (
516- *CCFile, InsertionPoint->Offset , 0 , *FuncDef);
571+ const tooling::Replacement InsertFunctionDef (*CCFile, *Offset, 0 , *FuncDef);
517572 auto Effect = Effect::mainFileEdit (
518573 SMFF.get (), tooling::Replacements (InsertFunctionDef));
519574 if (!Effect)
@@ -548,59 +603,188 @@ class DefineOutline : public Tweak {
548603 return std::move (*Effect);
549604 }
550605
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 ;
606+ std::optional<InsertionAnchor>
607+ getDefinitionOfAdjacentDecl (const Selection &Sel) {
608+ if (!Sel.Index )
609+ return {};
610+ std::optional<Location> Anchor;
611+ std::string TuURI = URI::createFile (Sel.AST ->tuPath ()).toString ();
612+ auto CheckCandidate = [&](Decl *Candidate) {
613+ assert (Candidate != Source);
614+ if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
615+ !Func || Func->isThisDeclarationADefinition ()) {
616+ return ;
617+ }
618+ std::optional<Location> CandidateLoc;
619+ Sel.Index ->lookup ({{getSymbolID (Candidate)}}, [&](const Symbol &S) {
620+ if (S.Definition ) {
621+ if (auto Loc = indexToLSPLocation (S.Definition , Sel.AST ->tuPath ()))
622+ CandidateLoc = *Loc;
623+ else
624+ log (" getDefinitionOfAdjacentDecl: {0}" , Loc.takeError ());
580625 }
581- unsigned Offset = Sel.AST ->getSourceManager ()
582- .getDecomposedLoc (It->endLocation ())
583- .second ;
584- return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
626+ });
627+ if (!CandidateLoc)
628+ return ;
629+
630+ // If our definition is constrained to the same file, ignore
631+ // definitions that are not located there.
632+ // If our definition is not constrained to the same file, but
633+ // our anchor definition is in the same file, then we also put our
634+ // definition there, because that appears to be the user preference.
635+ // Exception: If the existing definition is a template, then the
636+ // location is likely due to technical necessity rather than preference,
637+ // so ignore that definition.
638+ bool CandidateSameFile = TuURI == CandidateLoc->uri .uri ();
639+ if (SameFile && !CandidateSameFile)
640+ return ;
641+ if (!SameFile && CandidateSameFile) {
642+ if (Candidate->isTemplateDecl ())
643+ return ;
644+ SameFile = true ;
585645 }
586- return error (
587- " failed to determine insertion location: no end of class found" );
646+ Anchor = *CandidateLoc;
647+ };
648+
649+ // Try to find adjacent function declarations.
650+ // Determine the closest one by alternatingly going "up" and "down"
651+ // from our function in increasing steps.
652+ const DeclContext *ParentContext = Source->getParent ();
653+ const auto SourceIt = llvm::find_if (
654+ ParentContext->decls (), [this ](const Decl *D) { return D == Source; });
655+ if (SourceIt == ParentContext->decls_end ())
656+ return {};
657+ const int Preceding = std::distance (ParentContext->decls_begin (), SourceIt);
658+ const int Following =
659+ std::distance (SourceIt, ParentContext->decls_end ()) - 1 ;
660+ for (int Offset = 1 ; Offset <= Preceding || Offset <= Following; ++Offset) {
661+ if (Offset <= Preceding)
662+ CheckCandidate (
663+ *std::next (ParentContext->decls_begin (), Preceding - Offset));
664+ if (Anchor)
665+ return InsertionAnchor{*Anchor, RelativeInsertPos::After};
666+ if (Offset <= Following)
667+ CheckCandidate (*std::next (SourceIt, Offset));
668+ if (Anchor)
669+ return InsertionAnchor{*Anchor, RelativeInsertPos::Before};
588670 }
671+ return {};
672+ }
589673
590- auto Region = getEligiblePoints (
591- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
674+ // We don't know the actual start or end of the definition, only the position
675+ // of the name. Therefore, we heuristically try to locate the last token
676+ // before or in this function, respectively. Adapt as required by user code.
677+ std::optional<Position> getInsertionPointFromExistingDefinition (
678+ SourceManagerForFile &SMFF, const llvm::MemoryBuffer &Buffer,
679+ const Location &Loc, RelativeInsertPos RelInsertPos, ParsedAST *AST) {
680+ auto StartOffset = positionToOffset (Buffer.getBuffer (), Loc.range .start );
681+ if (!StartOffset)
682+ return {};
683+ SourceLocation InsertionLoc;
684+ SourceManager &SM = SMFF.get ();
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 (
694+ syntax::FileRange (SM.getMainFileID (), 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 =
724+ syntax::tokenize (syntax::FileRange (SM.getMainFileID (), *StartOffset,
725+ Buffer.getBuffer ().size ()),
726+ SM, 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+ if (!InsertionLoc. isValid ())
756+ return {} ;
757+ return sourceLocToPosition (SM, InsertionLoc);
758+ }
602759
603- return InsertionPoint{*TargetContext, *Offset};
760+ // Returns the most natural insertion point in this file.
761+ // This is a fallback for when we failed to find an existing definition to
762+ // place the new one next to. It only considers namespace proximity.
763+ llvm::Expected<InsertionPoint> getInsertionPointInMainFile (ParsedAST *AST) {
764+ // If the definition goes to the same file and there is a namespace,
765+ // we should (and, in the case of anonymous namespaces, need to)
766+ // put the definition into the original namespace block.
767+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
768+ if (!Klass)
769+ return error (" moving to same file not supported for free functions" );
770+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
771+ const auto &TokBuf = AST->getTokens ();
772+ auto Tokens = TokBuf.expandedTokens ();
773+ auto It = llvm::lower_bound (
774+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
775+ return Tok.location () < EndLoc;
776+ });
777+ while (It != Tokens.end ()) {
778+ if (It->kind () != tok::semi) {
779+ ++It;
780+ continue ;
781+ }
782+ unsigned Offset =
783+ AST->getSourceManager ().getDecomposedLoc (It->endLocation ()).second ;
784+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
785+ }
786+ return error (
787+ " failed to determine insertion location: no end of class found" );
604788 }
605789
606790private:
0 commit comments