@coldsmirk/inkstone-codemirror 0.8.2 → 0.9.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.
@@ -1,4 +1,4 @@
1
- import { i as resolveMembers, n as minijinjaContextField, o as typeAtPath, r as normalizeContext, t as elementType } from "./context-Dx6bPnKq.js";
1
+ import { i as resolveMembers, n as minijinjaContextField, o as typeAtPath, r as normalizeContext, t as elementType } from "./context-D86o2jK3.js";
2
2
  import { LanguageSupport, syntaxTree } from "@codemirror/language";
3
3
  import { jinja, jinjaLanguage } from "@codemirror/lang-jinja";
4
4
  import { linter } from "@codemirror/lint";
@@ -219,51 +219,110 @@ function loopField(kind) {
219
219
  nullable: false
220
220
  };
221
221
  }
222
+ const ROOT_SCOPE = -1;
222
223
  function scopeBindings(state, pos, ir) {
223
224
  const bindings = /* @__PURE__ */ new Map();
224
- for (let node = syntaxTree(state).resolveInner(pos, -1); node; node = node.parent) if (node.name === "ForStatement") collectForBindings(state, node, pos, ir, bindings);
225
- else if (node.name === "MacroStatement") collectMacroBindings(state, node, pos, bindings);
226
- collectSetBindings(state, pos, bindings);
225
+ const enclosing = [];
226
+ for (let node = syntaxTree(state).resolveInner(pos, -1); node; node = node.parent) if (node.name === "ForStatement" || node.name === "MacroStatement" || node.name === "WithStatement") enclosing.push(node);
227
+ const scopes = enclosing.toReversed();
228
+ const sets = collectSetBindings(state, pos, scopes);
229
+ const applySets = (scopeKey) => {
230
+ const definitions = sets.get(scopeKey) ?? [];
231
+ for (const definition of definitions) bindings.set(state.sliceDoc(definition.from, definition.to), { kind: "any" });
232
+ };
233
+ applySets(ROOT_SCOPE);
234
+ for (const node of scopes) {
235
+ if (node.name === "ForStatement") collectForBindings(state, node, pos, ir, bindings);
236
+ else if (node.name === "MacroStatement") collectMacroBindings(state, node, pos, bindings);
237
+ else collectWithBindings(state, node, pos, ir, bindings);
238
+ applySets(node.from);
239
+ }
227
240
  return bindings;
228
241
  }
