@joinezco/codeblock 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +661 -0
  2. package/dist/assets/clike-C8IJ2oj_.js +1 -0
  3. package/dist/assets/cmake-BQqOBYOt.js +1 -0
  4. package/dist/assets/dockerfile-C_y-rIpk.js +1 -0
  5. package/dist/assets/fs.worker-BwEqZcql.ts +109 -0
  6. package/dist/assets/go-CTD25R5P.js +1 -0
  7. package/dist/assets/haskell-BWDZoCOh.js +1 -0
  8. package/dist/assets/index-9HdhmM_Y.js +1 -0
  9. package/dist/assets/index-C-QhPFHP.js +3 -0
  10. package/dist/assets/index-C3BnE2cG.js +222 -0
  11. package/dist/assets/index-CGx5MZO7.js +6 -0
  12. package/dist/assets/index-CIuq3uTk.js +1 -0
  13. package/dist/assets/index-CXFONXS8.js +1 -0
  14. package/dist/assets/index-D5Z27j1C.js +1 -0
  15. package/dist/assets/index-DWOBdRjn.js +1 -0
  16. package/dist/assets/index-Dvu-FFzd.js +1 -0
  17. package/dist/assets/index-Dx_VuNNd.js +1 -0
  18. package/dist/assets/index-I0dlv-r3.js +1 -0
  19. package/dist/assets/index-MGle_v2x.js +1 -0
  20. package/dist/assets/index-N-GE7HTU.js +1 -0
  21. package/dist/assets/index-aEsF5o-7.js +2 -0
  22. package/dist/assets/index-as7ELo0J.js +1 -0
  23. package/dist/assets/index-gUUzXNuP.js +1 -0
  24. package/dist/assets/index-pGm0qkrJ.js +13 -0
  25. package/dist/assets/javascript.worker-C1zGArKk.js +527 -0
  26. package/dist/assets/lua-BgMRiT3U.js +1 -0
  27. package/dist/assets/perl-CdXCOZ3F.js +1 -0
  28. package/dist/assets/process-Dw9K5EnD.js +1357 -0
  29. package/dist/assets/properties-C78fOPTZ.js +1 -0
  30. package/dist/assets/ruby-B2Rjki9n.js +1 -0
  31. package/dist/assets/shell-CjFT_Tl9.js +1 -0
  32. package/dist/assets/swift-BzpIVaGY.js +1 -0
  33. package/dist/assets/toml-BXUEaScT.js +1 -0
  34. package/dist/assets/vb-CmGdzxic.js +1 -0
  35. package/dist/e2e/example.spec.d.ts +5 -0
  36. package/dist/e2e/example.spec.js +44 -0
  37. package/dist/editor.d.ts +53 -0
  38. package/dist/editor.js +248 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.html +16 -0
  41. package/dist/index.js +6 -0
  42. package/dist/lsps/index.d.ts +96 -0
  43. package/dist/lsps/index.js +198 -0
  44. package/dist/lsps/typescript.d.ts +55 -0
  45. package/dist/lsps/typescript.js +48 -0
  46. package/dist/panels/toolbar.d.ts +20 -0
  47. package/dist/panels/toolbar.js +453 -0
  48. package/dist/panels/toolbar.test.d.ts +1 -0
  49. package/dist/panels/toolbar.test.js +146 -0
  50. package/dist/resources/config.json +13 -0
  51. package/dist/rpc/serde.d.ts +11 -0
  52. package/dist/rpc/serde.js +49 -0
  53. package/dist/rpc/transport.d.ts +11 -0
  54. package/dist/rpc/transport.js +38 -0
  55. package/dist/snapshot.bin +0 -0
  56. package/dist/styles.css +7 -0
  57. package/dist/themes/index.d.ts +1 -0
  58. package/dist/themes/index.js +169 -0
  59. package/dist/themes/util.d.ts +24 -0
  60. package/dist/themes/util.js +63 -0
  61. package/dist/themes/vscode.d.ts +6 -0
  62. package/dist/themes/vscode.js +187 -0
  63. package/dist/types.d.ts +64 -0
  64. package/dist/types.js +1 -0
  65. package/dist/utils/fs.d.ts +29 -0
  66. package/dist/utils/fs.js +310 -0
  67. package/dist/utils/indent.d.ts +1 -0
  68. package/dist/utils/indent.js +38 -0
  69. package/dist/utils/index.d.ts +2 -0
  70. package/dist/utils/index.js +2 -0
  71. package/dist/utils/lsp.d.ts +26 -0
  72. package/dist/utils/lsp.js +74 -0
  73. package/dist/utils/search.d.ts +30 -0
  74. package/dist/utils/search.js +68 -0
  75. package/dist/utils/snapshot.d.ts +60 -0
  76. package/dist/utils/snapshot.js +299 -0
  77. package/dist/workers/fs.worker.d.ts +11 -0
  78. package/dist/workers/fs.worker.js +93 -0
  79. package/dist/workers/javascript.worker.d.ts +1 -0
  80. package/dist/workers/javascript.worker.js +20 -0
  81. package/package.json +95 -0
