Skip to content

Commit efd653f

Browse files
committed
fix(scripts): restore and enhance properties completion logic
1 parent 2a264c6 commit efd653f

1 file changed

Lines changed: 109 additions & 44 deletions

File tree

extensions/scripts/src/extension.ts

Lines changed: 109 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,41 @@ let scriptPropertiesPath: string;
3131
let extensionsFolder: string;
3232
let languageData: Map<string, Map<string, string>> = new Map();
3333

34+
// // Extract property completion logic into a function
35+
// function getPropertyCompletions(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
36+
// if (getDocumentScriptType(document) === '') {
37+
// return []; // Skip if the document is not valid
38+
// }
39+
40+
// // Get the current line up to the cursor position
41+
// const linePrefix = document.lineAt(position).text.substring(0, position.character);
42+
43+
// // First scenario: . was just typed
44+
// if (linePrefix.endsWith('.')) {
45+
// return Loc;
46+
// }
47+
48+
// // Second scenario: We're editing an existing variable
49+
// // Find the last $ character before the cursor
50+
// const lastDollarIndex = linePrefix.lastIndexOf('$');
51+
// if (lastDollarIndex >= 0) {
52+
// // Check if we're within a variable (no whitespace or special chars between $ and cursor)
53+
// const textBetweenDollarAndCursor = linePrefix.substring(lastDollarIndex + 1);
54+
55+
// // If this text is a valid variable name part
56+
// if (/^[a-zA-Z0-9_]*$/.test(textBetweenDollarAndCursor)) {
57+
// // Get the partial variable name we're typing (without the $)
58+
// const partialName = textBetweenDollarAndCursor;
59+
// // Get all variables and filter them by the current prefix
60+
// const allVariables = variableTracker.getAllVariablesForDocument(document.uri, partialName);
61+
// // Filter variables that match the partial name
62+
// return allVariables.filter((item) => item.label.toString().toLowerCase().startsWith(partialName.toLowerCase()));
63+
// }
64+
// }
65+
66+
// return []; // No completions if not in a variable context
67+
// }
68+
3469
// Extract variable completion logic into a function
3570
function getVariableCompletions(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
3671
if (getDocumentScriptType(document) === '') {
@@ -163,24 +198,27 @@ function getActionCompletions(document: vscode.TextDocument, position: vscode.Po
163198
}
164199

165200
// Function to check if we're in a specialized completion context
166-
function isSpecializedCompletionContext(document: vscode.TextDocument, position: vscode.Position): boolean {
201+
function specializedCompletionContext(
202+
document: vscode.TextDocument,
203+
position: vscode.Position
204+
): vscode.CompletionItem[] {
167205
// Check if any of the specialized completion functions return results
168206
const variableCompletions = getVariableCompletions(document, position);
169207
if (variableCompletions.length > 0) {
170-
return true;
208+
return variableCompletions;
171209
}
172210

173211
const labelCompletions = getLabelCompletions(document, position);
174212
if (labelCompletions.length > 0) {
175-
return true;
213+
return labelCompletions;
176214
}
177215

178216
const actionCompletions = getActionCompletions(document, position);
179217
if (actionCompletions.length > 0) {
180-
return true;
218+
return actionCompletions;
181219
}
182220

183-
return false;
221+
return [];
184222
}
185223

186224
// Flag to indicate if specialized completion is active
@@ -250,16 +288,23 @@ class TypeEntry {
250288
properties: Map<string, string> = new Map<string, string>();
251289
supertype?: string;
252290
literals: Set<string> = new Set<string>();
291+
details: Map<string, string> = new Map<string, string>();
253292
addProperty(value: string, type: string = '') {
254293
this.properties.set(value, type);
255294
}
256295
addLiteral(value: string) {
257296
this.literals.add(value);
258297
}
298+
addDetail(key: string, value: string) {
299+
this.details.set(key, value);
300+
}
259301
}
260302

261303
class CompletionDict implements vscode.CompletionItemProvider {
262304
typeDict: Map<string, TypeEntry> = new Map<string, TypeEntry>();
305+
allProp: Map<string, string> = new Map<string, string>();
306+
allPropItems: vscode.CompletionItem[] = [];
307+
defaultCompletions: vscode.CompletionList;
263308
addType(key: string, supertype?: string): void {
264309
const k = cleanStr(key);
265310
let entry = this.typeDict.get(k);
@@ -283,14 +328,26 @@ class CompletionDict implements vscode.CompletionItemProvider {
283328
entry.addLiteral(v);
284329
}
285330

286-
addProperty(key: string, prop: string, type?: string): void {
331+
addProperty(key: string, prop: string, type?: string, details?: string): void {
287332
const k = cleanStr(key);
288333
let entry = this.typeDict.get(k);
289334
if (entry === undefined) {
290335
entry = new TypeEntry();
291336
this.typeDict.set(k, entry);
292337
}
293338
entry.addProperty(prop, type);
339+
if (details !== undefined) {
340+
entry.addDetail(prop, details);
341+
}
342+
const shortProp = prop.split('.')[0];
343+
if (this.allProp.has(shortProp)) {
344+
// If the commonDict already has this property, we can skip adding it again
345+
return;
346+
} else if (type !== undefined) {
347+
this.allProp.set(shortProp, type);
348+
const item = new vscode.CompletionItem(shortProp);
349+
this.allPropItems.push(item);
350+
}
294351
}
295352

296353
addItem(items: Map<string, vscode.CompletionItem>, complete: string, info?: string): void {
@@ -306,16 +363,13 @@ class CompletionDict implements vscode.CompletionItemProvider {
306363
return;
307364
}
308365

309-
const result = new vscode.CompletionItem(complete);
310-
if (info !== undefined) {
311-
result.detail = info;
312-
} else {
313-
result.detail = complete;
314-
}
366+
const item = new vscode.CompletionItem(complete, vscode.CompletionItemKind.Property);
367+
item.documentation = info ? new vscode.MarkdownString(info) : undefined;
368+
315369
if (exceedinglyVerbose) {
316-
logger.info('\t\tAdded completion: ' + complete + ' info: ' + result.detail);
370+
logger.info('\t\tAdded completion: ' + complete + ' info: ' + item.detail);
317371
}
318-
items.set(complete, result);
372+
items.set(complete, item);
319373
}
320374
buildProperty(
321375
prefix: string,
@@ -330,9 +384,9 @@ class CompletionDict implements vscode.CompletionItemProvider {
330384
return;
331385
}
332386
// TODO handle better
333-
if (['', 'boolean', 'int', 'string', 'list', 'datatype'].indexOf(typeName) > -1) {
334-
return;
335-
}
387+
// if (['', 'boolean', 'int', 'string', 'list', 'datatype'].indexOf(typeName) > -1) {
388+
// return;
389+
// }
336390
if (exceedinglyVerbose) {
337391
logger.info('\tBuilding Property', typeName + '.' + propertyName, 'depth: ', depth, 'prefix: ', prefix);
338392
}
@@ -354,8 +408,8 @@ class CompletionDict implements vscode.CompletionItemProvider {
354408
// return;
355409
// });
356410
// } else {
357-
this.addItem(items, completion, typeName + '.' + propertyName);
358-
this.buildType(completion, propertyType, items, depth + 1);
411+
this.addItem(items, completion, /* typeName + '.' + */ propertyName);
412+
// this.buildType(completion, propertyType, items, depth /* + 1 */);
359413
// }
360414
}
361415

@@ -378,9 +432,9 @@ class CompletionDict implements vscode.CompletionItemProvider {
378432
return;
379433
}
380434

381-
if (depth > -1 && prefix !== '') {
382-
this.addItem(items, typeName);
383-
}
435+
// if (depth > -1 && prefix !== '') {
436+
// this.addItem(items, typeName);
437+
// }
384438

385439
if (items.size > 1000) {
386440
if (exceedinglyVerbose) {
@@ -390,13 +444,14 @@ class CompletionDict implements vscode.CompletionItemProvider {
390444
}
391445

392446
for (const prop of entry.properties.entries()) {
393-
this.buildProperty(prefix, typeName, prop[0], prop[1], items, depth + 1);
447+
// this.buildProperty('', typeName, prop[0], prop[1], items, depth /* + 1 */);
448+
this.addItem(items, prop[0], '**' + [typeName, prop[0]].join('.') + '**: ' + entry.details.get(prop[0]));
394449
}
395450
if (entry.supertype !== undefined) {
396451
if (exceedinglyVerbose) {
397452
logger.info('Recursing on supertype: ', entry.supertype);
398453
}
399-
this.buildType(typeName, entry.supertype, items, depth + 1);
454+
this.buildType(typeName, entry.supertype, items, depth /* + 1 */);
400455
}
401456
}
402457
makeCompletionList(items: Map<string, vscode.CompletionItem>): vscode.CompletionList {
@@ -409,8 +464,9 @@ class CompletionDict implements vscode.CompletionItemProvider {
409464
}
410465

411466
// Check if we're in a specialized completion context (variables, labels, actions)
412-
if (isSpecializedCompletionContext(document, position)) {
413-
return undefined; // Let the specialized providers handle it
467+
let specializedCompletion = specializedCompletionContext(document, position);
468+
if (specializedCompletion.length > 0) {
469+
return specializedCompletion;
414470
}
415471

416472
const items = new Map<string, vscode.CompletionItem>();
@@ -422,31 +478,37 @@ class CompletionDict implements vscode.CompletionItemProvider {
422478
}
423479
return this.makeCompletionList(items);
424480
}
425-
const prevToken = interesting[0];
481+
let prevToken = interesting[0];
426482
const newToken = interesting[1];
427483
if (exceedinglyVerbose) {
428484
logger.info('Previous token: ', interesting[0], ' New token: ', interesting[1]);
429485
}
430486
// If we have a previous token & it's in the typeDictionary, only use that's entries
431487
if (prevToken !== '') {
432-
const entry = this.typeDict.get(prevToken);
488+
let entry = this.typeDict.get(prevToken);
489+
if (entry === undefined && this.allProp.has(prevToken)) {
490+
prevToken = this.allProp.get(prevToken) || '';
491+
if (prevToken !== '') {
492+
entry = this.typeDict.get(prevToken);
493+
}
494+
}
433495
if (entry === undefined) {
434496
if (exceedinglyVerbose) {
435497
logger.info('Missing previous token!');
436498
}
437-
// TODO backtrack & search
438-
return;
499+
500+
return this.defaultCompletions;
439501
} else {
440502
if (exceedinglyVerbose) {
441503
logger.info('Matching on type!');
442504
}
443-
444-
entry.properties.forEach((v, k) => {
445-
if (exceedinglyVerbose) {
446-
logger.info('Top level property: ', k, v);
447-
}
448-
this.buildProperty('', prevToken, k, v, items, 0);
449-
});
505+
this.buildType('', prevToken, items, 0);
506+
// entry.properties.forEach((v, k) => {
507+
// if (exceedinglyVerbose) {
508+
// logger.info('Top level property: ' + k + ' ' + v);
509+
// }
510+
// this.buildProperty('', prevToken, k, v, items, 0);
511+
// });
450512
return this.makeCompletionList(items);
451513
}
452514
}
@@ -495,7 +557,7 @@ class LocationDict implements vscode.DefinitionProvider {
495557

496558
addLocation(name: string, file: string, start: vscode.Position, end: vscode.Position): void {
497559
const range = new vscode.Range(start, end);
498-
const uri = vscode.Uri.parse('file://' + file);
560+
const uri = vscode.Uri.file(file);
499561
this.dict.set(cleanStr(name), new vscode.Location(uri, range));
500562
}
501563
addLocationForRegexMatch(rawData: string, rawIdx: number, name: string) {
@@ -1394,8 +1456,7 @@ function readScriptProperties(filepath: string) {
13941456
// Process keywords and datatypes here, return the completed results
13951457
keywords = processKeywords(rawData, result['scriptproperties']['keyword']);
13961458
datatypes = processDatatypes(rawData, result['scriptproperties']['datatype']);
1397-
1398-
completionProvider.addTypeLiteral('boolean', '==true');
1459+
completionProvider.defaultCompletions = new vscode.CompletionList(completionProvider.allPropItems, true);
13991460
completionProvider.addTypeLiteral('boolean', '==false');
14001461
logger.info('Parsed scriptproperties.xml');
14011462
});
@@ -1417,7 +1478,7 @@ function processProperty(rawData: string, parent: string, parentType: string, pr
14171478
logger.info('\tProperty read: ', name);
14181479
}
14191480
definitionProvider.addPropertyLocation(rawData, name, parent, parentType);
1420-
completionProvider.addProperty(parent, name, prop.$.type);
1481+
completionProvider.addProperty(parent, name, prop.$.type, prop.$.result);
14211482
}
14221483

14231484
function processKeyword(rawData: string, e: Keyword) {
@@ -2191,8 +2252,12 @@ export function activate(context: vscode.ExtensionContext) {
21912252
if (definitionProvider.dict.has(relevant)) {
21922253
return definitionProvider.dict.get(relevant);
21932254
}
2194-
relevant = relevant.substring(relevant.indexOf('.') + 1);
2195-
} while (relevant.indexOf('.') !== -1);
2255+
if (relevant.indexOf('.') !== -1) {
2256+
relevant = relevant.substring(relevant.indexOf('.') + 1);
2257+
} else {
2258+
break; // No more dots to process
2259+
}
2260+
} while (relevant.length > 0);
21962261

21972262
return undefined;
21982263
};
@@ -2263,7 +2328,7 @@ export function activate(context: vscode.ExtensionContext) {
22632328
if (cursorPos.character < line.text.length) {
22642329
nextPos = cursorPos.translate(0, 1);
22652330
}
2266-
if (isSpecializedCompletionContext(event.document, nextPos)) {
2331+
if (specializedCompletionContext(event.document, nextPos).length > 0) {
22672332
// Programmatically trigger suggestions
22682333
vscode.commands.executeCommand('editor.action.triggerSuggest');
22692334
}

0 commit comments

Comments
 (0)