@@ -105,7 +105,7 @@ public SettexHoverHandler(SettexWorkspace workspace, ILogger<SettexHoverHandler>
105105 {
106106 // Le chemin complet inclut les blocs imbriqués traversés, donc
107107 // survoler "Port" dans `Server { Port = … }` cible bien "Server.Port".
108- var ( pathSegments , envName , isObjectHeader ) = assignmentInfo . Value ;
108+ var ( pathSegments , envName , isObjectHeader , segmentIndex ) = assignmentInfo . Value ;
109109
110110 // Vérifier que le mot sous le curseur est bien le path (ou une partie du path)
111111 var isOnPath = pathSegments . Any ( segment => segment == word ) ;
@@ -114,8 +114,14 @@ public SettexHoverHandler(SettexWorkspace workspace, ILogger<SettexHoverHandler>
114114 {
115115 var path = string . Join ( "." , pathSegments ) ;
116116
117- // Détecter si le mot survolé est un segment d'objet (pas le dernier segment)
118- var wordIndex = pathSegments . IndexOf ( word ) ;
117+ // Quel segment est sous le curseur — déduit de la colonne, pas d'un
118+ // IndexOf : sur `A { B { A = 1 } }` le chemin est A.B.A, et chercher
119+ // la première occurrence de "A" renvoyait toujours 0, si bien que
120+ // survoler l'affectation la plus interne affichait l'objet A.
121+ var wordIndex = segmentIndex >= 0 && segmentIndex < pathSegments . Count
122+ ? segmentIndex
123+ : pathSegments . IndexOf ( word ) ;
124+
119125 var isObjectSegment = isObjectHeader || ( wordIndex >= 0 && wordIndex < pathSegments . Count - 1 ) ;
120126
121127 if ( isObjectSegment )
@@ -548,7 +554,7 @@ private static string EscapeString(string str)
548554 /// <summary>
549555 /// Trouve une assignation à la position donnée et retourne l'assignation + l'environnement (null si dans base).
550556 /// </summary>
551- private static ( List < string > Path , string ? EnvName , bool IsObjectHeader ) ? FindAssignmentAtPosition (
557+ private static ( List < string > Path , string ? EnvName , bool IsObjectHeader , int SegmentIndex ) ? FindAssignmentAtPosition (
552558 Core . Parser . Ast . FileNode ast ,
553559 Position position ,
554560 string ? documentFilePath = null )
@@ -574,15 +580,15 @@ private static (List<string> Path, string? EnvName, bool IsObjectHeader)? FindAs
574580 var found = FindAssignmentInStatements ( settings . Block . Statements , new List < string > ( ) , line , column ) ;
575581 if ( found != null )
576582 {
577- return ( found . Value . Path , null , found . Value . IsObjectHeader ) ; // Base, pas d'environnement
583+ return ( found . Value . Path , null , found . Value . IsObjectHeader , found . Value . SegmentIndex ) ; // Base, pas d'environnement
578584 }
579585 }
580586 else if ( stmt is Core . Parser . Ast . EnvBlockNode env )
581587 {
582588 var found = FindAssignmentInStatements ( env . SettingsBlock . Block . Statements , new List < string > ( ) , line , column ) ;
583589 if ( found != null )
584590 {
585- return ( found . Value . Path , env . EnvironmentName , found . Value . IsObjectHeader ) ; // Dans un environnement
591+ return ( found . Value . Path , env . EnvironmentName , found . Value . IsObjectHeader , found . Value . SegmentIndex ) ; // Dans un environnement
586592 }
587593 }
588594 }
@@ -597,7 +603,7 @@ private static (List<string> Path, string? EnvName, bool IsObjectHeader)? FindAs
597603 /// (<c>Server.Port</c> et non <c>Port</c>) — c'est ce chemin que l'overlay
598604 /// recherche dans la configuration évaluée.
599605 /// </summary>
600- private static ( List < string > Path , bool IsObjectHeader ) ? FindAssignmentInStatements (
606+ private static ( List < string > Path , bool IsObjectHeader , int SegmentIndex ) ? FindAssignmentInStatements (
601607 IReadOnlyList < Core . Parser . Ast . IStatement > statements ,
602608 List < string > prefix ,
603609 int line ,
@@ -611,7 +617,8 @@ private static (List<string> Path, bool IsObjectHeader)? FindAssignmentInStateme
611617 {
612618 var fullPath = new List < string > ( prefix ) ;
613619 fullPath . AddRange ( assignment . Path . Segments ) ;
614- return ( fullPath , false ) ;
620+
621+ return ( fullPath , false , HoveredSegmentIndex ( assignment , prefix . Count , line , column ) ) ;
615622 }
616623 }
617624 else if ( stmt is Core . Parser . Ast . NestedBlockNode nested )
@@ -630,14 +637,50 @@ private static (List<string> Path, bool IsObjectHeader)? FindAssignmentInStateme
630637 // ways. Checked after the body so an inner match still wins.
631638 if ( IsPositionOnBlockName ( nested , line , column ) )
632639 {
633- return ( nestedPrefix , true ) ;
640+ // A header names the block itself: the last segment of its path.
641+ return ( nestedPrefix , true , nestedPrefix . Count - 1 ) ;
634642 }
635643 }
636644 }
637645
638646 return null ;
639647 }
640648
649+ /// <summary>
650+ /// Index, dans le chemin complet, du segment réellement sous le curseur.
651+ /// L'affectation démarre sur son chemin, donc les segments occupent des colonnes
652+ /// consécutives séparées par un point ; on les parcourt pour trouver celui qui
653+ /// contient la colonne. Renvoie -1 si le curseur est au-delà du chemin (sur la
654+ /// valeur, par exemple), auquel cas l'appelant retombe sur son ancien calcul.
655+ /// </summary>
656+ private static int HoveredSegmentIndex (
657+ Core . Parser . Ast . AssignmentNode assignment ,
658+ int prefixLength ,
659+ int line ,
660+ int column )
661+ {
662+ if ( line != assignment . Location . Line )
663+ {
664+ return - 1 ;
665+ }
666+
667+ var segmentStart = assignment . Location . Column ;
668+
669+ for ( var i = 0 ; i < assignment . Path . Segments . Count ; i ++ )
670+ {
671+ var segmentEnd = segmentStart + assignment . Path . Segments [ i ] . Length ;
672+
673+ if ( column >= segmentStart && column < segmentEnd )
674+ {
675+ return prefixLength + i ;
676+ }
677+
678+ segmentStart = segmentEnd + 1 ; // le point
679+ }
680+
681+ return - 1 ;
682+ }
683+
641684 /// <summary>
642685 /// Vérifie si une position est sur le <strong>nom</strong> d'un bloc imbriqué,
643686 /// c'est-à-dire sur son en-tête et non dans son corps. Le nœud démarre sur son
0 commit comments