pi-hashline-edit-pro 6.1.2 → 6.2.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.
- package/README.md +14 -12
- package/index.ts +20 -12
- package/package.json +3 -2
- package/src/auto-read-all.ts +39 -21
- package/src/config-ui.ts +135 -55
- package/src/config.ts +27 -9
- package/src/copy-move.ts +4 -4
- package/src/grep.ts +3 -3
- package/src/insert.ts +3 -3
- package/src/outline/index.ts +45 -0
- package/src/outline/parsers.ts +42 -0
- package/src/outline/render.ts +166 -0
- package/src/outline/vendor/LICENSE +201 -0
- package/src/outline/vendor/README.md +17 -0
- package/src/outline/vendor/parsers/blade.ts +138 -0
- package/src/outline/vendor/parsers/index.ts +75 -0
- package/src/outline/vendor/parsers/java.ts +501 -0
- package/src/outline/vendor/parsers/manager.ts +1721 -0
- package/src/outline/vendor/parsers/php.ts +332 -0
- package/src/outline/vendor/parsers/sass.ts +416 -0
- package/src/outline/vendor/parsers/scss.ts +566 -0
- package/src/outline/vendor/types.ts +82 -0
- package/src/outline/vendor/wasm/LICENSES.md +37 -0
- package/src/outline/vendor/wasm/tree-sitter-blade.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-go.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-java.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-javascript.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-php.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-python.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-rust.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-sass.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-scss.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-solidity.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-tsx.wasm +0 -0
- package/src/outline/vendor/wasm/tree-sitter-typescript.wasm +0 -0
- package/src/read.ts +108 -36
- package/src/replace-match.ts +3 -3
- package/src/replace-undo.ts +3 -3
- package/src/replace.ts +3 -3
- package/tool-prompts/read-guidelines.md +3 -0
- package/{prompts → tool-prompts}/read.md +2 -0
- package/prompts/read-guidelines.md +0 -1
- package/src/auto-read-all-state.ts +0 -27
- /package/{prompts → tool-prompts}/copy-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/copy-snippet.md +0 -0
- /package/{prompts → tool-prompts}/copy.md +0 -0
- /package/{prompts → tool-prompts}/grep-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/grep-snippet.md +0 -0
- /package/{prompts → tool-prompts}/grep.md +0 -0
- /package/{prompts → tool-prompts}/insert-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/insert-snippet.md +0 -0
- /package/{prompts → tool-prompts}/insert.md +0 -0
- /package/{prompts → tool-prompts}/move-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/move-snippet.md +0 -0
- /package/{prompts → tool-prompts}/move.md +0 -0
- /package/{prompts → tool-prompts}/read-snippet.md +0 -0
- /package/{prompts → tool-prompts}/replace-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/replace-match-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/replace-match-snippet.md +0 -0
- /package/{prompts → tool-prompts}/replace-match.md +0 -0
- /package/{prompts → tool-prompts}/replace-snippet.md +0 -0
- /package/{prompts → tool-prompts}/replace.md +0 -0
- /package/{prompts → tool-prompts}/transfer-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/undo-last-change-guidelines.md +0 -0
- /package/{prompts → tool-prompts}/undo-last-change-snippet.md +0 -0
- /package/{prompts → tool-prompts}/undo-last-change.md +0 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sass (indented syntax) tree-sitter symbol extractor.
|
|
3
|
+
*
|
|
4
|
+
* Tree-sitter grammar: bajrangCoder/tree-sitter-sass
|
|
5
|
+
* (https://github.com/bajrangCoder/tree-sitter-sass), MIT license.
|
|
6
|
+
* This is the tree-sitter grammar for the indented `.sass` dialect (whitespace-
|
|
7
|
+
* significant, no braces/semicolons). The grammar is not published on npm and
|
|
8
|
+
* ships no prebuilt WASM, so the repo vendors a compiled tree-sitter-sass.wasm
|
|
9
|
+
* under src/parsers/vendor/.
|
|
10
|
+
*
|
|
11
|
+
* Grammar node shapes:
|
|
12
|
+
* - `$var: value` parses as a `declaration` with a `variable_name` child;
|
|
13
|
+
* regular CSS properties parse as `declaration` with `property_name`.
|
|
14
|
+
* - `@mixin name(params)` / `@function name(params)` use a `name` child.
|
|
15
|
+
* - `@include name(...)` uses a `mixin_name` child.
|
|
16
|
+
* - `@use`/`@forward`/`@import` hold the module path in a `string_value`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Node } from "web-tree-sitter";
|
|
20
|
+
import type { SymbolInfo, ImportInfo } from "../types.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract structural symbols from Sass (indented syntax) source code.
|
|
24
|
+
*/
|
|
25
|
+
export function extractSass(
|
|
26
|
+
node: Node,
|
|
27
|
+
source: string,
|
|
28
|
+
depth: number,
|
|
29
|
+
): SymbolInfo[] {
|
|
30
|
+
const results: SymbolInfo[] = [];
|
|
31
|
+
const MAX_DEPTH = depth + 10;
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
34
|
+
const child = node.namedChild(i);
|
|
35
|
+
if (!child) continue;
|
|
36
|
+
|
|
37
|
+
const symbol = extractSassChild(child, source, depth, MAX_DEPTH);
|
|
38
|
+
if (symbol) results.push(symbol);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractSassChild(
|
|
45
|
+
child: Node,
|
|
46
|
+
source: string,
|
|
47
|
+
depth: number,
|
|
48
|
+
MAX_DEPTH: number,
|
|
49
|
+
): SymbolInfo | null {
|
|
50
|
+
switch (child.type) {
|
|
51
|
+
case "rule_set":
|
|
52
|
+
return extractRuleSet(child, source, depth, MAX_DEPTH);
|
|
53
|
+
case "mixin_statement":
|
|
54
|
+
return extractNamedBlock(
|
|
55
|
+
child,
|
|
56
|
+
source,
|
|
57
|
+
depth,
|
|
58
|
+
MAX_DEPTH,
|
|
59
|
+
"mixin",
|
|
60
|
+
);
|
|
61
|
+
case "function_statement":
|
|
62
|
+
return extractNamedBlock(
|
|
63
|
+
child,
|
|
64
|
+
source,
|
|
65
|
+
depth,
|
|
66
|
+
MAX_DEPTH,
|
|
67
|
+
"function",
|
|
68
|
+
);
|
|
69
|
+
case "media_statement":
|
|
70
|
+
return extractAtBlock(child, source, depth, MAX_DEPTH, extractMediaName);
|
|
71
|
+
case "keyframes_statement":
|
|
72
|
+
return extractKeyframes(child, source, depth, MAX_DEPTH);
|
|
73
|
+
case "declaration": {
|
|
74
|
+
// `$var: value` at top level parses as a declaration with variable_name.
|
|
75
|
+
const varName = childTextOfType(child, source, "variable_name");
|
|
76
|
+
if (varName && varName.startsWith("$")) {
|
|
77
|
+
return {
|
|
78
|
+
name: varName,
|
|
79
|
+
type: "variable",
|
|
80
|
+
startLine: child.startPosition.row + 1,
|
|
81
|
+
endLine: child.endPosition.row + 1,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function extractNamedBlock(
|
|
92
|
+
child: Node,
|
|
93
|
+
source: string,
|
|
94
|
+
depth: number,
|
|
95
|
+
MAX_DEPTH: number,
|
|
96
|
+
label: string,
|
|
97
|
+
): SymbolInfo | null {
|
|
98
|
+
let name = "<unknown>";
|
|
99
|
+
let detail: string | undefined;
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < child.namedChildCount; i++) {
|
|
102
|
+
const c = child.namedChild(i);
|
|
103
|
+
if (!c) continue;
|
|
104
|
+
if (c.type === "name") {
|
|
105
|
+
name = source.slice(c.startIndex, c.endIndex);
|
|
106
|
+
} else if (c.type === "parameters") {
|
|
107
|
+
detail =
|
|
108
|
+
"(" +
|
|
109
|
+
source.slice(c.startIndex, c.endIndex).replace(/^\(|\)$/g, "") +
|
|
110
|
+
")";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const [sl, el] = [child.startPosition.row + 1, child.endPosition.row + 1];
|
|
115
|
+
const symbol: SymbolInfo = {
|
|
116
|
+
name,
|
|
117
|
+
type: label,
|
|
118
|
+
startLine: sl,
|
|
119
|
+
endLine: el,
|
|
120
|
+
detail,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (depth < MAX_DEPTH) {
|
|
124
|
+
const block = findChildByType(child, ["block"]);
|
|
125
|
+
if (block) {
|
|
126
|
+
const children = extractBlockChildren(block, source, depth + 1, MAX_DEPTH);
|
|
127
|
+
if (children.length > 0) symbol.children = children;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return symbol;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function extractRuleSet(
|
|
135
|
+
child: Node,
|
|
136
|
+
source: string,
|
|
137
|
+
depth: number,
|
|
138
|
+
MAX_DEPTH: number,
|
|
139
|
+
): SymbolInfo | null {
|
|
140
|
+
const selectors = findChildByType(child, ["selectors"]);
|
|
141
|
+
const name = selectors
|
|
142
|
+
? source.slice(selectors.startIndex, selectors.endIndex).trim()
|
|
143
|
+
: "<unknown>";
|
|
144
|
+
|
|
145
|
+
const [sl, el] = [child.startPosition.row + 1, child.endPosition.row + 1];
|
|
146
|
+
const symbol: SymbolInfo = {
|
|
147
|
+
name,
|
|
148
|
+
// `%placeholder` selectors get their own type.
|
|
149
|
+
type: name.startsWith("%") ? "placeholder" : "ruleset",
|
|
150
|
+
startLine: sl,
|
|
151
|
+
endLine: el,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (depth < MAX_DEPTH) {
|
|
155
|
+
const block = findChildByType(child, ["block"]);
|
|
156
|
+
if (block) {
|
|
157
|
+
const children = extractBlockChildren(block, source, depth + 1, MAX_DEPTH);
|
|
158
|
+
if (children.length > 0) symbol.children = children;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return symbol;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function extractAtBlock(
|
|
166
|
+
child: Node,
|
|
167
|
+
source: string,
|
|
168
|
+
depth: number,
|
|
169
|
+
MAX_DEPTH: number,
|
|
170
|
+
nameOf: (node: Node, source: string) => string,
|
|
171
|
+
): SymbolInfo | null {
|
|
172
|
+
const name = nameOf(child, source);
|
|
173
|
+
const [sl, el] = [child.startPosition.row + 1, child.endPosition.row + 1];
|
|
174
|
+
const symbol: SymbolInfo = {
|
|
175
|
+
name,
|
|
176
|
+
type: "at-rule",
|
|
177
|
+
startLine: sl,
|
|
178
|
+
endLine: el,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (depth < MAX_DEPTH) {
|
|
182
|
+
const block = findChildByType(child, ["block"]);
|
|
183
|
+
if (block) {
|
|
184
|
+
const children = extractBlockChildren(block, source, depth + 1, MAX_DEPTH);
|
|
185
|
+
if (children.length > 0) symbol.children = children;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return symbol;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function extractMediaName(node: Node, source: string): string {
|
|
193
|
+
const start = source.slice(node.startIndex, node.endIndex).trim();
|
|
194
|
+
const newline = start.indexOf("\n");
|
|
195
|
+
return newline >= 0 ? start.slice(0, newline).trim() : start;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function extractKeyframes(
|
|
199
|
+
child: Node,
|
|
200
|
+
source: string,
|
|
201
|
+
depth: number,
|
|
202
|
+
MAX_DEPTH: number,
|
|
203
|
+
): SymbolInfo | null {
|
|
204
|
+
let name = "<unknown>";
|
|
205
|
+
const nameNode = findChildByType(child, ["keyframes_name"]);
|
|
206
|
+
if (nameNode) name = source.slice(nameNode.startIndex, nameNode.endIndex);
|
|
207
|
+
|
|
208
|
+
const [sl, el] = [child.startPosition.row + 1, child.endPosition.row + 1];
|
|
209
|
+
const symbol: SymbolInfo = {
|
|
210
|
+
name: `@keyframes ${name}`,
|
|
211
|
+
type: "keyframes",
|
|
212
|
+
startLine: sl,
|
|
213
|
+
endLine: el,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (depth < MAX_DEPTH) {
|
|
217
|
+
const block = findChildByType(child, ["block"]);
|
|
218
|
+
if (block) {
|
|
219
|
+
const children: SymbolInfo[] = [];
|
|
220
|
+
for (let i = 0; i < block.namedChildCount; i++) {
|
|
221
|
+
const kb = block.namedChild(i);
|
|
222
|
+
if (!kb) continue;
|
|
223
|
+
if (kb.type === "keyframe_block") {
|
|
224
|
+
const stepText = source
|
|
225
|
+
.slice(kb.startIndex, kb.endIndex)
|
|
226
|
+
.split("\n")[0]
|
|
227
|
+
.trim();
|
|
228
|
+
children.push({
|
|
229
|
+
name: stepText,
|
|
230
|
+
type: "keyframe",
|
|
231
|
+
startLine: kb.startPosition.row + 1,
|
|
232
|
+
endLine: kb.endPosition.row + 1,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (children.length > 0) symbol.children = children;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return symbol;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function extractBlockChildren(
|
|
244
|
+
block: Node,
|
|
245
|
+
source: string,
|
|
246
|
+
depth: number,
|
|
247
|
+
MAX_DEPTH: number,
|
|
248
|
+
): SymbolInfo[] {
|
|
249
|
+
const results: SymbolInfo[] = [];
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < block.namedChildCount; i++) {
|
|
252
|
+
const child = block.namedChild(i);
|
|
253
|
+
if (!child) continue;
|
|
254
|
+
|
|
255
|
+
switch (child.type) {
|
|
256
|
+
case "rule_set":
|
|
257
|
+
case "media_statement":
|
|
258
|
+
case "keyframes_statement":
|
|
259
|
+
case "mixin_statement":
|
|
260
|
+
case "function_statement": {
|
|
261
|
+
const inner = extractSassChild(child, source, depth, MAX_DEPTH);
|
|
262
|
+
if (inner) results.push(inner);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "include_statement": {
|
|
266
|
+
const inc = extractInclude(child, source);
|
|
267
|
+
if (inc) results.push(inc);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "extend_statement": {
|
|
271
|
+
const ext = extractExtend(child, source);
|
|
272
|
+
if (ext) results.push(ext);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case "declaration": {
|
|
276
|
+
// `$var: value` inside a block surfaces as a variable.
|
|
277
|
+
const varName = childTextOfType(child, source, "variable_name");
|
|
278
|
+
if (varName && varName.startsWith("$")) {
|
|
279
|
+
results.push({
|
|
280
|
+
name: varName,
|
|
281
|
+
type: "variable",
|
|
282
|
+
startLine: child.startPosition.row + 1,
|
|
283
|
+
endLine: child.endPosition.row + 1,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return results;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function extractInclude(child: Node, source: string): SymbolInfo | null {
|
|
295
|
+
let name: string | undefined;
|
|
296
|
+
let detail: string | undefined;
|
|
297
|
+
|
|
298
|
+
for (let i = 0; i < child.namedChildCount; i++) {
|
|
299
|
+
const c = child.namedChild(i);
|
|
300
|
+
if (!c) continue;
|
|
301
|
+
if (c.type === "mixin_name") {
|
|
302
|
+
name = source.slice(c.startIndex, c.endIndex);
|
|
303
|
+
} else if (c.type === "arguments") {
|
|
304
|
+
detail = source.slice(c.startIndex, c.endIndex);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!name) return null;
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
name: `@include ${name}`,
|
|
311
|
+
type: "include",
|
|
312
|
+
startLine: child.startPosition.row + 1,
|
|
313
|
+
endLine: child.endPosition.row + 1,
|
|
314
|
+
detail,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function extractExtend(child: Node, source: string): SymbolInfo | null {
|
|
319
|
+
let target: string | undefined;
|
|
320
|
+
for (let i = 0; i < child.namedChildCount; i++) {
|
|
321
|
+
const c = child.namedChild(i);
|
|
322
|
+
if (!c) continue;
|
|
323
|
+
if (
|
|
324
|
+
c.type === "placeholder_selector" ||
|
|
325
|
+
c.type === "class_selector" ||
|
|
326
|
+
c.type === "id_selector" ||
|
|
327
|
+
c.type === "identifier"
|
|
328
|
+
) {
|
|
329
|
+
target = source.slice(c.startIndex, c.endIndex);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!target) return null;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
name: `@extend ${target}`,
|
|
337
|
+
type: "extend",
|
|
338
|
+
startLine: child.startPosition.row + 1,
|
|
339
|
+
endLine: child.endPosition.row + 1,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ---- Sass imports ----
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Extract Sass imports: `@use`, `@forward`, and `@import` statements
|
|
347
|
+
* (indented syntax, but the directives use the same module-path strings).
|
|
348
|
+
*/
|
|
349
|
+
export function extractSassImports(
|
|
350
|
+
node: Node,
|
|
351
|
+
source: string,
|
|
352
|
+
): ImportInfo[] {
|
|
353
|
+
const results: ImportInfo[] = [];
|
|
354
|
+
|
|
355
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
356
|
+
const child = node.namedChild(i);
|
|
357
|
+
if (!child) continue;
|
|
358
|
+
|
|
359
|
+
let kind: string | null = null;
|
|
360
|
+
if (child.type === "use_statement") kind = "use";
|
|
361
|
+
else if (child.type === "forward_statement") kind = "forward";
|
|
362
|
+
else if (child.type === "import_statement") kind = "import";
|
|
363
|
+
else continue;
|
|
364
|
+
|
|
365
|
+
const path = valueStringOf(child, source);
|
|
366
|
+
if (!path) continue;
|
|
367
|
+
|
|
368
|
+
results.push({
|
|
369
|
+
source: path,
|
|
370
|
+
names: [path],
|
|
371
|
+
lineNumber: child.startPosition.row + 1,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return results;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function valueStringOf(node: Node, source: string): string | undefined {
|
|
379
|
+
// Direct string_value child first.
|
|
380
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
381
|
+
const c = node.namedChild(i);
|
|
382
|
+
if (c && c.type === "string_value") {
|
|
383
|
+
return stripQuotes(source.slice(c.startIndex, c.endIndex));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function stripQuotes(s: string): string {
|
|
390
|
+
return s.replace(/^["']|["']$/g, "");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ---- Shared helpers ----
|
|
394
|
+
|
|
395
|
+
function findChildByType(node: Node, types: string[]): Node | null {
|
|
396
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
397
|
+
const c = node.namedChild(i);
|
|
398
|
+
if (c && types.includes(c.type)) return c;
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Return the text of the first named child of a given type. */
|
|
404
|
+
function childTextOfType(
|
|
405
|
+
node: Node,
|
|
406
|
+
source: string,
|
|
407
|
+
type: string,
|
|
408
|
+
): string | undefined {
|
|
409
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
410
|
+
const c = node.namedChild(i);
|
|
411
|
+
if (c && c.type === type) {
|
|
412
|
+
return source.slice(c.startIndex, c.endIndex);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|