norn-cli 2.3.0 → 2.4.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 (92) hide show
  1. package/.claude/skills/norn-social-campaign/SKILL.md +70 -0
  2. package/CHANGELOG.md +6 -0
  3. package/demos/nornenv-region-refactor/README.md +64 -0
  4. package/dist/cli.js +360 -1
  5. package/out/apiResponseIntellisenseCache.js +394 -0
  6. package/out/assertionRunner.js +567 -0
  7. package/out/cacheDir.js +136 -0
  8. package/out/chatParticipant.js +763 -0
  9. package/out/cli/colors.js +127 -0
  10. package/out/cli/formatters/assertion.js +102 -0
  11. package/out/cli/formatters/index.js +23 -0
  12. package/out/cli/formatters/response.js +106 -0
  13. package/out/cli/formatters/summary.js +246 -0
  14. package/out/cli/redaction.js +237 -0
  15. package/out/cli/reporters/html.js +689 -0
  16. package/out/cli/reporters/index.js +22 -0
  17. package/out/cli/reporters/junit.js +226 -0
  18. package/out/codeLensProvider.js +351 -0
  19. package/out/compareContentProvider.js +85 -0
  20. package/out/completionProvider.js +3739 -0
  21. package/out/contractAssertionSummary.js +225 -0
  22. package/out/contractDecorationProvider.js +243 -0
  23. package/out/coverageCalculator.js +879 -0
  24. package/out/coveragePanel.js +597 -0
  25. package/out/debug/breakpointResolver.js +84 -0
  26. package/out/debug/breakpoints.js +52 -0
  27. package/out/debug/nornDebugAdapter.js +166 -0
  28. package/out/debug/nornDebugSession.js +613 -0
  29. package/out/debug/sequenceLocationIndex.js +77 -0
  30. package/out/debug/types.js +3 -0
  31. package/out/deepClone.js +21 -0
  32. package/out/diagnosticProvider.js +2554 -0
  33. package/out/environmentParser.js +736 -0
  34. package/out/environmentProvider.js +544 -0
  35. package/out/environmentTemplates.js +146 -0
  36. package/out/errors/formatError.js +113 -0
  37. package/out/errors/nornError.js +29 -0
  38. package/out/formUrlEncoded.js +89 -0
  39. package/out/httpClient.js +348 -0
  40. package/out/httpRuntimeOptions.js +16 -0
  41. package/out/importErrors.js +31 -0
  42. package/out/inlayHintResolver.js +70 -0
  43. package/out/jsonFileReader.js +323 -0
  44. package/out/mcpClient.js +193 -0
  45. package/out/mcpConfig.js +184 -0
  46. package/out/mcpToolIntellisenseCache.js +96 -0
  47. package/out/mcpToolSchema.js +50 -0
  48. package/out/nornConfig.js +132 -0
  49. package/out/nornHoverProvider.js +124 -0
  50. package/out/nornInlayHintsProvider.js +191 -0
  51. package/out/nornPrompt.js +755 -0
  52. package/out/nornSqlParser.js +286 -0
  53. package/out/nornapiHoverProvider.js +135 -0
  54. package/out/nornapiInlayHintsProvider.js +94 -0
  55. package/out/nornapiParser.js +324 -0
  56. package/out/nornenvCodeActionProvider.js +101 -0
  57. package/out/nornenvDecorationProvider.js +239 -0
  58. package/out/nornenvFoldingProvider.js +63 -0
  59. package/out/nornenvHoverProvider.js +114 -0
  60. package/out/nornenvInlayHintsProvider.js +99 -0
  61. package/out/nornenvLanguageModel.js +187 -0
  62. package/out/nornenvRegionRefactor.js +267 -0
  63. package/out/nornsqlHoverProvider.js +95 -0
  64. package/out/nornsqlInlayHintsProvider.js +114 -0
  65. package/out/parser.js +839 -0
  66. package/out/pathAccess.js +28 -0
  67. package/out/postmanImportPanel.js +732 -0
  68. package/out/postmanImportPlanner.js +1155 -0
  69. package/out/postmanImportSidebarView.js +532 -0
  70. package/out/quotedString.js +35 -0
  71. package/out/requestPreparation.js +179 -0
  72. package/out/requestValidation.js +146 -0
  73. package/out/responsePanel.js +7754 -0
  74. package/out/schemaGenerator.js +562 -0
  75. package/out/scriptRunner.js +419 -0
  76. package/out/secrets/cliSecrets.js +415 -0
  77. package/out/secrets/crypto.js +105 -0
  78. package/out/secrets/envFileSecrets.js +177 -0
  79. package/out/secrets/keyStore.js +259 -0
  80. package/out/sequenceDeclaration.js +15 -0
  81. package/out/sequenceRunner.js +3590 -0
  82. package/out/sqlAdapterRunner.js +122 -0
  83. package/out/sqlBuiltInAdapters.js +604 -0
  84. package/out/sqlConfig.js +184 -0
  85. package/out/starterCatalog.js +554 -0
  86. package/out/stringUtils.js +25 -0
  87. package/out/swaggerBodyIntellisenseCache.js +114 -0
  88. package/out/swaggerParser.js +464 -0
  89. package/out/testProvider.js +767 -0
  90. package/out/theoryCaseLoader.js +113 -0
  91. package/out/validationCache.js +211 -0
  92. package/package.json +6 -1
