trigger_system 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/browser/index.browser.js +48 -0
  4. package/dist/browser/index.browser.js.map +122 -0
  5. package/dist/cli/lsp-server.d.ts +3 -0
  6. package/dist/cli/lsp-server.d.ts.map +1 -0
  7. package/dist/cli/validate.d.ts +2 -0
  8. package/dist/cli/validate.d.ts.map +1 -0
  9. package/dist/core/action-registry.d.ts +12 -0
  10. package/dist/core/action-registry.d.ts.map +1 -0
  11. package/dist/core/context-adapter.d.ts +23 -0
  12. package/dist/core/context-adapter.d.ts.map +1 -0
  13. package/dist/core/dependency-graph.d.ts +18 -0
  14. package/dist/core/dependency-graph.d.ts.map +1 -0
  15. package/dist/core/engine.d.ts +24 -0
  16. package/dist/core/engine.d.ts.map +1 -0
  17. package/dist/core/event-queue.d.ts +25 -0
  18. package/dist/core/event-queue.d.ts.map +1 -0
  19. package/dist/core/expression-engine.d.ts +31 -0
  20. package/dist/core/expression-engine.d.ts.map +1 -0
  21. package/dist/core/index.d.ts +10 -0
  22. package/dist/core/index.d.ts.map +1 -0
  23. package/dist/core/persistence-browser.d.ts +20 -0
  24. package/dist/core/persistence-browser.d.ts.map +1 -0
  25. package/dist/core/persistence.d.ts +34 -0
  26. package/dist/core/persistence.d.ts.map +1 -0
  27. package/dist/core/persistence.node.d.ts +18 -0
  28. package/dist/core/persistence.node.d.ts.map +1 -0
  29. package/dist/core/plugin-manager.d.ts +15 -0
  30. package/dist/core/plugin-manager.d.ts.map +1 -0
  31. package/dist/core/rule-engine.d.ts +39 -0
  32. package/dist/core/rule-engine.d.ts.map +1 -0
  33. package/dist/core/state-manager.d.ts +41 -0
  34. package/dist/core/state-manager.d.ts.map +1 -0
  35. package/dist/domain/index.d.ts +2 -0
  36. package/dist/domain/index.d.ts.map +1 -0
  37. package/dist/domain/validator.d.ts +2433 -0
  38. package/dist/domain/validator.d.ts.map +1 -0
  39. package/dist/index.browser.d.ts +6 -0
  40. package/dist/index.browser.d.ts.map +1 -0
  41. package/dist/index.d.ts +7 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/io/index.d.ts +2 -0
  44. package/dist/io/index.d.ts.map +1 -0
  45. package/dist/io/loader.node.d.ts +14 -0
  46. package/dist/io/loader.node.d.ts.map +1 -0
  47. package/dist/lsp/completions.d.ts +6 -0
  48. package/dist/lsp/completions.d.ts.map +1 -0
  49. package/dist/lsp/completions.js +624 -0
  50. package/dist/lsp/completions.js.map +1 -0
  51. package/dist/lsp/data-context.d.ts +60 -0
  52. package/dist/lsp/data-context.d.ts.map +1 -0
  53. package/dist/lsp/data-context.js +172 -0
  54. package/dist/lsp/data-context.js.map +1 -0
  55. package/dist/lsp/diagnostics.d.ts +7 -0
  56. package/dist/lsp/diagnostics.d.ts.map +1 -0
  57. package/dist/lsp/diagnostics.js +373 -0
  58. package/dist/lsp/diagnostics.js.map +1 -0
  59. package/dist/lsp/directives.d.ts +44 -0
  60. package/dist/lsp/directives.d.ts.map +1 -0
  61. package/dist/lsp/directives.js +232 -0
  62. package/dist/lsp/directives.js.map +1 -0
  63. package/dist/lsp/domain/index.d.ts +2 -0
  64. package/dist/lsp/domain/index.d.ts.map +1 -0
  65. package/dist/lsp/domain/index.js +18 -0
  66. package/dist/lsp/domain/index.js.map +1 -0
  67. package/dist/lsp/domain/validator.d.ts +2433 -0
  68. package/dist/lsp/domain/validator.d.ts.map +1 -0
  69. package/dist/lsp/domain/validator.js +225 -0
  70. package/dist/lsp/domain/validator.js.map +1 -0
  71. package/dist/lsp/hover.d.ts +7 -0
  72. package/dist/lsp/hover.d.ts.map +1 -0
  73. package/dist/lsp/hover.js +462 -0
  74. package/dist/lsp/hover.js.map +1 -0
  75. package/dist/lsp/lsp/completions.d.ts +6 -0
  76. package/dist/lsp/lsp/completions.d.ts.map +1 -0
  77. package/dist/lsp/lsp/completions.js +624 -0
  78. package/dist/lsp/lsp/completions.js.map +1 -0
  79. package/dist/lsp/lsp/data-context.d.ts +60 -0
  80. package/dist/lsp/lsp/data-context.d.ts.map +1 -0
  81. package/dist/lsp/lsp/data-context.js +172 -0
  82. package/dist/lsp/lsp/data-context.js.map +1 -0
  83. package/dist/lsp/lsp/diagnostics.d.ts +7 -0
  84. package/dist/lsp/lsp/diagnostics.d.ts.map +1 -0
  85. package/dist/lsp/lsp/diagnostics.js +373 -0
  86. package/dist/lsp/lsp/diagnostics.js.map +1 -0
  87. package/dist/lsp/lsp/directives.d.ts +44 -0
  88. package/dist/lsp/lsp/directives.d.ts.map +1 -0
  89. package/dist/lsp/lsp/directives.js +232 -0
  90. package/dist/lsp/lsp/directives.js.map +1 -0
  91. package/dist/lsp/lsp/hover.d.ts +7 -0
  92. package/dist/lsp/lsp/hover.d.ts.map +1 -0
  93. package/dist/lsp/lsp/hover.js +462 -0
  94. package/dist/lsp/lsp/hover.js.map +1 -0
  95. package/dist/lsp/lsp/semantic_tokens.d.ts +4 -0
  96. package/dist/lsp/lsp/semantic_tokens.d.ts.map +1 -0
  97. package/dist/lsp/lsp/semantic_tokens.js +158 -0
  98. package/dist/lsp/lsp/semantic_tokens.js.map +1 -0
  99. package/dist/lsp/lsp/server.d.ts +2 -0
  100. package/dist/lsp/lsp/server.d.ts.map +1 -0
  101. package/dist/lsp/lsp/server.js +216 -0
  102. package/dist/lsp/lsp/server.js.map +1 -0
  103. package/dist/lsp/semantic_tokens.d.ts +4 -0
  104. package/dist/lsp/semantic_tokens.d.ts.map +1 -0
  105. package/dist/lsp/semantic_tokens.js +158 -0
  106. package/dist/lsp/semantic_tokens.js.map +1 -0
  107. package/dist/lsp/server.bundle.d.ts +2 -0
  108. package/dist/lsp/server.bundle.d.ts.map +1 -0
  109. package/dist/lsp/server.bundle.js +256 -0
  110. package/dist/lsp/server.d.ts +2 -0
  111. package/dist/lsp/server.d.ts.map +1 -0
  112. package/dist/lsp/server.js +216 -0
  113. package/dist/lsp/server.js.map +1 -0
  114. package/dist/lsp/types.d.ts +71 -0
  115. package/dist/lsp/types.d.ts.map +1 -0
  116. package/dist/lsp/types.js +4 -0
  117. package/dist/lsp/types.js.map +1 -0
  118. package/dist/node/index.js +186 -0
  119. package/dist/node/index.js.map +196 -0
  120. package/dist/node/node.js +187 -0
  121. package/dist/node/node.js.map +198 -0
  122. package/dist/node.d.ts +4 -0
  123. package/dist/node.d.ts.map +1 -0
  124. package/dist/sdk/builder.d.ts +39 -0
  125. package/dist/sdk/builder.d.ts.map +1 -0
  126. package/dist/sdk/exporter.d.ts +13 -0
  127. package/dist/sdk/exporter.d.ts.map +1 -0
  128. package/dist/sdk/index.d.ts +3 -0
  129. package/dist/sdk/index.d.ts.map +1 -0
  130. package/dist/types.d.ts +71 -0
  131. package/dist/types.d.ts.map +1 -0
  132. package/dist/utils/emitter.d.ts +25 -0
  133. package/dist/utils/emitter.d.ts.map +1 -0
  134. package/dist/utils/utils.d.ts +18 -0
  135. package/dist/utils/utils.d.ts.map +1 -0
  136. package/package.json +91 -0