@@ -0,0 +1,453 @@
1
+ import { StateEffect, StateField } from "@codemirror/state";
2
+ import { CodeblockFacet, openFileEffect, currentFileField } from "../editor";
3
+ import { extOrLanguageToLanguageId } from "../lsps";
4
+ // Type guards
5
+ function isCommandResult(result) {
6
+ return 'type' in result;
7
+ }
8
+ function isSearchResult(result) {
9
+ return 'score' in result;
10
+ }
11
+ // Search results state - now handles both commands and search results
12
+ export const setSearchResults = StateEffect.define();
13
+ export const searchResultsField = StateField.define({
14
+ create() {
15
+ return [];
16
+ },
17
+ update(value, tr) {
18
+ for (let e of tr.effects)
19
+ if (e.is(setSearchResults))
20
+ return e.value;
21
+ return value;
22
+ }
23
+ });
24
+ const mod = (n, m) => ((n % m) + m) % m;
25
+ // A safe dispatcher to avoid nested-update errors from UI events during CM updates
26
+ function safeDispatch(view, spec) {
27
+ // Always queue to a microtask so we never dispatch within an ongoing update cycle
28
+ queueMicrotask(() => {
29
+ try {
30
+ view.dispatch(spec);
31
+ }
32
+ catch (e) {
33
+ console.error(e);
34
+ }
35
+ });
36
+ }
37
+ // Check if query matches a programming language
38
+ function isValidProgrammingLanguage(query) {
39
+ const lowerQuery = query.toLowerCase();
40
+ return Object.keys(extOrLanguageToLanguageId).some(key => key.toLowerCase() === lowerQuery ||
41
+ extOrLanguageToLanguageId[key].toLowerCase() === lowerQuery);
42
+ }
43
+ // Get appropriate icon for language/extension
44
+ function getLanguageIcon(query) {
45
+ const lowerQuery = query.toLowerCase();
46
+ // Language/extension icons matching extOrLanguageToLanguageId
47
+ const iconMap = {
48
+ // JavaScript/TypeScript family
49
+ 'javascript': '🟨',
50
+ 'js': '🟨',
51
+ 'typescript': '🔷',
52
+ 'ts': '🔷',
53
+ 'jsx': '⚛️',
54
+ 'tsx': '⚛️',
55
+ // Python
56
+ 'python': '🐍',
57
+ 'py': '🐍',
58
+ // Ruby
59
+ 'ruby': '💎',
60
+ 'rb': '💎',
61
+ // PHP
62
+ 'php': '🐘',
63
+ // Java
64
+ 'java': '☕',
65
+ // C/C++
66
+ 'cpp': '⚙️',
67
+ 'c': '⚙️',
68
+ // C#
69
+ 'csharp': '🔷',
70
+ 'cs': '🔷',
71
+ // Go
72
+ 'go': '🐹',
73
+ // Swift
74
+ 'swift': '🦉',
75
+ // Kotlin
76
+ 'kotlin': '🟣',
77
+ 'kt': '🟣',
78
+ // Rust
79
+ 'rust': '🦀',
80
+ 'rs': '🦀',
81
+ // Scala
82
+ 'scala': '🔴',
83
+ // Visual Basic
84
+ 'vb': '🔵',
85
+ // Haskell
86
+ 'haskell': '🎭',
87
+ 'hs': '🎭',
88
+ // Lua
89
+ 'lua': '🌙',
90
+ // Perl
91
+ 'perl': '🐪',
92
+ 'pl': '🐪',
93
+ // Shell/Bash
94
+ 'bash': '🐚',
95
+ 'shell': '🐚',
96
+ 'sh': '🐚',
97
+ 'zsh': '🐚',
98
+ // SQL
99
+ 'mysql': '🗃️',
100
+ 'sql': '🗃️',
101
+ // Web technologies
102
+ 'html': '🌐',
103
+ 'css': '🎨',
104
+ 'scss': '🎨',
105
+ 'less': '🎨',
106
+ // Data formats
107
+ 'json': '📋',
108
+ 'yaml': '⚙️',
109
+ 'yml': '⚙️',
110
+ 'xml': '📄',
111
+ 'toml': '⚙️',
112
+ 'ini': '⚙️',
113
+ 'conf': '⚙️',
114
+ 'log': '📄',
115
+ 'env': '🔧',
116
+ // Documentation
117
+ 'markdown': '📝',
118
+ 'md': '📝',
119
+ // Docker/Build
120
+ 'dockerfile': '🐳',
121
+ 'makefile': '🔨',
122
+ 'dockerignore': '🐳',
123
+ 'gitignore': '📝'
124
+ };
125
+ return iconMap[lowerQuery] || '📄';
126
+ }
127
+ // Create command results for the first section
128
+ function createCommandResults(query, view, searchResults) {
129
+ const commands = [];
130
+ const currentFile = view.state.field(currentFileField);
131
+ const hasValidFile = currentFile.path && !currentFile.loading;
132
+ const isLanguageQuery = isValidProgrammingLanguage(query);
133
+ // TODO: fix language ext for new file with full language names, "typescript" -> "file.ts"
134
+ // Check if query matches an existing file (first search result with exact match)
135
+ const hasExactFileMatch = searchResults.length > 0 && searchResults[0].id === query;
136
+ if (query.trim()) {
137
+ // Create new file command (only if query doesn't match existing file)
138
+ if (!hasExactFileMatch) {
139
+ const createFileCommand = {
140
+ id: isLanguageQuery ? "Create new file" : `Create new file "${query}"`,
141
+ type: 'create-file',
142
+ icon: isLanguageQuery ? getLanguageIcon(query) : '📄',
143
+ query,
144
+ requiresInput: isLanguageQuery
145
+ };
146
+ commands.push(createFileCommand);
147
+ }
148
+ // Rename file command (only if file is open, query is not a language, and doesn't match current file)
149
+ if (hasValidFile && !isLanguageQuery && !hasExactFileMatch) {
150
+ const renameCommand = {
151
+ id: `Rename to "${query}"`,
152
+ type: 'rename-file',
153
+ icon: '✏️',
154
+ query
155
+ };
156
+ commands.push(renameCommand);
157
+ }
158
+ }
159
+ return commands;
160
+ }
161
+ // Toolbar Panel
162
+ export const toolbarPanel = (view) => {
163
+ let { filepath, language, index } = view.state.facet(CodeblockFacet);
164
+ const dom = document.createElement("div");
165
+ dom.className = "cm-toolbar-panel";
166
+ // Create state icon (left side)
167
+ const stateIcon = document.createElement("div");
168
+ stateIcon.className = "cm-toolbar-state-icon";
169
+ stateIcon.textContent = "📄"; // Default file icon
170
+ // Create container for state icon to help with alignment
171
+ const stateIconContainer = document.createElement("div");
172
+ stateIconContainer.className = "cm-toolbar-state-icon-container";
173
+ stateIconContainer.appendChild(stateIcon);
174
+ dom.appendChild(stateIconContainer);
175
+ // Create input container for the right-aligned input
176
+ const inputContainer = document.createElement("div");
177
+ inputContainer.className = "cm-toolbar-input-container";
178
+ dom.appendChild(inputContainer);
179
+ const input = document.createElement("input");
180
+ input.type = "text";
181
+ input.value = filepath || language || "";
182
+ input.className = "cm-toolbar-input";
183
+ inputContainer.appendChild(input);
184
+ const resultsList = document.createElement("ul");
185
+ resultsList.className = "cm-search-results";
186
+ dom.appendChild(resultsList);
187
+ let selectedIndex = 0;
188
+ let namingMode = { active: false, type: 'create-file', originalQuery: '' };
189
+ // Tracks gutter width for toolbar alignment
190
+ function updateGutterWidthVariables() {
191
+ const gutters = view.dom.querySelector('.cm-gutters');
192
+ if (gutters) {
193
+ const gutterWidth = gutters.getBoundingClientRect().width;
194
+ dom.style.setProperty('--cm-gutter-width', `${gutterWidth}px`);
195
+ const numberGutter = gutters.querySelector('.cm-lineNumbers');
196
+ if (numberGutter) {
197
+ const numberGutterWidth = numberGutter.getBoundingClientRect().width;
198
+ dom.style.setProperty('--cm-gutter-lineno-width', `${numberGutterWidth}px`);
199
+ }
200
+ }
201
+ }
202
+ // Set up ResizeObserver to watch gutter width changes
203
+ let gutterObserver = null;
204
+ function setupGutterObserver() {
205
+ const gutters = view.dom.querySelector('.cm-gutters');
206
+ if (gutters && window.ResizeObserver) {
207
+ gutterObserver = new ResizeObserver(() => {
208
+ updateGutterWidthVariables();
209
+ });
210
+ gutterObserver.observe(gutters);
211
+ }
212
+ }
213
+ // Initial width setup and observer
214
+ updateGutterWidthVariables();
215
+ setupGutterObserver();
216
+ const renderItem = (result, i) => {
217
+ const li = document.createElement("li");
218
+ li.className = `cm-search-result ${isCommandResult(result) ? 'cm-command-result' : 'cm-file-result'}`;
219
+ const resultIconContainer = document.createElement("div");
220
+ resultIconContainer.className = "cm-search-result-icon-container";
221
+ const resultIcon = document.createElement("div");
222
+ resultIcon.className = "cm-search-result-icon";
223
+ resultIcon.textContent = isCommandResult(result) ? result.icon : '📄';
224
+ resultIconContainer.appendChild(resultIcon);
225
+ li.appendChild(resultIconContainer);
226
+ const resultLabel = document.createElement("div");
227
+ resultLabel.className = "cm-search-result-label";
228
+ resultLabel.textContent = result.id;
229
+ li.appendChild(resultLabel);
230
+ if (i === selectedIndex)
231
+ li.classList.add("selected");
232
+ li.addEventListener("mousedown", (ev) => {
233
+ ev.preventDefault();
234
+ });
235
+ li.addEventListener("click", () => selectResult(result));
236
+ return li;
237
+ };
238
+ function updateDropdown() {
239
+ const results = view.state.field(searchResultsField);
240
+ const children = [];
241
+ // Separate commands from search results
242
+ const commands = results.filter(isCommandResult);
243
+ const searchResults = results.filter(isSearchResult);
244
+ let currentIndex = 0;
245
+ // Render commands section
246
+ commands.forEach((command) => {
247
+ children.push(renderItem(command, currentIndex));
248
+ currentIndex++;
249
+ });
250
+ // Render search results section
251
+ searchResults.forEach((result) => {
252
+ children.push(renderItem(result, currentIndex));
253
+ currentIndex++;
254
+ });
255
+ resultsList.replaceChildren(...children);
256
+ }
257
+ function selectResult(result) {
258
+ if (isCommandResult(result)) {
259
+ handleCommandResult(result);
260
+ }
261
+ else {
262
+ handleSearchResult(result);
263
+ }
264
+ }
265
+ function updateStateIcon() {
266
+ if (namingMode.active) {
267
+ stateIcon.textContent = namingMode.type === 'create-file' ? '📄' : '✏️';
268
+ }
269
+ else {
270
+ stateIcon.textContent = '📄'; // Default file icon
271
+ }
272
+ }
273
+ function enterNamingMode(type, originalQuery, languageExtension) {
274
+ namingMode = { active: true, type, originalQuery, languageExtension };
275
+ // Update state icon
276
+ updateStateIcon();
277
+ // Clear input and focus
278
+ input.value = '';
279
+ input.placeholder = languageExtension ? `filename.${languageExtension}` : 'filename';
280
+ input.focus();
281
+ // Clear search results
282
+ safeDispatch(view, { effects: setSearchResults.of([]) });
283
+ }
284
+ function exitNamingMode() {
285
+ namingMode = { active: false, type: 'create-file', originalQuery: '' };
286
+ updateStateIcon();
287
+ input.placeholder = '';
288
+ }
289
+ function handleCommandResult(command) {
290
+ if (command.type === 'create-file') {
291
+ if (command.requiresInput) {
292
+ // Enter naming mode for language-specific file
293
+ enterNamingMode('create-file', command.query, command.query);
294
+ }
295
+ else {
296
+ // Create file directly and populate toolbar
297
+ const pathToOpen = command.query.includes('.') ? command.query : `${command.query}.txt`;
298
+ input.value = pathToOpen;
299
+ safeDispatch(view, {
300
+ effects: [setSearchResults.of([]), openFileEffect.of({ path: pathToOpen })]
301
+ });
302
+ }
303
+ }
304
+ else if (command.type === 'rename-file') {
305
+ // Rename file directly since the new name is provided by the query
306
+ const currentFile = view.state.field(currentFileField);
307
+ if (currentFile.path) {
308
+ const newPath = command.query.includes('.') ? command.query : `${command.query}.txt`;
309
+ input.value = newPath;
310
+ // TODO: Implement actual file rename logic
311
+ console.log(`Rename ${currentFile.path} to ${newPath}`);
312
+ safeDispatch(view, {
313
+ effects: [setSearchResults.of([]), openFileEffect.of({ path: newPath })]
314
+ });
315
+ }
316
+ }
317
+ }
318
+ function handleSearchResult(result) {
319
+ input.value = result.id;
320
+ safeDispatch(view, {
321
+ effects: [setSearchResults.of([]), openFileEffect.of({ path: result.id })]
322
+ });
323
+ }
324
+ function executeNamingMode(filename) {
325
+ if (!namingMode.active || !filename.trim())
326
+ return;
327
+ if (namingMode.type === 'create-file') {
328
+ const pathToOpen = namingMode.languageExtension && !filename.includes('.')
329
+ ? `${filename}.${namingMode.languageExtension}`
330
+ : filename;
331
+ input.value = pathToOpen;
332
+ // TODO: handle edge-cases like trying to create folders, invalid characters, etc.
333
+ safeDispatch(view, {
334
+ effects: [setSearchResults.of([]), openFileEffect.of({ path: pathToOpen })]
335
+ });
336
+ }
337
+ else if (namingMode.type === 'rename-file') {
338
+ const currentFile = view.state.field(currentFileField);
339
+ if (currentFile.path) {
340
+ const newPath = filename.includes('.') ? filename : `${filename}.txt`;
341
+ input.value = newPath;
342
+ // TODO: Implement actual file rename logic
343
+ console.log(`Rename ${currentFile.path} to ${newPath}`);
344
+ safeDispatch(view, {
345
+ effects: [setSearchResults.of([]), openFileEffect.of({ path: newPath })]
346
+ });
347
+ }
348
+ }
349
+ exitNamingMode();
350
+ }
351
+ // Close dropdown when clicking outside
352
+ function handleClickOutside(event) {
353
+ if (!dom.contains(event.target)) {
354
+ safeDispatch(view, { effects: setSearchResults.of([]) });
355
+ }
356
+ }
357
+ input.addEventListener("click", () => {
358
+ // Open dropdown when input is clicked
359
+ if (!namingMode.active) {
360
+ const query = input.value;
361
+ let results = [];
362
+ if (query.trim()) {
363
+ // Get regular search results from index first
364
+ const searchResults = (index?.search(query) || []).slice(0, 100);
365
+ // Add command results first (passing search results to check for existing files)
366
+ const commands = createCommandResults(query, view, searchResults);
367
+ results = searchResults.concat(commands);
368
+ }
369
+ safeDispatch(view, { effects: setSearchResults.of(results) });
370
+ // Add click-outside listener when dropdown opens
371
+ document.addEventListener("click", handleClickOutside);
372
+ }
373
+ });
374
+ input.addEventListener("input", (event) => {
375
+ const query = event.target.value;
376
+ selectedIndex = 0;
377
+ // If in naming mode, don't show search results
378
+ if (namingMode.active) {
379
+ return;
380
+ }
381
+ let results = [];
382
+ if (query.trim()) {
383
+ // Get regular search results from index first
384
+ const searchResults = (index?.search(query) || []).slice(0, 1000);
385
+ // Add command results first (passing search results to check for existing files)
386
+ const commands = createCommandResults(query, view, searchResults);
387
+ results.push(...commands);
388
+ // Add search results
389
+ results.push(...searchResults);
390
+ }
391
+ safeDispatch(view, { effects: setSearchResults.of(results) });
392
+ });
393
+ input.addEventListener("keydown", (event) => {
394
+ if (namingMode.active) {
395
+ // Handle naming mode
396
+ if (event.key === "Enter") {
397
+ event.preventDefault();
398
+ executeNamingMode(input.value);
399
+ }
400
+ else if (event.key === "Escape") {
401
+ event.preventDefault();
402
+ exitNamingMode();
403
+ input.value = namingMode.originalQuery;
404
+ }
405
+ return;
406
+ }
407
+ // Normal search mode
408
+ const results = view.state.field(searchResultsField);
409
+ if (event.key === "ArrowDown") {
410
+ event.preventDefault();
411
+ if (results.length) {
412
+ selectedIndex = mod(selectedIndex + 1, results.length);
413
+ updateDropdown();
414
+ }
415
+ }
416
+ else if (event.key === "ArrowUp") {
417
+ event.preventDefault();
418
+ if (results.length) {
419
+ selectedIndex = mod(selectedIndex - 1, results.length);
420
+ updateDropdown();
421
+ }
422
+ }
423
+ else if (event.key === "Enter" && results.length && selectedIndex >= 0) {
424
+ event.preventDefault();
425
+ selectResult(results[selectedIndex]);
426
+ }
427
+ });
428
+ return {
429
+ dom,
430
+ top: true,
431
+ update(update) {
432
+ // Re-render dropdown when search results change
433
+ const a = update.startState.field(searchResultsField);
434
+ const b = update.state.field(searchResultsField);
435
+ if (a !== b) {
436
+ updateDropdown();
437
+ // Remove click-outside listener when dropdown closes
438
+ if (b.length === 0) {
439
+ document.removeEventListener("click", handleClickOutside);
440
+ }
441
+ }
442
+ },
443
+ destroy() {
444
+ // Clean up event listeners when panel is destroyed
445
+ document.removeEventListener("click", handleClickOutside);
446
+ // Clean up ResizeObserver
447
+ if (gutterObserver) {
448
+ gutterObserver.disconnect();
449
+ gutterObserver = null;
450
+ }
451
+ }
452
+ };
453
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { EditorState } from '@codemirror/state';
3
+ import { EditorView } from '@codemirror/view';
4
+ import { searchResultsField } from './toolbar';
5
+ import { CodeblockFacet, currentFileField } from '../editor';
6
+ // Mock dependencies
7
+ vi.mock('../lsps', () => ({
8
+ extOrLanguageToLanguageId: {
9
+ 'js': 'javascript',
10
+ 'ts': 'typescript',
11
+ 'py': 'python',
12
+ 'rs': 'rust',
13
+ 'go': 'go'
14
+ }
15
+ }));
16
+ vi.mock('../editor', () => ({
17
+ CodeblockFacet: {
18
+ of: vi.fn(),
19
+ },
20
+ currentFileField: {
21
+ init: vi.fn(() => ({ path: null, content: '', language: null, loading: false }))
22
+ },
23
+ openFileEffect: {
24
+ of: vi.fn()
25
+ }
26
+ }));
27
+ describe('Toolbar Panel', () => {
28
+ let view;
29
+ let mockFs;
30
+ beforeEach(() => {
31
+ mockFs = {
32
+ readFile: vi.fn(),
33
+ writeFile: vi.fn(),
34
+ exists: vi.fn()
35
+ };
36
+ const state = EditorState.create({
37
+ doc: '',
38
+ extensions: [
39
+ CodeblockFacet.of({
40
+ fs: mockFs,
41
+ cwd: '/',
42
+ filepath: null,
43
+ content: '',
44
+ toolbar: true,
45
+ index: null,
46
+ language: null
47
+ }),
48
+ searchResultsField,
49
+ currentFileField
50
+ ]
51
+ });
52
+ view = new EditorView({
53
+ state,
54
+ parent: document.createElement('div')
55
+ });
56
+ console.log(view);
57
+ });
58
+ describe('Command Results Generation', () => {
59
+ it('should generate create file command for any query', () => {
60
+ // Test will be implemented
61
+ expect(true).toBe(true);
62
+ });
63
+ it('should generate rename command when file is open and query is not a language', () => {
64
+ // Test will be implemented
65
+ expect(true).toBe(true);
66
+ });
67
+ it('should require input for language-specific file creation', () => {
68
+ // Test will be implemented
69
+ expect(true).toBe(true);
70
+ });
71
+ it('should not show rename command when no file is open', () => {
72
+ // Test will be implemented
73
+ expect(true).toBe(true);
74
+ });
75
+ it('should not show rename command when query matches a programming language', () => {
76
+ // Test will be implemented
77
+ expect(true).toBe(true);
78
+ });
79
+ });
80
+ describe('Search Results Separation', () => {
81
+ it('should separate command results from file search results', () => {
82
+ // Test will be implemented
83
+ expect(true).toBe(true);
84
+ });
85
+ it('should show divider between sections when both exist', () => {
86
+ // Test will be implemented
87
+ expect(true).toBe(true);
88
+ });
89
+ it('should not show divider when only one section exists', () => {
90
+ // Test will be implemented
91
+ expect(true).toBe(true);
92
+ });
93
+ });
94
+ describe('Command Execution', () => {
95
+ it('should create file directly for non-language queries', () => {
96
+ // Test will be implemented
97
+ expect(true).toBe(true);
98
+ });
99
+ it('should prompt for filename for language-specific queries', () => {
100
+ // Test will be implemented
101
+ expect(true).toBe(true);
102
+ });
103
+ it('should handle rename command correctly', () => {
104
+ // Test will be implemented
105
+ expect(true).toBe(true);
106
+ });
107
+ });
108
+ describe('UI Rendering', () => {
109
+ it('should render command results with icons', () => {
110
+ // Test will be implemented
111
+ expect(true).toBe(true);
112
+ });
113
+ it('should render file results without icons', () => {
114
+ // Test will be implemented
115
+ expect(true).toBe(true);
116
+ });
117
+ it('should apply correct CSS classes to different result types', () => {
118
+ // Test will be implemented
119
+ expect(true).toBe(true);
120
+ });
121
+ });
122
+ describe('Keyboard Navigation', () => {
123
+ it('should navigate through all results with arrow keys', () => {
124
+ // Test will be implemented
125
+ expect(true).toBe(true);
126
+ });
127
+ it('should execute selected result on Enter', () => {
128
+ // Test will be implemented
129
+ expect(true).toBe(true);
130
+ });
131
+ it('should handle navigation correctly with dividers', () => {
132
+ // Test will be implemented
133
+ expect(true).toBe(true);
134
+ });
135
+ });
136
+ describe('Language Detection', () => {
137
+ it('should correctly identify valid programming languages', () => {
138
+ // Test will be implemented
139
+ expect(true).toBe(true);
140
+ });
141
+ it('should handle case-insensitive language matching', () => {
142
+ // Test will be implemented
143
+ expect(true).toBe(true);
144
+ });
145
+ });
146
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "init": "/usr/bin/wasibox",
3
+ "initArgs": ["init"],
4
+ "rootfs": "https://antmicro.github.io/jswasi-rootfs/rootfs.tar.gz",
5
+ "mountConfig": {
6
+ "fsType": "fsa",
7
+ "opts": {
8
+ "name": "fsa1",
9
+ "keepMetadata": "true",
10
+ "create": "true"
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ import type { TransferHandler } from 'comlink';
2
+ export declare const asyncGeneratorTransferHandler: TransferHandler<AsyncGenerator<unknown>, unknown>;
3
+ export declare const watchOptionsTransferHandler: TransferHandler<{
4
+ signal: AbortSignal;
5
+ encoding?: string;
6
+ recursive?: boolean;
7
+ }, {
8
+ port: MessagePort;
9
+ encoding?: string;
10
+ recursive?: boolean;
11
+ }>;
@@ -0,0 +1,49 @@
1
+ import { transferHandlers, proxy } from 'comlink';
2
+ const proxyTransferHandler = transferHandlers.get('proxy');
3
+ // Allows us to use watch as a normal async generator
4
+ export const asyncGeneratorTransferHandler = {
5
+ canHandle(obj) {
6
+ return (obj &&
7
+ typeof obj === 'object' &&
8
+ typeof obj.next === 'function' &&
9
+ (typeof obj[Symbol.iterator] === 'function' ||
10
+ typeof obj[Symbol.asyncIterator] === 'function'));
11
+ },
12
+ serialize(obj) {
13
+ return proxyTransferHandler.serialize(proxy(obj));
14
+ },
15
+ async *deserialize(obj) {
16
+ const iterator = proxyTransferHandler.deserialize(obj);
17
+ while (true) {
18
+ const { value, done } = await iterator.next();
19
+ if (done) {
20
+ break;
21
+ }
22
+ yield value;
23
+ }
24
+ },
25
+ };
26
+ // Allows aborting watches across workers (like when our current file changes).
27
+ export const watchOptionsTransferHandler = {
28
+ canHandle: (obj) => {
29
+ return obj && typeof obj === "object" && obj.signal instanceof AbortSignal;
30
+ },
31
+ serialize: (options) => {
32
+ const { signal, ...rest } = options;
33
+ const { port1, port2 } = new MessageChannel();
34
+ signal.addEventListener("abort", () => {
35
+ port1.postMessage({});
36
+ port1.close();
37
+ }, { once: true });
38
+ return [{ port: port2, ...rest }, [port2]];
39
+ },
40
+ deserialize: (data) => {
41
+ const { port, ...rest } = data;
42
+ const controller = new AbortController();
43
+ port.onmessage = () => {
44
+ controller.abort();
45
+ port.close();
46
+ };
47
+ return { signal: controller.signal, ...rest };
48
+ },
49
+ };
@@ -0,0 +1,11 @@
1
+ import { Transport } from "@open-rpc/client-js/build/transports/Transport";
2
+ import type { JSONRPCRequestData } from "@open-rpc/client-js/src/Request";
3
+ export default class MessagePortTransport extends Transport {
4
+ port: MessagePort;
5
+ postMessageID: string;
6
+ constructor(port: MessagePort);
7
+ private messageHandler;
8
+ connect(): Promise<void>;
9
+ sendData(data: JSONRPCRequestData): Promise<any>;
10
+ close(): void;
11
+ }