1
1
import * as fs from 'node:fs/promises' ;
2
2
import * as path from 'node:path' ;
3
3
import * as vscode from 'vscode' ;
4
- import { CompletionItem , CompletionItemKind , type Disposable , Hover , MarkdownString } from 'vscode' ;
4
+ import { type Disposable , Hover , MarkdownString } from 'vscode' ;
5
5
6
- let outputChannel : vscode . OutputChannel ;
6
+ const DEBUG_ENABLED = false ;
7
+ export const outputChannel = vscode . window . createOutputChannel ( 'IAM Action Snippets' ) ;
8
+
9
+ export function log ( message : string ) {
10
+ if ( DEBUG_ENABLED ) {
11
+ outputChannel . appendLine ( `[DEBUG] ${ message } ` ) ;
12
+ }
13
+ }
7
14
8
15
interface IamActionData {
9
16
access_level : string ;
@@ -55,11 +62,9 @@ class IamActionMappings {
55
62
}
56
63
}
57
64
58
- outputChannel . appendLine (
59
- `Loaded ${ this . iamActionsMap . size } IAM actions across ${ this . servicePrefixMap . size } services` ,
60
- ) ;
65
+ log ( `Loaded ${ this . iamActionsMap . size } IAM actions across ${ this . servicePrefixMap . size } services` ) ;
61
66
} catch ( error ) {
62
- outputChannel . appendLine ( `Error loading IAM actions: ${ error } ` ) ;
67
+ log ( `Error loading IAM actions: ${ error } ` ) ;
63
68
}
64
69
}
65
70
@@ -100,8 +105,11 @@ class IamActionMappings {
100
105
}
101
106
102
107
export function activate ( context : vscode . ExtensionContext ) {
103
- outputChannel = vscode . window . createOutputChannel ( 'IAM Action Snippets' ) ;
104
- outputChannel . appendLine ( 'IAM Action Snippets extension activated' ) ;
108
+ if ( DEBUG_ENABLED ) {
109
+ log ( 'IAM Action Snippets extension activated (debug mode)' ) ;
110
+ } else {
111
+ outputChannel . appendLine ( 'IAM Action Snippets extension activated' ) ;
112
+ }
105
113
106
114
const iamActionMappings = new IamActionMappings ( ) ;
107
115
const disposable : Disposable [ ] = [ ] ;
@@ -113,7 +121,7 @@ export function activate(context: vscode.ExtensionContext) {
113
121
{
114
122
async provideCompletionItems ( document : vscode . TextDocument , position : vscode . Position ) {
115
123
if ( await isBelowActionKey ( document , position ) ) {
116
- outputChannel . appendLine ( `Providing completion items at position: ${ position . line } :${ position . character } ` ) ;
124
+ log ( `Providing completion items at position: ${ position . line } :${ position . character } ` ) ;
117
125
const allActions = await iamActionMappings . getAllIamActions ( ) ;
118
126
return Promise . all (
119
127
allActions . map ( async ( action ) => {
@@ -251,7 +259,7 @@ export function activate(context: vscode.ExtensionContext) {
251
259
252
260
if ( range ) {
253
261
const word = document . getText ( range ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, '' ) ;
254
- outputChannel . appendLine ( `Providing hover for: ${ word } ` ) ;
262
+ log ( `Providing hover for: ${ word } ` ) ;
255
263
256
264
if ( word . includes ( '*' ) ) {
257
265
const matchingActions = await iamActionMappings . getMatchingActions ( word ) ;
@@ -323,77 +331,120 @@ export function activate(context: vscode.ExtensionContext) {
323
331
}
324
332
325
333
export function deactivate ( ) {
326
- outputChannel . appendLine ( 'IAM Action Snippets extension deactivated' ) ;
334
+ log ( 'IAM Action Snippets extension deactivated' ) ;
327
335
}
328
336
329
337
async function isBelowActionKey ( document : vscode . TextDocument , position : vscode . Position ) : Promise < boolean > {
330
- const maxLinesUp = 40 ;
331
- const startLine = Math . max ( 0 , position . line - maxLinesUp ) ;
332
- const text = document . getText ( new vscode . Range ( startLine , 0 , position . line , position . character ) ) ;
338
+ // Use the full text from the top of the document until the current position
339
+ const text = document . getText ( new vscode . Range ( new vscode . Position ( 0 , 0 ) , position ) ) ;
333
340
341
+ // JSON and Terraform
334
342
if ( document . languageId === 'json' || document . languageId === 'terraform' ) {
335
343
const lines = text . split ( '\n' ) . reverse ( ) ;
344
+ const jsonRegex = / " a c t i o n " \s * : \s * \[ / i;
345
+ const terraformRegex = / a c t i o n \s * = \s * \[ / i;
336
346
for ( const line of lines ) {
337
- const trimmedLine = line . trim ( ) . toLowerCase ( ) ;
338
- if ( document . languageId === 'json' ) {
339
- if ( trimmedLine . includes ( '"action"' ) && trimmedLine . includes ( '[' ) ) {
340
- return true ;
341
- }
342
- } else if ( document . languageId === 'terraform' ) {
343
- if ( trimmedLine . includes ( 'action' ) && trimmedLine . includes ( '=' ) && trimmedLine . includes ( '[' ) ) {
344
- return true ;
345
- }
347
+ const trimmedLine = line . trim ( ) ;
348
+ if ( document . languageId === 'json' && jsonRegex . test ( trimmedLine ) ) {
349
+ return true ;
350
+ }
351
+ if ( document . languageId === 'terraform' && terraformRegex . test ( trimmedLine ) ) {
352
+ return true ;
346
353
}
347
354
if ( trimmedLine . startsWith ( '}' ) || trimmedLine . startsWith ( ']' ) ) {
348
355
break ;
349
356
}
350
357
}
351
358
}
352
359
360
+ // YAML / YML
353
361
if ( document . languageId === 'yaml' || document . languageId === 'yml' ) {
354
- const lines = text . split ( '\n' ) . reverse ( ) ;
355
- for ( const line of lines ) {
356
- const trimmedLine = line . trim ( ) . toLowerCase ( ) ;
357
- if (
358
- trimmedLine . startsWith ( 'action:' ) ||
359
- trimmedLine . startsWith ( '- action:' ) ||
360
- trimmedLine . startsWith ( 'notaction:' ) ||
361
- trimmedLine . startsWith ( '- notaction:' )
362
- ) {
363
- return true ;
362
+ const allLines = document . getText ( ) . split ( '\n' ) ;
363
+ const currentLineIndex = position . line ;
364
+ const currentLine = allLines [ currentLineIndex ] || '' ;
365
+ const currentIndent = currentLine . search ( / \S / ) ;
366
+ log ( `[YAML] Current line (${ currentLineIndex } ): ${ currentLine } ` ) ;
367
+ log ( `[YAML] Current indent: ${ currentIndent } ` ) ;
368
+
369
+ // If the current line itself is a key, disable autocomplete.
370
+ if ( / ^ [ - \s ] * \w + \s * : / . test ( currentLine ) ) {
371
+ log ( '[YAML] Current line appears to be a key, disabling autocomplete' ) ;
372
+ return false ;
373
+ }
374
+
375
+ // Scan upward for the nearest parent key with lower indent
376
+ for ( let i = currentLineIndex - 1 ; i >= 0 ; i -- ) {
377
+ const line = allLines [ i ] ;
378
+ const trimmedLine = line . trim ( ) ;
379
+ if ( trimmedLine === '' || trimmedLine . startsWith ( '#' ) ) {
380
+ continue ;
364
381
}
365
- if (
366
- trimmedLine !== '' &&
367
- ! trimmedLine . startsWith ( '-' ) &&
368
- ! trimmedLine . startsWith ( '- ' ) &&
369
- ! trimmedLine . startsWith ( '#' )
370
- ) {
371
- break ;
382
+ if ( ! line . includes ( ':' ) ) {
383
+ continue ;
384
+ }
385
+ log ( `[YAML] Scanning line ${ i } : ${ line } ` ) ;
386
+ const match = line . match ( / ^ ( [ \t ] * ) (?: - \s * ) ? ( \w + ) \s * : / ) ;
387
+ if ( match ) {
388
+ const parentIndent = match [ 1 ] . length ;
389
+ if ( parentIndent < currentIndent ) {
390
+ const parentKey = match [ 2 ] . toLowerCase ( ) ;
391
+ log ( `[YAML] Found parent key: ${ parentKey } at indent: ${ parentIndent } ` ) ;
392
+ if ( parentKey === 'action' ) {
393
+ log ( `[YAML] Position is under 'action' key, enabling autocomplete` ) ;
394
+ return true ;
395
+ }
396
+ log ( `[YAML] Parent key is not 'action', stopping search.` ) ;
397
+ break ;
398
+ }
372
399
}
373
400
}
374
401
}
375
402
403
+ // CDK TypeScript
376
404
if ( document . languageId === 'typescript' ) {
377
- const lines = text . split ( '\n' ) . reverse ( ) ;
378
- for ( const line of lines ) {
379
- const trimmedLine = line . trim ( ) . toLowerCase ( ) ;
380
- if ( trimmedLine . includes ( 'actions:' ) && trimmedLine . includes ( '[' ) ) {
381
- return true ;
382
- }
383
- if ( trimmedLine . startsWith ( '}' ) || trimmedLine . startsWith ( ']' ) ) {
405
+ const allLines = document . getText ( ) . split ( '\n' ) ;
406
+ const currentLineIndex = position . line ;
407
+ const currentLine = allLines [ currentLineIndex ] || '' ;
408
+ const currentIndent = currentLine . search ( / \S / ) ;
409
+ log ( `[TS] Current line (${ currentLineIndex } ): ${ currentLine } ` ) ;
410
+ log ( `[TS] Current indent: ${ currentIndent } ` ) ;
411
+
412
+ const actionsRegex = / a c t i o n s \s * : \s * \[ / ;
413
+ for ( let i = currentLineIndex ; i >= 0 ; i -- ) {
414
+ const line = allLines [ i ] ;
415
+ log ( `[TS] Scanning line ${ i } : ${ line } ` ) ;
416
+ if ( actionsRegex . test ( line ) ) {
417
+ const match = line . match ( / ^ ( \s * ) / ) ;
418
+ const parentIndent = match ? match [ 1 ] . length : 0 ;
419
+ if ( currentIndent > parentIndent ) {
420
+ log ( `[TS] Found actions array starting at line ${ i } , enabling autocomplete` ) ;
421
+ return true ;
422
+ }
384
423
break ;
385
424
}
386
425
}
387
426
}
388
427
428
+ // CDK Python
389
429
if ( document . languageId === 'python' ) {
390
- const lines = text . split ( '\n' ) . reverse ( ) ;
391
- for ( const line of lines ) {
392
- const trimmedLine = line . trim ( ) . toLowerCase ( ) ;
393
- if ( trimmedLine . startsWith ( 'actions=' ) && trimmedLine . includes ( '[' ) ) {
394
- return true ;
395
- }
396
- if ( trimmedLine . endsWith ( ']' ) || trimmedLine . endsWith ( '],' ) ) {
430
+ const allLines = document . getText ( ) . split ( '\n' ) ;
431
+ const currentLineIndex = position . line ;
432
+ const currentLine = allLines [ currentLineIndex ] || '' ;
433
+ const currentIndent = currentLine . search ( / \S / ) ;
434
+ log ( `[Python] Current line (${ currentLineIndex } ): ${ currentLine } ` ) ;
435
+ log ( `[Python] Current indent: ${ currentIndent } ` ) ;
436
+
437
+ const actionsRegex = / a c t i o n s \s * = \s * \[ / ;
438
+ for ( let i = currentLineIndex ; i >= 0 ; i -- ) {
439
+ const line = allLines [ i ] ;
440
+ log ( `[Python] Scanning line ${ i } : ${ line } ` ) ;
441
+ if ( actionsRegex . test ( line ) ) {
442
+ const match = line . match ( / ^ ( \s * ) / ) ;
443
+ const parentIndent = match ? match [ 1 ] . length : 0 ;
444
+ if ( currentIndent > parentIndent ) {
445
+ log ( `[Python] Found actions list starting at line ${ i } , enabling autocomplete` ) ;
446
+ return true ;
447
+ }
397
448
break ;
398
449
}
399
450
}
0 commit comments