229
242
  function collectForBindings(state, forNode, pos, ir, bindings) {
230
243
  const tag = forNode.firstChild;
231
- if (!tag || tag.name !== "Tag" || pos < tag.to) return;
244
+ if (!tag || !tagBodyStarted(tag, pos)) return;
232
245
  const definitions = [];
233
246
  let iterable = null;
234
247
  let seenIn = false;
235
248
  for (let child = tag.firstChild; child; child = child.nextSibling) if (child.name === "in") seenIn = true;
236
249
  else if (!seenIn && child.name === "Definition") definitions.push(child);
237
250
  else if (seenIn && !iterable && child.name !== "%}") iterable = child;
238
- if (!bindings.has("loop")) bindings.set("loop", LOOP_TYPE);
239
- const [only] = definitions;
240
- if (only && definitions.length === 1 && iterable) {
251
+ let element = null;
252
+ if (definitions.length === 1 && iterable) {
241
253
  const path = walkBase(state, iterable);
242
- const iterableType = path ? typeAtPath(ir, path) : null;
243
- const element = iterableType ? elementType(ir, iterableType) : null;
244
- bindings.set(state.sliceDoc(only.from, only.to), element ?? { kind: "any" });
245
- return;
254
+ const iterableType = path ? typeAtPath(ir, path, bindings) : null;
255
+ element = iterableType ? elementType(ir, iterableType) : null;
256
+ }
257
+ bindings.set("loop", LOOP_TYPE);
258
+ for (const definition of definitions) bindings.set(state.sliceDoc(definition.from, definition.to), element ?? { kind: "any" });
259
+ }
260
+ function collectWithBindings(state, withNode, pos, ir, bindings) {
261
+ const tag = withNode.firstChild;
262
+ if (!tag || tag.name !== "Tag") return;
263
+ const inBody = tagBodyStarted(tag, pos);
264
+ let targets = [];
265
+ for (let child = tag.firstChild; child; child = child.nextSibling) {
266
+ if (child.name === "Definition") {
267
+ targets.push(child);
268
+ continue;
269
+ }
270
+ if (child.name !== "AssignOp" || targets.length === 0) continue;
271
+ const value = child.nextSibling;
272
+ const delimiter = value?.nextSibling;
273
+ if (inBody || delimiter?.name === "," && delimiter.to <= pos) {
274
+ const path = targets.length === 1 && value ? walkBase(state, value) : null;
275
+ const type = path ? typeAtPath(ir, path, bindings) : null;
276
+ for (const target of targets) bindings.set(state.sliceDoc(target.from, target.to), type ?? { kind: "any" });
277
+ }
278
+ targets = [];
246
279
  }
247
- for (const definition of definitions) bindings.set(state.sliceDoc(definition.from, definition.to), { kind: "any" });
248
280
  }
249
281
  function collectMacroBindings(state, macroNode, pos, bindings) {
250
282
  const tag = macroNode.firstChild;
251
- if (!tag || tag.name !== "Tag" || pos < tag.to) return;
283
+ if (!tag || !tagBodyStarted(tag, pos)) return;
252
284
  const paramList = tag.getChild("ParamList");
253
285
  if (!paramList) return;
254
286
  for (let child = paramList.firstChild; child; child = child.nextSibling) if (child.name === "Definition") bindings.set(state.sliceDoc(child.from, child.to), { kind: "any" });
255
287
  }
256
- function collectSetBindings(state, pos, bindings) {
288
+ function tagBodyStarted(tag, pos) {
289
+ return tag.name === "Tag" && tag.getChild("%}") !== null && pos >= tag.to;
290
+ }
291
+ function collectSetBindings(state, pos, scopes) {
292
+ const scopeStarts = new Set(scopes.map((scope) => scope.from));
293
+ const sets = /* @__PURE__ */ new Map();
257
294
  syntaxTree(state).iterate({
258
295
  to: pos,
259
296
  enter: (node) => {
260
297
  if (node.name !== "Tag" || node.to > pos) return;
261
298
  const keyword = node.node.firstChild?.nextSibling;
262
299
  if (!keyword || state.sliceDoc(keyword.from, keyword.to) !== "set") return;
263
- const definition = keyword.nextSibling;
264
- if (definition && definition.name === "Definition") bindings.set(state.sliceDoc(definition.from, definition.to), { kind: "any" });
300
+ const definitions = [];
301
+ let assignment = false;
302
+ for (let child = keyword.nextSibling; child; child = child.nextSibling) {
303
+ if (child.name === "AssignOp") {
304
+ assignment = true;
305
+ break;
306
+ }
307
+ if (child.name === "Definition") definitions.push(child);
308
+ }
309
+ if (definitions.length === 0) return;
310
+ const statement = node.node.parent;
311
+ if (assignment) {
312
+ if (!node.node.getChild("%}")) return;
313
+ } else if (statement?.name !== "SetStatement" || statement.to > pos || !statement.getChild("EndTag")) return;
314
+ let scopeKey = ROOT_SCOPE;
315
+ for (let { parent } = node.node; parent; parent = parent.parent) if (parent.name === "ForStatement" || parent.name === "MacroStatement" || parent.name === "WithStatement") {
316
+ if (!scopeStarts.has(parent.from)) return;
317
+ scopeKey = parent.from;
318
+ break;
319
+ }
320
+ const bucket = sets.get(scopeKey);
321
+ if (bucket) bucket.push(...definitions);
322
+ else sets.set(scopeKey, definitions);
265
323
  }
266
324
  });
325
+ return sets;
267
326
  }
268
327
  function jinjaContext(state, pos, side) {
269
328
  for (let node = syntaxTree(state).resolveInner(pos, side); node; node = node.parent) {
@@ -279,7 +338,11 @@ function minijinjaCompletion(context) {
279
338
  if (!word) return null;
280
339
  const { from } = word;
281
340
  const before = context.state.sliceDoc(Math.max(0, from - 24), from);
282
- const side = from === context.pos ? 1 : -1;
341
+ let side = from === context.pos ? 1 : -1;
342
+ if (side === 1 && context.pos === context.state.doc.length) {
343
+ const left = syntaxTree(context.state).resolveInner(context.pos, -1);
344
+ if (left.name !== "}}" && left.name !== "%}" && left.name !== "#}") side = -1;
345
+ }
283
346
  if (jinjaContext(context.state, context.pos, side) !== "expr") return null;
284
347
  if (/\{%[-+]?\s*$/.test(before)) return {
285
348
  from,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coldsmirk/inkstone-codemirror",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Framework-agnostic CodeMirror 6 plumbing: a controlled editor host (create-once, external-value reconcile, in-place theme swap) and the standard document extension bundle.",
5
5
  "keywords": [
6
6
  "codemirror",
@@ -24,24 +24,17 @@
24
24
  "type": "module",
25
25
  "exports": {
26
26
  ".": {
27
- "import": {
28
- "types": "./dist/index.d.ts",
29
- "default": "./dist/index.js"
30
- },
31
- "require": {
32
- "types": "./dist/index.d.cts",
33
- "default": "./dist/index.cjs"
34
- }
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
35
29
  },
36
30
  "./package.json": "./package.json"
37
31
  },
38
- "main": "./dist/index.cjs",
39
- "module": "./dist/index.js",
40
32
  "types": "./dist/index.d.ts",
41
33
  "files": [
42
34
  "dist"
43
35
  ],
44
36
  "dependencies": {
37
+ "@codemirror/lang-angular": "^0.1.4",
45
38
  "@codemirror/lang-cpp": "^6.0.3",
46
39
  "@codemirror/lang-css": "^6.3.1",
47
40
  "@codemirror/lang-go": "^6.0.1",
@@ -51,6 +44,7 @@
51
44
  "@codemirror/lang-jinja": "^6.0.1",
52
45
  "@codemirror/lang-json": "^6.0.2",
53
46
  "@codemirror/lang-less": "^6.0.2",
47
+ "@codemirror/lang-lezer": "^6.0.2",
54
48
  "@codemirror/lang-liquid": "^6.3.2",
55
49
  "@codemirror/lang-markdown": "^6.5.0",
56
50
  "@codemirror/lang-php": "^6.0.2",
@@ -62,6 +56,7 @@
62
56
  "@codemirror/lang-wast": "^6.0.2",
63
57
  "@codemirror/lang-xml": "^6.1.0",
64
58
  "@codemirror/lang-yaml": "^6.1.3",
59
+ "@codemirror/legacy-modes": "^6.5.3",
65
60
  "@codemirror/lint": "^6.9.7",
66
61
  "@lezer/common": "^1.5.2"
67
62
  },
@@ -82,7 +77,7 @@
82
77
  "@codemirror/view": "^6.43.0"
83
78
  },
84
79
  "engines": {
85
- "node": ">=22"
80
+ "node": ">=24"
86
81
  },
87
82
  "publishConfig": {
88
83
  "access": "public"
@@ -1,270 +0,0 @@
1
- let _codemirror_state = require("@codemirror/state");
2
- //#region src/context.ts
3
- const ANY = { kind: "any" };
4
- const NULL = { kind: "null" };
5
- const MAX_DEREF = 100;
6
- const setMinijinjaContext = _codemirror_state.StateEffect.define();
7
- const minijinjaContextField = _codemirror_state.StateField.define({
8
- create() {
9
- return null;
10
- },
11
- update(value, transaction) {
12
- let next = value;
13
- for (const effect of transaction.effects) if (effect.is(setMinijinjaContext)) next = effect.value;
14
- return next;
15
- }
16
- });
17
- function normalizeContext(input) {
18
- if ("schema" in input) return fromJsonSchema(input.schema);
19
- return {
20
- root: fromSample(input.sample),
21
- defs: {}
22
- };
23
- }
24
- function resolveMembers(ir, path, scope) {
25
- const [first, ...rest] = path;
26
- if (first === void 0) {
27
- const top = membersOf(ir, ir.root);
28
- if (!scope || scope.size === 0) return top;
29
- return [...[...scope].map(([name, type]) => {
30
- return {
31
- name,
32
- type,
33
- required: true,
34
- nullable: false
35
- };
36
- }), ...top];
37
- }
38
- let type = scope?.get(first) ?? stepInto(ir, ir.root, first);
39
- for (const key of rest) {
40
- if (!type) break;
41
- type = stepInto(ir, type, key);
42
- }
43
- return type ? membersOf(ir, type) : [];
44
- }
45
- function typeAtPath(ir, path) {
46
- let type = ir.root;
47
- for (const key of path) {
48
- const next = stepInto(ir, type, key);
49
- if (next === null) return null;
50
- type = next;
51
- }
52
- return deref(ir, type);
53
- }
54
- function elementType(ir, type) {
55
- const resolved = deref(ir, type);
56
- return resolved.kind === "array" ? deref(ir, resolved.element) : null;
57
- }
58
- function deref(ir, type) {
59
- let current = type;
60
- for (let i = 0; i < MAX_DEREF && current.kind === "ref"; i++) {
61
- const next = ir.defs[current.name];
62
- if (!next) return ANY;
63
- current = next;
64
- }
65
- return current;
66
- }
67
- function stepInto(ir, type, key) {
68
- const resolved = deref(ir, type);
69
- if (resolved.kind !== "object") return null;
70
- const field = resolved.fields[key];
71
- return field ? field.type : null;
72
- }
73
- function membersOf(ir, type) {
74
- const resolved = deref(ir, type);
75
- if (resolved.kind !== "object") return [];
76
- return Object.entries(resolved.fields).map(([name, field]) => {
77
- return {
78
- name,
79
- type: field.type,
80
- required: field.required,
81
- nullable: field.nullable,
82
- doc: field.doc
83
- };
84
- });
85
- }
86
- const REF_PREFIXES = ["#/$defs/", "#/definitions/"];
87
- function fromJsonSchema(schema) {
88
- if (!isObject(schema)) return {
89
- root: ANY,
90
- defs: {}
91
- };
92
- const defsSource = {
93
- ...asObject(schema.definitions),
94
- ...asObject(schema.$defs)
95
- };
96
- const defs = {};
97
- for (const [name, sub] of Object.entries(defsSource)) defs[name] = convert(sub);
98
- return {
99
- root: convert(schema),
100
- defs
101
- };
102
- }
103
- function convert(node) {
104
- if (typeof node === "boolean" || !isObject(node)) return ANY;
105
- const ref = refName(node.$ref);
106
- if (ref !== null) return {
107
- kind: "ref",
108
- name: ref
109
- };
110
- if (Array.isArray(node.allOf)) return mergeAll(node.allOf.map((entry) => convert(entry)));
111
- const union = node.anyOf ?? node.oneOf;
112
- if (Array.isArray(union)) {
113
- const nonNull = union.filter((branch) => !isNullBranch(branch));
114
- if (nonNull.length === 0) return NULL;
115
- return nonNull.length === 1 ? convert(nonNull[0]) : mergeAll(nonNull.map((entry) => convert(entry)));
116
- }
117
- if (Array.isArray(node.enum)) return {
118
- kind: "enum",
119
- values: node.enum.filter(isLiteral)
120
- };
121
- if ("const" in node && isLiteral(node.const)) return {
122
- kind: "enum",
123
- values: [node.const]
124
- };
125
- const type = primaryType(node);
126
- if (type === "object" || isObject(node.properties)) return objectType(node);
127
- if (type === "array") return {
128
- kind: "array",
129
- element: convert(Array.isArray(node.items) ? node.items[0] : node.items)
130
- };
131
- if (type === "string") return { kind: "string" };
132
- if (type === "integer" || type === "number") return { kind: "number" };
133
- if (type === "boolean") return { kind: "boolean" };
134
- if (type === "null") return NULL;
135
- return ANY;
136
- }
137
- function objectType(node) {
138
- const properties = asObject(node.properties);
139
- const required = new Set(Array.isArray(node.required) ? node.required.filter((name) => typeof name === "string") : []);
140
- const fields = {};
141
- for (const [name, sub] of Object.entries(properties)) fields[name] = {
142
- type: convert(sub),
143
- required: required.has(name),
144
- nullable: isNullable(sub),
145
- doc: description(sub)
146
- };
147
- return {
148
- kind: "object",
149
- fields
150
- };
151
- }
152
- function mergeAll(types) {
153
- const objects = types.filter((type) => type.kind === "object");
154
- if (objects.length > 0) {
155
- const fields = {};
156
- for (const object of objects) Object.assign(fields, object.fields);
157
- return {
158
- kind: "object",
159
- fields
160
- };
161
- }
162
- return types.find((type) => type.kind !== "any") ?? ANY;
163
- }
164
- function fromSample(value, ancestors = /* @__PURE__ */ new WeakSet()) {
165
- if (value === null || value === void 0) return ANY;
166
- if (typeof value === "object") {
167
- if (ancestors.has(value)) return ANY;
168
- ancestors.add(value);
169
- const type = Array.isArray(value) ? sampleArrayType(value, ancestors) : sampleObjectType(value, ancestors);
170
- ancestors.delete(value);
171
- return type;
172
- }
173
- if (typeof value === "string") return { kind: "string" };
174
- if (typeof value === "number") return { kind: "number" };
175
- if (typeof value === "boolean") return { kind: "boolean" };
176
- return ANY;
177
- }
178
- function sampleArrayType(value, ancestors) {
179
- return {
180
- kind: "array",
181
- element: value.length > 0 ? fromSample(value[0], ancestors) : ANY
182
- };
183
- }
184
- function sampleObjectType(value, ancestors) {
185
- const fields = {};
186
- for (const [name, member] of Object.entries(value)) fields[name] = {
187
- type: fromSample(member, ancestors),
188
- required: true,
189
- nullable: member === null
190
- };
191
- return {
192
- kind: "object",
193
- fields
194
- };
195
- }
196
- function refName(ref) {
197
- if (typeof ref !== "string") return null;
198
- for (const prefix of REF_PREFIXES) if (ref.startsWith(prefix)) return decodeURIComponent(ref.slice(prefix.length));
199
- return null;
200
- }
201
- function primaryType(node) {
202
- const { type } = node;
203
- if (typeof type === "string") return type;
204
- if (Array.isArray(type)) {
205
- const nonNull = type.find((entry) => typeof entry === "string" && entry !== "null");
206
- if (typeof nonNull === "string") return nonNull;
207
- return type.includes("null") ? "null" : null;
208
- }
209
- return null;
210
- }
211
- function isNullable(node) {
212
- if (!isObject(node)) return false;
213
- const { type } = node;
214
- if (type === "null" || Array.isArray(type) && type.includes("null")) return true;
215
- const union = node.anyOf ?? node.oneOf;
216
- return Array.isArray(union) && union.some((branch) => isNullBranch(branch));
217
- }
218
- function isNullBranch(branch) {
219
- if (!isObject(branch)) return false;
220
- return branch.type === "null" || Array.isArray(branch.type) && branch.type.length === 1 && branch.type[0] === "null";
221
- }
222
- function description(node) {
223
- return isObject(node) && typeof node.description === "string" ? node.description : void 0;
224
- }
225
- function isObject(value) {
226
- return typeof value === "object" && value !== null && !Array.isArray(value);
227
- }
228
- function asObject(value) {
229
- return isObject(value) ? value : {};
230
- }
231
- function isLiteral(value) {
232
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
233
- }
234
- //#endregion
235
- Object.defineProperty(exports, "elementType", {
236
- enumerable: true,
237
- get: function() {
238
- return elementType;
239
- }
240
- });
241
- Object.defineProperty(exports, "minijinjaContextField", {
242
- enumerable: true,
243
- get: function() {
244
- return minijinjaContextField;
245
- }
246
- });
247
- Object.defineProperty(exports, "normalizeContext", {
248
- enumerable: true,
249
- get: function() {
250
- return normalizeContext;
251
- }
252
- });
253
- Object.defineProperty(exports, "resolveMembers", {
254
- enumerable: true,
255
- get: function() {
256
- return resolveMembers;
257
- }
258
- });
259
- Object.defineProperty(exports, "setMinijinjaContext", {
260
- enumerable: true,
261
- get: function() {
262
- return setMinijinjaContext;
263
- }
264
- });
265
- Object.defineProperty(exports, "typeAtPath", {
266
- enumerable: true,
267
- get: function() {
268
- return typeAtPath;
269
- }
270
- });