@@ -0,0 +1,624 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCompletionItems = getCompletionItems;
4
+ exports.findPathAtOffset = findPathAtOffset;
5
+ const node_1 = require("vscode-languageserver/node");
6
+ const yaml_1 = require("yaml");
7
+ const data_context_1 = require("./data-context");
8
+ const directives_1 = require("./directives");
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
11
+ // --- CONSTANTS & DEFINITIONS ---
12
+ const TOP_LEVEL_KEYS = [
13
+ { label: 'id', kind: node_1.CompletionItemKind.Field, detail: 'Unique identifier for the rule' },
14
+ { label: 'name', kind: node_1.CompletionItemKind.Field, detail: 'Human readable name' },
15
+ { label: 'description', kind: node_1.CompletionItemKind.Field, detail: 'What this rule does' },
16
+ { label: 'on', kind: node_1.CompletionItemKind.Keyword, detail: 'The event that triggers this rule' },
17
+ { label: 'if', kind: node_1.CompletionItemKind.Keyword, detail: 'Conditions that must be met' },
18
+ { label: 'do', kind: node_1.CompletionItemKind.Keyword, detail: 'Actions to perform when triggered' },
19
+ { label: 'priority', kind: node_1.CompletionItemKind.Property, detail: 'Rule execution priority (higher = first)' },
20
+ { label: 'enabled', kind: node_1.CompletionItemKind.Property, detail: 'Whether this rule is active' },
21
+ { label: 'cooldown', kind: node_1.CompletionItemKind.Property, detail: 'Wait time in ms between executions' },
22
+ { label: 'tags', kind: node_1.CompletionItemKind.Property, detail: 'Categorization tags' },
23
+ { label: 'comment', kind: node_1.CompletionItemKind.Text, detail: 'Internal developer note' }
24
+ ];
25
+ const EVENTS = [
26
+ { label: 'minecraft:player_join', kind: node_1.CompletionItemKind.Event },
27
+ { label: 'minecraft:player_quit', kind: node_1.CompletionItemKind.Event },
28
+ { label: 'minecraft:chat', kind: node_1.CompletionItemKind.Event },
29
+ { label: 'tiktok:chat', kind: node_1.CompletionItemKind.Event },
30
+ { label: 'tiktok:gift', kind: node_1.CompletionItemKind.Event },
31
+ { label: 'tiktok:like', kind: node_1.CompletionItemKind.Event },
32
+ { label: 'twitch:chat', kind: node_1.CompletionItemKind.Event },
33
+ { label: 'twitch:follow', kind: node_1.CompletionItemKind.Event },
34
+ { label: 'bopl:webhook', kind: node_1.CompletionItemKind.Event },
35
+ { label: 'ANY_EVENT', kind: node_1.CompletionItemKind.Event },
36
+ { label: 'USER_LOGIN', kind: node_1.CompletionItemKind.Event },
37
+ { label: 'GAME_OVER', kind: node_1.CompletionItemKind.Event },
38
+ { label: 'COMMAND', kind: node_1.CompletionItemKind.Event },
39
+ { label: 'ALERT', kind: node_1.CompletionItemKind.Event }
40
+ ];
41
+ const OPERATORS = [
42
+ { label: 'EQ', kind: node_1.CompletionItemKind.Operator, detail: 'Equal (==)' },
43
+ { label: 'NEQ', kind: node_1.CompletionItemKind.Operator, detail: 'Not Equal (!=)' },
44
+ { label: 'GT', kind: node_1.CompletionItemKind.Operator, detail: 'Greater Than (>)' },
45
+ { label: 'GTE', kind: node_1.CompletionItemKind.Operator, detail: 'Greater Than Equals (>=)' },
46
+ { label: 'LT', kind: node_1.CompletionItemKind.Operator, detail: 'Less Than (<)' },
47
+ { label: 'LTE', kind: node_1.CompletionItemKind.Operator, detail: 'Less Than Equals (<=)' },
48
+ { label: 'IN', kind: node_1.CompletionItemKind.Operator, detail: 'Value exists in the provided list' },
49
+ { label: 'NOT_IN', kind: node_1.CompletionItemKind.Operator, detail: 'Value does not exist in the list' },
50
+ { label: 'CONTAINS', kind: node_1.CompletionItemKind.Operator, detail: 'String contains substring or List contains item' },
51
+ { label: 'MATCHES', kind: node_1.CompletionItemKind.Operator, detail: 'Regex pattern match' },
52
+ { label: 'RANGE', kind: node_1.CompletionItemKind.Operator, detail: 'Numeric value between [min, max]' },
53
+ { label: 'SINCE', kind: node_1.CompletionItemKind.Operator, detail: 'Date is after or equal to value' },
54
+ { label: 'AFTER', kind: node_1.CompletionItemKind.Operator, detail: 'Alias for SINCE' },
55
+ { label: 'BEFORE', kind: node_1.CompletionItemKind.Operator, detail: 'Date is before value' },
56
+ { label: 'UNTIL', kind: node_1.CompletionItemKind.Operator, detail: 'Alias for BEFORE' },
57
+ { label: 'AND', kind: node_1.CompletionItemKind.Operator, detail: 'Logical AND (for groups)' },
58
+ { label: 'OR', kind: node_1.CompletionItemKind.Operator, detail: 'Logical OR (for groups)' }
59
+ ];
60
+ const ACTION_TYPES = [
61
+ { label: 'log', kind: node_1.CompletionItemKind.EnumMember, detail: 'Print message to console' },
62
+ { label: 'execute', kind: node_1.CompletionItemKind.EnumMember, detail: 'Run local command' },
63
+ { label: 'forward', kind: node_1.CompletionItemKind.EnumMember, detail: 'Forward event to URL' },
64
+ { label: 'response', kind: node_1.CompletionItemKind.EnumMember, detail: 'Return HTTP response' },
65
+ { label: 'STATE_SET', kind: node_1.CompletionItemKind.EnumMember, detail: 'Save value to global state' },
66
+ { label: 'STATE_INCREMENT', kind: node_1.CompletionItemKind.EnumMember, detail: 'Increment numeric state key' },
67
+ { label: 'EMIT_EVENT', kind: node_1.CompletionItemKind.EnumMember, detail: 'Trigger another event internally' },
68
+ ];
69
+ const CONDITION_KEYS = [
70
+ { label: 'field', kind: node_1.CompletionItemKind.Field, detail: 'Path to context data (e.g. data.user)' },
71
+ { label: 'operator', kind: node_1.CompletionItemKind.Field, detail: 'Comparison operator (EQ, GT, etc.)' },
72
+ { label: 'value', kind: node_1.CompletionItemKind.Value, detail: 'The value to compare against' },
73
+ { label: 'conditions', kind: node_1.CompletionItemKind.Field, detail: 'Sub-conditions for grouping' }
74
+ ];
75
+ const ACTION_KEYS = [
76
+ { label: 'type', kind: node_1.CompletionItemKind.Field, detail: 'The type of action to perform' },
77
+ { label: 'params', kind: node_1.CompletionItemKind.Variable, detail: 'Configuration for the action' },
78
+ { label: 'delay', kind: node_1.CompletionItemKind.Property, detail: 'Delay in ms (integer)' },
79
+ { label: 'probability', kind: node_1.CompletionItemKind.Property, detail: 'Execution chance (0-1)' },
80
+ { label: 'mode', kind: node_1.CompletionItemKind.Property, detail: 'Grouping mode (ALL, SEQUENCE, EITHER)' },
81
+ { label: 'actions', kind: node_1.CompletionItemKind.Property, detail: 'List of sub-actions' }
82
+ ];
83
+ const PARAM_KEYS = {
84
+ 'log': [
85
+ { label: 'message', kind: node_1.CompletionItemKind.Property },
86
+ { label: 'content', kind: node_1.CompletionItemKind.Property },
87
+ { label: 'level', kind: node_1.CompletionItemKind.Property, detail: 'info, warn, error' },
88
+ ],
89
+ 'execute': [
90
+ { label: 'command', kind: node_1.CompletionItemKind.Property },
91
+ { label: 'safe', kind: node_1.CompletionItemKind.Property, detail: 'boolean (default: false)' },
92
+ { label: 'dir', kind: node_1.CompletionItemKind.Property, detail: 'working directory' },
93
+ ],
94
+ 'forward': [
95
+ { label: 'url', kind: node_1.CompletionItemKind.Property },
96
+ { label: 'method', kind: node_1.CompletionItemKind.Property, detail: 'POST, GET, PUT...' },
97
+ { label: 'headers', kind: node_1.CompletionItemKind.Property },
98
+ { label: 'body', kind: node_1.CompletionItemKind.Property },
99
+ ],
100
+ 'response': [
101
+ { label: 'content', kind: node_1.CompletionItemKind.Property },
102
+ { label: 'statusCode', kind: node_1.CompletionItemKind.Property, detail: '200, 404, etc.' },
103
+ { label: 'contentType', kind: node_1.CompletionItemKind.Property, detail: 'application/json' },
104
+ ],
105
+ 'STATE_SET': [
106
+ { label: 'key', kind: node_1.CompletionItemKind.Property },
107
+ { label: 'value', kind: node_1.CompletionItemKind.Property },
108
+ { label: 'ttl', kind: node_1.CompletionItemKind.Property, detail: 'Time to live in ms' },
109
+ ],
110
+ 'STATE_INCREMENT': [
111
+ { label: 'key', kind: node_1.CompletionItemKind.Property },
112
+ { label: 'amount', kind: node_1.CompletionItemKind.Property },
113
+ ],
114
+ 'EMIT_EVENT': [
115
+ { label: 'event', kind: node_1.CompletionItemKind.Property },
116
+ { label: 'data', kind: node_1.CompletionItemKind.Property },
117
+ ]
118
+ };
119
+ const SNIPPETS = [
120
+ {
121
+ label: 'trigger_rule',
122
+ kind: node_1.CompletionItemKind.Snippet,
123
+ insertText: '- id: ${1:rule-id}\n on: ${2:EVENT}\n if:\n field: ${3:data.field}\n operator: ${4:EQ}\n value: ${5:target}\n do:\n type: ${6:log}\n params:\n message: ${7:Done}',
124
+ insertTextFormat: node_1.InsertTextFormat.Snippet,
125
+ detail: 'New rule template'
126
+ },
127
+ {
128
+ label: 'log_action',
129
+ kind: node_1.CompletionItemKind.Snippet,
130
+ insertText: 'type: log\nparams:\n message: ${1:message}',
131
+ insertTextFormat: node_1.InsertTextFormat.Snippet,
132
+ detail: 'Log action template'
133
+ },
134
+ {
135
+ label: 'condition_nested',
136
+ kind: node_1.CompletionItemKind.Snippet,
137
+ insertText: 'operator: ${1|AND,OR|}\nconditions:\n - field: ${2:data.x}\n operator: ${3:EQ}\n value: ${4:val}',
138
+ insertTextFormat: node_1.InsertTextFormat.Snippet,
139
+ detail: 'Nested condition group'
140
+ }
141
+ ];
142
+ // --- MAIN LOGIC ---
143
+ function getCompletionItems(document, position) {
144
+ console.log(`[LSP] getCompletionItems called for document: ${document.uri}`);
145
+ console.log(`[LSP] Position: line ${position.line}, character ${position.character}`);
146
+ // Load data from import directives only (declarative approach)
147
+ const imports = (0, directives_1.getImportDirectives)(document, document.uri);
148
+ console.log(`[LSP] Found ${imports.length} import directives`);
149
+ if (imports.length > 0) {
150
+ console.log(`[LSP] Loading data from imports:`, imports);
151
+ (0, data_context_1.loadDataFromImports)(imports);
152
+ // Verificar datos cargados
153
+ const allData = data_context_1.globalDataContext.getValue('');
154
+ console.log(`[LSP] Data loaded in context:`, allData);
155
+ console.log(`[LSP] Available top-level keys:`, allData ? Object.keys(allData) : 'none');
156
+ }
157
+ else {
158
+ console.log(`[LSP] No imports found, clearing data context`);
159
+ // Clear data context when no imports are defined
160
+ data_context_1.globalDataContext.clear();
161
+ }
162
+ const text = document.getText();
163
+ const doc = (0, yaml_1.parseDocument)(text);
164
+ const lines = text.split('\n');
165
+ const line = lines[position.line] || '';
166
+ const offset = document.offsetAt(position);
167
+ console.log(`[LSP] Current line: "${line}"`);
168
+ console.log(`[LSP] Offset: ${offset}`);
169
+ // Check if we're in a comment line (for directive completion)
170
+ if (line.trim().startsWith('#')) {
171
+ const directiveCompletions = getDirectiveCompletions(line, position.character, document);
172
+ if (directiveCompletions.length > 0) {
173
+ return directiveCompletions;
174
+ }
175
+ }
176
+ // Check if we're inside a template variable ${...}
177
+ const templateMatch = checkTemplateVariable(line, position.character);
178
+ if (templateMatch) {
179
+ return getTemplateVariableCompletions(templateMatch);
180
+ }
181
+ // 1. Check if we are in a VALUE position (after colon)
182
+ const colonIndex = line.indexOf(':');
183
+ if (colonIndex !== -1 && position.character > colonIndex) {
184
+ const key = line.substring(0, colonIndex).trim().replace(/^- /, '');
185
+ const path = findPathAtOffset(doc.contents, offset) || [];
186
+ return getValueCompletionsByKey(key, path);
187
+ }
188
+ // 2. We are in a KEY position or start of line
189
+ const path = findPathAtOffset(doc.contents, offset) || [];
190
+ const completions = getKeyCompletions(path, line);
191
+ console.log(`[LSP] Returning ${completions.length} completion items`);
192
+ if (completions.length > 0) {
193
+ console.log(`[LSP] First few items:`, completions.slice(0, 3).map(c => c.label));
194
+ }
195
+ return completions;
196
+ }
197
+ /**
198
+ * Check if cursor is inside a template variable and return the context
199
+ */
200
+ function checkTemplateVariable(line, character) {
201
+ console.log(`[LSP] checkTemplateVariable - line: "${line}", character: ${character}`);
202
+ // Find all template variable positions in the line
203
+ const regex = /\$\{([^}]*)\}/g;
204
+ let match;
205
+ while ((match = regex.exec(line)) !== null) {
206
+ const start = match.index;
207
+ const end = match.index + match[0].length;
208
+ console.log(`[LSP] Found template: "${match[0]}" at positions ${start}-${end}`);
209
+ // Check if cursor is inside this template (inclusive of start and end)
210
+ if (character >= start && character <= end) {
211
+ const content = match[1] || '';
212
+ const dotIndex = content.lastIndexOf('.');
213
+ console.log(`[LSP] Cursor inside template, content: "${content}", dotIndex: ${dotIndex}`);
214
+ return {
215
+ prefix: dotIndex >= 0 ? content.substring(0, dotIndex + 1) : content,
216
+ inTemplate: true
217
+ };
218
+ }
219
+ }
220
+ // Check if we're typing after ${
221
+ const beforeCursor = line.substring(0, character);
222
+ const lastDollarBrace = beforeCursor.lastIndexOf('${');
223
+ const lastCloseBrace = beforeCursor.lastIndexOf('}');
224
+ console.log(`[LSP] Checking for incomplete template - lastDollarBrace: ${lastDollarBrace}, lastCloseBrace: ${lastCloseBrace}`);
225
+ if (lastDollarBrace > lastCloseBrace) {
226
+ const content = beforeCursor.substring(lastDollarBrace + 2);
227
+ const dotIndex = content.lastIndexOf('.');
228
+ console.log(`[LSP] Found incomplete template, content: "${content}", dotIndex: ${dotIndex}`);
229
+ return {
230
+ prefix: dotIndex >= 0 ? content.substring(0, dotIndex + 1) : content,
231
+ inTemplate: true
232
+ };
233
+ }
234
+ // Check if we're right at the $ or { position and should start a new template
235
+ if (character > 0) {
236
+ const charBefore = line[character - 1];
237
+ if (charBefore === '$' || charBefore === '{') {
238
+ console.log(`[LSP] Found $ or { at position ${character - 1}`);
239
+ return {
240
+ prefix: '',
241
+ inTemplate: true
242
+ };
243
+ }
244
+ }
245
+ // Special check: if we're at the very beginning of a potential template
246
+ if (character < line.length) {
247
+ const remaining = line.substring(character);
248
+ if (remaining.startsWith('${') || remaining.startsWith('{')) {
249
+ console.log(`[LSP] Found potential template start at current position`);
250
+ return {
251
+ prefix: '',
252
+ inTemplate: true
253
+ };
254
+ }
255
+ }
256
+ console.log(`[LSP] No template found`);
257
+ return null;
258
+ }
259
+ /**
260
+ * Get completions for template variables
261
+ */
262
+ function getTemplateVariableCompletions(context) {
263
+ const prefix = context.prefix.trim();
264
+ console.log(`[LSP] Template variable completion - prefix: "${prefix}"`);
265
+ // Handle different variable types based on what's loaded in globalDataContext
266
+ const allData = data_context_1.globalDataContext.getValue('');
267
+ console.log(`[LSP] Current data context:`, allData);
268
+ if (!allData || typeof allData !== 'object') {
269
+ console.log(`[LSP] No data available in context`);
270
+ return [{
271
+ label: 'data',
272
+ kind: node_1.CompletionItemKind.Variable,
273
+ detail: 'No imported data available',
274
+ documentation: 'Add a data import directive like: # @import data from ./data.json'
275
+ }];
276
+ }
277
+ // Check if we're at the root level (after ${ or ${data. etc)
278
+ const cleanPrefix = prefix.replace('${', '').replace(/\.$/, '');
279
+ console.log(`[LSP] Clean prefix: "${cleanPrefix}"`);
280
+ // If we're at root level, suggest all available top-level variables (aliases)
281
+ if (!cleanPrefix || cleanPrefix === '') {
282
+ const suggestions = Object.keys(allData).map(key => {
283
+ const value = allData[key];
284
+ const valueType = Array.isArray(value) ? 'array' : typeof value;
285
+ const sampleValue = valueType === 'object' ? JSON.stringify(value).substring(0, 50) + '...' : String(value);
286
+ return {
287
+ label: key,
288
+ kind: node_1.CompletionItemKind.Variable,
289
+ detail: `${valueType} (imported data)`,
290
+ documentation: `Sample value: ${sampleValue}`,
291
+ insertText: key
292
+ };
293
+ });
294
+ console.log(`[LSP] Root level suggestions:`, suggestions.map(s => s.label));
295
+ return suggestions;
296
+ }
297
+ // If we have a specific prefix, get fields from that path
298
+ // The prefix might be something like "data" or "data.server" or "config.username"
299
+ const fields = data_context_1.globalDataContext.getFields(cleanPrefix);
300
+ console.log(`[LSP] Fields for prefix "${cleanPrefix}":`, fields.map(f => f.name));
301
+ if (fields.length > 0) {
302
+ const suggestions = fields.map(field => {
303
+ const sampleValue = field.type === 'object' ?
304
+ JSON.stringify(field.value).substring(0, 50) + '...' :
305
+ data_context_1.globalDataContext.getFormattedValue(field.value);
306
+ return {
307
+ label: field.name,
308
+ kind: field.type === 'object' ? node_1.CompletionItemKind.Module : node_1.CompletionItemKind.Field,
309
+ detail: `${field.type} (imported data)`,
310
+ documentation: `Sample value: ${sampleValue}`,
311
+ insertText: field.name
312
+ };
313
+ });
314
+ console.log(`[LSP] Field suggestions:`, suggestions.map(s => s.label));
315
+ return suggestions;
316
+ }
317
+ // If no fields found, suggest similar paths or provide helpful message
318
+ console.log(`[LSP] No suggestions found for prefix "${prefix}"`);
319
+ // Check if we have any data at all to provide suggestions
320
+ const allKeys = Object.keys(allData);
321
+ if (allKeys.length > 0) {
322
+ return [{
323
+ label: cleanPrefix,
324
+ kind: node_1.CompletionItemKind.Text,
325
+ detail: 'Path not found in imported data',
326
+ documentation: `Available top-level keys: ${allKeys.join(', ')}`,
327
+ insertText: cleanPrefix
328
+ }];
329
+ }
330
+ return [];
331
+ }
332
+ function getValueCompletionsByKey(key, path) {
333
+ switch (key) {
334
+ case 'on': return EVENTS;
335
+ case 'operator': return OPERATORS;
336
+ case 'type': return ACTION_TYPES;
337
+ case 'mode':
338
+ return [
339
+ { label: 'ALL', kind: node_1.CompletionItemKind.EnumMember, detail: 'Execute all' },
340
+ { label: 'SEQUENCE', kind: node_1.CompletionItemKind.EnumMember, detail: 'Wait for each' },
341
+ { label: 'EITHER', kind: node_1.CompletionItemKind.EnumMember, detail: 'Random choice' }
342
+ ];
343
+ case 'enabled':
344
+ return [
345
+ { label: 'true', kind: node_1.CompletionItemKind.Value },
346
+ { label: 'false', kind: node_1.CompletionItemKind.Value }
347
+ ];
348
+ case 'field':
349
+ // Only suggest imported data fields
350
+ const fields = data_context_1.globalDataContext.getFields('data');
351
+ return fields.map(field => ({
352
+ label: field.name,
353
+ kind: node_1.CompletionItemKind.Field,
354
+ detail: `${field.type}${field.value !== undefined ? ` = ${data_context_1.globalDataContext.getFormattedValue(field.value)}` : ''}`
355
+ }));
356
+ case 'value':
357
+ return getValueSpecificToOperator(path);
358
+ }
359
+ // Return empty array instead of DYNAMIC_VALUES
360
+ return [];
361
+ }
362
+ function getKeyCompletions(path, line) {
363
+ // If line starts with '- ', we might be in a list
364
+ if (line.trim().startsWith('-')) {
365
+ const parentPair = findEffectiveParentPair(path);
366
+ if (parentPair) {
367
+ const pk = String(parentPair.key.value);
368
+ if (pk === 'do' || pk === 'actions')
369
+ return ACTION_KEYS;
370
+ if (pk === 'if' || pk === 'conditions')
371
+ return CONDITION_KEYS;
372
+ }
373
+ return SNIPPETS.length > 0 ? [SNIPPETS[0]] : [];
374
+ }
375
+ const contextPair = findEffectiveParentPair(path);
376
+ if (!contextPair)
377
+ return TOP_LEVEL_KEYS;
378
+ const key = String(contextPair.key.value);
379
+ if (key === 'if' || key === 'conditions')
380
+ return CONDITION_KEYS;
381
+ if (key === 'do' || key === 'actions')
382
+ return ACTION_KEYS;
383
+ if (key === 'params') {
384
+ const actionMap = findNearestActionMap(path);
385
+ if (actionMap) {
386
+ const typePair = actionMap.items.find(item => (0, yaml_1.isPair)(item) && String(item.key.value) === 'type');
387
+ if (typePair && (0, yaml_1.isScalar)(typePair.value)) {
388
+ return PARAM_KEYS[String(typePair.value.value)] || [];
389
+ }
390
+ }
391
+ }
392
+ return TOP_LEVEL_KEYS;
393
+ }
394
+ // --- HELPERS ---
395
+ function isKeyOfParent(node, path) {
396
+ const parent = path[path.length - 2];
397
+ if ((0, yaml_1.isPair)(parent))
398
+ return parent.key === node;
399
+ return false;
400
+ }
401
+ function findEffectiveParentPair(path) {
402
+ for (let i = path.length - 1; i >= 0; i--) {
403
+ const item = path[i];
404
+ if ((0, yaml_1.isPair)(item))
405
+ return item;
406
+ }
407
+ return null;
408
+ }
409
+ function findNearestActionMap(path) {
410
+ for (let i = path.length - 1; i >= 0; i--) {
411
+ const item = path[i];
412
+ if ((0, yaml_1.isMap)(item)) {
413
+ const hasType = item.items.some(p => (0, yaml_1.isPair)(p) && String(p.key.value) === 'type');
414
+ if (hasType)
415
+ return item;
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ function getValueSpecificToOperator(path) {
421
+ // Look for a map in the path that contains an 'operator' key
422
+ const map = path.slice().reverse().find(n => (0, yaml_1.isMap)(n));
423
+ if (!map)
424
+ return [];
425
+ const opPair = map.items.find(item => (0, yaml_1.isPair)(item) && String(item.key.value) === 'operator');
426
+ if (!opPair || !(0, yaml_1.isScalar)(opPair.value))
427
+ return [];
428
+ const op = String(opPair.value.value);
429
+ switch (op) {
430
+ case 'RANGE':
431
+ return [{ label: '[min, max]', kind: node_1.CompletionItemKind.Snippet, insertText: '[$1, $2]', insertTextFormat: node_1.InsertTextFormat.Snippet }];
432
+ case 'IN':
433
+ case 'NOT_IN':
434
+ return [{ label: '[item1, item2]', kind: node_1.CompletionItemKind.Snippet, insertText: '[$1, $2]', insertTextFormat: node_1.InsertTextFormat.Snippet }];
435
+ case 'MATCHES':
436
+ return [{ label: '"regex"', kind: node_1.CompletionItemKind.Snippet, insertText: '"^$1$"', insertTextFormat: node_1.InsertTextFormat.Snippet }];
437
+ }
438
+ return [];
439
+ }
440
+ function findPathAtOffset(node, offset, currentPath = []) {
441
+ if (!node)
442
+ return null;
443
+ // Check range
444
+ const range = node.range;
445
+ if (range) {
446
+ // [start, end, optional_something]
447
+ // Parser range is [start, end].
448
+ // We want to be inclusive and a bit more for completions at the end of a line.
449
+ if (offset < range[0] || offset > range[1] + 1) {
450
+ // If we are exactly 1 char past the end (like at the end of "mode: "),
451
+ // we still might want this node if it's the most specific one.
452
+ }
453
+ }
454
+ const newPath = [...currentPath, node];
455
+ if ((0, yaml_1.isMap)(node)) {
456
+ for (const item of node.items) {
457
+ if ((0, yaml_1.isPair)(item)) {
458
+ const itemRange = item.range;
459
+ if (itemRange && offset >= itemRange[0] && offset <= itemRange[1] + 1) {
460
+ return findPathAtOffset(item, offset, newPath);
461
+ }
462
+ }
463
+ }
464
+ return newPath;
465
+ }
466
+ if ((0, yaml_1.isSeq)(node)) {
467
+ for (const item of node.items) {
468
+ const itemRange = item.range;
469
+ if (itemRange && offset >= itemRange[0] && offset <= itemRange[1] + 1) {
470
+ return findPathAtOffset(item, offset, newPath);
471
+ }
472
+ }
473
+ return newPath;
474
+ }
475
+ if ((0, yaml_1.isPair)(node)) {
476
+ // If we are in a pair, we could be in key or value
477
+ const keyRange = node.key?.range;
478
+ if (keyRange && offset >= keyRange[0] && offset <= keyRange[1] + 1) {
479
+ return findPathAtOffset(node.key, offset, newPath);
480
+ }
481
+ // If there's a value, check it
482
+ if (node.value) {
483
+ const valRange = node.value?.range;
484
+ if (valRange && offset >= valRange[0] && offset <= valRange[1] + 1) {
485
+ return findPathAtOffset(node.value, offset, newPath);
486
+ }
487
+ }
488
+ return newPath;
489
+ }
490
+ return newPath;
491
+ }
492
+ /**
493
+ * Get completions for directive comments (lines starting with #)
494
+ */
495
+ function getDirectiveCompletions(line, character, document) {
496
+ console.log(`[LSP] Checking directive completions for line: "${line}" at character ${character}`);
497
+ // Check if we're in a directive context
498
+ const directiveMatch = line.match(/#\s*@?([\w-]*)$/);
499
+ if (!directiveMatch)
500
+ return [];
501
+ const partialDirective = directiveMatch[1] || '';
502
+ console.log(`[LSP] Partial directive: "${partialDirective}"`);
503
+ // Check if we're in an import directive and need file path completion
504
+ if (partialDirective.startsWith('import') || line.includes('@import')) {
505
+ const importMatch = line.match(/@import\s+\w+\s+from\s+['"]?([^'"]*)$/);
506
+ if (importMatch) {
507
+ const partialPath = importMatch[1] || '';
508
+ return getImportFileCompletions(document.uri, partialPath);
509
+ }
510
+ }
511
+ // If we're just starting a directive (after # or @)
512
+ if (partialDirective === '' || line.match(/#\s*$/)) {
513
+ return getAllDirectiveCompletions();
514
+ }
515
+ // If we have a partial directive, filter completions
516
+ const allDirectives = getAllDirectiveCompletions();
517
+ return allDirectives.filter(item => item.label.toLowerCase().startsWith(partialDirective.toLowerCase()));
518
+ }
519
+ /**
520
+ * Get all available directive completions with enhanced categorization and colors
521
+ */
522
+ function getAllDirectiveCompletions() {
523
+ const directives = [
524
+ // Import directives (Macro category - distinctive color)
525
+ {
526
+ label: 'import',
527
+ kind: node_1.CompletionItemKind.Function, // Function type for imports
528
+ detail: 'Import data from JSON/YAML file',
529
+ insertText: 'import ${1:alias} from ${2:./path/to/file.json}',
530
+ insertTextFormat: node_1.InsertTextFormat.Snippet,
531
+ documentation: 'Imports data from a JSON or YAML file for use in autocompletion and validation. Example: @import data from ./data.json',
532
+ data: { category: 'import', color: 'macro' }
533
+ },
534
+ // Global lint control (Namespace category)
535
+ {
536
+ label: 'disable-lint',
537
+ kind: node_1.CompletionItemKind.Module, // Module type for global controls
538
+ detail: 'Disable all linting for subsequent lines',
539
+ insertText: 'disable-lint',
540
+ documentation: 'Disables all linting and validation for the rest of the document or until @enable-lint is encountered',
541
+ data: { category: 'global-control', color: 'namespace' }
542
+ },
543
+ {
544
+ label: 'enable-lint',
545
+ kind: node_1.CompletionItemKind.Module, // Module type for global controls
546
+ detail: 'Enable all linting (default state)',
547
+ insertText: 'enable-lint',
548
+ documentation: 'Enables linting and validation (this is the default state)',
549
+ data: { category: 'global-control', color: 'namespace' }
550
+ },
551
+ // Line-specific control (EnumMember category)
552
+ {
553
+ label: 'disable-next-line',
554
+ kind: node_1.CompletionItemKind.EnumMember, // EnumMember for line-specific
555
+ detail: 'Disable lint for the next line only',
556
+ insertText: 'disable-next-line',
557
+ documentation: 'Disables linting and validation for the next line only',
558
+ data: { category: 'line-control', color: 'enumMember' }
559
+ },
560
+ {
561
+ label: 'disable-line',
562
+ kind: node_1.CompletionItemKind.EnumMember, // EnumMember for line-specific
563
+ detail: 'Disable lint for current line',
564
+ insertText: 'disable-line',
565
+ documentation: 'Disables linting and validation for the current line',
566
+ data: { category: 'line-control', color: 'enumMember' }
567
+ },
568
+ // Rule-specific control (Type category)
569
+ {
570
+ label: 'disable-rule',
571
+ kind: node_1.CompletionItemKind.TypeParameter, // TypeParameter for rule-specific
572
+ detail: 'Disable specific rule(s)',
573
+ insertText: 'disable-rule ${1:rule-name}',
574
+ insertTextFormat: node_1.InsertTextFormat.Snippet,
575
+ documentation: 'Disables specific validation rules. Example: @disable-rule missing-id, invalid-operator',
576
+ data: { category: 'rule-control', color: 'type' }
577
+ },
578
+ {
579
+ label: 'enable-rule',
580
+ kind: node_1.CompletionItemKind.TypeParameter, // TypeParameter for rule-specific
581
+ detail: 'Enable specific rule(s)',
582
+ insertText: 'enable-rule ${1:rule-name}',
583
+ insertTextFormat: node_1.InsertTextFormat.Snippet,
584
+ documentation: 'Enables specific validation rules that were previously disabled',
585
+ data: { category: 'rule-control', color: 'type' }
586
+ }
587
+ ];
588
+ return directives;
589
+ }
590
+ /**
591
+ * Get file path completions for import directives
592
+ */
593
+ function getImportFileCompletions(documentPath, partialPath) {
594
+ const completions = [];
595
+ try {
596
+ const decodedUri = decodeURIComponent(documentPath);
597
+ const documentDir = (0, path_1.dirname)(decodedUri.replace('file:///', '').replace(/^\/([A-Z]:)/, '$1'));
598
+ const currentDir = partialPath.includes('/') ? (0, path_1.dirname)((0, path_1.join)(documentDir, partialPath)) : documentDir;
599
+ // Get list of JSON and YAML files in the directory
600
+ const fs = require('fs');
601
+ if ((0, fs_1.existsSync)(currentDir)) {
602
+ const files = fs.readdirSync(currentDir);
603
+ const validExtensions = ['.json', '.yaml', '.yml'];
604
+ files.forEach((file) => {
605
+ const ext = (0, path_1.extname)(file).toLowerCase();
606
+ if (validExtensions.includes(ext)) {
607
+ const relativePath = './' + (partialPath.includes('/') ?
608
+ (0, path_1.dirname)(partialPath) + '/' + file : file);
609
+ completions.push({
610
+ label: relativePath,
611
+ kind: node_1.CompletionItemKind.File,
612
+ detail: `${ext.toUpperCase()} data file`,
613
+ insertText: `"${relativePath}"`
614
+ });
615
+ }
616
+ });
617
+ }
618
+ }
619
+ catch (error) {
620
+ console.log(`[LSP] Error getting file completions:`, error);
621
+ }
622
+ return completions;
623
+ }
624
+ //# sourceMappingURL=completions.js.map