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,186 @@ 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;
580623 }
581- unsigned Offset = Sel.AST ->getSourceManager ()
582- .getDecomposedLoc (It->endLocation ())
583- .second ;
584- return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
624+ });
625+ if (!CandidateLoc)
626+ return ;
627+
628+ // If our definition is constrained to the same file, ignore
629+ // definitions that are not located there.
630+ // If our definition is not constrained to the same file, but
631+ // our anchor definition is in the same file, then we also put our
632+ // definition there, because that appears to be the user preference.
633+ // Exception: If the existing definition is a template, then the
634+ // location is likely due to technical necessity rather than preference,
635+ // so ignore that definition.
636+ bool CandidateSameFile = TuURI == CandidateLoc->uri .uri ();
637+ if (SameFile && !CandidateSameFile)
638+ return ;
639+ if (!SameFile && CandidateSameFile) {
640+ if (Candidate->isTemplateDecl ())
641+ return ;
642+ SameFile = true ;
585643 }
586- return error (
587- " failed to determine insertion location: no end of class found" );
644+ Anchor = *CandidateLoc;
645+ };
646+
647+ // Try to find adjacent function declarations.
648+ // Determine the closest one by alternatingly going "up" and "down"
649+ // from our function in increasing steps.
650+ const DeclContext *ParentContext = Source->getParent ();
651+ const auto SourceIt = llvm::find_if (
652+ ParentContext->decls (), [this ](const Decl *D) { return D == Source; });
653+ if (SourceIt == ParentContext->decls_end ())
654+ return {};
655+ const int Preceding = std::distance (ParentContext->decls_begin (), SourceIt);
656+ const int Following =
657+ std::distance (SourceIt, ParentContext->decls_end ()) - 1 ;
658+ for (int Offset = 1 ; Offset <= Preceding || Offset <= Following; ++Offset) {
659+ if (Offset <= Preceding)
660+ CheckCandidate (
661+ *std::next (ParentContext->decls_begin (), Preceding - Offset));
662+ if (Anchor)
663+ return InsertionAnchor{*Anchor, RelativeInsertPos::After};
664+ if (Offset <= Following)
665+ CheckCandidate (*std::next (SourceIt, Offset));
666+ if (Anchor)
667+ return InsertionAnchor{*Anchor, RelativeInsertPos::Before};
588668 }
669+ return {};
670+ }
589671
590- auto Region = getEligiblePoints (
591- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
672+ // We don't know the actual start or end of the definition, only the position
673+ // of the name. Therefore, we heuristically try to locate the last token
674+ // before or in this function, respectively. Adapt as required by user code.
675+ std::optional<Position> getInsertionPointFromExistingDefinition (
676+ SourceManagerForFile &SMFF, const llvm::MemoryBuffer &Buffer,
677+ const Location &Loc, RelativeInsertPos RelInsertPos, ParsedAST *AST) {
678+ auto StartOffset = positionToOffset (Buffer.getBuffer (), Loc.range .start );
679+ if (!StartOffset)
680+ return {};
681+ SourceLocation InsertionLoc;
682+ SourceManager &SM = SMFF.get ();
683+
684+ auto InsertBefore = [&] {
685+ // Go backwards until we encounter one of the following:
686+ // - An opening brace (of a namespace).
687+ // - A closing brace (of a function definition).
688+ // - A semicolon (of a declaration).
689+ // If no such token was found, then the first token in the file starts the
690+ // definition.
691+ auto Tokens = syntax::tokenize (
692+ syntax::FileRange (SM.getMainFileID (), 0 , *StartOffset), SM,
693+ AST->getLangOpts ());
694+ if (Tokens.empty ())
695+ return ;
696+ for (auto I = std::rbegin (Tokens);
697+ InsertionLoc.isInvalid () && I != std::rend (Tokens); ++I) {
698+ switch (I->kind ()) {
699+ case tok::l_brace:
700+ case tok::r_brace:
701+ case tok::semi:
702+ if (I != std::rbegin (Tokens))
703+ InsertionLoc = std::prev (I)->location ();
704+ else
705+ InsertionLoc = I->endLocation ();
706+ break ;
707+ default :
708+ break ;
709+ }
710+ }
711+ if (InsertionLoc.isInvalid ())
712+ InsertionLoc = Tokens.front ().location ();
713+ };
592714
593- assert (!Region.EligiblePoints .empty ());
594- auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
595- if (!Offset)
596- return Offset.takeError ();
715+ if (RelInsertPos == RelativeInsertPos::Before) {
716+ InsertBefore ();
717+ } else {
718+ // Skip over one top-level pair of parentheses (for the parameter list)
719+ // and one pair of curly braces (for the code block).
720+ // If that fails, insert before the function instead.
721+ auto Tokens =
722+ syntax::tokenize (syntax::FileRange (SM.getMainFileID (), *StartOffset,
723+ Buffer.getBuffer ().size ()),
724+ SM, 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+ if (!InsertionLoc. isValid ())
754+ return {} ;
755+ return sourceLocToPosition (SM, InsertionLoc);
756+ }
602757
603- return InsertionPoint{*TargetContext, *Offset};
758+ // Returns the most natural insertion point in this file.
759+ // This is a fallback for when we failed to find an existing definition to
760+ // place the new one next to. It only considers namespace proximity.
761+ llvm::Expected<InsertionPoint> getInsertionPointInMainFile (ParsedAST *AST) {
762+ // If the definition goes to the same file and there is a namespace,
763+ // we should (and, in the case of anonymous namespaces, need to)
764+ // put the definition into the original namespace block.
765+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
766+ if (!Klass)
767+ return error (" moving to same file not supported for free functions" );
768+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
769+ const auto &TokBuf = AST->getTokens ();
770+ auto Tokens = TokBuf.expandedTokens ();
771+ auto It = llvm::lower_bound (
772+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
773+ return Tok.location () < EndLoc;
774+ });
775+ while (It != Tokens.end ()) {
776+ if (It->kind () != tok::semi) {
777+ ++It;
778+ continue ;
779+ }
780+ unsigned Offset =
781+ AST->getSourceManager ().getDecomposedLoc (It->endLocation ()).second ;
782+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
783+ }
784+ return error (
785+ " failed to determine insertion location: no end of class found" );
604786 }
605787
606788private:
0 commit comments