11import { extension } from "../state" ;
2- import { ContextHistory , Selection , Variable } from "../types/context" ;
2+ import { LJContext , Range , LJVariable } from "../types/context" ;
3+ import { SourcePosition } from "../types/diagnostics" ;
4+ import { getOriginalVariableName } from "../utils/utils" ;
35
4- export function handleContextHistory ( contextHistory : ContextHistory ) {
5- extension . contextHistory = contextHistory ;
6+ export function handleContext ( context : LJContext ) {
7+ extension . context = context ;
8+ updateContextForSelection ( extension . currentSelection ) ;
9+ extension . webview . sendMessage ( { type : "context" , context : extension . context , errorAtCursor : extension . errorAtCursor } ) ;
610}
711
8- // Gets the variables in scope for a given file and position
9- // Returns null if position not in any scope
10- export function getVariablesInScope ( file : string , selection : Selection ) : Variable [ ] | null {
11- if ( ! extension . contextHistory || ! selection || ! file ) return null ;
12-
13- // get variables in file
14- const fileVars = extension . contextHistory . vars [ file ] ;
15- if ( ! fileVars ) return null ;
16-
17- // get variables in the current scope based on the selection
18- let mostSpecificScope : string | null = null ;
19- let minScopeSize = Infinity ;
20-
21- // find the most specific scope that contains the selection
22- for ( const scope of Object . keys ( fileVars ) ) {
23- const scopeSelection = parseScopeString ( scope ) ;
24- if ( isSelectionWithinScope ( selection , scopeSelection ) ) {
25- const scopeSize = ( scopeSelection . endLine - scopeSelection . startLine ) * 10000 + ( scopeSelection . endColumn - scopeSelection . startColumn ) ;
26- if ( scopeSize < minScopeSize ) {
27- mostSpecificScope = scope ;
28- minScopeSize = scopeSize ;
29- }
12+ export function updateContextForSelection ( selection : Range ) {
13+ if ( ! selection ) return ;
14+
15+ const globalVars = extension . context . globalVars || [ ] ;
16+ const localVars = extension . context . localVars || [ ] ;
17+ const variablesInScope = getVariablesInScope ( localVars , extension . file , selection ) ;
18+ const visibleVarsByPosition = getVisibleVariables ( variablesInScope , extension . file , selection , false ) ;
19+ const visibleVarsByAnnotationPosition = getVisibleVariables ( variablesInScope , extension . file , selection , true ) ;
20+ const allVars = sortVariables ( normalizeVariableRefinements ( [ ...globalVars , ...visibleVarsByPosition ] ) ) ;
21+ extension . context . visibleVars = visibleVarsByAnnotationPosition ;
22+ extension . context . allVars = allVars ;
23+ }
24+
25+ function getVariablesInScope ( variables : LJVariable [ ] , file : string , selection : Range ) : LJVariable [ ] {
26+ const scopes = extension . context . fileScopes [ file ] || [ ] ;
27+ const enclosingScopes = scopes . filter ( scope => isRangeWithin ( selection , scope ) ) ;
28+ return variables . filter ( v =>
29+ v . position ?. file === file &&
30+ enclosingScopes . some ( scope => isRangeWithin ( v . position , scope ) )
31+ ) ;
32+ }
33+
34+ function getVisibleVariables ( variables : LJVariable [ ] , file : string , selection : Range , useAnnotationPosition : boolean ) : LJVariable [ ] {
35+ const isCollapsedRange = selection . lineStart === selection . lineEnd && selection . colStart === selection . colEnd ;
36+ const fileScopes = isCollapsedRange ? ( extension . context . fileScopes [ file ] || [ ] ) : [ ] ;
37+ return variables . filter ( ( variable ) => {
38+ // variable must be declared in the same file
39+ if ( ! variable . position || variable . position ?. file !== file ) return false ;
40+
41+ // single point cursor
42+ if ( isCollapsedRange ) {
43+ const position : SourcePosition = ( useAnnotationPosition && variable . annotationPosition ) || variable . position ;
44+
45+ // variable was declared before the cursor line or its in the same line but before the cursor column
46+ const beforeCursor = isPositionBefore ( position , selection ) ;
47+ if ( ! beforeCursor ) return false ;
48+
49+ // exclude variables that in unreachable scopes
50+ const isInUnreachableScope = fileScopes . some ( scope =>
51+ isRangeWithin ( variable . position ! , scope ) && ! isRangeWithin ( selection , scope )
52+ ) ;
53+ return ! isInUnreachableScope ;
3054 }
31- }
32- if ( mostSpecificScope === null )
33- return null ;
55+ // normal range, filter variables that intersect the selection
56+ return rangesIntersect ( variable . position , selection ) ;
57+ } ) ;
58+ }
3459
35- // filter variables to only include those that are reachable based on their position
36- const variablesInScope = fileVars [ mostSpecificScope ] ;
37- const reachableVariables = getReachableVariables ( variablesInScope , selection ) ;
38- return reachableVariables . filter ( v => ! v . name . startsWith ( "this#" ) ) ;
60+ // Normalizes the range to ensure start is before end
61+ export function normalizeRange ( range : Range ) : Range {
62+ if ( isBefore ( range . lineStart , range . colStart , range . lineEnd , range . colEnd ) ) return range ;
63+ return { lineStart : range . lineEnd , colStart : range . colEnd , lineEnd : range . lineStart , colEnd : range . colStart } ;
3964}
4065
41- function parseScopeString ( scope : string ) : Selection {
42- const [ start , end ] = scope . split ( "-" ) ;
43- const [ startLine , startColumn ] = start . split ( ":" ) . map ( Number ) ;
44- const [ endLine , endColumn ] = end . split ( ":" ) . map ( Number ) ;
45- return { startLine, startColumn, endLine, endColumn } ;
66+ export function rangesIntersect ( a : Range , b : Range ) : boolean {
67+ return isBeforeOrEqual ( a . lineStart , a . colStart , b . lineEnd , b . colEnd ) &&
68+ isBeforeOrEqual ( b . lineStart , b . colStart , a . lineEnd , a . colEnd ) ;
4669}
4770
48- function isSelectionWithinScope ( selection : Selection , scope : Selection ) : boolean {
49- const startsWithin = selection . startLine > scope . startLine ||
50- ( selection . startLine === scope . startLine && selection . startColumn >= scope . startColumn ) ;
51- const endsWithin = selection . endLine < scope . endLine ||
52- ( selection . endLine === scope . endLine && selection . endColumn <= scope . endColumn ) ;
53- return startsWithin && endsWithin ;
71+ export function isRangeWithin ( range : Range , another : Range ) : boolean {
72+ return isBeforeOrEqual ( another . lineStart , another . colStart , range . lineStart , range . colStart ) &&
73+ isBeforeOrEqual ( range . lineEnd , range . colEnd , another . lineEnd , another . colEnd ) ;
5474}
5575
56- function getReachableVariables ( variables : Variable [ ] , selection : Selection ) : Variable [ ] {
57- return variables . filter ( ( variable ) => {
58- const placement = variable . placementInCode ?. position ;
59- const startPosition = variable . annPosition || placement ;
60- if ( ! startPosition || variable . isParameter ) return true ; // if is parameter we need to access it even if it's declared after the selection (for method and parameter refinements)
61-
62- // variable was declared before the cursor line or its in the same line but before the cursor column
63- return startPosition . line < selection . startLine || startPosition . line === selection . startLine && startPosition . column <= selection . startColumn ;
76+ export function isPositionBefore ( range : Range , another : Range ) : boolean {
77+ return isBefore ( range . lineStart , range . colStart , another . lineStart , another . colStart ) ;
78+ }
79+
80+ function isBefore ( line1 : number , col1 : number , line2 : number , col2 : number ) : boolean {
81+ return line1 < line2 || ( line1 === line2 && col1 < col2 ) ;
82+ }
83+
84+ function isBeforeOrEqual ( line1 : number , col1 : number , line2 : number , col2 : number ) : boolean {
85+ return line1 < line2 || ( line1 === line2 && col1 <= col2 ) ;
86+ }
87+
88+ export function filterInstanceVariables ( variables : LJVariable [ ] ) : LJVariable [ ] {
89+ return variables . filter ( v => ! v . name . includes ( "#" ) ) ;
90+ }
91+
92+ export function filterDuplicateVariables ( variables : LJVariable [ ] ) : LJVariable [ ] {
93+ const uniqueVariables : Map < string , LJVariable > = new Map ( ) ;
94+ for ( const variable of variables ) {
95+ if ( ! uniqueVariables . has ( variable . name ) ) {
96+ uniqueVariables . set ( variable . name , variable ) ;
97+ }
98+ }
99+ return Array . from ( uniqueVariables . values ( ) ) ;
100+ }
101+
102+ // Sorts variables by their position or name
103+ function sortVariables ( variables : LJVariable [ ] ) : LJVariable [ ] {
104+ return variables . sort ( ( left , right ) => {
105+ if ( ! left . position && ! right . position ) return compareVariableNames ( left , right ) ;
106+ if ( ! left . position ) return 1 ;
107+ if ( ! right . position ) return - 1 ;
108+ if ( left . position . lineStart !== right . position . lineStart ) return left . position . lineStart - right . position . lineStart ;
109+ if ( left . position . colStart !== right . position . colStart ) return right . position . colStart - left . position . colStart ;
110+ return compareVariableNames ( left , right ) ;
111+ } ) ;
112+ }
113+
114+ function compareVariableNames ( a : LJVariable , b : LJVariable ) : number {
115+ if ( a . name . startsWith ( "#" ) && b . name . startsWith ( "#" ) ) return getOriginalVariableName ( a . name ) . localeCompare ( getOriginalVariableName ( b . name ) ) ;
116+ if ( a . name . startsWith ( "#" ) ) return 1 ;
117+ if ( b . name . startsWith ( "#" ) ) return - 1 ;
118+ return a . name . localeCompare ( b . name ) ;
119+ }
120+
121+ function normalizeVariableRefinements ( variables : LJVariable [ ] ) : LJVariable [ ] {
122+ return Array . from ( new Map ( variables . map ( v => [ v . refinement , v ] ) ) . values ( ) ) . flatMap ( v => {
123+ if ( ! v . refinement || v . refinement === "true" ) return [ ] ; // filter out trivial refinements
124+ if ( v . refinement . includes ( "==" ) ) {
125+ const [ left , right ] = v . refinement . split ( "==" ) . map ( s => s . trim ( ) ) ;
126+ return left !== right ? [ v ] : [ ] ; // filter tautologies like x == x
127+ }
128+ if ( v . refinement . includes ( "!=" ) || v . refinement . includes ( ">" ) || v . refinement . includes ( "<" ) ) return [ v ] ;
129+ return [ { ...v , refinement : `${ v . name } == ${ v . refinement } ` } ] ;
64130 } ) ;
65131}
0 commit comments