@@ -427,6 +427,67 @@ export default class Analyzer {
427427 }
428428 }
429429
430+ /**
431+ * Like findOriginalDeclaration, but only searches within the file and doesn't
432+ * take parent scopes into consideration.
433+ *
434+ * TODO: Improve handling of multiple variable assignments and declarations in
435+ * one declaration command.
436+ */
437+ public findOriginalDeclarationWithinFile ( {
438+ uri,
439+ word,
440+ kind,
441+ } : {
442+ uri : string
443+ word : string
444+ kind : LSP . SymbolKind
445+ } ) : LSP . Location | null {
446+ const rootNode = this . uriToAnalyzedDocument [ uri ] ?. tree . rootNode
447+
448+ if ( ! rootNode ) {
449+ return null
450+ }
451+
452+ let declaration : Parser . SyntaxNode | null | undefined
453+ let continueSearching = false
454+
455+ TreeSitterUtil . forEach ( rootNode , ( n ) => {
456+ if ( ( declaration && ! continueSearching ) || n . type === 'subshell' ) {
457+ return false
458+ }
459+
460+ let definedSymbol : Parser . SyntaxNode | null | undefined
461+
462+ if (
463+ kind === LSP . SymbolKind . Variable &&
464+ ( [ 'declaration_command' , 'variable_assignment' , 'for_statement' ] . includes (
465+ n . type ,
466+ ) ||
467+ ( n . type === 'command' && n . text . includes ( ':=' ) ) )
468+ ) {
469+ definedSymbol = n . descendantsOfType ( 'variable_name' ) . at ( 0 )
470+ } else if ( kind === LSP . SymbolKind . Function && n . type === 'function_definition' ) {
471+ definedSymbol = n . firstNamedChild
472+ }
473+
474+ if ( definedSymbol ?. text === word ) {
475+ declaration = definedSymbol
476+ continueSearching = n . type === 'command'
477+ }
478+
479+ if ( n . type === 'function_definition' ) {
480+ return false
481+ }
482+
483+ return true
484+ } )
485+
486+ return declaration
487+ ? LSP . Location . create ( uri , TreeSitterUtil . range ( declaration ) )
488+ : null
489+ }
490+
430491 /**
431492 * Find all the locations where the given word was defined or referenced.
432493 * This will include commands, functions, variables, etc.
@@ -495,12 +556,14 @@ export default class Analyzer {
495556 word,
496557 kind,
497558 start,
559+ end,
498560 scope,
499561 } : {
500562 uri : string
501563 word : string
502564 kind : LSP . SymbolKind
503565 start ?: LSP . Position
566+ end ?: LSP . Position
504567 scope ?: LSP . Range
505568 } ) : LSP . Range [ ] {
506569 const scopeNode = scope
@@ -526,6 +589,9 @@ export default class Analyzer {
526589 const startPosition = start
527590 ? { row : start . line , column : start . character }
528591 : baseNode . startPosition
592+ const endPosition = end
593+ ? { row : end . line + 1 , column : end . character + 1 }
594+ : baseNode . endPosition
529595
530596 const ignoredRanges : LSP . Range [ ] = [ ]
531597 const filter =
@@ -545,18 +611,26 @@ export default class Analyzer {
545611 const definedVariable = definition ?. descendantsOfType ( 'variable_name' ) ?. at ( 0 )
546612
547613 // Special handling for var="$var" cases
548- if ( definedVariable ?. text === word && ! n . equals ( definedVariable ) ) {
549- // `start?.line` is assumed to be the same as the variable's
550- // original declaration line.
551- if ( definition ?. startPosition . row === start ?. line ) {
552- return false
614+ if ( definedVariable ?. text === word ) {
615+ if ( n . equals ( definedVariable ) ) {
616+ // `end?.line` is assumed to be the same as the variable's
617+ // declaration line.
618+ if ( definition ?. startPosition . row === end ?. line ) {
619+ return false
620+ }
621+ } else {
622+ // `start?.line` is assumed to be the same as the variable's
623+ // original declaration line.
624+ if ( definition ?. startPosition . row === start ?. line ) {
625+ return false
626+ }
627+
628+ // Returning true here is a good enough heuristic for most
629+ // cases. It breaks down when redeclaration happens in multiple
630+ // nested scopes, handling those more complex situations can be
631+ // done later on if use cases arise.
632+ return true
553633 }
554-
555- // Returning true here is a good enough heuristic for most cases.
556- // It breaks down when redeclaration happens in multiple nested
557- // scopes, handling those more complex situations can be done
558- // later on if use cases arise.
559- return true
560634 }
561635
562636 if ( ! parentScope || baseNode . equals ( parentScope ) ) {
@@ -621,7 +695,7 @@ export default class Analyzer {
621695 }
622696
623697 return baseNode
624- . descendantsOfType ( typeOfDescendants , startPosition )
698+ . descendantsOfType ( typeOfDescendants , startPosition , endPosition )
625699 . filter ( filter )
626700 . map ( ( n ) => {
627701 if ( n . type === 'function_definition' && n . firstNamedChild ) {
@@ -867,9 +941,9 @@ export default class Analyzer {
867941 /**
868942 * If `includeAllWorkspaceSymbols` is true, this returns all URIs from the
869943 * background analysis. Else, it returns the URIs of the files that are
870- * connected to `uri` via sourcing.
944+ * linked to `uri` via sourcing.
871945 */
872- public findAllConnectedUris ( uri : string ) : string [ ] {
946+ public findAllLinkedUris ( uri : string ) : string [ ] {
873947 if ( this . includeAllWorkspaceSymbols ) {
874948 return Object . keys ( this . uriToAnalyzedDocument )
875949 }
0 commit comments