@@ -10,6 +10,7 @@ export interface CELCompletionItem {
1010 documentation ?: string ;
1111 insertText ?: string ;
1212 insertTextRules ?: "insertAsSnippet" ;
13+ v2Only ?: boolean ;
1314}
1415
1516// CEL Macros - expanded at parse time into comprehensions
@@ -359,6 +360,7 @@ export const celWorkshopFunctions: CELCompletionItem[] = [
359360 "Returns REQUIRE_TOUCHID. The cooldown parameter specifies the number of minutes before TouchID is required again." ,
360361 insertText : "require_touchid_with_cooldown_minutes(${1:minutes})" ,
361362 insertTextRules : "insertAsSnippet" ,
363+ v2Only : true ,
362364 } ,
363365 {
364366 label : "require_touchid_only_with_cooldown_minutes" ,
@@ -369,6 +371,7 @@ export const celWorkshopFunctions: CELCompletionItem[] = [
369371 "Returns REQUIRE_TOUCHID_ONLY. The cooldown parameter specifies the number of minutes before TouchID is required again." ,
370372 insertText : "require_touchid_only_with_cooldown_minutes(${1:minutes})" ,
371373 insertTextRules : "insertAsSnippet" ,
374+ v2Only : true ,
372375 } ,
373376] ;
374377
@@ -697,6 +700,11 @@ export interface CELVariable {
697700 name : string ;
698701 type : CELVariableType ;
699702 documentation ?: string ;
703+ dynamic ?: boolean ;
704+ v2Only ?: boolean ;
705+ // For list-type variables, describes the fields available on each element
706+ // (used for completions inside comprehensions like ancestors.filter(a, a.))
707+ itemFields ?: CELVariable [ ] ;
700708}
701709
702710// Track registration state to prevent duplicate registrations
@@ -766,8 +774,13 @@ export function registerCELLanguage(
766774
767775 // Build a map of variable names to types for quick lookup
768776 const variableTypes = new Map < string , CELVariableType > ( ) ;
777+ // Build a map of list variable names to their item fields
778+ const listItemFields = new Map < string , CELVariable [ ] > ( ) ;
769779 for ( const v of variables ) {
770780 variableTypes . set ( v . name , v . type ) ;
781+ if ( v . type === "list" && v . itemFields ) {
782+ listItemFields . set ( v . name , v . itemFields ) ;
783+ }
771784 }
772785
773786 // Build general completion items (not after a dot)
@@ -920,17 +933,19 @@ export function registerCELLanguage(
920933 endColumn : position . column ,
921934 } ) ;
922935
923- // Check if we're typing after a dot (e.g., "args." or "target.signing_time.")
924- // Captures the full dotted path before the final dot
936+ // Check if we're typing after a dot (e.g., "args.", "target.signing_time.", "ancestors[0] .")
937+ // Captures the full expression before the final dot, including bracket indexing
925938 const dotMatch = textUntilPosition . match (
926- / ( [ \w ] + (?: \. [ \w ] + ) * ) \. [ \w ] * $ / ,
939+ / ( [ \w ] + (?: \[ [ ^ \] ] * \] ) * (?: \ .[ \w ] + (?: \[ [ ^ \] ] * \] ) * ) * ) \. [ \w ] * $ / ,
927940 ) ;
928941 if ( dotMatch ) {
929942 const varPath = dotMatch [ 1 ] ;
930- const varType = variableTypes . get ( varPath ) ;
943+ // Strip bracket indices to resolve the base variable name (e.g. "ancestors[0]" → "ancestors")
944+ const basePath = varPath . replace ( / \[ [ ^ \] ] * \] / g, "" ) ;
945+ const varType = variableTypes . get ( basePath ) ;
931946
932947 // Check for sub-field completions (e.g., "target." → "signing_time")
933- const prefix = varPath + "." ;
948+ const prefix = basePath + "." ;
934949 const fieldCompletions : any [ ] = [ ] ;
935950 const seenFields = new Set < string > ( ) ;
936951
@@ -961,6 +976,21 @@ export function registerCELLanguage(
961976 suggestions : [ ...fieldCompletions , ...mapMethodCompletions ] ,
962977 } ;
963978 } else if ( varType === "list" ) {
979+ // If indexing into the list (e.g. "ancestors[0]."), offer item fields
980+ if ( varPath !== basePath ) {
981+ const itemFields = listItemFields . get ( basePath ) ;
982+ if ( itemFields ) {
983+ return {
984+ suggestions : itemFields . map ( ( f ) => ( {
985+ label : f . name ,
986+ kind : monaco . languages . CompletionItemKind . Field ,
987+ insertText : f . name ,
988+ detail : f . type ,
989+ documentation : f . documentation ,
990+ } ) ) ,
991+ } ;
992+ }
993+ }
964994 return {
965995 suggestions : [ ...fieldCompletions , ...listMethodCompletions ] ,
966996 } ;
@@ -977,6 +1007,26 @@ export function registerCELLanguage(
9771007 return { suggestions : fieldCompletions } ;
9781008 }
9791009
1010+ // Check if this is a comprehension iteration variable
1011+ // e.g. "ancestors.filter(a, a." → varPath is "a", look back for "ancestors.filter(a,"
1012+ const comprehensionMatch = textUntilPosition . match (
1013+ / ( [ \w ] + ) \. (?: a l l | e x i s t s | e x i s t s _ o n e | f i l t e r | m a p | s o r t B y ) \( \s * ( \w + ) \s * , / ,
1014+ ) ;
1015+ if ( comprehensionMatch && comprehensionMatch [ 2 ] === varPath ) {
1016+ const listName = comprehensionMatch [ 1 ] ;
1017+ const itemFields = listItemFields . get ( listName ) ;
1018+ if ( itemFields ) {
1019+ const itemFieldCompletions = itemFields . map ( ( f ) => ( {
1020+ label : f . name ,
1021+ kind : monaco . languages . CompletionItemKind . Field ,
1022+ insertText : f . name ,
1023+ detail : f . type ,
1024+ documentation : f . documentation ,
1025+ } ) ) ;
1026+ return { suggestions : itemFieldCompletions } ;
1027+ }
1028+ }
1029+
9801030 // No type info: return fields if any, otherwise all methods as fallback
9811031 if ( fieldCompletions . length > 0 ) {
9821032 return { suggestions : fieldCompletions } ;
0 commit comments