dsh-plugin-workbench 0.0.1
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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/cordis.patch.yml +11 -0
- package/lib/client.js +8629 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +179 -0
- package/package.json +71 -0
- package/scripts/patch-layout.mjs +323 -0
- package/src/client/FileExplorer.tsx +374 -0
- package/src/client/FilePreview.tsx +353 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/files.module.css +536 -0
- package/src/client/highlight.ts +88 -0
- package/src/client/index.ts +45 -0
- package/src/client/locales.ts +39 -0
- package/src/client/store.ts +151 -0
- package/src/dsh.d.ts +62 -0
- package/src/index.ts +203 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/* VS Code-style file browser chrome. Uses its own color variables (--fe-*)
|
|
2
|
+
instead of the app skin tokens, so the file column and preview keep a
|
|
3
|
+
consistent editor look in both light and dark themes, toggled via the
|
|
4
|
+
data-fe-theme attribute on the column/preview roots. */
|
|
5
|
+
|
|
6
|
+
/* ---- VS Code color palettes (light / dark) ---- */
|
|
7
|
+
:global([data-fe-theme='light']) {
|
|
8
|
+
--fe-bg: #ffffff;
|
|
9
|
+
--fe-sidebar-bg: #f3f3f3;
|
|
10
|
+
--fe-fg: #1f1f1f;
|
|
11
|
+
--fe-muted: #6e6e6e;
|
|
12
|
+
--fe-faint: #9d9d9d;
|
|
13
|
+
--fe-border: #e5e5e5;
|
|
14
|
+
--fe-border-strong: #c8c8c8;
|
|
15
|
+
--fe-accent: #005fb8;
|
|
16
|
+
--fe-hover: #e8e8e8;
|
|
17
|
+
--fe-selection: #add6ff;
|
|
18
|
+
|
|
19
|
+
--fe-token-keyword: #0000ff;
|
|
20
|
+
--fe-token-string: #a31515;
|
|
21
|
+
--fe-token-comment: #008000;
|
|
22
|
+
--fe-token-number: #098658;
|
|
23
|
+
--fe-token-function: #795e26;
|
|
24
|
+
--fe-token-type: #267f99;
|
|
25
|
+
--fe-token-variable: #001080;
|
|
26
|
+
--fe-token-tag: #800000;
|
|
27
|
+
--fe-token-meta: #6e6e6e;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:global([data-fe-theme='dark']) {
|
|
31
|
+
--fe-bg: #1e1e1e;
|
|
32
|
+
--fe-sidebar-bg: #252526;
|
|
33
|
+
--fe-fg: #d4d4d4;
|
|
34
|
+
--fe-muted: #cccccc;
|
|
35
|
+
--fe-faint: #9d9d9d;
|
|
36
|
+
--fe-border: #3c3c3c;
|
|
37
|
+
--fe-border-strong: #454545;
|
|
38
|
+
--fe-accent: #007acc;
|
|
39
|
+
--fe-hover: #2a2d2e;
|
|
40
|
+
--fe-selection: #264f78;
|
|
41
|
+
|
|
42
|
+
--fe-token-keyword: #569cd6;
|
|
43
|
+
--fe-token-string: #ce9178;
|
|
44
|
+
--fe-token-comment: #6a9955;
|
|
45
|
+
--fe-token-number: #b5cea8;
|
|
46
|
+
--fe-token-function: #dcdcaa;
|
|
47
|
+
--fe-token-type: #4ec9b0;
|
|
48
|
+
--fe-token-variable: #9cdcfe;
|
|
49
|
+
--fe-token-tag: #569cd6;
|
|
50
|
+
--fe-token-meta: #9b9b9b;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ---- Explorer column (file tree) ---- */
|
|
54
|
+
.column {
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
height: 100%;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
background: var(--fe-sidebar-bg);
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
color: var(--fe-fg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.header {
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
flex: none;
|
|
69
|
+
height: 34px;
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 6px;
|
|
73
|
+
padding: 0 8px;
|
|
74
|
+
border-bottom: 1px solid var(--fe-border);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.headerTitle {
|
|
78
|
+
flex: 1;
|
|
79
|
+
min-width: 0;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
font-size: 11px;
|
|
85
|
+
text-transform: uppercase;
|
|
86
|
+
letter-spacing: 0.04em;
|
|
87
|
+
color: var(--fe-muted);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.headerActions {
|
|
91
|
+
flex: none;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 2px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.action {
|
|
98
|
+
appearance: none;
|
|
99
|
+
border: none;
|
|
100
|
+
background: transparent;
|
|
101
|
+
color: var(--fe-muted);
|
|
102
|
+
width: 22px;
|
|
103
|
+
height: 22px;
|
|
104
|
+
border-radius: 5px;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
padding: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.action:hover:not(:disabled) {
|
|
115
|
+
background: var(--fe-hover);
|
|
116
|
+
color: var(--fe-fg);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.action:disabled {
|
|
120
|
+
opacity: 0.5;
|
|
121
|
+
cursor: default;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.treeArea {
|
|
125
|
+
flex: 1;
|
|
126
|
+
min-height: 0;
|
|
127
|
+
overflow-y: auto;
|
|
128
|
+
padding: 4px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.row {
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 6px;
|
|
135
|
+
height: 22px;
|
|
136
|
+
border-radius: 3px;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
user-select: none;
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
color: var(--fe-fg);
|
|
141
|
+
font-size: 13px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.row:hover {
|
|
145
|
+
background: var(--fe-hover);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.rowSelected {
|
|
149
|
+
background: var(--fe-selection);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.rowSelected .name {
|
|
153
|
+
color: var(--fe-fg);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.chevron {
|
|
157
|
+
flex: none;
|
|
158
|
+
width: 12px;
|
|
159
|
+
text-align: center;
|
|
160
|
+
color: var(--fe-muted);
|
|
161
|
+
font-size: 10px;
|
|
162
|
+
line-height: 22px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.icon {
|
|
166
|
+
flex: none;
|
|
167
|
+
width: 16px;
|
|
168
|
+
text-align: center;
|
|
169
|
+
font-size: 13px;
|
|
170
|
+
line-height: 22px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.name {
|
|
174
|
+
flex: 1;
|
|
175
|
+
min-width: 0;
|
|
176
|
+
overflow: hidden;
|
|
177
|
+
text-overflow: ellipsis;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.size {
|
|
181
|
+
flex: none;
|
|
182
|
+
color: var(--fe-faint);
|
|
183
|
+
font-size: 11px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.actions {
|
|
187
|
+
flex: none;
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
gap: 2px;
|
|
191
|
+
opacity: 0;
|
|
192
|
+
transition: opacity 0.12s;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.row:hover .actions,
|
|
196
|
+
.row:focus-within .actions {
|
|
197
|
+
opacity: 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.rowHint {
|
|
201
|
+
color: var(--fe-faint);
|
|
202
|
+
font-size: 12px;
|
|
203
|
+
line-height: 20px;
|
|
204
|
+
padding: 2px 8px 2px 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.rowError {
|
|
208
|
+
color: #f48771;
|
|
209
|
+
font-size: 12px;
|
|
210
|
+
line-height: 18px;
|
|
211
|
+
padding: 2px 8px 2px 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* ---- Split preview + tabs ---- */
|
|
215
|
+
.preview {
|
|
216
|
+
flex: 0 0 55%;
|
|
217
|
+
min-width: 240px;
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
min-height: 0;
|
|
221
|
+
overflow: hidden;
|
|
222
|
+
border-right: 1px solid var(--fe-border);
|
|
223
|
+
background: var(--fe-bg);
|
|
224
|
+
animation: fe-preview-in 0.18s ease;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.previewClosing {
|
|
228
|
+
animation: fe-preview-out 0.2s ease forwards;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@keyframes fe-preview-in {
|
|
232
|
+
from {
|
|
233
|
+
opacity: 0;
|
|
234
|
+
}
|
|
235
|
+
to {
|
|
236
|
+
opacity: 1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@keyframes fe-preview-out {
|
|
241
|
+
from {
|
|
242
|
+
opacity: 1;
|
|
243
|
+
}
|
|
244
|
+
to {
|
|
245
|
+
opacity: 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.handle {
|
|
250
|
+
flex: none;
|
|
251
|
+
width: 6px;
|
|
252
|
+
margin-left: -4px;
|
|
253
|
+
margin-right: -2px;
|
|
254
|
+
cursor: col-resize;
|
|
255
|
+
touch-action: none;
|
|
256
|
+
background: transparent;
|
|
257
|
+
z-index: 2;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.handle:hover,
|
|
261
|
+
.handle:active {
|
|
262
|
+
background: var(--fe-accent);
|
|
263
|
+
opacity: 0.35;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.tabBar {
|
|
267
|
+
flex: none;
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: stretch;
|
|
270
|
+
height: 34px;
|
|
271
|
+
border-bottom: 1px solid var(--fe-border);
|
|
272
|
+
background: var(--fe-sidebar-bg);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.tabScroller {
|
|
276
|
+
flex: 1;
|
|
277
|
+
min-width: 0;
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: stretch;
|
|
280
|
+
overflow-x: auto;
|
|
281
|
+
scrollbar-width: thin;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.collapse {
|
|
285
|
+
flex: none;
|
|
286
|
+
appearance: none;
|
|
287
|
+
border: none;
|
|
288
|
+
background: transparent;
|
|
289
|
+
color: var(--fe-muted);
|
|
290
|
+
width: 26px;
|
|
291
|
+
height: 26px;
|
|
292
|
+
margin: auto 4px;
|
|
293
|
+
border-radius: 4px;
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
font-size: 14px;
|
|
296
|
+
line-height: 1;
|
|
297
|
+
display: inline-flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
justify-content: center;
|
|
300
|
+
padding: 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.collapse:hover {
|
|
304
|
+
background: var(--fe-hover);
|
|
305
|
+
color: var(--fe-fg);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.tab {
|
|
309
|
+
flex: none;
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 6px;
|
|
313
|
+
padding: 0 8px 0 12px;
|
|
314
|
+
max-width: 180px;
|
|
315
|
+
font-size: 13px;
|
|
316
|
+
color: var(--fe-muted);
|
|
317
|
+
border-right: 1px solid var(--fe-border);
|
|
318
|
+
cursor: pointer;
|
|
319
|
+
user-select: none;
|
|
320
|
+
white-space: nowrap;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.tab:hover {
|
|
324
|
+
background: var(--fe-hover);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.tabActive {
|
|
328
|
+
background: var(--fe-bg);
|
|
329
|
+
color: var(--fe-fg);
|
|
330
|
+
box-shadow: inset 0 -1px 0 var(--fe-accent);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.tabName {
|
|
334
|
+
flex: 1;
|
|
335
|
+
min-width: 0;
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
text-overflow: ellipsis;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.tabClose {
|
|
341
|
+
appearance: none;
|
|
342
|
+
border: none;
|
|
343
|
+
background: transparent;
|
|
344
|
+
color: var(--fe-faint);
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
font-size: 11px;
|
|
347
|
+
width: 16px;
|
|
348
|
+
height: 16px;
|
|
349
|
+
border-radius: 3px;
|
|
350
|
+
display: inline-flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
padding: 0;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.tabClose:hover {
|
|
357
|
+
background: var(--fe-hover);
|
|
358
|
+
color: var(--fe-fg);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.tabDot {
|
|
362
|
+
flex: none;
|
|
363
|
+
width: 8px;
|
|
364
|
+
height: 8px;
|
|
365
|
+
border-radius: 50%;
|
|
366
|
+
background: var(--fe-fg);
|
|
367
|
+
opacity: 0.85;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.previewBody {
|
|
371
|
+
flex: 1;
|
|
372
|
+
min-height: 0;
|
|
373
|
+
overflow: auto;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Directly-editable editor: a transparent textarea over a highlighted layer. */
|
|
377
|
+
.editor {
|
|
378
|
+
position: relative;
|
|
379
|
+
height: 100%;
|
|
380
|
+
min-height: 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.editorHighlight {
|
|
384
|
+
position: absolute;
|
|
385
|
+
inset: 0;
|
|
386
|
+
margin: 0;
|
|
387
|
+
padding: 12px;
|
|
388
|
+
overflow: hidden;
|
|
389
|
+
pointer-events: none;
|
|
390
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
391
|
+
font-size: 13px;
|
|
392
|
+
line-height: 1.5;
|
|
393
|
+
white-space: pre;
|
|
394
|
+
color: var(--fe-fg);
|
|
395
|
+
background: transparent;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.editorHighlight code {
|
|
399
|
+
font-family: inherit;
|
|
400
|
+
font-size: inherit;
|
|
401
|
+
line-height: inherit;
|
|
402
|
+
white-space: inherit;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.editorTextarea {
|
|
406
|
+
position: absolute;
|
|
407
|
+
inset: 0;
|
|
408
|
+
box-sizing: border-box;
|
|
409
|
+
margin: 0;
|
|
410
|
+
padding: 12px;
|
|
411
|
+
border: none;
|
|
412
|
+
outline: none;
|
|
413
|
+
resize: none;
|
|
414
|
+
background: transparent;
|
|
415
|
+
color: transparent;
|
|
416
|
+
caret-color: var(--fe-fg);
|
|
417
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
418
|
+
font-size: 13px;
|
|
419
|
+
line-height: 1.5;
|
|
420
|
+
white-space: pre;
|
|
421
|
+
overflow: auto;
|
|
422
|
+
tab-size: 2;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.saveError {
|
|
426
|
+
position: absolute;
|
|
427
|
+
right: 8px;
|
|
428
|
+
bottom: 8px;
|
|
429
|
+
padding: 4px 10px;
|
|
430
|
+
border-radius: 4px;
|
|
431
|
+
background: #f48771;
|
|
432
|
+
color: #1e1e1e;
|
|
433
|
+
font-size: 12px;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.previewHint {
|
|
437
|
+
padding: 12px;
|
|
438
|
+
color: var(--fe-faint);
|
|
439
|
+
font-size: 12px;
|
|
440
|
+
line-height: 18px;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.previewMeta {
|
|
444
|
+
color: var(--fe-faint);
|
|
445
|
+
font-size: 11px;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.placeholder {
|
|
449
|
+
flex: 1;
|
|
450
|
+
display: flex;
|
|
451
|
+
align-items: center;
|
|
452
|
+
justify-content: center;
|
|
453
|
+
padding: 16px;
|
|
454
|
+
text-align: center;
|
|
455
|
+
color: var(--fe-faint);
|
|
456
|
+
font-size: 12px;
|
|
457
|
+
line-height: 18px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ---- Syntax highlighting (highlight.js token classes) ---- */
|
|
461
|
+
.editorHighlight :global(.hljs-keyword),
|
|
462
|
+
.editorHighlight :global(.hljs-selector-tag),
|
|
463
|
+
.editorHighlight :global(.hljs-literal) {
|
|
464
|
+
color: var(--fe-token-keyword);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.editorHighlight :global(.hljs-string),
|
|
468
|
+
.editorHighlight :global(.hljs-regexp),
|
|
469
|
+
.editorHighlight :global(.hljs-addition) {
|
|
470
|
+
color: var(--fe-token-string);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.editorHighlight :global(.hljs-comment),
|
|
474
|
+
.editorHighlight :global(.hljs-quote) {
|
|
475
|
+
color: var(--fe-token-comment);
|
|
476
|
+
font-style: italic;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.editorHighlight :global(.hljs-number),
|
|
480
|
+
.editorHighlight :global(.hljs-symbol),
|
|
481
|
+
.editorHighlight :global(.hljs-bullet) {
|
|
482
|
+
color: var(--fe-token-number);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.editorHighlight :global(.hljs-title),
|
|
486
|
+
.editorHighlight :global(.hljs-section),
|
|
487
|
+
.editorHighlight :global(.hljs-title.function_) {
|
|
488
|
+
color: var(--fe-token-function);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.editorHighlight :global(.hljs-type),
|
|
492
|
+
.editorHighlight :global(.hljs-built_in),
|
|
493
|
+
.editorHighlight :global(.hljs-class .hljs-title) {
|
|
494
|
+
color: var(--fe-token-type);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.editorHighlight :global(.hljs-attr),
|
|
498
|
+
.editorHighlight :global(.hljs-attribute),
|
|
499
|
+
.editorHighlight :global(.hljs-variable),
|
|
500
|
+
.editorHighlight :global(.hljs-template-variable),
|
|
501
|
+
.editorHighlight :global(.hljs-params) {
|
|
502
|
+
color: var(--fe-token-variable);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.editorHighlight :global(.hljs-tag),
|
|
506
|
+
.editorHighlight :global(.hljs-name) {
|
|
507
|
+
color: var(--fe-token-tag);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.editorHighlight :global(.hljs-meta) {
|
|
511
|
+
color: var(--fe-token-meta);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.editorHighlight :global(.hljs-emphasis) {
|
|
515
|
+
font-style: italic;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.editorHighlight :global(.hljs-strong) {
|
|
519
|
+
font-weight: bold;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* ---- Skin chrome fix ----
|
|
523
|
+
Shift the maid-atelier fixed top/bottom trim past the explorer column and
|
|
524
|
+
the split preview, so it only decorates the chat instead of overlaying the
|
|
525
|
+
file column, preview header/buttons, or file content. */
|
|
526
|
+
body[data-dsh-maid-atelier] [data-skin-chrome=top-trim] {
|
|
527
|
+
translate: calc(var(--maid-sidebar-width, 0px) + var(--dsh-explorer-width, 0px) + var(--dsh-preview-width, 0px)) 0 !important;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
body[data-dsh-maid-atelier] [data-skin-chrome=bottom-trim] {
|
|
531
|
+
translate: calc(var(--maid-sidebar-width, 0px) + var(--dsh-explorer-width, 0px) + var(--dsh-preview-width, 0px)) 0 !important;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
[class*='explorerCol'] {
|
|
535
|
+
border-right: 1px solid var(--fe-border) !important;
|
|
536
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax highlighting for the file preview. Uses highlight.js core with a
|
|
3
|
+
* hand-picked set of common languages so the bundle stays lean; unknown
|
|
4
|
+
* languages fall back to plain (HTML-escaped) text.
|
|
5
|
+
*/
|
|
6
|
+
import hljs from 'highlight.js/lib/core'
|
|
7
|
+
import bash from 'highlight.js/lib/languages/bash'
|
|
8
|
+
import cpp from 'highlight.js/lib/languages/cpp'
|
|
9
|
+
import csharp from 'highlight.js/lib/languages/csharp'
|
|
10
|
+
import css from 'highlight.js/lib/languages/css'
|
|
11
|
+
import go from 'highlight.js/lib/languages/go'
|
|
12
|
+
import ini from 'highlight.js/lib/languages/ini'
|
|
13
|
+
import java from 'highlight.js/lib/languages/java'
|
|
14
|
+
import javascript from 'highlight.js/lib/languages/javascript'
|
|
15
|
+
import json from 'highlight.js/lib/languages/json'
|
|
16
|
+
import markdown from 'highlight.js/lib/languages/markdown'
|
|
17
|
+
import python from 'highlight.js/lib/languages/python'
|
|
18
|
+
import rust from 'highlight.js/lib/languages/rust'
|
|
19
|
+
import sql from 'highlight.js/lib/languages/sql'
|
|
20
|
+
import typescript from 'highlight.js/lib/languages/typescript'
|
|
21
|
+
import xml from 'highlight.js/lib/languages/xml'
|
|
22
|
+
import yaml from 'highlight.js/lib/languages/yaml'
|
|
23
|
+
|
|
24
|
+
hljs.registerLanguage('bash', bash)
|
|
25
|
+
hljs.registerLanguage('cpp', cpp)
|
|
26
|
+
hljs.registerLanguage('csharp', csharp)
|
|
27
|
+
hljs.registerLanguage('css', css)
|
|
28
|
+
hljs.registerLanguage('go', go)
|
|
29
|
+
hljs.registerLanguage('ini', ini)
|
|
30
|
+
hljs.registerLanguage('java', java)
|
|
31
|
+
hljs.registerLanguage('javascript', javascript)
|
|
32
|
+
hljs.registerLanguage('json', json)
|
|
33
|
+
hljs.registerLanguage('markdown', markdown)
|
|
34
|
+
hljs.registerLanguage('python', python)
|
|
35
|
+
hljs.registerLanguage('rust', rust)
|
|
36
|
+
hljs.registerLanguage('sql', sql)
|
|
37
|
+
hljs.registerLanguage('typescript', typescript)
|
|
38
|
+
hljs.registerLanguage('xml', xml)
|
|
39
|
+
hljs.registerLanguage('yaml', yaml)
|
|
40
|
+
|
|
41
|
+
const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
|
42
|
+
js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
|
|
43
|
+
ts: 'typescript', tsx: 'typescript', mts: 'typescript', cts: 'typescript',
|
|
44
|
+
py: 'python', pyw: 'python',
|
|
45
|
+
json: 'json', jsonc: 'json',
|
|
46
|
+
html: 'xml', htm: 'xml', xml: 'xml', svg: 'xml', vue: 'xml',
|
|
47
|
+
css: 'css', scss: 'css', less: 'css',
|
|
48
|
+
md: 'markdown', markdown: 'markdown',
|
|
49
|
+
sh: 'bash', bash: 'bash', zsh: 'bash',
|
|
50
|
+
java: 'java',
|
|
51
|
+
c: 'cpp', h: 'cpp', cc: 'cpp', cpp: 'cpp', hpp: 'cpp',
|
|
52
|
+
cs: 'csharp',
|
|
53
|
+
go: 'go',
|
|
54
|
+
rs: 'rust',
|
|
55
|
+
sql: 'sql',
|
|
56
|
+
yml: 'yaml', yaml: 'yaml',
|
|
57
|
+
ini: 'ini', conf: 'ini',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Map a file path to a registered highlight.js language id, or undefined. */
|
|
61
|
+
export function detectLanguage(path: string): string | undefined {
|
|
62
|
+
const idx = path.lastIndexOf('.')
|
|
63
|
+
if (idx < 0) return undefined
|
|
64
|
+
const ext = path.slice(idx + 1).toLowerCase()
|
|
65
|
+
return EXTENSION_TO_LANGUAGE[ext]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function escapeHtml(value: string): string {
|
|
69
|
+
return value
|
|
70
|
+
.replace(/&/g, '&')
|
|
71
|
+
.replace(/</g, '<')
|
|
72
|
+
.replace(/>/g, '>')
|
|
73
|
+
.replace(/"/g, '"')
|
|
74
|
+
.replace(/'/g, ''')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Highlight code for a file; returns safe HTML (code is HTML-escaped). */
|
|
78
|
+
export function highlightCode(code: string, path: string): string {
|
|
79
|
+
const language = detectLanguage(path)
|
|
80
|
+
if (language !== undefined && hljs.getLanguage(language) !== undefined) {
|
|
81
|
+
try {
|
|
82
|
+
return hljs.highlight(code, { language, ignoreIllegals: true }).value
|
|
83
|
+
} catch {
|
|
84
|
+
// fall through to plain text
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return escapeHtml(code)
|
|
88
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client half of the files-explorer plugin.
|
|
3
|
+
*
|
|
4
|
+
* Registers the file tree into the `explorer` slot and the split preview into
|
|
5
|
+
* the `explorer.preview` slot (both declared by the patched ui-layout), plus a
|
|
6
|
+
* `files` locale namespace. The inject factories return plain callbacks:
|
|
7
|
+
* filesystem reads go through the generic connection RPC channel into the host
|
|
8
|
+
* half, and `openPath` reuses the existing workspaces capability.
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { FileExplorer } from './FileExplorer'
|
|
12
|
+
import { FilePreview } from './FilePreview'
|
|
13
|
+
import { NS, zh, en } from './locales'
|
|
14
|
+
|
|
15
|
+
const CHANNEL = '/dsh-plugin-files'
|
|
16
|
+
|
|
17
|
+
/** Required client services (sequencing only — data reads use global hooks). */
|
|
18
|
+
export const inject = ['slots', 'sessions', 'workspaces', 'locale', 'connection']
|
|
19
|
+
|
|
20
|
+
export function apply(ctx: Context): void {
|
|
21
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'files-explorer: dictionaries')
|
|
22
|
+
|
|
23
|
+
const listDir = (path: string, signal?: AbortSignal) => unwrap(ctx.connection.rpc.call(CHANNEL, 'list', { path }, signal))
|
|
24
|
+
const readFile = (path: string, signal?: AbortSignal) => unwrap(ctx.connection.rpc.call(CHANNEL, 'read', { path }, signal))
|
|
25
|
+
const writeFile = (path: string, content: string, signal?: AbortSignal) => unwrap(ctx.connection.rpc.call(CHANNEL, 'write', { path, content }, signal))
|
|
26
|
+
const openPath = (path: string) => ctx.workspaces.openPath(path)
|
|
27
|
+
|
|
28
|
+
ctx.slots.inject('explorer', () => ctx.slots.register({
|
|
29
|
+
name: 'explorer',
|
|
30
|
+
locale: NS,
|
|
31
|
+
inject: () => ({ listDir, openPath }),
|
|
32
|
+
}, FileExplorer))
|
|
33
|
+
|
|
34
|
+
ctx.slots.inject('explorer.preview', () => ctx.slots.register({
|
|
35
|
+
name: 'explorer.preview',
|
|
36
|
+
locale: NS,
|
|
37
|
+
inject: () => ({ readFile, writeFile }),
|
|
38
|
+
}, FilePreview))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function unwrap<T>(result: Promise<{ ok: true; value: T } | { ok: false; error: { message: string } }>): Promise<T> {
|
|
42
|
+
const resolved = await result
|
|
43
|
+
if (!resolved.ok) throw new Error(resolved.error.message)
|
|
44
|
+
return resolved.value
|
|
45
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Locale namespace and dictionaries for the files-explorer column. */
|
|
2
|
+
|
|
3
|
+
export const NS = 'files'
|
|
4
|
+
|
|
5
|
+
export const zh = {
|
|
6
|
+
'placeholder.noSession': '暂无会话',
|
|
7
|
+
'placeholder.noCwd': '当前会话没有工作目录',
|
|
8
|
+
'action.refresh': '刷新',
|
|
9
|
+
'action.open': '在系统中打开',
|
|
10
|
+
'action.theme': '切换浅色/暗色主题',
|
|
11
|
+
'action.saveHint': 'Ctrl+S 保存',
|
|
12
|
+
'tab.close': '关闭标签页',
|
|
13
|
+
'tab.collapse': '收起文件详情',
|
|
14
|
+
'tab.expand': '弹出文件详情',
|
|
15
|
+
'preview.loading': '加载中…',
|
|
16
|
+
'preview.binary': '二进制文件,无法预览',
|
|
17
|
+
'preview.tooLarge': '文件过大,仅支持预览不超过 {limit}',
|
|
18
|
+
'preview.emptyDir': '空目录',
|
|
19
|
+
'preview.error': '加载失败',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export type FilesKey = keyof typeof zh
|
|
23
|
+
|
|
24
|
+
export const en: Record<FilesKey, string> = {
|
|
25
|
+
'placeholder.noSession': 'No session',
|
|
26
|
+
'placeholder.noCwd': 'No working directory for this session',
|
|
27
|
+
'action.refresh': 'Refresh',
|
|
28
|
+
'action.open': 'Open in system',
|
|
29
|
+
'action.theme': 'Toggle light/dark theme',
|
|
30
|
+
'action.saveHint': 'Ctrl+S to save',
|
|
31
|
+
'tab.close': 'Close tab',
|
|
32
|
+
'tab.collapse': 'Collapse file details',
|
|
33
|
+
'tab.expand': 'Expand file details',
|
|
34
|
+
'preview.loading': 'Loading…',
|
|
35
|
+
'preview.binary': 'Binary file, cannot preview',
|
|
36
|
+
'preview.tooLarge': 'File too large to preview (limit {limit})',
|
|
37
|
+
'preview.emptyDir': 'Empty directory',
|
|
38
|
+
'preview.error': 'Load failed',
|
|
39
|
+
}
|