Skip to content

Commit d6c9412

Browse files
author
Deep Furiya
committed
feat: related resources for resources already authored in template showed using inline completion suggestions
1 parent 4364e5c commit d6c9412

File tree

4 files changed

+568
-39
lines changed

4 files changed

+568
-39
lines changed

src/autocomplete/InlineCompletionRouter.ts

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { InlineCompletionList, InlineCompletionParams, InlineCompletionItem } from 'vscode-languageserver-protocol';
2+
import { Context } from '../context/Context';
23
import { ContextManager } from '../context/ContextManager';
4+
import { DocumentManager } from '../document/DocumentManager';
35
import { Closeable, Configurable, ServerComponents } from '../server/ServerComponents';
6+
import { RelationshipSchemaService } from '../services/RelationshipSchemaService';
47
import {
58
CompletionSettings,
69
DefaultSettings,
@@ -9,8 +12,10 @@ import {
912
SettingsSubscription,
1013
} from '../settings/Settings';
1114
import { LoggerFactory } from '../telemetry/LoggerFactory';
15+
import { InlineCompletionProvider } from './InlineCompletionProvider';
16+
import { RelatedResourcesInlineCompletionProvider } from './RelatedResourcesInlineCompletionProvider';
1217

13-
export type InlineCompletionProviderType = 'ResourceBlock' | 'PropertyBlock' | 'TemplateSection' | 'AIGenerated';
18+
export type InlineCompletionProviderType = 'RelatedResources' | 'ResourceBlock' | 'PropertyBlock';
1419
type ReturnType = InlineCompletionList | InlineCompletionItem[] | null | undefined;
1520

1621
export class InlineCompletionRouter implements Configurable, Closeable {
@@ -20,7 +25,10 @@ export class InlineCompletionRouter implements Configurable, Closeable {
2025
private editorSettingsSubscription?: SettingsSubscription;
2126
private readonly log = LoggerFactory.getLogger(InlineCompletionRouter);
2227

23-
constructor(private readonly contextManager: ContextManager) {}
28+
constructor(
29+
private readonly contextManager: ContextManager,
30+
private readonly inlineCompletionProviderMap: Map<InlineCompletionProviderType, InlineCompletionProvider>,
31+
) {}
2432

2533
getInlineCompletions(params: InlineCompletionParams): Promise<ReturnType> | ReturnType {
2634
if (!this.completionSettings.enabled) return;
@@ -31,6 +39,31 @@ export class InlineCompletionRouter implements Configurable, Closeable {
3139
return;
3240
}
3341

42+
this.log.debug(
43+
{
44+
position: params.position,
45+
section: context.section,
46+
propertyPath: context.propertyPath,
47+
},
48+
'Processing inline completion request',
49+
);
50+
51+
// Check if we are authoring a new resource
52+
if (this.isAuthoringNewResource(context)) {
53+
const relatedResourcesProvider = this.inlineCompletionProviderMap.get('RelatedResources');
54+
if (relatedResourcesProvider) {
55+
const result = relatedResourcesProvider.getlineCompletion(context, params, this.editorSettings);
56+
57+
if (result instanceof Promise) {
58+
return result.then((items) => {
59+
return { items };
60+
});
61+
} else if (result && Array.isArray(result)) {
62+
return { items: result };
63+
}
64+
}
65+
}
66+
3467
return;
3568
}
3669

@@ -42,18 +75,14 @@ export class InlineCompletionRouter implements Configurable, Closeable {
4275
this.editorSettingsSubscription.unsubscribe();
4376
}
4477

45-
// Get initial settings
46-
this.completionSettings = settingsManager.getCurrentSettings().completion;
47-
this.editorSettings = settingsManager.getCurrentSettings().editor;
48-
4978
// Subscribe to completion settings changes
5079
this.settingsSubscription = settingsManager.subscribe('completion', (newCompletionSettings) => {
51-
this.onCompletionSettingsChanged(newCompletionSettings);
80+
this.completionSettings = newCompletionSettings;
5281
});
5382

5483
// Subscribe to editor settings changes
5584
this.editorSettingsSubscription = settingsManager.subscribe('editor', (newEditorSettings) => {
56-
this.onEditorSettingsChanged(newEditorSettings);
85+
this.editorSettings = newEditorSettings;
5786
});
5887
}
5988

@@ -68,15 +97,36 @@ export class InlineCompletionRouter implements Configurable, Closeable {
6897
}
6998
}
7099

71-
private onCompletionSettingsChanged(settings: CompletionSettings): void {
72-
this.completionSettings = settings;
73-
}
74-
75-
private onEditorSettingsChanged(settings: EditorSettings): void {
76-
this.editorSettings = settings;
100+
private isAuthoringNewResource(context: Context): boolean {
101+
// Only provide suggestions in Resources section when positioned for a new resource
102+
return (
103+
String(context.section) === 'Resources' &&
104+
(context.propertyPath.length === 1 ||
105+
(context.propertyPath.length === 2 &&
106+
context.hasLogicalId &&
107+
context.isKey() &&
108+
!context.atEntityKeyLevel()))
109+
);
77110
}
78111

79112
static create(components: ServerComponents) {
80-
return new InlineCompletionRouter(components.contextManager);
113+
return new InlineCompletionRouter(
114+
components.contextManager,
115+
createInlineCompletionProviders(components.documentManager, RelationshipSchemaService.getInstance()),
116+
);
81117
}
82118
}
119+
120+
export function createInlineCompletionProviders(
121+
documentManager: DocumentManager,
122+
relationshipSchemaService: RelationshipSchemaService,
123+
): Map<InlineCompletionProviderType, InlineCompletionProvider> {
124+
const inlineCompletionProviderMap = new Map<InlineCompletionProviderType, InlineCompletionProvider>();
125+
126+
inlineCompletionProviderMap.set(
127+
'RelatedResources',
128+
new RelatedResourcesInlineCompletionProvider(relationshipSchemaService, documentManager),
129+
);
130+
131+
return inlineCompletionProviderMap;
132+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { InlineCompletionItem, InlineCompletionParams } from 'vscode-languageserver-protocol';
2+
import { Context } from '../context/Context';
3+
import { DocumentManager } from '../document/DocumentManager';
4+
import { RelationshipSchemaService } from '../services/RelationshipSchemaService';
5+
import { EditorSettings } from '../settings/Settings';
6+
import { LoggerFactory } from '../telemetry/LoggerFactory';
7+
import { InlineCompletionProvider } from './InlineCompletionProvider';
8+
9+
export class RelatedResourcesInlineCompletionProvider implements InlineCompletionProvider {
10+
private readonly log = LoggerFactory.getLogger(RelatedResourcesInlineCompletionProvider);
11+
12+
constructor(
13+
private readonly relationshipSchemaService: RelationshipSchemaService,
14+
private readonly documentManager: DocumentManager,
15+
) {}
16+
17+
getlineCompletion(
18+
context: Context,
19+
params: InlineCompletionParams,
20+
_editorSettings: EditorSettings,
21+
): Promise<InlineCompletionItem[]> | InlineCompletionItem[] | undefined {
22+
this.log.debug(
23+
{
24+
provider: 'RelatedResourcesInlineCompletion',
25+
position: params.position,
26+
section: context.section,
27+
propertyPath: context.propertyPath,
28+
},
29+
'Processing related resources inline completion request',
30+
);
31+
32+
try {
33+
const document = this.documentManager.get(params.textDocument.uri);
34+
if (!document) {
35+
return undefined;
36+
}
37+
38+
const existingResourceTypes = this.relationshipSchemaService.extractResourceTypesFromTemplate(
39+
document.getText(),
40+
);
41+
42+
if (existingResourceTypes.length === 0) {
43+
return undefined;
44+
}
45+
46+
const relatedResourceTypes = this.getRelatedResourceTypes(existingResourceTypes);
47+
48+
if (relatedResourceTypes.length === 0) {
49+
return undefined;
50+
}
51+
52+
return this.generateInlineCompletionItems(relatedResourceTypes, params);
53+
} catch (error) {
54+
this.log.error({ error: String(error) }, 'Error generating related resources inline completion');
55+
return undefined;
56+
}
57+
}
58+
59+
private getRelatedResourceTypes(existingResourceTypes: string[]): string[] {
60+
const existingRelationships = new Map<string, Set<string>>();
61+
const allRelatedTypes = new Set<string>();
62+
63+
for (const resourceType of existingResourceTypes) {
64+
const relatedTypes = this.relationshipSchemaService.getAllRelatedResourceTypes(resourceType);
65+
existingRelationships.set(resourceType, relatedTypes);
66+
for (const type of relatedTypes) {
67+
allRelatedTypes.add(type);
68+
}
69+
}
70+
71+
const existingTypesSet = new Set(existingResourceTypes);
72+
const suggestedTypes = [...allRelatedTypes].filter((type) => !existingTypesSet.has(type));
73+
74+
return this.rankSuggestionsByFrequency(suggestedTypes, existingRelationships);
75+
}
76+
77+
private rankSuggestionsByFrequency(
78+
suggestedTypes: string[],
79+
existingRelationships: Map<string, Set<string>>,
80+
): string[] {
81+
const frequencyMap = new Map<string, number>();
82+
83+
for (const suggestedType of suggestedTypes) {
84+
let frequency = 0;
85+
for (const relatedTypes of existingRelationships.values()) {
86+
if (relatedTypes.has(suggestedType)) {
87+
frequency++;
88+
}
89+
}
90+
frequencyMap.set(suggestedType, frequency);
91+
}
92+
93+
return suggestedTypes.sort((a, b) => {
94+
const freqA = frequencyMap.get(a) ?? 0;
95+
const freqB = frequencyMap.get(b) ?? 0;
96+
97+
if (freqA !== freqB) {
98+
return freqB - freqA;
99+
}
100+
101+
return a.localeCompare(b);
102+
});
103+
}
104+
105+
private generateInlineCompletionItems(
106+
relatedResourceTypes: string[],
107+
params: InlineCompletionParams,
108+
): InlineCompletionItem[] {
109+
const completionItems: InlineCompletionItem[] = [];
110+
111+
const topSuggestions = relatedResourceTypes.slice(0, 5);
112+
113+
for (const resourceType of topSuggestions) {
114+
const insertText = this.generatePropertySnippet(resourceType);
115+
116+
completionItems.push({
117+
insertText,
118+
range: {
119+
start: params.position,
120+
end: params.position,
121+
},
122+
filterText: `${resourceType}`,
123+
});
124+
}
125+
126+
this.log.debug(
127+
{
128+
suggestedCount: completionItems.length,
129+
suggestions: completionItems.map((item) => item.insertText),
130+
},
131+
'Generated related resource inline completions',
132+
);
133+
134+
return completionItems;
135+
}
136+
137+
private generatePropertySnippet(resourceType: string): string {
138+
// TODO: Convert AWS::Service::Resource to a complete resource type snippet
139+
return `${resourceType}:`;
140+
}
141+
}

0 commit comments

Comments
 (0)