elixir-data-viewer 0.1.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 +418 -0
- package/dist/filter.d.ts +78 -0
- package/dist/fold.d.ts +30 -0
- package/dist/highlighter.d.ts +19 -0
- package/dist/index.cjs +6 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +1581 -0
- package/dist/inspect.d.ts +33 -0
- package/dist/parser.d.ts +5 -0
- package/dist/preprocess.d.ts +43 -0
- package/dist/renderer.d.ts +408 -0
- package/dist/search.d.ts +82 -0
- package/dist/standalone.d.ts +13 -0
- package/dist/state.d.ts +61 -0
- package/dist/style.css +1 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# Elixir Data Viewer
|
|
2
|
+
|
|
3
|
+
A read-only web viewer for Elixir data structures with syntax highlighting, code folding, line numbers, and a VS Code Dark+ theme.
|
|
4
|
+
|
|
5
|
+
Built with vanilla TypeScript + DOM — no CodeMirror, no React — powered by [`lezer-elixir`](https://github.com/livebook-dev/lezer-elixir) for accurate parsing.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Syntax Highlighting** — Accurate Elixir syntax coloring via `lezer-elixir` parser, matching VS Code Dark+ theme
|
|
10
|
+
- **Code Folding** — Collapse/expand maps, lists, tuples, keyword lists, bitstrings, and multi-line strings
|
|
11
|
+
- **Line Numbers** — Gutter with line numbers and fold indicators
|
|
12
|
+
- **Floating Toolbar** — Per-viewer toolbar (appears on hover) with:
|
|
13
|
+
- ⊟ Fold All
|
|
14
|
+
- ⊞ Unfold All
|
|
15
|
+
- ↩ Word Wrap toggle
|
|
16
|
+
- ⎘ Copy to clipboard
|
|
17
|
+
- ⧩ Filter by key
|
|
18
|
+
- **Key Filtering** — Hide specific key-value pairs by key name (e.g. filter out `socket`, `secret_key_base`)
|
|
19
|
+
- **Multiple Viewers** — Support multiple independent viewer instances on the same page
|
|
20
|
+
- **Configurable Toolbar** — Show/hide individual toolbar buttons via options or HTML `data-*` attributes
|
|
21
|
+
- **Word Wrap** — Toggle word wrap for long lines
|
|
22
|
+
- **Zero Dependencies** — Only peer dependencies on `lezer-elixir` and `@lezer/common`/`@lezer/highlight`
|
|
23
|
+
|
|
24
|
+
## Supported Elixir Types
|
|
25
|
+
|
|
26
|
+
Maps (`%{}`), Lists (`[]`), Tuples (`{}`), Keyword Lists, Atoms (`:ok`), Strings (`"..."`), Integers, Floats, Booleans, `nil`, Charlists, Bitstrings (`<<>>`), Sigils, Heredoc strings, Character literals (`?A`)
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install elixir-data-viewer
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### As an npm Package
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { ElixirDataViewer } from "elixir-data-viewer";
|
|
40
|
+
import "elixir-data-viewer/style.css";
|
|
41
|
+
|
|
42
|
+
const container = document.getElementById("viewer")!;
|
|
43
|
+
const viewer = new ElixirDataViewer(container);
|
|
44
|
+
viewer.setContent(`%{name: "Alice", age: 30, roles: [:admin, :user]}`);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### With HTML Inline Data
|
|
48
|
+
|
|
49
|
+
Add `.edv-viewer` elements with `<script type="text/elixir-data">` blocks:
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<div class="edv-viewer">
|
|
53
|
+
<script type="text/elixir-data">
|
|
54
|
+
%{
|
|
55
|
+
name: "Alice",
|
|
56
|
+
age: 30,
|
|
57
|
+
roles: [:admin, :user]
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<script type="module">
|
|
63
|
+
import { ElixirDataViewer } from "elixir-data-viewer";
|
|
64
|
+
import "elixir-data-viewer/style.css";
|
|
65
|
+
|
|
66
|
+
document.querySelectorAll(".edv-viewer").forEach((el) => {
|
|
67
|
+
const script = el.querySelector('script[type="text/elixir-data"]');
|
|
68
|
+
if (!script) return;
|
|
69
|
+
const data = script.textContent?.trim() ?? "";
|
|
70
|
+
script.remove();
|
|
71
|
+
const viewer = new ElixirDataViewer(el);
|
|
72
|
+
viewer.setContent(data);
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Standalone / Phoenix (Single ESM File)
|
|
78
|
+
|
|
79
|
+
Build a single JS file with all dependencies and CSS bundled in:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run build:standalone
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This produces `dist/elixir-data-viewer.js` (~55 kB gzipped) — a single ESM file that:
|
|
86
|
+
- Bundles all dependencies (`lezer-elixir`, `@lezer/common`, `@lezer/highlight`)
|
|
87
|
+
- Injects CSS automatically via `<style>` tag (no separate CSS file needed)
|
|
88
|
+
- Exports `ElixirDataViewer` as the default export, plus all named exports
|
|
89
|
+
|
|
90
|
+
#### Phoenix Integration
|
|
91
|
+
|
|
92
|
+
1. Copy the built file into your Phoenix project:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
cp dist/elixir-data-viewer.js your_phoenix_app/assets/vendor/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
2. Import it in your `assets/js/app.js`:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
import ElixirDataViewer from "../vendor/elixir-data-viewer"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
3. Create a LiveView Hook:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
let Hooks = {};
|
|
108
|
+
|
|
109
|
+
Hooks.ElixirDataViewer = {
|
|
110
|
+
mounted() {
|
|
111
|
+
const viewer = new ElixirDataViewer(this.el);
|
|
112
|
+
viewer.setContent(this.el.dataset.content || this.el.innerText);
|
|
113
|
+
this.viewer = viewer;
|
|
114
|
+
|
|
115
|
+
// Optional: handle LiveView updates
|
|
116
|
+
this.handleEvent("update-viewer", ({ content }) => {
|
|
117
|
+
this.viewer.setContent(content);
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
updated() {
|
|
121
|
+
this.viewer.setContent(this.el.dataset.content || this.el.innerText);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Pass hooks to LiveSocket
|
|
126
|
+
let liveSocket = new LiveSocket("/live", Socket, {
|
|
127
|
+
hooks: Hooks,
|
|
128
|
+
// ...
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
4. Use in your LiveView template:
|
|
133
|
+
|
|
134
|
+
```heex
|
|
135
|
+
<div id="data-viewer" phx-hook="ElixirDataViewer" data-content={inspect(@data, pretty: true)}>
|
|
136
|
+
</div>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## API Reference
|
|
140
|
+
|
|
141
|
+
### `ElixirDataViewer`
|
|
142
|
+
|
|
143
|
+
The main viewer class.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const viewer = new ElixirDataViewer(container: HTMLElement, options?: ElixirDataViewerOptions);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Constructor Options
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
interface ElixirDataViewerOptions {
|
|
153
|
+
toolbar?: {
|
|
154
|
+
foldAll?: boolean; // Show "Fold All" button (default: true)
|
|
155
|
+
unfoldAll?: boolean; // Show "Unfold All" button (default: true)
|
|
156
|
+
wordWrap?: boolean; // Show "Word Wrap" toggle (default: true)
|
|
157
|
+
copy?: boolean; // Show "Copy" button (default: true)
|
|
158
|
+
search?: boolean; // Show "Search" button (default: true)
|
|
159
|
+
filter?: boolean; // Show "Filter" button (default: true)
|
|
160
|
+
};
|
|
161
|
+
/** Default fold level — regions deeper than this are auto-folded on setContent().
|
|
162
|
+
* E.g. 3 = show first 3 levels expanded, fold level 4+.
|
|
163
|
+
* 0 or undefined = no auto-folding (all expanded). */
|
|
164
|
+
defaultFoldLevel?: number;
|
|
165
|
+
/** Whether word wrap is enabled by default. Default: false */
|
|
166
|
+
defaultWordWrap?: boolean;
|
|
167
|
+
/** Keys to filter out by default when setContent() is called */
|
|
168
|
+
defaultFilterKeys?: string[];
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Examples:**
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// All toolbar buttons visible (default)
|
|
176
|
+
new ElixirDataViewer(container);
|
|
177
|
+
|
|
178
|
+
// Hide the copy button
|
|
179
|
+
new ElixirDataViewer(container, { toolbar: { copy: false } });
|
|
180
|
+
|
|
181
|
+
// No toolbar at all
|
|
182
|
+
new ElixirDataViewer(container, {
|
|
183
|
+
toolbar: { foldAll: false, unfoldAll: false, wordWrap: false, copy: false, search: false, filter: false }
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Auto-fold: show only top 3 levels, fold level 4+
|
|
187
|
+
new ElixirDataViewer(container, { defaultFoldLevel: 3 });
|
|
188
|
+
|
|
189
|
+
// Enable word wrap by default
|
|
190
|
+
new ElixirDataViewer(container, { defaultWordWrap: true });
|
|
191
|
+
|
|
192
|
+
// Filter out specific keys by default
|
|
193
|
+
new ElixirDataViewer(container, { defaultFilterKeys: ["socket", "secret_key_base"] });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### HTML Data Attributes
|
|
197
|
+
|
|
198
|
+
When using auto-discovery, toolbar buttons and fold level can be configured via `data-*` attributes:
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<!-- Hide copy and fold-all buttons -->
|
|
202
|
+
<div class="edv-viewer" data-toolbar-copy="false" data-toolbar-fold-all="false">
|
|
203
|
+
<script type="text/elixir-data">...</script>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- Auto-fold: show only top 3 levels -->
|
|
207
|
+
<div class="edv-viewer" data-fold-level="3">
|
|
208
|
+
<script type="text/elixir-data">...</script>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Filter out specific keys by default -->
|
|
212
|
+
<div class="edv-viewer" data-filter-keys="socket,secret_key_base">
|
|
213
|
+
<script type="text/elixir-data">...</script>
|
|
214
|
+
</div>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Available attributes: `data-toolbar-fold-all`, `data-toolbar-unfold-all`, `data-toolbar-word-wrap`, `data-toolbar-copy`, `data-toolbar-search`, `data-toolbar-filter`, `data-fold-level`, `data-filter-keys`
|
|
218
|
+
|
|
219
|
+
#### Methods
|
|
220
|
+
|
|
221
|
+
| Method | Description |
|
|
222
|
+
|--------|-------------|
|
|
223
|
+
| `setContent(code: string): void` | Set the Elixir data content and render |
|
|
224
|
+
| `getContent(): string` | Get the raw Elixir data string |
|
|
225
|
+
| `foldAll(): void` | Collapse all foldable regions |
|
|
226
|
+
| `unfoldAll(): void` | Expand all folded regions |
|
|
227
|
+
| `foldToLevel(level: number): void` | Fold regions deeper than `level` (1 = top-level). `foldToLevel(3)` shows levels 1–3, folds 4+. `foldToLevel(0)` unfolds all. |
|
|
228
|
+
| `toggleWordWrap(): void` | Toggle word wrap mode |
|
|
229
|
+
| `isWordWrap(): boolean` | Get current word wrap state |
|
|
230
|
+
| `copyContent(): Promise<void>` | Copy raw content to clipboard |
|
|
231
|
+
| `onRender(callback: () => void): void` | Set a callback after each render |
|
|
232
|
+
| `onInspect(callback: ((event: InspectEvent) => void) \| null): void` | Set a callback when a value is clicked (see below) |
|
|
233
|
+
| `setFilterKeys(keys: string[]): void` | Set keys to filter out (replaces existing). Re-renders. |
|
|
234
|
+
| `addFilterKey(key: string): void` | Add a single key to filter. Re-renders. |
|
|
235
|
+
| `removeFilterKey(key: string): void` | Remove a single key from filter. Re-renders. |
|
|
236
|
+
| `getFilterKeys(): string[]` | Get currently filtered key names |
|
|
237
|
+
| `getAvailableKeys(): string[]` | Get all key names detected in current content |
|
|
238
|
+
| `clearFilter(): void` | Remove all key filters. Re-renders. |
|
|
239
|
+
| `openFilter(): void` | Open the filter bar UI |
|
|
240
|
+
| `closeFilter(): void` | Close the filter bar UI |
|
|
241
|
+
| `toggleFilter(): void` | Toggle filter bar visibility |
|
|
242
|
+
|
|
243
|
+
#### `onInspect` — Custom Click Handling
|
|
244
|
+
|
|
245
|
+
Register a callback that fires when the user clicks an inspectable value (string, atom, number, structure, etc.). The callback receives an `InspectEvent` with the value's type, text, DOM element reference, and a `preventDefault()` method to suppress the default copy-to-clipboard behavior.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
interface InspectEvent {
|
|
249
|
+
type: InspectType; // "String" | "Atom" | "Integer" | "Map" | ...
|
|
250
|
+
copyText: string; // The text that would be copied
|
|
251
|
+
target: InspectTarget; // Full target with from/to offsets
|
|
252
|
+
element: HTMLElement; // The clicked DOM element
|
|
253
|
+
mouseEvent: MouseEvent; // The original click event
|
|
254
|
+
preventDefault(): void; // Call to suppress default copy + toast
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Example: Log to console, suppress copy for strings**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
viewer.onInspect((event) => {
|
|
262
|
+
console.log(`Clicked ${event.type}: ${event.copyText}`);
|
|
263
|
+
|
|
264
|
+
if (event.type === "String") {
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
// Custom handling — e.g. open a modal
|
|
267
|
+
}
|
|
268
|
+
// Other types still get the default copy behavior
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Example: Render string content as Markdown in a modal**
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
viewer.onInspect((event) => {
|
|
276
|
+
if (event.type === "String") {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
const content = event.copyText.slice(1, -1); // strip quotes
|
|
279
|
+
showMarkdownModal(content, event.element);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Unregister the callback:**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
viewer.onInspect(null); // Restore default copy behavior
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Key Filtering — Hide Keys by Name
|
|
291
|
+
|
|
292
|
+
Filter out specific key-value pairs from the rendered view. The data is not modified — only the visual rendering skips the lines belonging to filtered keys.
|
|
293
|
+
|
|
294
|
+
**Example: Programmatic filtering via API**
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const viewer = new ElixirDataViewer(container);
|
|
298
|
+
viewer.setContent(`%{
|
|
299
|
+
name: "Alice",
|
|
300
|
+
socket: #Port<0.80>,
|
|
301
|
+
secret: "s3cr3t"
|
|
302
|
+
}`);
|
|
303
|
+
|
|
304
|
+
// Hide "socket" and "secret" keys
|
|
305
|
+
viewer.setFilterKeys(["socket", "secret"]);
|
|
306
|
+
|
|
307
|
+
// Add one more key to hide
|
|
308
|
+
viewer.addFilterKey("pid");
|
|
309
|
+
|
|
310
|
+
// Remove a key from the filter
|
|
311
|
+
viewer.removeFilterKey("secret");
|
|
312
|
+
|
|
313
|
+
// Get all keys detected in the content
|
|
314
|
+
console.log(viewer.getAvailableKeys());
|
|
315
|
+
// → ["name", "secret", "socket"]
|
|
316
|
+
|
|
317
|
+
// Clear all filters
|
|
318
|
+
viewer.clearFilter();
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Example: Default filter keys via options**
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
new ElixirDataViewer(container, {
|
|
325
|
+
defaultFilterKeys: ["socket", "secret_key_base"]
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Example: Filter via HTML data attribute**
|
|
330
|
+
|
|
331
|
+
```html
|
|
332
|
+
<div class="edv-viewer" data-filter-keys="socket,secret_key_base">
|
|
333
|
+
<script type="text/elixir-data">...</script>
|
|
334
|
+
</div>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Lower-Level Exports
|
|
338
|
+
|
|
339
|
+
For advanced use, the parser, highlighter, fold, and filter modules are also exported:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import {
|
|
343
|
+
parseElixir, // Parse Elixir code → Lezer Tree
|
|
344
|
+
highlight, // Tree → HighlightToken[]
|
|
345
|
+
getLineTokens, // Filter tokens for a specific line
|
|
346
|
+
detectFoldRegions, // Tree → FoldRegion[]
|
|
347
|
+
buildFoldMap, // FoldRegion[] → Map<number, FoldRegion>
|
|
348
|
+
FoldState, // Fold state manager
|
|
349
|
+
FilterState, // Filter state manager
|
|
350
|
+
} from "elixir-data-viewer";
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Theming
|
|
354
|
+
|
|
355
|
+
The viewer uses CSS classes for all styling. Import the default VS Code Dark+ theme:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import "elixir-data-viewer/style.css";
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Key CSS classes you can override:
|
|
362
|
+
|
|
363
|
+
| Class | Description |
|
|
364
|
+
|-------|-------------|
|
|
365
|
+
| `.edv-container` | Main viewer container |
|
|
366
|
+
| `.edv-line` | A single line row |
|
|
367
|
+
| `.edv-gutter` | Line number + fold gutter |
|
|
368
|
+
| `.edv-code` | Code content area |
|
|
369
|
+
| `.edv-toolbar` | Floating toolbar |
|
|
370
|
+
| `.edv-toolbar-btn` | Toolbar button |
|
|
371
|
+
| `.tok-atom`, `.tok-string`, etc. | Syntax token colors |
|
|
372
|
+
|
|
373
|
+
## Development
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Install dependencies
|
|
377
|
+
npm install
|
|
378
|
+
|
|
379
|
+
# Start dev server with hot reload
|
|
380
|
+
npm run dev
|
|
381
|
+
|
|
382
|
+
# Build for production (library: ES + CJS)
|
|
383
|
+
npm run build
|
|
384
|
+
|
|
385
|
+
# Build standalone IIFE (single file with all deps + CSS)
|
|
386
|
+
npm run build:standalone
|
|
387
|
+
|
|
388
|
+
# Preview production build
|
|
389
|
+
npm run preview
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
The dev server starts at `http://localhost:5173` with the demo page showing multiple viewer instances.
|
|
393
|
+
|
|
394
|
+
## Project Structure
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
├── index.html # Demo page with multiple viewers
|
|
398
|
+
├── src/
|
|
399
|
+
│ ├── main.ts # Demo entry — auto-discovers .edv-viewer elements
|
|
400
|
+
│ ├── index.ts # Library entry — public exports
|
|
401
|
+
│ ├── standalone.ts # Standalone entry — default export for Phoenix/vendor
|
|
402
|
+
│ ├── renderer.ts # ElixirDataViewer class with toolbar
|
|
403
|
+
│ ├── parser.ts # lezer-elixir parser wrapper
|
|
404
|
+
│ ├── highlighter.ts # Syntax tree → highlighted tokens
|
|
405
|
+
│ ├── fold.ts # Fold region detection
|
|
406
|
+
│ ├── state.ts # Fold state management
|
|
407
|
+
│ └── styles/
|
|
408
|
+
│ └── theme.css # Tokyo Night theme
|
|
409
|
+
├── package.json
|
|
410
|
+
├── tsconfig.json
|
|
411
|
+
├── tsconfig.build.json
|
|
412
|
+
├── vite.config.ts # Library build config (ES + CJS)
|
|
413
|
+
└── vite.config.standalone.ts # Standalone ESM build config (single file)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## License
|
|
417
|
+
|
|
418
|
+
MIT
|
package/dist/filter.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Tree } from "@lezer/common";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a key-value pair's line range in the source.
|
|
4
|
+
*/
|
|
5
|
+
export interface KeyRange {
|
|
6
|
+
/** The key name (e.g. "socket", "name") */
|
|
7
|
+
key: string;
|
|
8
|
+
/** 0-indexed first line of the key-value pair */
|
|
9
|
+
startLine: number;
|
|
10
|
+
/** 0-indexed last line of the key-value pair (inclusive) */
|
|
11
|
+
endLine: number;
|
|
12
|
+
/** Nesting depth (1 = direct child of root structure) */
|
|
13
|
+
depth: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Manages the filter state for the viewer — which keys are hidden.
|
|
17
|
+
*
|
|
18
|
+
* Walks the Lezer syntax tree to detect all key-value pair ranges,
|
|
19
|
+
* then hides lines belonging to filtered keys during rendering.
|
|
20
|
+
*/
|
|
21
|
+
export declare class FilterState {
|
|
22
|
+
/** Keys to filter out (hide) */
|
|
23
|
+
private filteredKeys;
|
|
24
|
+
/** All detected key-value ranges from the parsed content */
|
|
25
|
+
private keyRanges;
|
|
26
|
+
/** Pre-computed set of hidden line indices (rebuilt when filter changes) */
|
|
27
|
+
private hiddenLines;
|
|
28
|
+
/**
|
|
29
|
+
* Detect all key-value ranges from the syntax tree and source code.
|
|
30
|
+
* Called when content changes via setContent().
|
|
31
|
+
*/
|
|
32
|
+
detectKeys(tree: Tree, code: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Set the keys to filter out (replaces existing filter).
|
|
35
|
+
*/
|
|
36
|
+
setKeys(keys: string[]): void;
|
|
37
|
+
/**
|
|
38
|
+
* Add a single key to the filter.
|
|
39
|
+
*/
|
|
40
|
+
addKey(key: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Remove a single key from the filter.
|
|
43
|
+
*/
|
|
44
|
+
removeKey(key: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a key is currently being filtered.
|
|
47
|
+
*/
|
|
48
|
+
hasKey(key: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Get all currently filtered keys.
|
|
51
|
+
*/
|
|
52
|
+
getKeys(): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Get all available keys detected in the content.
|
|
55
|
+
* Returns unique key names sorted alphabetically.
|
|
56
|
+
*/
|
|
57
|
+
getAvailableKeys(): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Clear all filters (show all keys).
|
|
60
|
+
*/
|
|
61
|
+
clear(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Check if a specific line should be hidden due to filtering.
|
|
64
|
+
*/
|
|
65
|
+
isLineFiltered(lineIdx: number): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Check if any filter is active.
|
|
68
|
+
*/
|
|
69
|
+
isActive(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Get the total number of filtered keys.
|
|
72
|
+
*/
|
|
73
|
+
getFilteredCount(): number;
|
|
74
|
+
/**
|
|
75
|
+
* Rebuild the set of hidden line indices based on current filtered keys.
|
|
76
|
+
*/
|
|
77
|
+
private rebuildHiddenLines;
|
|
78
|
+
}
|
package/dist/fold.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Tree } from "@lezer/common";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a foldable region in the code.
|
|
4
|
+
*/
|
|
5
|
+
export interface FoldRegion {
|
|
6
|
+
/** 0-indexed line where the fold starts */
|
|
7
|
+
startLine: number;
|
|
8
|
+
/** 0-indexed line where the fold ends */
|
|
9
|
+
endLine: number;
|
|
10
|
+
/** Character offset in the source where the opening bracket is */
|
|
11
|
+
startOffset: number;
|
|
12
|
+
/** Character offset in the source where the closing bracket is */
|
|
13
|
+
endOffset: number;
|
|
14
|
+
/** The opening bracket text, e.g. "[", "%{", "{" */
|
|
15
|
+
openText: string;
|
|
16
|
+
/** The closing bracket text, e.g. "]", "}", ">>" */
|
|
17
|
+
closeText: string;
|
|
18
|
+
/** Number of direct child items in the structure (e.g. list elements, map pairs). -1 if not applicable. */
|
|
19
|
+
itemCount: number;
|
|
20
|
+
/** Nesting depth of this foldable region (1 = top-level, 2 = nested inside one, etc.) */
|
|
21
|
+
depth: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Detect all foldable regions in the given Elixir code.
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectFoldRegions(code: string, tree: Tree): FoldRegion[];
|
|
27
|
+
/**
|
|
28
|
+
* Build a map from startLine to its FoldRegion for quick lookup.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildFoldMap(regions: FoldRegion[]): Map<number, FoldRegion>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Tree } from "@lezer/common";
|
|
2
|
+
/**
|
|
3
|
+
* A highlighted token span with positional info and CSS classes.
|
|
4
|
+
*/
|
|
5
|
+
export interface HighlightToken {
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
classes: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Walk the Lezer syntax tree and emit highlight tokens using
|
|
12
|
+
* a custom tagHighlighter that maps Elixir tags to tok-* CSS classes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function highlight(code: string, tree: Tree): HighlightToken[];
|
|
15
|
+
/**
|
|
16
|
+
* Given a line's start/end offsets within the full code, extract the
|
|
17
|
+
* tokens that overlap this line and adjust their offsets to be line-relative.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getLineTokens(tokens: HighlightToken[], lineStart: number, lineEnd: number): HighlightToken[];
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";var $=Object.defineProperty;var q=(r,t,e)=>t in r?$(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var a=(r,t,e)=>q(r,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const V=require("lezer-elixir"),p=require("@lezer/highlight");function k(r){return V.parser.parse(r)}const _=p.tagHighlighter([{tag:p.tags.atom,class:"tok-atom"},{tag:p.tags.namespace,class:"tok-namespace"},{tag:p.tags.bool,class:"tok-bool"},{tag:p.tags.null,class:"tok-null"},{tag:p.tags.integer,class:"tok-number"},{tag:p.tags.float,class:"tok-number"},{tag:p.tags.character,class:"tok-character"},{tag:p.tags.variableName,class:"tok-variableName"},{tag:p.tags.function(p.tags.variableName),class:"tok-function"},{tag:p.tags.definition(p.tags.function(p.tags.variableName)),class:"tok-definition"},{tag:p.tags.special(p.tags.variableName),class:"tok-special"},{tag:p.tags.string,class:"tok-string"},{tag:p.tags.special(p.tags.string),class:"tok-string"},{tag:p.tags.escape,class:"tok-escape"},{tag:p.tags.keyword,class:"tok-keyword"},{tag:p.tags.operator,class:"tok-operator"},{tag:p.tags.lineComment,class:"tok-comment"},{tag:p.tags.comment,class:"tok-underscore"},{tag:p.tags.paren,class:"tok-punctuation"},{tag:p.tags.squareBracket,class:"tok-punctuation"},{tag:p.tags.brace,class:"tok-punctuation"},{tag:p.tags.special(p.tags.brace),class:"tok-punctuation"},{tag:p.tags.separator,class:"tok-separator"},{tag:p.tags.angleBracket,class:"tok-angleBracket"},{tag:p.tags.attributeName,class:"tok-attributeName"},{tag:p.tags.docString,class:"tok-docString"}]);function x(r,t){const e=[];return p.highlightTree(t,_,(s,i,n)=>{e.push({from:s,to:i,classes:n})}),e}function C(r,t,e){const s=[];for(const i of r)i.to<=t||i.from>=e||s.push({from:Math.max(i.from,t)-t,to:Math.min(i.to,e)-t,classes:i.classes});return s}const U={List:{open:"[",close:"]"},Tuple:{open:"{",close:"}"},Map:{open:"%{",close:"}"},Bitstring:{open:"<<",close:">>"},AnonymousFunction:{open:"fn",close:"end"},String:{open:'"""',close:'"""'},Charlist:{open:"'''",close:"'''"}},z=new Set(["List","Tuple","Map","MapContent","Keywords","Bitstring"]),Q=new Set([",","[","]","{","}","<<",">>","%","|","=>",":"]);function B(r){const t=r.type.name;if(!z.has(t))return-1;let e=0,s=r.firstChild;for(;s;){const i=s.type.name;if(Q.has(i)){s=s.nextSibling;continue}if(i==="MapContent"||i==="Keywords")return B(s);if(i==="Struct"){s=s.nextSibling;continue}e++,s=s.nextSibling}return e}function Y(r){const t=[0];for(let e=0;e<r.length;e++)r[e]===`
|
|
2
|
+
`&&t.push(e+1);return t}function S(r,t){let e=0,s=r.length-1;for(;e<s;){const i=e+s+1>>1;r[i]<=t?e=i:s=i-1}return e}function T(r,t,e,s){const i=U[r.type.name];if(i){const l=S(e,r.from),o=S(e,r.to-1);if(o>l){let c=i.open,d=r.from;if(r.type.name==="String"||r.type.name==="Charlist"){const g=t.slice(r.from,Math.min(r.from+3,r.to));g==='"""'||g==="'''"?c=g:c=g[0]||i.open}s.push({startLine:l,endLine:o,startOffset:d,endOffset:r.to,openText:c,closeText:i.close,itemCount:B(r),depth:0})}}let n=r.firstChild;for(;n;)T(n,t,e,s),n=n.nextSibling}function F(r,t){const e=Y(r),s=[];T(t.topNode,r,e,s),s.sort((n,l)=>n.startLine-l.startLine||n.startOffset-l.startOffset);const i=[];for(const n of s){for(;i.length>0&&i[i.length-1].endOffset<=n.startOffset;)i.pop();n.depth=i.length+1,i.push(n)}return s}function A(r){const t=new Map;for(const e of r)t.has(e.startLine)||t.set(e.startLine,e);return t}class K{constructor(){a(this,"foldedLines",new Set);a(this,"regionMap",new Map);a(this,"regions",[])}setRegions(t,e){this.regions=t,this.regionMap=e,this.foldedLines.clear()}toggle(t){this.foldedLines.has(t)?this.foldedLines.delete(t):this.regionMap.has(t)&&this.foldedLines.add(t)}isFolded(t){return this.foldedLines.has(t)}getRegion(t){return this.regionMap.get(t)}isLineHidden(t){for(const e of this.foldedLines){const s=this.regionMap.get(e);if(s&&t>s.startLine&&t<=s.endLine)return s}}isFoldable(t){return this.regionMap.has(t)}getRegions(){return this.regions}foldAll(){for(const t of this.regions)this.foldedLines.add(t.startLine)}unfoldAll(){this.foldedLines.clear()}foldToLevel(t){if(this.foldedLines.clear(),!(t<=0))for(const e of this.regions)e.depth>t&&this.foldedLines.add(e.startLine)}revealLine(t){for(const e of this.foldedLines){const s=this.regionMap.get(e);s&&t>s.startLine&&t<=s.endLine&&this.foldedLines.delete(e)}}}class M{constructor(){a(this,"query","");a(this,"caseSensitive",!1);a(this,"matches",[]);a(this,"currentIndex",-1)}search(t,e,s){if(this.query=e,this.caseSensitive=s,this.matches=[],this.currentIndex=-1,!e)return!0;const i=s?e:e.toLowerCase();for(let n=0;n<t.length;n++){const l=s?t[n]:t[n].toLowerCase();let o=0;for(;o<=l.length-i.length;){const c=l.indexOf(i,o);if(c===-1)break;this.matches.push({line:n,from:c,to:c+i.length}),o=c+1}}return this.matches.length>0&&(this.currentIndex=0),!0}clear(){this.query="",this.matches=[],this.currentIndex=-1}getQuery(){return this.query}isCaseSensitive(){return this.caseSensitive}getMatches(){return this.matches}getLineMatches(t){return this.matches.filter(e=>e.line===t)}getCurrentIndex(){return this.currentIndex}getCurrentMatch(){if(!(this.currentIndex<0||this.currentIndex>=this.matches.length))return this.matches[this.currentIndex]}getMatchCount(){return this.matches.length}next(){if(this.matches.length!==0)return this.currentIndex=(this.currentIndex+1)%this.matches.length,this.matches[this.currentIndex]}prev(){if(this.matches.length!==0)return this.currentIndex=(this.currentIndex-1+this.matches.length)%this.matches.length,this.matches[this.currentIndex]}setCurrentToLine(t){if(this.matches.length!==0){for(let e=0;e<this.matches.length;e++)if(this.matches[e].line>=t){this.currentIndex=e;return}this.currentIndex=0}}isCurrentMatch(t,e){const s=this.getCurrentMatch();return s?s.line===t&&s.from===e:!1}isActive(){return this.query.length>0}}class D{constructor(){a(this,"filteredKeys",new Set);a(this,"keyRanges",[]);a(this,"hiddenLines",new Set)}detectKeys(t,e){const s=j(e);this.keyRanges=[],R(t.topNode,e,s,this.keyRanges,0),this.rebuildHiddenLines()}setKeys(t){this.filteredKeys=new Set(t),this.rebuildHiddenLines()}addKey(t){this.filteredKeys.add(t),this.rebuildHiddenLines()}removeKey(t){this.filteredKeys.delete(t),this.rebuildHiddenLines()}hasKey(t){return this.filteredKeys.has(t)}getKeys(){return Array.from(this.filteredKeys)}getAvailableKeys(){const t=new Set;for(const e of this.keyRanges)t.add(e.key);return Array.from(t).sort()}clear(){this.filteredKeys.clear(),this.hiddenLines.clear()}isLineFiltered(t){return this.hiddenLines.has(t)}isActive(){return this.filteredKeys.size>0}getFilteredCount(){return this.filteredKeys.size}rebuildHiddenLines(){if(this.hiddenLines.clear(),this.filteredKeys.size!==0){for(const t of this.keyRanges)if(this.filteredKeys.has(t.key))for(let e=t.startLine;e<=t.endLine;e++)this.hiddenLines.add(e)}}}function j(r){const t=[0];for(let e=0;e<r.length;e++)r[e]===`
|
|
3
|
+
`&&t.push(e+1);return t}function L(r,t){let e=0,s=r.length-1;for(;e<s;){const i=e+s+1>>1;r[i]<=t?e=i:s=i-1}return e}function R(r,t,e,s,i){const n=r.type.name;if(n==="Pair"){const d=Z(r,t);if(d!==null){const g=L(e,r.from),f=L(e,r.to-1);s.push({key:d,startLine:g,endLine:f,depth:i})}}const o=n==="Map"||n==="List"||n==="Tuple"||n==="MapContent"||n==="Keywords"?i+1:i;let c=r.firstChild;for(;c;)R(c,t,e,s,o),c=c.nextSibling}function Z(r,t){const e=r.firstChild;if(!e)return null;const s=e.type.name,i=t.slice(e.from,e.to);return s==="Keyword"?i.replace(/:\s*$/,""):s==="Atom"?i.replace(/^:/,""):s==="String"?i.replace(/^"/,"").replace(/"$/,""):i.length>0?i:null}const b=new Set(["Map","List","Tuple","Bitstring"]),X=new Set(["{","}","[","]","<<",">>"]),I=new Set(["String","Atom","Alias","Integer","Float","Boolean","Nil","Char","Charlist","Sigil"]),G=new Set(["QuotedContent"]);function N(r,t,e){if(e<0||e>=t.length)return null;const s=r.resolveInner(e,1);return s?J(s,t):null}function J(r,t){const e=r.type.name;if(X.has(e)){const i=r.parent;if(i&&b.has(i.type.name))return{from:i.from,to:i.to,copyText:t.slice(i.from,i.to),isStructure:!0,type:i.type.name}}if(b.has(e))return{from:r.from,to:r.to,copyText:t.slice(r.from,r.to),isStructure:!0,type:e};const s=tt(r,t);if(s)return s;if(G.has(e)){const i=r.parent;if(i&&I.has(i.type.name))return{from:i.from,to:i.to,copyText:t.slice(i.from,i.to),isStructure:!1,type:i.type.name}}if(I.has(e))return{from:r.from,to:r.to,copyText:t.slice(r.from,r.to),isStructure:!1,type:e};if(e==="Keyword"){const n=":"+t.slice(r.from,r.to).replace(/:\s*$/,"");return{from:r.from,to:r.to,copyText:n,isStructure:!1,type:"Keyword"}}return e==="Pair"?{from:r.from,to:r.to,copyText:t.slice(r.from,r.to),isStructure:!1,type:"Pair"}:null}function O(r,t){if(r.type.name!=="BinaryOperator")return!1;let e=r.firstChild;for(;e;){if(e.type.name==="Operator"&&t.slice(e.from,e.to)==="..")return!0;e=e.nextSibling}return!1}function w(r,t){if(r.type.name!=="BinaryOperator")return!1;let e=!1,s=!1,i=r.firstChild;for(;i;)i.type.name==="Operator"&&t.slice(i.from,i.to)==="//"&&(e=!0),O(i,t)&&(s=!0),i=i.nextSibling;return e&&s}function tt(r,t){let e=r;for(;e;){if(e.type.name==="BinaryOperator"){if(w(e,t))return{from:e.from,to:e.to,copyText:t.slice(e.from,e.to),isStructure:!1,type:"Range"};if(O(e,t))return e.parent&&w(e.parent,t)?{from:e.parent.from,to:e.parent.to,copyText:t.slice(e.parent.from,e.parent.to),isStructure:!1,type:"Range"}:{from:e.from,to:e.to,copyText:t.slice(e.from,e.to),isStructure:!1,type:"Range"}}e=e.parent}return null}const et=/#[A-Z][\w.]*<[^>\n]*>/g;function H(r){const t=[];return{modifiedCode:r.replace(et,(s,i)=>(t.push({from:i,to:i+s.length,originalText:s}),":"+"_".repeat(s.length-1))),inspectLiterals:t}}class st{constructor(t,e){a(this,"container");a(this,"innerEl");a(this,"scrollEl");a(this,"toolbarEl",null);a(this,"wrapBtn",null);a(this,"copyBtn",null);a(this,"searchBtn",null);a(this,"filterBtn",null);a(this,"copyResetTimer",null);a(this,"code","");a(this,"lines",[]);a(this,"lineOffsets",[]);a(this,"tokens",[]);a(this,"tree",null);a(this,"foldState",new K);a(this,"filterState",new D);a(this,"defaultFoldLevel",0);a(this,"defaultFilterKeys",[]);a(this,"searchState",new M);a(this,"onRenderCallback",null);a(this,"wordWrap",!1);a(this,"toolbarConfig");a(this,"searchBarEl",null);a(this,"searchInputEl",null);a(this,"searchInfoEl",null);a(this,"searchCaseBtn",null);a(this,"searchVisible",!1);a(this,"filterBarEl",null);a(this,"filterInputEl",null);a(this,"filterTagsEl",null);a(this,"filterInfoEl",null);a(this,"filterDropdownEl",null);a(this,"filterCopyBtn",null);a(this,"filterCopyResetTimer",null);a(this,"filterVisible",!1);a(this,"filterDropdownIndex",-1);a(this,"filterDropdownItems",[]);a(this,"currentInspect",null);a(this,"inspectCallback",null);a(this,"inspectLiterals",[]);this.container=t,this.container.classList.add("edv-container"),this.defaultFoldLevel=(e==null?void 0:e.defaultFoldLevel)??0,this.defaultFilterKeys=(e==null?void 0:e.defaultFilterKeys)??[],this.wordWrap=(e==null?void 0:e.defaultWordWrap)??!1;const s=(e==null?void 0:e.toolbar)??{};this.toolbarConfig={foldAll:s.foldAll!==!1,unfoldAll:s.unfoldAll!==!1,wordWrap:s.wordWrap!==!1,copy:s.copy!==!1,search:s.search!==!1,filter:s.filter!==!1},this.buildToolbar(),this.wordWrap&&(this.container.classList.add("edv-word-wrap"),this.wrapBtn&&this.wrapBtn.classList.add("edv-toolbar-btn--active")),this.buildSearchBar(),this.buildFilterBar(),this.innerEl=document.createElement("div"),this.innerEl.classList.add("edv-inner"),this.container.appendChild(this.innerEl),this.scrollEl=document.createElement("div"),this.scrollEl.classList.add("edv-scroll"),this.innerEl.appendChild(this.scrollEl),this.scrollEl.addEventListener("mouseover",i=>this.handleInspectHover(i)),this.scrollEl.addEventListener("mouseout",i=>this.handleInspectOut(i)),this.scrollEl.addEventListener("click",i=>this.handleInspectClick(i)),this.container.setAttribute("tabindex","0"),this.container.addEventListener("keydown",i=>this.handleKeyDown(i))}buildToolbar(){const t=this.toolbarConfig;if(t.foldAll||t.unfoldAll||t.wordWrap||t.copy||t.search||t.filter){if(this.toolbarEl=document.createElement("div"),this.toolbarEl.classList.add("edv-toolbar"),t.search&&(this.searchBtn=this.createToolbarButton("⌕","Search (Ctrl+F)",()=>this.toggleSearch()),this.toolbarEl.appendChild(this.searchBtn)),t.filter&&(this.filterBtn=this.createToolbarButton("⧩","Filter Keys",()=>this.toggleFilter()),this.toolbarEl.appendChild(this.filterBtn)),t.foldAll){const s=this.createToolbarButton("⊟","Fold All",()=>this.foldAll());this.toolbarEl.appendChild(s)}if(t.unfoldAll){const s=this.createToolbarButton("⊞","Unfold All",()=>this.unfoldAll());this.toolbarEl.appendChild(s)}t.wordWrap&&(this.wrapBtn=this.createToolbarButton("↩","Word Wrap (Alt+Z)",()=>{this.toggleWordWrap()}),this.toolbarEl.appendChild(this.wrapBtn)),t.copy&&(this.copyBtn=this.createToolbarButton("⎘","Copy",()=>this.copyContent()),this.toolbarEl.appendChild(this.copyBtn)),this.container.appendChild(this.toolbarEl)}}buildSearchBar(){this.searchBarEl=document.createElement("div"),this.searchBarEl.classList.add("edv-search-bar");const t=document.createElement("div");t.classList.add("edv-search-input-wrapper"),this.searchInputEl=document.createElement("input"),this.searchInputEl.type="text",this.searchInputEl.classList.add("edv-search-input"),this.searchInputEl.placeholder="Search…",this.searchInputEl.addEventListener("input",()=>this.onSearchInput()),this.searchInputEl.addEventListener("keydown",n=>this.handleSearchKeyDown(n)),this.searchCaseBtn=document.createElement("button"),this.searchCaseBtn.classList.add("edv-search-case-btn"),this.searchCaseBtn.textContent="Aa",this.searchCaseBtn.title="Match Case",this.searchCaseBtn.addEventListener("click",n=>{n.stopPropagation(),this.toggleCaseSensitive()}),t.appendChild(this.searchInputEl),t.appendChild(this.searchCaseBtn),this.searchBarEl.appendChild(t),this.searchInfoEl=document.createElement("span"),this.searchInfoEl.classList.add("edv-search-info"),this.searchBarEl.appendChild(this.searchInfoEl);const e=document.createElement("button");e.classList.add("edv-search-nav-btn"),e.textContent="↑",e.title="Previous Match (Shift+Enter)",e.addEventListener("click",n=>{n.stopPropagation(),this.searchPrev()}),this.searchBarEl.appendChild(e);const s=document.createElement("button");s.classList.add("edv-search-nav-btn"),s.textContent="↓",s.title="Next Match (Enter)",s.addEventListener("click",n=>{n.stopPropagation(),this.searchNext()}),this.searchBarEl.appendChild(s);const i=document.createElement("button");i.classList.add("edv-search-nav-btn"),i.textContent="✕",i.title="Close (Escape)",i.addEventListener("click",n=>{n.stopPropagation(),this.closeSearch()}),this.searchBarEl.appendChild(i),this.container.appendChild(this.searchBarEl)}handleKeyDown(t){(t.metaKey||t.ctrlKey)&&t.key==="f"&&(t.preventDefault(),t.stopPropagation(),this.openSearch()),t.key==="Escape"&&this.searchVisible&&(t.preventDefault(),this.closeSearch())}handleSearchKeyDown(t){t.key==="Enter"&&(t.preventDefault(),t.shiftKey?this.searchPrev():this.searchNext()),t.key==="Escape"&&(t.preventDefault(),this.closeSearch())}createToolbarButton(t,e,s){const i=document.createElement("button");return i.classList.add("edv-toolbar-btn"),i.textContent=t,i.title=e,i.addEventListener("click",n=>{n.stopPropagation(),s()}),i}toggleWordWrap(){this.wordWrap=!this.wordWrap,this.container.classList.toggle("edv-word-wrap",this.wordWrap),this.wrapBtn&&this.wrapBtn.classList.toggle("edv-toolbar-btn--active",this.wordWrap)}isWordWrap(){return this.wordWrap}foldAll(){this.foldState.foldAll(),this.render()}unfoldAll(){this.foldState.unfoldAll(),this.render()}foldToLevel(t){this.foldState.foldToLevel(t),this.render()}getContent(){return this.code}async copyContent(){try{await navigator.clipboard.writeText(this.code)}catch{const t=document.createElement("textarea");t.value=this.code,t.style.position="fixed",t.style.opacity="0",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}this.showCopyFeedback()}showCopyFeedback(){if(!this.copyBtn)return;this.copyResetTimer&&clearTimeout(this.copyResetTimer);const t="⎘";this.copyBtn.textContent="✓",this.copyBtn.classList.add("edv-toolbar-btn--active"),this.copyBtn.title="Copied!",this.copyResetTimer=setTimeout(()=>{this.copyBtn&&(this.copyBtn.textContent=t,this.copyBtn.classList.remove("edv-toolbar-btn--active"),this.copyBtn.title="Copy"),this.copyResetTimer=null},2e3)}openSearch(){var t;this.searchVisible=!0,(t=this.searchBarEl)==null||t.classList.add("edv-search-bar--visible"),this.searchBtn&&this.searchBtn.classList.add("edv-toolbar-btn--active"),this.searchInputEl&&(this.searchInputEl.focus(),this.searchInputEl.select())}closeSearch(){var t;this.searchVisible=!1,(t=this.searchBarEl)==null||t.classList.remove("edv-search-bar--visible"),this.searchBtn&&this.searchBtn.classList.remove("edv-toolbar-btn--active"),this.searchState.clear(),this.updateSearchInfo(),this.render(),this.container.focus()}toggleSearch(){this.searchVisible?this.closeSearch():this.openSearch()}searchNext(){const t=this.searchState.next();t&&this.revealAndScrollToMatch(t),this.updateSearchInfo(),this.render()}searchPrev(){const t=this.searchState.prev();t&&this.revealAndScrollToMatch(t),this.updateSearchInfo(),this.render()}getSearchState(){return this.searchState}search(t,e){var n;const s=(e==null?void 0:e.caseSensitive)??this.searchState.isCaseSensitive();this.searchState.search(this.lines,t,s),this.searchInputEl&&(this.searchInputEl.value=t),(n=this.searchCaseBtn)==null||n.classList.toggle("edv-search-case-btn--active",s),this.updateSearchInfo();const i=this.searchState.getCurrentMatch();i&&this.revealAndScrollToMatch(i),this.render()}clearSearch(){this.searchState.clear(),this.searchInputEl&&(this.searchInputEl.value=""),this.updateSearchInfo(),this.render()}onSearchInput(){var s;const t=((s=this.searchInputEl)==null?void 0:s.value)??"";this.searchState.search(this.lines,t,this.searchState.isCaseSensitive()),this.updateSearchInfo();const e=this.searchState.getCurrentMatch();e&&this.revealAndScrollToMatch(e),this.render()}toggleCaseSensitive(){var i,n;const t=!this.searchState.isCaseSensitive();(i=this.searchCaseBtn)==null||i.classList.toggle("edv-search-case-btn--active",t);const e=((n=this.searchInputEl)==null?void 0:n.value)??"";this.searchState.search(this.lines,e,t),this.updateSearchInfo();const s=this.searchState.getCurrentMatch();s&&this.revealAndScrollToMatch(s),this.render()}updateSearchInfo(){if(!this.searchInfoEl)return;const t=this.searchState.getMatchCount();if(!this.searchState.getQuery())this.searchInfoEl.textContent="",this.searchInfoEl.classList.remove("edv-search-info--no-results");else if(t===0)this.searchInfoEl.textContent="No results",this.searchInfoEl.classList.add("edv-search-info--no-results");else{const s=this.searchState.getCurrentIndex()+1;this.searchInfoEl.textContent=`${s} of ${t}`,this.searchInfoEl.classList.remove("edv-search-info--no-results")}}revealAndScrollToMatch(t){this.foldState.revealLine(t.line)}scrollToCurrentMatch(){const t=this.scrollEl.querySelector(".edv-search-current");t&&t.scrollIntoView({block:"center",behavior:"smooth"})}openFilter(){var t;this.filterVisible=!0,(t=this.filterBarEl)==null||t.classList.add("edv-filter-bar--visible"),this.filterBtn&&this.filterBtn.classList.add("edv-toolbar-btn--active"),this.updateFilterTags(),this.updateFilterInfo(),this.filterInputEl&&this.filterInputEl.focus()}closeFilter(){var t;this.filterVisible=!1,(t=this.filterBarEl)==null||t.classList.remove("edv-filter-bar--visible"),this.hideFilterDropdown(),this.filterBtn&&this.filterBtn.classList.toggle("edv-toolbar-btn--active",this.filterState.isActive()),this.container.focus()}toggleFilter(){this.filterVisible?this.closeFilter():this.openFilter()}setFilterKeys(t){this.filterState.setKeys(t),this.updateFilterTags(),this.updateFilterInfo(),this.updateFilterBtnState(),this.render()}addFilterKey(t){this.filterState.addKey(t),this.updateFilterTags(),this.updateFilterInfo(),this.updateFilterBtnState(),this.render()}removeFilterKey(t){this.filterState.removeKey(t),this.updateFilterTags(),this.updateFilterInfo(),this.updateFilterBtnState(),this.render()}getFilterKeys(){return this.filterState.getKeys()}getAvailableKeys(){return this.filterState.getAvailableKeys()}clearFilter(){this.filterState.clear(),this.updateFilterTags(),this.updateFilterInfo(),this.updateFilterBtnState(),this.render()}getFilterState(){return this.filterState}getFilteredContent(){if(!this.filterState.isActive())return this.code;const t=[];for(let e=0;e<this.lines.length;e++)this.filterState.isLineFiltered(e)||t.push(this.lines[e]);return t.join(`
|
|
4
|
+
`)}async copyFilteredContent(){const t=this.getFilteredContent();try{await navigator.clipboard.writeText(t)}catch{const e=document.createElement("textarea");e.value=t,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)}this.showFilterCopyFeedback()}showFilterCopyFeedback(){this.filterCopyBtn&&(this.filterCopyResetTimer&&clearTimeout(this.filterCopyResetTimer),this.filterCopyBtn.textContent="✓",this.filterCopyBtn.title="Copied!",this.filterCopyResetTimer=setTimeout(()=>{this.filterCopyBtn&&(this.filterCopyBtn.textContent="⎘",this.filterCopyBtn.title="Copy Filtered Content"),this.filterCopyResetTimer=null},2e3))}buildFilterBar(){this.filterBarEl=document.createElement("div"),this.filterBarEl.classList.add("edv-filter-bar");const t=document.createElement("div");t.classList.add("edv-filter-input-wrapper"),this.filterInputEl=document.createElement("input"),this.filterInputEl.type="text",this.filterInputEl.classList.add("edv-filter-input"),this.filterInputEl.placeholder="Filter by key…",this.filterInputEl.addEventListener("input",()=>this.onFilterInput()),this.filterInputEl.addEventListener("keydown",i=>this.handleFilterKeyDown(i)),this.filterInputEl.addEventListener("focus",()=>this.onFilterInput()),t.appendChild(this.filterInputEl),this.filterDropdownEl=document.createElement("div"),this.filterDropdownEl.classList.add("edv-filter-dropdown"),t.appendChild(this.filterDropdownEl),this.filterBarEl.appendChild(t),this.filterTagsEl=document.createElement("div"),this.filterTagsEl.classList.add("edv-filter-tags"),this.filterBarEl.appendChild(this.filterTagsEl),this.filterInfoEl=document.createElement("span"),this.filterInfoEl.classList.add("edv-filter-info"),this.filterBarEl.appendChild(this.filterInfoEl),this.filterCopyBtn=document.createElement("button"),this.filterCopyBtn.classList.add("edv-search-nav-btn"),this.filterCopyBtn.textContent="⎘",this.filterCopyBtn.title="Copy Filtered Content",this.filterCopyBtn.addEventListener("click",i=>{i.stopPropagation(),this.copyFilteredContent()}),this.filterBarEl.appendChild(this.filterCopyBtn);const e=document.createElement("button");e.classList.add("edv-search-nav-btn"),e.textContent="⌫",e.title="Clear All Filters",e.addEventListener("click",i=>{i.stopPropagation(),this.clearFilter()}),this.filterBarEl.appendChild(e);const s=document.createElement("button");s.classList.add("edv-search-nav-btn"),s.textContent="✕",s.title="Close",s.addEventListener("click",i=>{i.stopPropagation(),this.closeFilter()}),this.filterBarEl.appendChild(s),document.addEventListener("click",i=>{var n;(n=this.filterBarEl)!=null&&n.contains(i.target)||this.hideFilterDropdown()}),this.container.appendChild(this.filterBarEl)}onFilterInput(){var n;const t=((n=this.filterInputEl)==null?void 0:n.value.trim().toLowerCase())??"",e=this.filterState.getAvailableKeys(),s=this.filterState.getKeys(),i=e.filter(l=>!s.includes(l)&&(t===""||l.toLowerCase().includes(t)));this.showFilterDropdown(i)}handleFilterKeyDown(t){var i,n,l;const e=this.filterDropdownItems.length,s=(i=this.filterDropdownEl)==null?void 0:i.classList.contains("edv-filter-dropdown--visible");if(t.key==="ArrowDown"&&s&&e>0){t.preventDefault(),this.filterDropdownIndex=Math.min(this.filterDropdownIndex+1,e-1),this.updateFilterDropdownHighlight();return}if(t.key==="ArrowUp"&&s&&e>0){t.preventDefault(),this.filterDropdownIndex=Math.max(this.filterDropdownIndex-1,0),this.updateFilterDropdownHighlight();return}if(t.key==="Enter"){if(t.preventDefault(),s&&this.filterDropdownIndex>=0&&this.filterDropdownIndex<e){const c=this.filterDropdownItems[this.filterDropdownIndex];c&&!this.filterState.hasKey(c)&&(this.addFilterKey(c),this.filterInputEl&&(this.filterInputEl.value=""),this.hideFilterDropdown());return}const o=((n=this.filterInputEl)==null?void 0:n.value.trim())??"";if(o){const d=this.filterState.getAvailableKeys().find(g=>g.toLowerCase()===o.toLowerCase());d&&!this.filterState.hasKey(d)&&(this.addFilterKey(d),this.filterInputEl&&(this.filterInputEl.value=""),this.hideFilterDropdown())}}t.key==="Escape"&&(t.preventDefault(),this.hideFilterDropdown(),(l=this.filterInputEl)!=null&&l.value?this.filterInputEl&&(this.filterInputEl.value=""):this.closeFilter())}updateFilterDropdownHighlight(){if(!this.filterDropdownEl)return;const t=this.filterDropdownEl.querySelectorAll(".edv-filter-dropdown-item");t.forEach((s,i)=>{s.classList.toggle("edv-filter-dropdown-item--active",i===this.filterDropdownIndex)});const e=t[this.filterDropdownIndex];e&&e.scrollIntoView({block:"nearest"})}showFilterDropdown(t){if(this.filterDropdownEl){if(this.filterDropdownEl.innerHTML="",this.filterDropdownItems=t,this.filterDropdownIndex=-1,t.length===0){this.filterDropdownEl.classList.remove("edv-filter-dropdown--visible");return}for(const e of t){const s=document.createElement("div");s.classList.add("edv-filter-dropdown-item"),s.textContent=e,s.addEventListener("mousedown",i=>{i.preventDefault(),i.stopPropagation(),this.addFilterKey(e),this.filterInputEl&&(this.filterInputEl.value="",this.filterInputEl.focus()),this.hideFilterDropdown()}),this.filterDropdownEl.appendChild(s)}this.filterDropdownEl.classList.add("edv-filter-dropdown--visible")}}hideFilterDropdown(){var t;(t=this.filterDropdownEl)==null||t.classList.remove("edv-filter-dropdown--visible")}updateFilterTags(){if(!this.filterTagsEl)return;this.filterTagsEl.innerHTML="";const t=this.filterState.getKeys();for(const e of t){const s=document.createElement("span");s.classList.add("edv-filter-tag");const i=document.createElement("span");i.classList.add("edv-filter-tag-label"),i.textContent=e,s.appendChild(i);const n=document.createElement("button");n.classList.add("edv-filter-tag-remove"),n.textContent="✕",n.title=`Remove filter: ${e}`,n.addEventListener("click",l=>{l.stopPropagation(),this.removeFilterKey(e)}),s.appendChild(n),this.filterTagsEl.appendChild(s)}}updateFilterInfo(){if(!this.filterInfoEl)return;const t=this.filterState.getFilteredCount();t===0?this.filterInfoEl.textContent="":this.filterInfoEl.textContent=`${t} key${t>1?"s":""} hidden`}updateFilterBtnState(){this.filterBtn&&this.filterBtn.classList.toggle("edv-toolbar-btn--active",this.filterState.isActive())}setContent(t){this.code=t,this.lines=t.split(`
|
|
5
|
+
`),this.buildLineOffsets();const{modifiedCode:e,inspectLiterals:s}=H(t);this.inspectLiterals=s;const i=k(e);this.tree=i,this.tokens=x(e,i),this.fixInspectLiteralTokenClasses();const n=F(e,i),l=A(n);this.foldState.setRegions(n,l),this.filterState.detectKeys(i,e),this.defaultFilterKeys.length>0&&!this.filterState.isActive()&&(this.filterState.setKeys(this.defaultFilterKeys),this.updateFilterTags(),this.updateFilterInfo(),this.updateFilterBtnState()),this.defaultFoldLevel>0&&this.foldState.foldToLevel(this.defaultFoldLevel),this.searchState.isActive()&&(this.searchState.search(this.lines,this.searchState.getQuery(),this.searchState.isCaseSensitive()),this.updateSearchInfo()),this.render()}onRender(t){this.onRenderCallback=t}fixInspectLiteralTokenClasses(){if(this.inspectLiterals.length!==0)for(const t of this.inspectLiterals)for(const e of this.tokens)e.from>=t.from&&e.to<=t.to&&(e.classes="tok-inspect-literal")}findInspectLiteral(t,e){for(const s of this.inspectLiterals)if(t>=s.from&&e<=s.to)return s;return null}buildLineOffsets(){this.lineOffsets=[0];for(let t=0;t<this.code.length;t++)this.code[t]===`
|
|
6
|
+
`&&this.lineOffsets.push(t+1)}render(){var i;this.scrollEl.innerHTML="";const t=this.lines.length,e=String(t).length;let s=0;for(;s<t;){if(this.foldState.isLineHidden(s)){s++;continue}if(this.filterState.isLineFiltered(s)){s++;continue}const l=this.foldState.isFoldable(s),o=this.foldState.isFolded(s),c=this.foldState.getRegion(s),d=this.createLineElement(s,e,l,o,c);this.scrollEl.appendChild(d),s++}(i=this.onRenderCallback)==null||i.call(this),this.searchState.isActive()&&this.searchState.getCurrentMatch()&&requestAnimationFrame(()=>this.scrollToCurrentMatch())}createLineElement(t,e,s,i,n){const l=document.createElement("div");l.classList.add("edv-line"),l.dataset.line=String(t),this.searchState.getLineMatches(t).length>0&&l.classList.add("edv-line--has-match");const c=document.createElement("div");c.classList.add("edv-gutter");const d=document.createElement("span");d.classList.add("edv-line-number"),d.textContent=String(t+1).padStart(e," "),c.appendChild(d);const g=document.createElement("span");g.classList.add("edv-fold-indicator"),s&&(g.classList.add("edv-foldable"),g.textContent=i?"▶":"▼",g.addEventListener("click",h=>{h.stopPropagation(),this.foldState.toggle(t),this.render()})),c.appendChild(g),l.appendChild(c);const f=document.createElement("div");return f.classList.add("edv-code"),i&&n?this.renderFoldedLine(f,t,n):this.renderHighlightedLine(f,t),l.appendChild(f),l}renderHighlightedLine(t,e){const s=this.lines[e],i=this.lineOffsets[e],n=i+s.length,l=C(this.tokens,i,n),o=this.searchState.getLineMatches(e);if(s.length===0){t.innerHTML=" ";return}if(o.length===0){this.renderTokenizedText(t,s,l,i);return}this.renderWithSearchHighlights(t,s,l,o,e)}renderTokenizedText(t,e,s,i){const n=i??0;if(s.length===0){const o=document.createElement("span");o.dataset.from=String(n),o.dataset.to=String(n+e.length),o.textContent=e,t.appendChild(o);return}let l=0;for(const o of s){if(o.from>l){const d=document.createElement("span");d.dataset.from=String(n+l),d.dataset.to=String(n+o.from),d.textContent=e.slice(l,o.from),t.appendChild(d)}const c=document.createElement("span");c.className=o.classes,c.dataset.from=String(n+o.from),c.dataset.to=String(n+o.to),c.textContent=e.slice(o.from,o.to),t.appendChild(c),l=o.to}if(l<e.length){const o=document.createElement("span");o.dataset.from=String(n+l),o.dataset.to=String(n+e.length),o.textContent=e.slice(l),t.appendChild(o)}}renderWithSearchHighlights(t,e,s,i,n){const l=new Array(e.length).fill(null);for(const f of s)for(let h=f.from;h<f.to&&h<e.length;h++)l[h]=f.classes;const o=[],c=new Array(e.length).fill(null);for(const f of i)for(let h=f.from;h<f.to&&h<e.length;h++)c[h]=f;let d=0;for(;d<e.length;){const f=l[d],h=c[d],m=h!==null,v=h?this.searchState.isCurrentMatch(n,h.from):!1;let u=d+1;for(;u<e.length;){const y=l[u],E=c[u],P=E!==null,W=E?this.searchState.isCurrentMatch(n,E.from):!1;if(y!==f||P!==m||W!==v)break;u++}o.push({from:d,to:u,tokenClass:f,isMatch:m,isCurrent:v}),d=u}const g=this.lineOffsets[n];for(const f of o){const h=e.slice(f.from,f.to),m=g+f.from,v=g+f.to;if(f.isMatch){const u=document.createElement("mark");if(u.classList.add("edv-search-match"),f.isCurrent&&u.classList.add("edv-search-current"),u.dataset.from=String(m),u.dataset.to=String(v),f.tokenClass){const y=document.createElement("span");y.className=f.tokenClass,y.textContent=h,u.appendChild(y)}else u.textContent=h;t.appendChild(u)}else if(f.tokenClass){const u=document.createElement("span");u.className=f.tokenClass,u.dataset.from=String(m),u.dataset.to=String(v),u.textContent=h,t.appendChild(u)}else{const u=document.createElement("span");u.dataset.from=String(m),u.dataset.to=String(v),u.textContent=h,t.appendChild(u)}}}renderFoldedLine(t,e,s){const i=this.lines[e],n=this.lineOffsets[e],l=n+i.length,o=C(this.tokens,n,l);let c=0;for(const h of o){if(h.from>c){const v=document.createElement("span");v.dataset.from=String(n+c),v.dataset.to=String(n+h.from),v.textContent=i.slice(c,h.from),t.appendChild(v)}const m=document.createElement("span");m.className=h.classes,m.dataset.from=String(n+h.from),m.dataset.to=String(n+h.to),m.textContent=i.slice(h.from,h.to),t.appendChild(m),c=h.to}if(c<i.length){const h=document.createElement("span");h.dataset.from=String(n+c),h.dataset.to=String(n+i.length),h.textContent=i.slice(c),t.appendChild(h)}const d=document.createElement("span");d.classList.add("edv-fold-ellipsis");const g=s.endLine-s.startLine;if(s.itemCount>0)d.textContent=`${s.itemCount} items`,d.title=`${s.itemCount} items, ${g} lines folded`;else{const m=s.openText==='"""'||s.openText==="'''"?g-1:g;d.textContent=`${m} lines`,d.title=`${m} lines folded`}d.dataset.from=String(s.startOffset),d.dataset.to=String(s.endOffset),d.addEventListener("click",h=>{h.stopPropagation(),this.foldState.toggle(e),this.render()}),t.appendChild(d);const f=document.createElement("span");f.classList.add("tok-punctuation"),f.dataset.from=String(s.endOffset-s.closeText.length),f.dataset.to=String(s.endOffset),f.textContent=s.closeText,t.appendChild(f)}handleInspectHover(t){const e=t.target;if(!e||!this.tree)return;const s=e.closest("[data-from]");if(!s){this.clearInspectHighlight();return}const i=parseInt(s.dataset.from,10);if(isNaN(i))return;const n=N(this.tree,this.code,i);if(!n){this.clearInspectHighlight();return}this.findInspectLiteral(n.from,n.to)&&(n.type="InspectLiteral"),!(this.currentInspect&&this.currentInspect.from===n.from&&this.currentInspect.to===n.to)&&(this.clearInspectHighlight(),this.currentInspect=n,this.applyInspectHighlight(n))}handleInspectOut(t){const s=t.relatedTarget;s&&this.scrollEl.contains(s)||this.clearInspectHighlight()}onInspect(t){this.inspectCallback=t}handleInspectClick(t){if(!this.currentInspect)return;const e=t.target;if(!e)return;const s=e.closest(".edv-fold-indicator, .edv-fold-ellipsis");if(s&&!s.dataset.from)return;const i=e.closest("[data-from]");if(!i)return;const n=this.currentInspect.copyText;let l=!1;if(this.inspectCallback){const o={type:this.currentInspect.type,copyText:n,target:this.currentInspect,element:i,mouseEvent:t,preventDefault(){l=!0}};this.inspectCallback(o)}this.flashInspectHighlight(),l||(this.copyToClipboard(n),this.showInspectToast(t))}applyInspectHighlight(t){if(t.isStructure){const e=this.offsetToLine(t.from),s=this.offsetToLine(t.to-1),i=this.scrollEl.querySelectorAll(".edv-line");for(const n of i){const l=parseInt(n.dataset.line,10);isNaN(l)||l>=e&&l<=s&&n.classList.add("edv-inspect-line")}this.highlightSpansInRange(t.from,t.to,"edv-inspect-bracket")}else this.highlightSpansInRange(t.from,t.to,"edv-inspect-token")}highlightSpansInRange(t,e,s){const i=this.scrollEl.querySelectorAll("[data-from]");for(const n of i){const l=parseInt(n.dataset.from,10),o=parseInt(n.dataset.to,10);isNaN(l)||isNaN(o)||l<e&&o>t&&n.classList.add(s)}}clearInspectHighlight(){if(!this.currentInspect)return;const t=this.scrollEl.querySelectorAll(".edv-inspect-line");for(const s of t)s.classList.remove("edv-inspect-line");const e=this.scrollEl.querySelectorAll(".edv-inspect-token, .edv-inspect-bracket");for(const s of e)s.classList.remove("edv-inspect-token","edv-inspect-bracket");this.currentInspect=null}flashInspectHighlight(){const t=this.scrollEl.querySelectorAll(".edv-inspect-token, .edv-inspect-bracket");for(const e of t)e.classList.add("edv-inspect-copied"),e.addEventListener("animationend",()=>e.classList.remove("edv-inspect-copied"),{once:!0})}showInspectToast(t){const e=document.createElement("div");e.classList.add("edv-copied-toast"),e.textContent="Copied!",e.style.left=`${t.clientX+8}px`,e.style.top=`${t.clientY-24}px`,document.body.appendChild(e),e.addEventListener("animationend",()=>{e.remove()})}async copyToClipboard(t){try{await navigator.clipboard.writeText(t)}catch{const e=document.createElement("textarea");e.value=t,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)}}offsetToLine(t){let e=0,s=this.lineOffsets.length-1;for(;e<s;){const i=e+s+1>>1;this.lineOffsets[i]<=t?e=i:s=i-1}return e}}exports.ElixirDataViewer=st;exports.FilterState=D;exports.FoldState=K;exports.SearchState=M;exports.buildFoldMap=A;exports.detectFoldRegions=F;exports.getLineTokens=C;exports.highlight=x;exports.parseElixir=k;exports.preprocessInspectLiterals=H;exports.resolveInspectTarget=N;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Elixir Data Viewer — A read-only web viewer for Elixir data structures
|
|
3
|
+
* with syntax highlighting, code folding, and line numbers.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { ElixirDataViewer } from "elixir-data-viewer";
|
|
8
|
+
* import "elixir-data-viewer/style.css";
|
|
9
|
+
*
|
|
10
|
+
* const viewer = new ElixirDataViewer(document.getElementById("container")!);
|
|
11
|
+
* viewer.setContent('%{name: "Alice", age: 30}');
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import "./styles/theme.css";
|
|
15
|
+
export { ElixirDataViewer } from "./renderer";
|
|
16
|
+
export type { ElixirDataViewerOptions, ToolbarOptions, InspectEvent } from "./renderer";
|
|
17
|
+
export { parseElixir } from "./parser";
|
|
18
|
+
export { highlight, getLineTokens } from "./highlighter";
|
|
19
|
+
export type { HighlightToken } from "./highlighter";
|
|
20
|
+
export { detectFoldRegions, buildFoldMap } from "./fold";
|
|
21
|
+
export type { FoldRegion } from "./fold";
|
|
22
|
+
export { FoldState } from "./state";
|
|
23
|
+
export { SearchState } from "./search";
|
|
24
|
+
export type { SearchMatch } from "./search";
|
|
25
|
+
export { FilterState } from "./filter";
|
|
26
|
+
export type { KeyRange } from "./filter";
|
|
27
|
+
export { resolveInspectTarget } from "./inspect";
|
|
28
|
+
export type { InspectTarget, InspectType } from "./inspect";
|
|
29
|
+
export { preprocessInspectLiterals } from "./preprocess";
|
|
30
|
+
export type { InspectLiteral, PreprocessResult } from "./preprocess";
|