Skip to content

Commit 1b13008

Browse files
committed
Update
1 parent 0b6354d commit 1b13008

File tree

5 files changed

+132
-41
lines changed

5 files changed

+132
-41
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "%extension.description%",
55
"publisher": "sysprofile",
66
"icon": "icon.png",
7-
"version": "0.6.51",
7+
"version": "0.6.55",
88
"repository": {
99
"type": "git",
1010
"url": "https://github.com/SysProfile/Scripting-Assistant-For-vMix.git"

src/completion.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { vMixFunctions, inputsList, objectsList } from './globals';
3-
import { findRange, getRangeDescription, getRangeCompletionItems } from './ranges';
3+
import { findRange, getRangeDescription, getRangeCompletionItems, getRangeProgressiveValues, getRangeProgressiveOverloads } from './ranges';
44
import { dataSourceTypes, isDataSourceFunction } from './datasources';
55
import { t } from './i18n';
66

@@ -41,7 +41,6 @@ function buildSmartSnippet(funcName: string, parameters: any, includeOptional: b
4141
paramKeys.forEach(key => {
4242
const param = parameters[key];
4343

44-
// Filtrar opcionales si no se incluyen
4544
if (param.optional && !includeOptional) { return; }
4645

4746
if (param.composites) {
@@ -85,6 +84,30 @@ function buildSmartSnippet(funcName: string, parameters: any, includeOptional: b
8584
return new vscode.SnippetString(`${funcName}(${parts.join(', ')})`);
8685
}
8786

87+
// Snippet para overloads progresivos: el Input es tab stop y cada letra es su propia constante tab stop
88+
// letters = ["M"] → AudioBus(${1:InputsList}, ${2:M})
89+
// letters = ["M","A"] → AudioBus(${1:InputsList}, ${2:M}, ${3:A})
90+
function buildProgressiveSnippet(funcName: string, parameters: any, letters: string[]): vscode.SnippetString {
91+
const parts: string[] = [];
92+
let tabIndex = 1;
93+
94+
// Primer parámetro: siempre Input
95+
const paramKeys = Object.keys(parameters || {});
96+
const inputKey = paramKeys.find(k => (parameters[k] as any).type === 'input');
97+
if (inputKey) {
98+
parts.push(`InputsList\${${tabIndex}}`);
99+
tabIndex++;
100+
}
101+
102+
// Cada letra es un parámetro separado sin comillas (constante)
103+
letters.forEach(letter => {
104+
parts.push(`\${${tabIndex}:${letter}}`);
105+
tabIndex++;
106+
});
107+
108+
return new vscode.SnippetString(`${funcName}(${parts.join(', ')})`);
109+
}
110+
88111
// Generar label de firma para el detail del CompletionItem
89112
function buildSignatureLabel(parameters: any, includeOptional: boolean): string {
90113
const paramKeys = Object.keys(parameters || {});
@@ -255,6 +278,22 @@ export function getCompletionProvider(): vscode.Disposable {
255278
const rangeData = findRange(f.category, f.function);
256279
const rangeDesc = rangeData ? ` | ${getRangeDescription(rangeData.range)}` : '';
257280
const params = f.parameters;
281+
282+
// Overloads progresivos para rangos tipo !
283+
// Genera un overload por cada profundidad: (Input, M), (Input, M, A), ...
284+
if (rangeData && rangeData.range.startsWith('!')) {
285+
const overloads = getRangeProgressiveOverloads(rangeData.range);
286+
overloads.forEach((letters, overloadIndex) => {
287+
const item = new vscode.CompletionItem(f.function, vscode.CompletionItemKind.Method);
288+
item.detail = `${f.function}(Input, ${letters.join(', ')})`;
289+
item.documentation = new vscode.MarkdownString(f.description + rangeDesc);
290+
item.insertText = buildProgressiveSnippet(f.function, params, letters);
291+
item.sortText = `${String(funcIndex).padStart(4, '0')}${String.fromCharCode(97 + overloadIndex)}`;
292+
completionItems.push(item);
293+
});
294+
return;
295+
}
296+
258297
const hasParams = params && typeof params === 'object' && Object.keys(params).length > 0;
259298
const hasOptional = hasParams && Object.values(params).some((p: any) => p.optional);
260299

@@ -303,6 +342,24 @@ export function getCompletionProvider(): vscode.Disposable {
303342
f.function.toLowerCase() === funcName.toLowerCase()
304343
);
305344

345+
// Para funciones con rango !, ofrecer letras de bus como constantes en posición 1+
346+
if (funcData) {
347+
const rangeData = findRange(category, funcName);
348+
if (rangeData && rangeData.range.startsWith('!') && currentArgIndex >= 1) {
349+
const values = getRangeProgressiveValues(rangeData.range);
350+
// Excluir letras ya usadas en args anteriores
351+
const alreadyUsed = argsTyped.split(',').slice(1).map(a => a.trim().toUpperCase());
352+
return values
353+
.filter(v => !alreadyUsed.includes(v.toUpperCase()))
354+
.map(v => {
355+
const item = new vscode.CompletionItem(v, vscode.CompletionItemKind.Constant);
356+
item.detail = `Bus letter`;
357+
item.insertText = v;
358+
return item;
359+
});
360+
}
361+
}
362+
306363
if (funcData && funcData.parameters && typeof funcData.parameters === 'object') {
307364
const paramKeys = Object.keys(funcData.parameters);
308365
const hasSelected = functionHasSelectedParam(funcData);

src/ranges.ts

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export function getRangeDescription(range: string): string {
2727
if (range.startsWith('{') && range.endsWith('}')) {
2828
return `Format: ${range.substring(1, range.length - 1)}`;
2929
}
30+
if (range.startsWith('!') && range.includes(',')) {
31+
const vals = range.substring(1).split(',').map(v => v.trim());
32+
return `Bus letters (one per parameter): ${vals.join(', ')}`;
33+
}
3034
if (range.startsWith('+') && range.includes(',')) {
3135
const vals = range.substring(1).split(',');
3236
return `Accumulative values: ${vals.join(', ')} (can combine, e.g. ${vals.slice(0, 3).join('')})`;
@@ -57,14 +61,34 @@ export function getRangeDescription(range: string): string {
5761
return range;
5862
}
5963

64+
// Devuelve los valores individuales del rango ! (e.g. ["M","A","B","C","D","E","F","G"])
65+
export function getRangeProgressiveValues(range: string): string[] {
66+
if (!range.startsWith('!') || !range.includes(',')) { return []; }
67+
return range.substring(1).split(',').map(v => v.trim());
68+
}
69+
70+
// Devuelve un array de arrays, uno por overload.
71+
// !M,A,B → [["M"], ["M","A"], ["M","A","B"]]
72+
export function getRangeProgressiveOverloads(range: string): string[][] {
73+
const values = getRangeProgressiveValues(range);
74+
const result: string[][] = [];
75+
for (let i = 0; i < values.length; i++) {
76+
result.push(values.slice(0, i + 1));
77+
}
78+
return result;
79+
}
80+
6081
export function getRangeCompletionItems(range: string): string[] {
82+
if (range.startsWith('!') && range.includes(',')) {
83+
return getRangeProgressiveValues(range);
84+
}
6185
if (range.startsWith('+') && range.includes(',')) {
6286
return range.substring(1).split(',').map(v => v.trim());
6387
}
6488
if (range.startsWith('=') && range.includes(',')) {
6589
return range.substring(1).split(',').map(v => v.trim());
6690
}
67-
if (!range.startsWith('+') && !range.startsWith('=') && !range.startsWith('{') && !range.startsWith('@') && !range.startsWith('<') && !range.startsWith('?') && range !== 'url' && range !== '' && range.includes(',') && !range.includes(':')) {
91+
if (!range.startsWith('+') && !range.startsWith('=') && !range.startsWith('!') && !range.startsWith('{') && !range.startsWith('@') && !range.startsWith('<') && !range.startsWith('?') && range !== 'url' && range !== '' && range.includes(',') && !range.includes(':')) {
6892
return range.split(',').map(v => v.trim());
6993
}
7094
return [];
@@ -82,22 +106,23 @@ export function validateValueAgainstRange(value: string, range: string, valueTyp
82106
return null;
83107
}
84108

85-
if (range.startsWith('@')) {
86-
return null;
87-
}
88-
89-
if (range.startsWith('{') && range.endsWith('}')) {
90-
return null;
91-
}
109+
if (range.startsWith('@')) { return null; }
110+
if (range.startsWith('{') && range.endsWith('}')) { return null; }
92111

93112
if (range === '+') {
94-
if (trimmedValue === '""') {
95-
return 'Value cannot be empty';
96-
}
113+
if (trimmedValue === '""') { return 'Value cannot be empty'; }
97114
return null;
98115
}
99116

100-
if (range === '') {
117+
if (range === '') { return null; }
118+
119+
// Para ! el valor es un identificador sin comillas (constante de letra)
120+
if (range.startsWith('!') && range.includes(',')) {
121+
const validVals = getRangeProgressiveValues(range).map(v => v.toLowerCase());
122+
const unquoted = trimmedValue.replace(/^"|"$/g, '').toLowerCase();
123+
if (!validVals.includes(unquoted)) {
124+
return `Invalid bus letter '${trimmedValue}'. Valid: ${getRangeProgressiveValues(range).join(', ')}`;
125+
}
101126
return null;
102127
}
103128

@@ -118,20 +143,14 @@ export function validateValueAgainstRange(value: string, range: string, valueTyp
118143
return `Invalid character '${ch}'. Valid: ${range.substring(1)}`;
119144
}
120145
}
121-
if (unquoted.length === 0) {
122-
return 'Value cannot be empty';
123-
}
146+
if (unquoted.length === 0) { return 'Value cannot be empty'; }
124147
return null;
125148
}
126149

127150
if (range === '<?:?' || range === '?:?') {
128151
const num = parseFloat(unquoted);
129-
if (isNaN(num)) {
130-
return 'Value must be a number';
131-
}
132-
if (range === '?:?' && num < 0) {
133-
return 'Value must be >= 0';
134-
}
152+
if (isNaN(num)) { return 'Value must be a number'; }
153+
if (range === '?:?' && num < 0) { return 'Value must be >= 0'; }
135154
return null;
136155
}
137156

@@ -140,20 +159,14 @@ export function validateValueAgainstRange(value: string, range: string, valueTyp
140159
const min = parseFloat(parts[0]);
141160
const max = parseFloat(parts[1]);
142161
const num = parseFloat(unquoted);
143-
if (isNaN(num)) {
144-
return 'Value must be a number';
145-
}
146-
if (num < min || num > max) {
147-
return `Value out of range. Expected: ${min} to ${max}`;
148-
}
162+
if (isNaN(num)) { return 'Value must be a number'; }
163+
if (num < min || num > max) { return `Value out of range. Expected: ${min} to ${max}`; }
149164
return null;
150165
}
151166

152-
if (!range.startsWith('+') && !range.startsWith('=') && !range.startsWith('{') && !range.startsWith('@') && !range.startsWith('<') && !range.startsWith('?') && range !== 'url' && range !== '' && range.includes(',') && !range.includes(':')) {
167+
if (!range.startsWith('+') && !range.startsWith('=') && !range.startsWith('!') && !range.startsWith('{') && !range.startsWith('@') && !range.startsWith('<') && !range.startsWith('?') && range !== 'url' && range !== '' && range.includes(',') && !range.includes(':')) {
153168
const validVals = range.split(',').map(v => v.trim());
154-
if (!validVals.includes(unquoted)) {
155-
return `Invalid value. Valid options: ${range}`;
156-
}
169+
if (!validVals.includes(unquoted)) { return `Invalid value. Valid options: ${range}`; }
157170
return null;
158171
}
159172

src/transpiler.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { vMixFunctions, inputsList, objectsList } from './globals';
22
import { findDataSourceByEnum, findDataSourceByNative, isDataSourceFunction } from './datasources';
3+
import { findRange, getRangeProgressiveValues } from './ranges';
34

45
export function findClosingParen(text: string, openIndex: number): number {
56
let depth = 0;
@@ -119,8 +120,16 @@ export function exportApiCalls(text: string): { result: string; unknownFunctions
119120
const args = splitArguments(argsString);
120121
const namedArgs: string[] = [];
121122

122-
// DataSource functions: DataSource.X + resto de args → Value:="nativo,arg2,arg3"
123-
if (isDataSourceFunction(funcData.function) && args.length > 0) {
123+
// Rango progresivo !: args[0]=Input, args[1..N]=letras de bus → Value:="MAB"
124+
const rangeData = findRange(category, funcName);
125+
if (rangeData && rangeData.range.startsWith('!') && args.length > 0) {
126+
namedArgs.push(`Input:=${args[0]}`);
127+
if (args.length > 1) {
128+
const busStr = args.slice(1).map(a => a.replace(/^"|"$/g, '')).join('');
129+
namedArgs.push(`Value:="${busStr}"`);
130+
}
131+
} else if (isDataSourceFunction(funcData.function) && args.length > 0) {
132+
// DataSource functions: DataSource.X + resto de args → Value:="nativo,arg2,arg3"
124133
const dsMatch = args[0].match(/^DataSource\.(\w+)$/i);
125134
if (dsMatch) {
126135
const dsEntry = findDataSourceByEnum(dsMatch[1]);
@@ -229,14 +238,26 @@ export function importApiCalls(text: string): string {
229238
positionalArgs = splitArguments(argsString);
230239
}
231240
}
232-
// DataSource functions: primer arg nativo → DataSource.X
233-
if (isDataSourceFunction(funcData.function) && positionalArgs.length > 0) {
241+
242+
// Rango progresivo !: Value:="MAB" → args separados: InputsList.X, M, A, B
243+
const rangeData = findRange(funcData.category, funcData.function);
244+
if (rangeData && rangeData.range.startsWith('!')) {
245+
const inputArg = positionalArgs.length > 0 ? positionalArgs[0] : '';
246+
const valueArg = positionalArgs.length > 1 ? positionalArgs[1] : '';
247+
const busStr = valueArg.replace(/^"|"$/g, '');
248+
const validLetters = getRangeProgressiveValues(rangeData.range).map(v => v.toUpperCase());
249+
// Separar la cadena en letras individuales válidas
250+
const busLetters = busStr.toUpperCase().split('').filter(ch => validLetters.includes(ch));
251+
positionalArgs = inputArg ? [inputArg, ...busLetters] : busLetters;
252+
} else if (isDataSourceFunction(funcData.function) && positionalArgs.length > 0) {
253+
// DataSource functions: primer arg nativo → DataSource.X
234254
const firstArg = positionalArgs[0].replace(/^"|"$/g, '');
235255
const dsEntry = findDataSourceByNative(firstArg);
236256
if (dsEntry) {
237257
positionalArgs[0] = `DataSource.${dsEntry.enumName}`;
238258
}
239259
}
260+
240261
result += `API.${funcData.category}.${funcData.function}(${positionalArgs.join(', ')})`;
241262
} else {
242263
result += text.substring(fullMatchStart, closeParenIndex + 1);

vMixValuesRange.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@
2222
{
2323
"category": "Audio",
2424
"function": "AudioBus",
25-
"range": "+M,A,B,C,D,E,F,G"
25+
"range": "!M,A,B,C,D,E,F,G"
2626
},
2727
{
2828
"category": "Audio",
2929
"function": "AudioBusOff",
30-
"range": "+M,A,B,C,D,E,F,G"
30+
"range": "!M,A,B,C,D,E,F,G"
3131
},
3232
{
3333
"category": "Audio",
3434
"function": "AudioBusOn",
35-
"range": "+M,A,B,C,D,E,F,G"
35+
"range": "!M,A,B,C,D,E,F,G"
3636
},
3737
{
3838
"category": "Audio",

0 commit comments

Comments
 (0)