@@ -0,0 +1,324 @@
1
+ "use strict";
2
+ /**
3
+ * Parser for .nornapi files
4
+ *
5
+ * Parses header groups and endpoint definitions from .nornapi files.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseNornApiFile = parseNornApiFile;
9
+ exports.getHeaderGroup = getHeaderGroup;
10
+ exports.getEndpoint = getEndpoint;
11
+ exports.resolveEndpointPath = resolveEndpointPath;
12
+ exports.resolveHeaderValues = resolveHeaderValues;
13
+ exports.isApiRequestLine = isApiRequestLine;
14
+ exports.parseApiRequest = parseApiRequest;
15
+ const parser_1 = require("./parser");
16
+ /**
17
+ * Extracts parameter names from an endpoint path
18
+ * e.g., /users/{id}/orders/{orderId} -> ['id', 'orderId']
19
+ *
20
+ * Note: Does NOT extract {{variable}} references (double braces) - only {param} (single braces)
21
+ */
22
+ function extractPathParameters(path) {
23
+ const params = [];
24
+ // Match {param} but NOT {{variable}}
25
+ // Use negative lookbehind (?<!{) and negative lookahead (?!})
26
+ const regex = /(?<!\{)\{([a-zA-Z_][a-zA-Z0-9_]*)\}(?!\})/g;
27
+ let match;
28
+ while ((match = regex.exec(path)) !== null) {
29
+ params.push(match[1]);
30
+ }
31
+ return params;
32
+ }
33
+ /**
34
+ * Parses a .nornapi file and returns header groups and endpoint definitions
35
+ */
36
+ function parseNornApiFile(content) {
37
+ const lines = content.split('\n');
38
+ const headerGroups = [];
39
+ const endpoints = [];
40
+ let currentHeaderGroup = null;
41
+ let inEndpointsBlock = false;
42
+ for (let i = 0; i < lines.length; i++) {
43
+ const line = lines[i];
44
+ const trimmed = line.trim();
45
+ // Skip empty lines and comments
46
+ if (!trimmed || trimmed.startsWith('#')) {
47
+ continue;
48
+ }
49
+ // Check for headers block start: headers Name
50
+ const headersStartMatch = trimmed.match(/^headers\s+([a-zA-Z_][a-zA-Z0-9_]*)$/i);
51
+ if (headersStartMatch) {
52
+ currentHeaderGroup = {
53
+ name: headersStartMatch[1],
54
+ headers: {}
55
+ };
56
+ continue;
57
+ }
58
+ // Check for headers block end
59
+ if (/^end\s+headers$/i.test(trimmed)) {
60
+ if (currentHeaderGroup) {
61
+ headerGroups.push(currentHeaderGroup);
62
+ currentHeaderGroup = null;
63
+ }
64
+ continue;
65
+ }
66
+ // Inside headers block - parse header lines
67
+ if (currentHeaderGroup) {
68
+ const headerMatch = trimmed.match(/^([a-zA-Z][a-zA-Z0-9\-]*)\s*:\s*(.+)$/);
69
+ if (headerMatch) {
70
+ currentHeaderGroup.headers[headerMatch[1]] = headerMatch[2].trim();
71
+ }
72
+ continue;
73
+ }
74
+ // Check for endpoints block start
75
+ if (/^endpoints$/i.test(trimmed)) {
76
+ inEndpointsBlock = true;
77
+ continue;
78
+ }
79
+ // Check for endpoints block end
80
+ if (/^end\s+endpoints$/i.test(trimmed)) {
81
+ inEndpointsBlock = false;
82
+ continue;
83
+ }
84
+ // Inside endpoints block - parse endpoint lines
85
+ // Format: EndpointName: METHOD /path/{param}
86
+ if (inEndpointsBlock) {
87
+ const endpointMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(.+)$/i);
88
+ if (endpointMatch) {
89
+ const path = endpointMatch[3].trim();
90
+ endpoints.push({
91
+ name: endpointMatch[1],
92
+ method: endpointMatch[2].toUpperCase(),
93
+ path,
94
+ parameters: extractPathParameters(path)
95
+ });
96
+ }
97
+ continue;
98
+ }
99
+ }
100
+ return { headerGroups, endpoints };
101
+ }
102
+ /**
103
+ * Gets a header group by name (case-sensitive)
104
+ */
105
+ function getHeaderGroup(definition, name) {
106
+ return definition.headerGroups.find(hg => hg.name === name);
107
+ }
108
+ /**
109
+ * Gets an endpoint by name (case-sensitive)
110
+ */
111
+ function getEndpoint(definition, name) {
112
+ return definition.endpoints.find(ep => ep.name === name);
113
+ }
114
+ /**
115
+ * Resolves an endpoint path with provided parameters
116
+ * e.g., path: /users/{id}/orders, params: { id: '123' } -> /users/123/orders
117
+ */
118
+ function resolveEndpointPath(endpoint, params) {
119
+ let resolvedPath = endpoint.path;
120
+ for (const paramName of endpoint.parameters) {
121
+ const value = params[paramName];
122
+ if (value !== undefined) {
123
+ resolvedPath = resolvedPath.replace(`{${paramName}}`, value);
124
+ }
125
+ }
126
+ return resolvedPath;
127
+ }
128
+ /**
129
+ * Applies variable substitution to header values
130
+ */
131
+ function resolveHeaderValues(headerGroup, variables) {
132
+ const resolved = {};
133
+ for (const [name, value] of Object.entries(headerGroup.headers)) {
134
+ resolved[name] = (0, parser_1.substituteVariables)(value, variables);
135
+ }
136
+ return resolved;
137
+ }
138
+ /**
139
+ * Checks if a request line uses endpoint syntax instead of a URL
140
+ * Endpoint syntax: METHOD EndpointName(param, ...) [HeaderGroups...]
141
+ * URL syntax: METHOD http://... or METHOD /path
142
+ *
143
+ * Returns true if the URL part starts with a word that matches an endpoint name
144
+ */
145
+ function isApiRequestLine(requestContent, endpoints) {
146
+ const lines = requestContent.split('\n');
147
+ // Find the first non-empty, non-comment, non-import line
148
+ let firstLine = '';
149
+ for (const line of lines) {
150
+ const trimmed = line.trim();
151
+ if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('import ')) {
152
+ firstLine = trimmed;
153
+ break;
154
+ }
155
+ }
156
+ if (!firstLine) {
157
+ return false;
158
+ }
159
+ // Match METHOD followed by non-URL pattern
160
+ const match = firstLine.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+([a-zA-Z_][a-zA-Z0-9_]*)/i);
161
+ if (!match) {
162
+ return false;
163
+ }
164
+ const potentialEndpointName = match[2];
165
+ // Check if this matches a known endpoint
166
+ return endpoints.some(ep => ep.name === potentialEndpointName);
167
+ }
168
+ /**
169
+ * Parses an API request block (endpoint-based syntax)
170
+ *
171
+ * Handles two syntax styles:
172
+ * 1. Single line: GET GetUser("123") Auth Json
173
+ * 2. Multi-line:
174
+ * GET GetUser("123")
175
+ * Auth
176
+ * Json
177
+ *
178
+ * Returns parsed information or undefined if not an API request
179
+ */
180
+ function parseApiRequest(requestContent, endpoints, headerGroups) {
181
+ const lines = requestContent.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#') && !l.startsWith('import '));
182
+ if (lines.length === 0) {
183
+ return undefined;
184
+ }
185
+ const firstLine = lines[0];
186
+ // Parse: METHOD EndpointName(params...) [HeaderGroups...]
187
+ // Params can be: positional values or key=value pairs
188
+ const lineMatch = firstLine.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+([a-zA-Z_][a-zA-Z0-9_]*)(?:\(([^)]*)\))?\s*(.*)$/i);
189
+ if (!lineMatch) {
190
+ return undefined;
191
+ }
192
+ const methodFromLine = lineMatch[1].toUpperCase();
193
+ const endpointName = lineMatch[2];
194
+ const paramsStr = lineMatch[3] || '';
195
+ const headerGroupsOnLine = lineMatch[4]?.trim() || '';
196
+ // Find the endpoint
197
+ const endpoint = endpoints.find(ep => ep.name === endpointName);
198
+ if (!endpoint) {
199
+ return undefined;
200
+ }
201
+ // Verify the method matches (or allow method from line to override)
202
+ // Actually, the user might use a different method - we use the line's method
203
+ const method = methodFromLine;
204
+ // Parse parameters: positional or key=value
205
+ const params = {};
206
+ if (paramsStr) {
207
+ // Split by comma, respecting quotes
208
+ const paramTokens = parseParamTokens(paramsStr);
209
+ // Check if key=value or positional
210
+ if (paramTokens.length > 0 && paramTokens[0].includes(':')) {
211
+ // Key=value format: GetUser(id: "123")
212
+ for (const token of paramTokens) {
213
+ const kvMatch = token.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
214
+ if (kvMatch) {
215
+ params[kvMatch[1]] = unquote(kvMatch[2].trim());
216
+ }
217
+ }
218
+ }
219
+ else {
220
+ // Positional format: GetUser("123") -> map to endpoint parameters in order
221
+ for (let i = 0; i < paramTokens.length && i < endpoint.parameters.length; i++) {
222
+ params[endpoint.parameters[i]] = unquote(paramTokens[i].trim());
223
+ }
224
+ }
225
+ }
226
+ // Collect header group names and inline headers
227
+ const headerGroupNames = [];
228
+ const inlineHeaders = {};
229
+ // From same line
230
+ if (headerGroupsOnLine) {
231
+ const names = headerGroupsOnLine.split(/\s+/).filter(n => n);
232
+ for (const name of names) {
233
+ if (headerGroups.some(hg => hg.name === name)) {
234
+ headerGroupNames.push(name);
235
+ }
236
+ }
237
+ }
238
+ // From subsequent lines:
239
+ // - Header group names (single word matching a defined group)
240
+ // - Inline headers (HeaderName: value format)
241
+ // - Body content (everything else after headers section)
242
+ const bodyLines = [];
243
+ let inBodySection = false;
244
+ for (let i = 1; i < lines.length; i++) {
245
+ const line = lines[i];
246
+ // Once we start the body section, everything goes to body
247
+ if (inBodySection) {
248
+ bodyLines.push(line);
249
+ continue;
250
+ }
251
+ // Check if this line is a header group name
252
+ if (headerGroups.some(hg => hg.name === line)) {
253
+ headerGroupNames.push(line);
254
+ continue;
255
+ }
256
+ // Check if this is an inline header (HeaderName: value)
257
+ const headerMatch = line.match(/^([a-zA-Z][a-zA-Z0-9\-]*)\s*:\s*(.+)$/);
258
+ if (headerMatch) {
259
+ let headerValue = headerMatch[2].trim();
260
+ // Remove surrounding quotes if present
261
+ if ((headerValue.startsWith('"') && headerValue.endsWith('"')) ||
262
+ (headerValue.startsWith("'") && headerValue.endsWith("'"))) {
263
+ headerValue = headerValue.slice(1, -1);
264
+ }
265
+ inlineHeaders[headerMatch[1]] = headerValue;
266
+ continue;
267
+ }
268
+ // Not a header group or inline header - start of body section
269
+ inBodySection = true;
270
+ bodyLines.push(line);
271
+ }
272
+ const body = bodyLines.length > 0 ? bodyLines.join('\n') : undefined;
273
+ return {
274
+ method,
275
+ endpointName,
276
+ params,
277
+ headerGroupNames,
278
+ inlineHeaders,
279
+ body
280
+ };
281
+ }
282
+ /**
283
+ * Splits parameter string by commas, respecting quotes
284
+ */
285
+ function parseParamTokens(paramsStr) {
286
+ const tokens = [];
287
+ let current = '';
288
+ let inQuote = false;
289
+ let quoteChar = '';
290
+ for (const char of paramsStr) {
291
+ if ((char === '"' || char === "'") && !inQuote) {
292
+ inQuote = true;
293
+ quoteChar = char;
294
+ current += char;
295
+ }
296
+ else if (char === quoteChar && inQuote) {
297
+ inQuote = false;
298
+ quoteChar = '';
299
+ current += char;
300
+ }
301
+ else if (char === ',' && !inQuote) {
302
+ tokens.push(current.trim());
303
+ current = '';
304
+ }
305
+ else {
306
+ current += char;
307
+ }
308
+ }
309
+ if (current.trim()) {
310
+ tokens.push(current.trim());
311
+ }
312
+ return tokens;
313
+ }
314
+ /**
315
+ * Removes surrounding quotes from a string value
316
+ */
317
+ function unquote(value) {
318
+ if ((value.startsWith('"') && value.endsWith('"')) ||
319
+ (value.startsWith("'") && value.endsWith("'"))) {
320
+ return value.slice(1, -1);
321
+ }
322
+ return value;
323
+ }
324
+ //# sourceMappingURL=nornapiParser.js.map
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NornenvCodeActionProvider = void 0;
37
+ const vscode = __importStar(require("vscode"));
38
+ const nornenvLanguageModel_1 = require("./nornenvLanguageModel");
39
+ function levenshteinDistance(left, right) {
40
+ const a = left.toLowerCase();
41
+ const b = right.toLowerCase();
42
+ const previous = Array.from({ length: b.length + 1 }, (_, index) => index);
43
+ const current = Array.from({ length: b.length + 1 }, () => 0);
44
+ for (let i = 1; i <= a.length; i++) {
45
+ current[0] = i;
46
+ for (let j = 1; j <= b.length; j++) {
47
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
48
+ current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, previous[j - 1] + cost);
49
+ }
50
+ for (let j = 0; j <= b.length; j++) {
51
+ previous[j] = current[j];
52
+ }
53
+ }
54
+ return previous[b.length];
55
+ }
56
+ function closestName(name, candidates) {
57
+ let best;
58
+ let bestDistance = Number.POSITIVE_INFINITY;
59
+ for (const candidate of candidates) {
60
+ const distance = levenshteinDistance(name, candidate);
61
+ if (distance < bestDistance) {
62
+ bestDistance = distance;
63
+ best = candidate;
64
+ }
65
+ }
66
+ return bestDistance <= (name.length <= 5 ? 1 : 2) ? best : undefined;
67
+ }
68
+ class NornenvCodeActionProvider {
69
+ static providedCodeActionKinds = [vscode.CodeActionKind.QuickFix];
70
+ provideCodeActions(document, _range, context) {
71
+ const actions = [];
72
+ const unknownParentDiagnostics = context.diagnostics.filter(diagnostic => diagnostic.code === 'unknown-extends-parent');
73
+ if (unknownParentDiagnostics.length === 0) {
74
+ return actions;
75
+ }
76
+ let candidates;
77
+ try {
78
+ const filePath = document.uri.scheme === 'file' ? document.uri.fsPath : undefined;
79
+ const config = (0, nornenvLanguageModel_1.loadResolvedNornenvConfigFromText)(document.getText(), filePath);
80
+ candidates = config.templates.map(template => template.name);
81
+ }
82
+ catch {
83
+ return actions;
84
+ }
85
+ for (const diagnostic of unknownParentDiagnostics) {
86
+ const unknownName = document.getText(diagnostic.range);
87
+ const suggestion = closestName(unknownName, candidates);
88
+ if (!suggestion) {
89
+ continue;
90
+ }
91
+ const action = new vscode.CodeAction(`Replace '${unknownName}' with '${suggestion}'`, vscode.CodeActionKind.QuickFix);
92
+ action.diagnostics = [diagnostic];
93
+ action.edit = new vscode.WorkspaceEdit();
94
+ action.edit.replace(document.uri, diagnostic.range, suggestion);
95
+ actions.push(action);
96
+ }
97
+ return actions;
98
+ }
99
+ }
100
+ exports.NornenvCodeActionProvider = NornenvCodeActionProvider;
101
+ //# sourceMappingURL=nornenvCodeActionProvider.js.map
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NornenvDecorationProvider = void 0;
37
+ const vscode = __importStar(require("vscode"));
38
+ const environmentParser_1 = require("./environmentParser");
39
+ const environmentProvider_1 = require("./environmentProvider");
40
+ const nornenvLanguageModel_1 = require("./nornenvLanguageModel");
41
+ // Norn brand palette — keep in sync with `norn_website/src/styles/global.css`
42
+ // (`.file-pill.norn / .nornsql / .nornenv`) so editor chips match website chips.
43
+ const BRAND_TEAL = '#4ec9b0';
44
+ const BRAND_BLUE = '#569cd6';
45
+ const BRAND_AMBER = '#d7ba7d';
46
+ const BRAND_PURPLE = '#c586c0';
47
+ const BRAND_TEAL_SUBTLE = 'rgba(78, 201, 176, 0.15)';
48
+ const BRAND_TEAL_BORDER = 'rgba(78, 201, 176, 0.35)';
49
+ const BRAND_AMBER_SUBTLE = 'rgba(215, 186, 125, 0.15)';
50
+ const BRAND_AMBER_BORDER = 'rgba(215, 186, 125, 0.35)';
51
+ const BRAND_PURPLE_SUBTLE = 'rgba(197, 134, 192, 0.15)';
52
+ const BRAND_PURPLE_BORDER = 'rgba(197, 134, 192, 0.35)';
53
+ /**
54
+ * Builds an `after.contentText` decoration with proper margin spacing for the *first*
55
+ * chip on a line, and (for the rest) chained tightly so each new chip just adds a
56
+ * separator dot and the chip text.
57
+ *
58
+ * VS Code does not let us set per-chip background colors via a single
59
+ * `after.contentText`, so each chip kind gets its own `TextEditorDecorationType`.
60
+ * Multiple decorations stack at the end of the line in registration order.
61
+ */
62
+ class NornenvDecorationProvider {
63
+ countChipDecoration;
64
+ inheritedChipDecoration;
65
+ overridesChipDecoration;
66
+ activeChipDecoration;
67
+ cycleChipDecoration;
68
+ unknownParentChipDecoration;
69
+ envParentChipDecoration;
70
+ environmentSubscription;
71
+ constructor() {
72
+ // `N vars` — neutral muted count.
73
+ // Leading spacing comes from per-instance contentText so the *first* non-pill
74
+ // chip on a line gets the gap regardless of which kind it happens to be.
75
+ // (The `extends X, Y` chip used to live here; it was pure noise because the same
76
+ // text already appears in the section header immediately to the left.)
77
+ this.countChipDecoration = vscode.window.createTextEditorDecorationType({
78
+ after: {
79
+ color: new vscode.ThemeColor('descriptionForeground'),
80
+ fontStyle: 'italic'
81
+ }
82
+ });
83
+ // `N inherited` — brand blue (matches website's .nornapi pill text)
84
+ this.inheritedChipDecoration = vscode.window.createTextEditorDecorationType({
85
+ after: {
86
+ color: BRAND_BLUE,
87
+ fontStyle: 'italic'
88
+ }
89
+ });
90
+ // `N overrides` — brand teal (the "self-contribution" color)
91
+ this.overridesChipDecoration = vscode.window.createTextEditorDecorationType({
92
+ after: {
93
+ color: BRAND_TEAL,
94
+ fontStyle: 'italic'
95
+ }
96
+ });
97
+ // Active pill — subtle teal-on-teal (.file-pill.norn from the website).
98
+ // The `border` field's value is serialized verbatim into the inline `style`
99
+ // attribute, so we smuggle padding + border-radius through trailing semicolons.
100
+ this.activeChipDecoration = this.createSubtlePillDecoration(BRAND_TEAL, BRAND_TEAL_SUBTLE, BRAND_TEAL_BORDER);
101
+ // Error pills — subtle purple-on-purple (.file-pill.nornenv): cycle, env-as-parent
102
+ this.cycleChipDecoration = this.createSubtlePillDecoration(BRAND_PURPLE, BRAND_PURPLE_SUBTLE, BRAND_PURPLE_BORDER);
103
+ this.envParentChipDecoration = this.createSubtlePillDecoration(BRAND_PURPLE, BRAND_PURPLE_SUBTLE, BRAND_PURPLE_BORDER);
104
+ // Warning pill — subtle amber-on-amber (.file-pill.nornsql): unknown parent
105
+ this.unknownParentChipDecoration = this.createSubtlePillDecoration(BRAND_AMBER, BRAND_AMBER_SUBTLE, BRAND_AMBER_BORDER);
106
+ this.environmentSubscription = (0, environmentProvider_1.onDidChangeActiveEnvironment)(() => {
107
+ this.updateVisibleEditors();
108
+ });
109
+ }
110
+ /**
111
+ * Pastel pill matching the website's `.file-pill.<lang>` styling:
112
+ * subtle-tinted background + colored text + 35%-alpha border, fully rounded.
113
+ * `font-weight: 500` to match the website pill's weight.
114
+ */
115
+ createSubtlePillDecoration(textColor, backgroundColor, borderColor) {
116
+ return vscode.window.createTextEditorDecorationType({
117
+ after: {
118
+ color: textColor,
119
+ backgroundColor,
120
+ border: `1px solid ${borderColor}; border-radius: 100px; padding: 0 8px; margin: 0 0 0 8px; font-weight: 500;`,
121
+ fontStyle: 'normal'
122
+ }
123
+ });
124
+ }
125
+ updateVisibleEditors() {
126
+ for (const editor of vscode.window.visibleTextEditors) {
127
+ this.update(editor);
128
+ }
129
+ }
130
+ update(editor) {
131
+ if (!editor || (editor.document.languageId !== 'nornenv' && !editor.document.uri.fsPath.endsWith('.nornenv'))) {
132
+ return;
133
+ }
134
+ try {
135
+ const text = editor.document.getText();
136
+ const filePath = editor.document.uri.scheme === 'file' ? editor.document.uri.fsPath : undefined;
137
+ const config = (0, nornenvLanguageModel_1.loadResolvedNornenvConfigFromText)(text, filePath);
138
+ const model = (0, nornenvLanguageModel_1.parseNornenvDocumentModel)(text);
139
+ const activeEnvironment = (0, environmentProvider_1.getActiveEnvironment)(filePath);
140
+ const cycles = new Set((0, environmentParser_1.detectExtendsCycles)(config).flatMap(cycle => cycle.path));
141
+ const sectionChips = model.sections.map(section => ({
142
+ section,
143
+ chips: this.buildChips(section, config, cycles, activeEnvironment)
144
+ }));
145
+ // For each chip kind, build the DecorationOptions[] for the lines that need it.
146
+ // Each chip on a line gets a separator dot from the previous chip's text — except
147
+ // the first chip in this line, which gets a leading margin via the decoration type.
148
+ const optionsByKind = {
149
+ count: [],
150
+ inherited: [],
151
+ overrides: [],
152
+ active: [],
153
+ cycle: [],
154
+ 'unknown-parent': [],
155
+ 'env-parent': []
156
+ };
157
+ for (const { section, chips } of sectionChips) {
158
+ const line = editor.document.lineAt(section.lineNumber);
159
+ const endOfLine = new vscode.Range(section.lineNumber, line.text.length, section.lineNumber, line.text.length);
160
+ for (let index = 0; index < chips.length; index++) {
161
+ const chip = chips[index];
162
+ const isFirstOnLine = index === 0;
163
+ // Pills (active / warnings) carry their own left margin via the border CSS
164
+ // hack, so they never need the separator dot. Non-pill chips need a leading
165
+ // gap when they're the first on the line — otherwise they slam into `]`.
166
+ const isPill = chip.kind === 'active' || chip.kind === 'cycle' || chip.kind === 'unknown-parent' || chip.kind === 'env-parent';
167
+ const separator = isFirstOnLine ? ' ' : ' · ';
168
+ const contentText = isPill ? chip.text : `${separator}${chip.text}`;
169
+ optionsByKind[chip.kind].push({
170
+ range: endOfLine,
171
+ renderOptions: {
172
+ after: { contentText }
173
+ }
174
+ });
175
+ }
176
+ }
177
+ editor.setDecorations(this.countChipDecoration, optionsByKind.count);
178
+ editor.setDecorations(this.inheritedChipDecoration, optionsByKind.inherited);
179
+ editor.setDecorations(this.overridesChipDecoration, optionsByKind.overrides);
180
+ editor.setDecorations(this.activeChipDecoration, optionsByKind.active);
181
+ editor.setDecorations(this.cycleChipDecoration, optionsByKind.cycle);
182
+ editor.setDecorations(this.unknownParentChipDecoration, optionsByKind['unknown-parent']);
183
+ editor.setDecorations(this.envParentChipDecoration, optionsByKind['env-parent']);
184
+ }
185
+ catch {
186
+ const emptyDecorations = [];
187
+ editor.setDecorations(this.countChipDecoration, emptyDecorations);
188
+ editor.setDecorations(this.inheritedChipDecoration, emptyDecorations);
189
+ editor.setDecorations(this.overridesChipDecoration, emptyDecorations);
190
+ editor.setDecorations(this.activeChipDecoration, emptyDecorations);
191
+ editor.setDecorations(this.cycleChipDecoration, emptyDecorations);
192
+ editor.setDecorations(this.unknownParentChipDecoration, emptyDecorations);
193
+ editor.setDecorations(this.envParentChipDecoration, emptyDecorations);
194
+ }
195
+ }
196
+ buildChips(section, config, cycles, activeEnvironment) {
197
+ const found = (0, environmentParser_1.findExtendsNode)(section.name, config);
198
+ const ownVariables = found ? Object.keys(found.node.variables) : [];
199
+ const inherited = (0, environmentParser_1.resolveInheritedVariableDetails)(section.name, config);
200
+ const ancestorOrCommonNames = new Set([
201
+ ...Object.keys(config.common),
202
+ ...(0, environmentParser_1.collectAncestorVariableNames)(section.name, config)
203
+ ]);
204
+ const overrides = ownVariables.filter(name => ancestorOrCommonNames.has(name)).length;
205
+ const chips = [];
206
+ chips.push({ kind: 'count', text: `${ownVariables.length} vars` });
207
+ if (inherited.size > 0) {
208
+ chips.push({ kind: 'inherited', text: `${inherited.size} inherited` });
209
+ }
210
+ if (overrides > 0) {
211
+ chips.push({ kind: 'overrides', text: `${overrides} overrides` });
212
+ }
213
+ if (section.kind === 'env' && section.name === activeEnvironment) {
214
+ chips.push({ kind: 'active', text: 'Active' });
215
+ }
216
+ if (section.parents.some(parent => config.environments.some(env => env.name === parent) && !(0, environmentParser_1.findExtendsTemplate)(parent, config))) {
217
+ chips.push({ kind: 'env-parent', text: '! env parent' });
218
+ }
219
+ else if (section.parents.some(parent => !(0, environmentParser_1.findExtendsTemplate)(parent, config))) {
220
+ chips.push({ kind: 'unknown-parent', text: '! unknown parent' });
221
+ }
222
+ if (cycles.has(section.name)) {
223
+ chips.push({ kind: 'cycle', text: '! cycle' });
224
+ }
225
+ return chips;
226
+ }
227
+ dispose() {
228
+ this.environmentSubscription.dispose();
229
+ this.countChipDecoration.dispose();
230
+ this.inheritedChipDecoration.dispose();
231
+ this.overridesChipDecoration.dispose();
232
+ this.activeChipDecoration.dispose();
233
+ this.cycleChipDecoration.dispose();
234
+ this.unknownParentChipDecoration.dispose();
235
+ this.envParentChipDecoration.dispose();
236
+ }
237
+ }
238
+ exports.NornenvDecorationProvider = NornenvDecorationProvider;
239
+ //# sourceMappingURL=nornenvDecorationProvider.js.map